From 814ee02aeadbc5d1e8e64e25b3517d384a9d906d Mon Sep 17 00:00:00 2001 From: Sam Demeulemeester Date: Mon, 27 Jul 2026 16:47:14 +0200 Subject: [PATCH] Add FAT file timestamps to USB report saving (RTC on x86, build date elsewhere) --- app/reports.c | 81 ++++------------------------ build/aarch64/Makefile | 1 + build/i586/Makefile | 1 + build/loongarch64/Makefile | 1 + build/x86_64/Makefile | 1 + system/fat32.c | 19 +++++++ system/rtc.c | 105 +++++++++++++++++++++++++++++++++++++ system/rtc.h | 35 +++++++++++++ 8 files changed, 174 insertions(+), 70 deletions(-) create mode 100644 system/rtc.c create mode 100644 system/rtc.h diff --git a/app/reports.c b/app/reports.c index 620e4df..f012901 100644 --- a/app/reports.c +++ b/app/reports.c @@ -3,11 +3,11 @@ #include "cpuinfo.h" #include "tsc.h" -#include "io.h" #include "heap.h" #include "hwctrl.h" #include "memctrl.h" #include "pmem.h" +#include "rtc.h" #include "serial.h" #include "smbios.h" #include "smp.h" @@ -59,63 +59,6 @@ // Private Functions //------------------------------------------------------------------------------ -// RTC (CMOS) register reading. Ports 0x70/0x71 BCD format. -// ISA-only; LoongArch has no equivalent fixed-port RTC. - -#if defined(__i386__) || defined(__x86_64__) -static uint8_t rtc_read(uint8_t reg) -{ - outb(reg, 0x70); - return inb(0x71); -} - -static uint8_t bcd_to_bin(uint8_t bcd) -{ - return (bcd >> 4) * 10 + (bcd & 0x0F); -} - -static void rtc_get_datetime(int *year, int *mon, int *day, int *hour, int *min, int *sec) -{ - // Wait for any update in progress to complete (UIP bit, status register A). - for (int i = 0; i < 20000 && (rtc_read(0x0A) & 0x80); i++) {} - - uint8_t rtc_stb = rtc_read(0x0B); - uint8_t rtc_sec = rtc_read(0x00); - uint8_t rtc_min = rtc_read(0x02); - uint8_t rtc_hour = rtc_read(0x04); - uint8_t rtc_day = rtc_read(0x07); - uint8_t rtc_mon = rtc_read(0x08); - uint8_t rtc_year = rtc_read(0x09); - uint8_t rtc_cent = rtc_read(0x32); - - bool rtc_pm = rtc_hour & 0x80; - rtc_hour &= 0x7F; - - // Registers are BCD unless the RTC is in binary mode (status register B, DM bit). - if (!(rtc_stb & 0x04)) { - rtc_sec = bcd_to_bin(rtc_sec); - rtc_min = bcd_to_bin(rtc_min); - rtc_hour = bcd_to_bin(rtc_hour); - rtc_day = bcd_to_bin(rtc_day); - rtc_mon = bcd_to_bin(rtc_mon); - rtc_year = bcd_to_bin(rtc_year); - rtc_cent = bcd_to_bin(rtc_cent); - } - - // Convert 12-hour mode (hour bit 7 = PM) to 24-hour. - if (!(rtc_stb & 0x02)) { - rtc_hour = rtc_hour % 12 + (rtc_pm ? 12 : 0); - } - - *year = (rtc_cent ? rtc_cent * 100 : 2000) + rtc_year; - *mon = rtc_mon; - *day = rtc_day; - *hour = rtc_hour; - *min = rtc_min; - *sec = rtc_sec; -} -#endif - // Minimal buffer printf supporting %s, %i, %u, %x with field width. // %u and %x consume a uintptr_t argument - cast at every call site: on x86_64 // the upper half of a default-promoted 32-bit vararg register is undefined. @@ -249,13 +192,12 @@ static int format_results(char *buf, int bufsize) pos = buf_printf(pos, "=======================\r\n\r\n"); // Date & time from RTC (x86 ISA CMOS only). -#if defined(__i386__) || defined(__x86_64__) - int year, mon, day, hour, min, sec; - rtc_get_datetime(&year, &mon, &day, &hour, &min, &sec); - pos = buf_printf(pos, "Date: %04i-%02i-%02i %02i:%02i:%02i\r\n", - year, mon, day, hour, min, sec); - pos = buf_printf(pos, "\r\n"); -#endif + rtc_time_t now; + if (rtc_get_time(&now)) { + pos = buf_printf(pos, "Date: %04i-%02i-%02i %02i:%02i:%02i\r\n", + now.year, now.month, now.day, now.hour, now.min, now.sec); + pos = buf_printf(pos, "\r\n"); + } // System info. if (cpu_model) { @@ -570,11 +512,10 @@ void serial_log_run_start(void) { if (!enable_tty_log) return; -#if defined(__i386__) || defined(__x86_64__) - int year, mon, day, hour, min, sec; - rtc_get_datetime(&year, &mon, &day, &hour, &min, &sec); - slog("info", " rtc=\"%04i-%02i-%02i %02i:%02i:%02i\"", year, mon, day, hour, min, sec); -#endif + rtc_time_t now; + if (rtc_get_time(&now)) { + slog("info", " rtc=\"%04i-%02i-%02i %02i:%02i:%02i\"", now.year, now.month, now.day, now.hour, now.min, now.sec); + } if (cpu_model) { slog("info", " cpu=\"%s\"", cpu_model); diff --git a/build/aarch64/Makefile b/build/aarch64/Makefile index 142aeb5..0188632 100644 --- a/build/aarch64/Makefile +++ b/build/aarch64/Makefile @@ -47,6 +47,7 @@ SYS_OBJS = system/acpi.o \ system/ohci.o \ system/pmem.o \ system/reloc.o \ + system/rtc.o \ system/screen.o \ system/serial.o \ system/smbios.o \ diff --git a/build/i586/Makefile b/build/i586/Makefile index 8158415..816fd29 100644 --- a/build/i586/Makefile +++ b/build/i586/Makefile @@ -40,6 +40,7 @@ SYS_OBJS = system/acpi.o \ system/pci.o \ system/pmem.o \ system/reloc.o \ + system/rtc.o \ system/screen.o \ system/serial.o \ system/smbios.o \ diff --git a/build/loongarch64/Makefile b/build/loongarch64/Makefile index f95e67d..f6513af 100644 --- a/build/loongarch64/Makefile +++ b/build/loongarch64/Makefile @@ -38,6 +38,7 @@ SYS_OBJS = system/acpi.o \ system/pci_mmio.o \ system/pmem.o \ system/reloc.o \ + system/rtc.o \ system/screen.o \ system/serial.o \ system/smbios.o \ diff --git a/build/x86_64/Makefile b/build/x86_64/Makefile index af5b6ef..893737a 100644 --- a/build/x86_64/Makefile +++ b/build/x86_64/Makefile @@ -39,6 +39,7 @@ SYS_OBJS = system/acpi.o \ system/pci.o \ system/pmem.o \ system/reloc.o \ + system/rtc.o \ system/screen.o \ system/serial.o \ system/smbios.o \ diff --git a/system/fat32.c b/system/fat32.c index b608f26..a6c1b20 100644 --- a/system/fat32.c +++ b/system/fat32.c @@ -4,6 +4,8 @@ #include #include +#include "rtc.h" + #include "string.h" #include "fat32.h" @@ -508,6 +510,23 @@ bool fat32_write_file(fat32_fs_t *fs, const char *name_8_3, const void *data, ui // Attributes. entry[11] = DIR_ATTR_ARCHIVE; + // Creation/access/write timestamps. DIR_WrtDate is mandatory and 0 encodes an + // invalid date (day/month 0), so fall back to the build date when there is no RTC. + rtc_time_t now; + (void)rtc_get_time(&now); + uint16_t fat_time = now.hour << 11 | now.min << 5 | now.sec / 2; + uint16_t fat_date = (now.year - 1980) << 9 | now.month << 5 | now.day; + entry[14] = fat_time & 0xFF; // creation time (bytes 14-15) + entry[15] = (fat_time >> 8) & 0xFF; + entry[16] = fat_date & 0xFF; // creation date (bytes 16-17) + entry[17] = (fat_date >> 8) & 0xFF; + entry[18] = fat_date & 0xFF; // last access date (bytes 18-19) + entry[19] = (fat_date >> 8) & 0xFF; + entry[22] = fat_time & 0xFF; // last write time (bytes 22-23) + entry[23] = (fat_time >> 8) & 0xFF; + entry[24] = fat_date & 0xFF; // last write date (bytes 24-25) + entry[25] = (fat_date >> 8) & 0xFF; + // First cluster high word (bytes 20-21). entry[20] = (first_cluster >> 16) & 0xFF; entry[21] = (first_cluster >> 24) & 0xFF; diff --git a/system/rtc.c b/system/rtc.c new file mode 100644 index 0000000..e855bad --- /dev/null +++ b/system/rtc.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 Sam Demeulemeester. + +#include +#include + +#include "io.h" + +#include "string.h" + +#include "rtc.h" + +//------------------------------------------------------------------------------ +// Private Functions +//------------------------------------------------------------------------------ + +// RTC (CMOS) register reading. Ports 0x70/0x71 BCD format. +// ISA-only; LoongArch has no equivalent fixed-port RTC. + +#if defined(__i386__) || defined(__x86_64__) +static uint8_t rtc_read(uint8_t reg) +{ + outb(reg, 0x70); + return inb(0x71); +} + +static uint8_t bcd_to_bin(uint8_t bcd) +{ + return (bcd >> 4) * 10 + (bcd & 0x0F); +} +#endif + +//------------------------------------------------------------------------------ +// Public Functions +//------------------------------------------------------------------------------ + +bool rtc_get_time(rtc_time_t *dt) +{ +#if defined(__i386__) || defined(__x86_64__) + // Wait for any update in progress to complete (UIP bit, status register A). + for (int i = 0; i < 20000 && (rtc_read(0x0A) & 0x80); i++) {} + + uint8_t rtc_stb = rtc_read(0x0B); + uint8_t rtc_sec = rtc_read(0x00); + uint8_t rtc_min = rtc_read(0x02); + uint8_t rtc_hour = rtc_read(0x04); + uint8_t rtc_day = rtc_read(0x07); + uint8_t rtc_mon = rtc_read(0x08); + uint8_t rtc_year = rtc_read(0x09); + uint8_t rtc_cent = rtc_read(0x32); + + bool rtc_pm = rtc_hour & 0x80; + rtc_hour &= 0x7F; + + // Registers are BCD unless the RTC is in binary mode (status register B, DM bit). + if (!(rtc_stb & 0x04)) { + rtc_sec = bcd_to_bin(rtc_sec); + rtc_min = bcd_to_bin(rtc_min); + rtc_hour = bcd_to_bin(rtc_hour); + rtc_day = bcd_to_bin(rtc_day); + rtc_mon = bcd_to_bin(rtc_mon); + rtc_year = bcd_to_bin(rtc_year); + rtc_cent = bcd_to_bin(rtc_cent); + } + + // Convert 12-hour mode (hour bit 7 = PM) to 24-hour. + if (!(rtc_stb & 0x02)) { + rtc_hour = rtc_hour % 12 + (rtc_pm ? 12 : 0); + } + + dt->year = (rtc_cent ? rtc_cent * 100 : 2000) + rtc_year; + dt->month = rtc_mon; + dt->day = rtc_day; + dt->hour = rtc_hour; + dt->min = rtc_min; + dt->sec = rtc_sec; + + // Reject out-of-range values (ie: dead CMOS battery) and use the build date instead. + if (dt->month >= 1 && dt->month <= 12 && dt->day >= 1 && dt->day <= 31 + && dt->year >= 1980 && dt->year <= 2107 + && dt->hour <= 23 && dt->min <= 59 && dt->sec <= 59) { + return true; + } +#endif + + // Build date & time of the binary: __DATE__ is "Mmm dd yyyy", __TIME__ is "hh:mm:ss". + static const char bdate[] = __DATE__; + static const char btime[] = __TIME__; + static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; + + dt->month = 1; + for (int i = 0; i < 12; i++) { + if (memcmp(bdate, &months[i * 3], 3) == 0) { + dt->month = i + 1; + break; + } + } + dt->day = (bdate[4] == ' ' ? 0 : (bdate[4] - '0') * 10) + bdate[5] - '0'; + dt->year = (bdate[7] - '0') * 1000 + (bdate[8] - '0') * 100 + (bdate[9] - '0') * 10 + bdate[10] - '0'; + dt->hour = (btime[0] - '0') * 10 + btime[1] - '0'; + dt->min = (btime[3] - '0') * 10 + btime[4] - '0'; + dt->sec = (btime[6] - '0') * 10 + btime[7] - '0'; + + return false; +} diff --git a/system/rtc.h b/system/rtc.h new file mode 100644 index 0000000..66cdd11 --- /dev/null +++ b/system/rtc.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef RTC_H +#define RTC_H +/** + * \file + * + * Provides the current date & time, from the CMOS RTC where available, + * falling back to the build date & time of the binary. + * + *//* + * Copyright (C) 2026 Sam Demeulemeester. + */ + +#include + +/** + * A calendar date & time. + */ +typedef struct { + int year; // 4-digit year + int month; // 1 - 12 + int day; // 1 - 31 + int hour; // 0 - 23 + int min; // 0 - 59 + int sec; // 0 - 59 +} rtc_time_t; + +/** + * Fills dt with the current RTC date & time on x86, or with the binary's + * build date & time elsewhere (and when the RTC returns out-of-range + * values, e.g. dead CMOS battery). Returns true only for live RTC data. + */ +bool rtc_get_time(rtc_time_t *dt); + +#endif // RTC_H