Files
nt-lite/code/kernel/hal/hal_rtc.c
T

175 lines
5.5 KiB
C

// HAL RTC support. This file reads the CMOS clock as the fallback wall-clock source.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <intrin.h>
#include <ntlite/status.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_debug.h>
#define CMOS_INDEX_PORT 0x70
#define CMOS_DATA_PORT 0x71
#define RTC_REG_SECONDS 0x00
#define RTC_REG_MINUTES 0x02
#define RTC_REG_HOURS 0x04
#define RTC_REG_WEEKDAY 0x06
#define RTC_REG_DAY 0x07
#define RTC_REG_MONTH 0x08
#define RTC_REG_YEAR 0x09
#define RTC_REG_STATUS_A 0x0A
#define RTC_REG_STATUS_B 0x0B
static uint8_t gCenturyRegister;
// Implements the rtc Read operation.
static uint8_t RtcRead(uint8_t reg) {
__outbyte(CMOS_INDEX_PORT, (uint8_t)(reg | 0x80u)); // keep NMI masked while selecting.
return __inbyte(CMOS_DATA_PORT);
}
// Implements the rtc Update In Progress operation.
static bool RtcUpdateInProgress(void) {
return (RtcRead(RTC_REG_STATUS_A) & 0x80u) != 0;
}
// Implements the rtc Bcd To Binary operation.
static uint8_t RtcBcdToBinary(uint8_t value) {
return (uint8_t)(((value >> 4) * 10u) + (value & 0x0Fu));
}
// Implements the rtc Read Stable operation.
static bool RtcReadStable(HAL_RTC_TIME* out) {
if (!out) return false;
uint8_t sec1 = 0, min1 = 0, hour1 = 0, day1 = 0, month1 = 0, year1 = 0, century1 = 0;
uint8_t sec2 = 0, min2 = 0, hour2 = 0, day2 = 0, month2 = 0, year2 = 0, century2 = 0;
bool stable = false;
for (uint32_t tries = 0; tries < 100000u; tries++) {
while (RtcUpdateInProgress()) _mm_pause();
sec1 = RtcRead(RTC_REG_SECONDS);
min1 = RtcRead(RTC_REG_MINUTES);
hour1 = RtcRead(RTC_REG_HOURS);
day1 = RtcRead(RTC_REG_DAY);
month1 = RtcRead(RTC_REG_MONTH);
year1 = RtcRead(RTC_REG_YEAR);
century1 = gCenturyRegister ? RtcRead(gCenturyRegister) : 0;
while (RtcUpdateInProgress()) _mm_pause();
sec2 = RtcRead(RTC_REG_SECONDS);
min2 = RtcRead(RTC_REG_MINUTES);
hour2 = RtcRead(RTC_REG_HOURS);
day2 = RtcRead(RTC_REG_DAY);
month2 = RtcRead(RTC_REG_MONTH);
year2 = RtcRead(RTC_REG_YEAR);
century2 = gCenturyRegister ? RtcRead(gCenturyRegister) : 0;
if (sec1 == sec2 && min1 == min2 && hour1 == hour2 && day1 == day2 &&
month1 == month2 && year1 == year2 && century1 == century2) {
stable = true;
break;
}
}
if (!stable) return false;
uint8_t statusB = RtcRead(RTC_REG_STATUS_B);
bool binary = (statusB & 0x04u) != 0;
bool hour24 = (statusB & 0x02u) != 0;
bool pm = (!hour24 && (hour1 & 0x80u) != 0);
if (!binary) {
sec1 = RtcBcdToBinary(sec1);
min1 = RtcBcdToBinary(min1);
hour1 = (uint8_t)((hour1 & 0x80u) | RtcBcdToBinary((uint8_t)(hour1 & 0x7Fu)));
day1 = RtcBcdToBinary(day1);
month1 = RtcBcdToBinary(month1);
year1 = RtcBcdToBinary(year1);
if (gCenturyRegister) century1 = RtcBcdToBinary(century1);
}
if (!hour24) {
hour1 = (uint8_t)(hour1 & 0x7Fu);
if (pm && hour1 < 12u) hour1 = (uint8_t)(hour1 + 12u);
if (!pm && hour1 == 12u) hour1 = 0;
}
uint16_t year;
if (gCenturyRegister && century1) {
year = (uint16_t)((uint16_t)century1 * 100u + year1);
} else {
year = (uint16_t)((year1 < 70u) ? (2000u + year1) : (1900u + year1));
}
out->Year = year;
out->Month = month1;
out->Day = day1;
out->Hour = hour1;
out->Minute = min1;
out->Second = sec1;
out->Nanosecond = 0;
out->Flags = HAL_RTC_TIME_FLAG_VALID | HAL_RTC_TIME_FLAG_RTC;
out->Reserved = 0;
return true;
}
// Implements the rtc Leap Year operation.
static bool RtcLeapYear(uint16_t year) {
return ((year % 4u) == 0u && (year % 100u) != 0u) || ((year % 400u) == 0u);
}
// Implements the rtc Days In Month operation.
static uint8_t RtcDaysInMonth(uint16_t year, uint8_t month) {
static const uint8_t days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
if (month < 1u || month > 12u) return 0;
if (month == 2u && RtcLeapYear(year)) return 29;
return days[month - 1u];
}
// Implements the rtc Valid operation.
static bool RtcValid(const HAL_RTC_TIME* t) {
if (!t) return false;
if (t->Year < 1970u || t->Year > 9999u) return false;
if (t->Month < 1u || t->Month > 12u) return false;
uint8_t dim = RtcDaysInMonth(t->Year, t->Month);
if (t->Day < 1u || t->Day > dim) return false;
if (t->Hour > 23u || t->Minute > 59u || t->Second > 59u) return false;
return true;
}
// Implements the rtc Query Time operation.
static KSTATUS HalRtcQueryTime(HAL_RTC_TIME* outTime) {
if (!outTime) return NTLSTATUS_INVALID_PARAMETER;
HAL_RTC_TIME t;
memset(&t, 0, sizeof(t));
if (!RtcReadStable(&t) || !RtcValid(&t))
return NTLSTATUS_DEVICE_ERROR;
*outTime = t;
return NTLSTATUS_SUCCESS;
}
// Implements the rtc Set Century Register operation.
void HalRtcSetCenturyRegister(uint8_t centuryRegister) {
gCenturyRegister = centuryRegister;
}
// Implements the rtc Initialize operation.
void HalRtcInitialize(HAL_RTC* out) {
if (!out) return;
out->QueryTime = HalRtcQueryTime;
HAL_RTC_TIME t;
if (HalRtcQueryTime(&t) == NTLSTATUS_SUCCESS) {
HalDbgPrint("HAL: RTC %04u-%02u-%02u %02u:%02u:%02u century-reg=0x%02x\n",
t.Year, t.Month, t.Day, t.Hour, t.Minute, t.Second,
gCenturyRegister);
} else {
HalDbgPrint("HAL: RTC unavailable or invalid\n");
}
}