193 lines
6.3 KiB
C
193 lines
6.3 KiB
C
#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;
|
|
}
|