From f31dd38429f2b5c8d0e3828b2c05e9225ebfe565 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 6 Sep 2026 20:05:50 +0200 Subject: [PATCH] feat: add cmosRead and waitWhileUpdateInProgress --- src/rtc.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/rtc.c diff --git a/src/rtc.c b/src/rtc.c new file mode 100644 index 0000000..d994637 --- /dev/null +++ b/src/rtc.c @@ -0,0 +1,192 @@ +#include "rtc.h" +#include "ports.h" +#include "string.h" + +#define rtcPortIndex 0x70 +#define rtcPortData 0x71 + +#define rtcRegSeconds 0x00 +#define rtcRegMinutes 0x02 +#define rtcRegHours 0x04 +#define rtcRegDay 0x07 +#define rtcRegMonth 0x08 +#define rtcRegYear 0x09 +#define rtcRegStatusA 0x0A +#define rtcRegStatusB 0x0B + +#define rtcStatusAUip 0x80 + +#define rtcStatusBHourFormat24 0x02 +#define rtcStatusBBinaryMode 0x04 +#define rtcHourPmBit 0x80 + +/* Bounded like ata.c's ataPollTimeout -- a wedged or absent RTC (never + observed on real hardware or under QEMU, but this driver has no way to + independently confirm the chip exists the way ataInit's IDENTIFY does) + returns false/0 instead of spinning forever with interrupts disabled. */ +#define rtcUipTimeout 100000u +#define rtcReadAttempts 8u + +#define rtcAssumedCentury 20 + +static uint8 cmosRead(uint8 reg) +{ + /* Bit 0x80 on the index port disables NMI for the duration of the + read, the same convention the OSDev wiki's own RTC driver uses -- + harmless if the platform ignores it, correct if it does not. */ + outb(rtcPortIndex, (uint8)(reg | 0x80)); + ioWait(); + return inb(rtcPortData); +} + +static bool waitWhileUpdateInProgress(void) +{ + for (uint32 i = 0; i < rtcUipTimeout; i++) { + if ((cmosRead(rtcRegStatusA) & rtcStatusAUip) == 0) + return true; + } + return false; +} + +typedef struct RtcRaw { + uint8 seconds; + uint8 minutes; + uint8 hours; + uint8 day; + uint8 month; + uint8 year; +} RtcRaw; + +static void readRawOnce(RtcRaw* out) +{ + out->seconds = cmosRead(rtcRegSeconds); + out->minutes = cmosRead(rtcRegMinutes); + out->hours = cmosRead(rtcRegHours); + out->day = cmosRead(rtcRegDay); + out->month = cmosRead(rtcRegMonth); + out->year = cmosRead(rtcRegYear); +} + +uint8 rtcBcdToBinary(uint8 bcd) +{ + return (uint8)(((bcd >> 4) & 0x0F) * 10 + (bcd & 0x0F)); +} + +bool rtcReadDateTime(RtcDateTime* out) +{ + RtcRaw a; + RtcRaw b; + bool agree = false; + + for (uint32 attempt = 0; attempt < rtcReadAttempts && !agree; attempt++) { + if (!waitWhileUpdateInProgress()) + return false; + readRawOnce(&a); + if (!waitWhileUpdateInProgress()) + return false; + readRawOnce(&b); + agree = memcmp(&a, &b, sizeof(RtcRaw)) == 0; + } + if (!agree) + return false; + + uint8 statusB = cmosRead(rtcRegStatusB); + bool binaryMode = (statusB & rtcStatusBBinaryMode) != 0; + bool is24Hour = (statusB & rtcStatusBHourFormat24) != 0; + + bool pm = (a.hours & rtcHourPmBit) != 0; + uint8 rawHours = (uint8)(a.hours & (uint8)~rtcHourPmBit); + + uint8 seconds = binaryMode ? a.seconds : rtcBcdToBinary(a.seconds); + uint8 minutes = binaryMode ? a.minutes : rtcBcdToBinary(a.minutes); + uint8 hours = binaryMode ? rawHours : rtcBcdToBinary(rawHours); + uint8 day = binaryMode ? a.day : rtcBcdToBinary(a.day); + uint8 month = binaryMode ? a.month : rtcBcdToBinary(a.month); + uint8 year = binaryMode ? a.year : rtcBcdToBinary(a.year); + + if (!is24Hour) { + if (pm && hours != 12) + hours = (uint8)(hours + 12); + else if (!pm && hours == 12) + hours = 0; + } + + out->year = rtcAssumedCentury * 100 + year; + out->month = month; + out->day = day; + out->hour = hours; + out->minute = minutes; + out->second = seconds; + return true; +} + +/* Howard Hinnant's days_from_civil (proleptic Gregorian calendar), see + http://howardhinnant.github.io/date_algorithms.html -- converts a civil + calendar date into a day count relative to 1970-01-01 with no lookup + tables, correct for the whole int64 range this kernel's RtcDateTime.year + field can carry, including leap years computed structurally (divisible + by 4, not by 100 unless also by 400) rather than by a hand-rolled rule. */ +static int64 daysFromCivil(int64 y, int64 m, int64 d) +{ + y -= (m <= 2) ? 1 : 0; + int64 era = (y >= 0 ? y : y - 399) / 400; + int64 yoe = y - era * 400; /* [0, 399] */ + int64 doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; /* [0, 365] */ + int64 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; /* [0, 146096] */ + return era * 146097 + doe - 719468; +} + +int64 rtcEpochSeconds(const RtcDateTime* dt) +{ + int64 days = daysFromCivil(dt->year, dt->month, dt->day); + return days * 86400 + dt->hour * 3600 + dt->minute * 60 + dt->second; +} + +int64 rtcNow(void) +{ + RtcDateTime dt; + if (!rtcReadDateTime(&dt)) + return 0; + return rtcEpochSeconds(&dt); +} + +/* Howard Hinnant's civil_from_days, the exact inverse of daysFromCivil + above (same source) -- given a day count relative to 1970-01-01, returns + the proleptic Gregorian year/month/day it names. Structurally mirrors + daysFromCivil's era/yoe/doy decomposition in reverse, so the two stay + easy to cross-check against each other by inspection. */ +static void civilFromDays(int64 z, int64* year, int64* month, int64* day) +{ + z += 719468; + int64 era = (z >= 0 ? z : z - 146096) / 146097; + int64 doe = z - era * 146097; /* [0, 146096] */ + int64 yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; /* [0, 399] */ + int64 y = yoe + era * 400; + int64 doy = doe - (365 * yoe + yoe / 4 - yoe / 100); /* [0, 365] */ + int64 mp = (5 * doy + 2) / 153; /* [0, 11] */ + int64 d = doy - (153 * mp + 2) / 5 + 1; /* [1, 31] */ + int64 m = mp + (mp < 10 ? 3 : -9); /* [1, 12] */ + *year = y + (m <= 2 ? 1 : 0); + *month = m; + *day = d; +} + +void rtcCivilFromEpoch(int64 epochSeconds, RtcDateTime* out) +{ + int64 days = epochSeconds / 86400; + int64 secondsOfDay = epochSeconds % 86400; + /* C's integer division truncates toward zero, not toward negative + infinity -- for a negative epochSeconds that does not land exactly + on a day boundary, that rounds `days` the wrong way (toward 1970) + and leaves secondsOfDay negative. Both need shifting back by + exactly one day/86400s together, never one without the other. */ + if (secondsOfDay < 0) { + secondsOfDay += 86400; + days -= 1; + } + + civilFromDays(days, &out->year, &out->month, &out->day); + out->hour = secondsOfDay / 3600; + out->minute = (secondsOfDay % 3600) / 60; + out->second = secondsOfDay % 60; +}