feat: add cmosRead and waitWhileUpdateInProgress
This commit is contained in:
1 parent
6d4ec19efb
commit
f31dd38429
1 file changed
+192
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user