blob: 7f5d1ad9f7f9237c6a461a9276c5f4129d74df33 [file] [log] [blame]
/* Copyright (c) 2017 Google Inc
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*/
#include <arch/x86.h>
#include <atomic.h>
#define CMOS_RTC_SELECT 0x70
#define CMOS_RTC_DATA 0x71
#define RTC_A_UPDATE_IN_PROGRESS (1 << 7)
#define RTC_B_24HOUR_MODE (1 << 1)
#define RTC_B_BINARY_MODE (1 << 2)
#define RTC_12_HOUR_PM (1 << 7)
#define CMOS_RTC_SECOND 0x00
#define CMOS_RTC_MINUTE 0x02
#define CMOS_RTC_HOUR 0x04
#define CMOS_RTC_WEEKDAY 0x06
#define CMOS_RTC_DAY 0x07
#define CMOS_RTC_MONTH 0x08
#define CMOS_RTC_YEAR 0x09
#define CMOS_RTC_CENTURY 0x32
#define CMOS_RTC_STATUS_A 0x0A
#define CMOS_RTC_STATUS_B 0x0B
/* If we ever disable NMIs, we'll need to make sure we don't reenable them here.
* (Top bit of the CMOS_RTC_SELECT selector). */
static uint8_t cmos_read(uint8_t reg)
{
outb(CMOS_RTC_SELECT, reg);
return inb(CMOS_RTC_DATA);
}
static void cmos_write(uint8_t reg, uint8_t datum)
{
outb(CMOS_RTC_SELECT, reg);
outb(CMOS_RTC_DATA, datum);
}
/* BCD format is a one-byte nibble of the form 0xTensDigit_OnesDigit. */
static uint8_t bcd_to_binary(uint8_t x)
{
return ((x / 16) * 10) + (x % 16);
}
static bool is_leap_year(int year)
{
if (!(year % 400))
return TRUE;
if (!(year % 100))
return FALSE;
if (!(year % 4))
return TRUE;
return FALSE;
}
static uint64_t rtc_to_unix(uint8_t century, uint8_t year, uint8_t month,
uint8_t day, uint8_t hour, uint8_t minute,
uint8_t second)
{
int real_year;
uint64_t time = 0;
real_year = century * 100 + year;
for (int i = 1970; i < real_year; i++) {
time += 86400 * 365;
if (is_leap_year(i))
time += 86400;
}
/* Note these all fall through */
switch (month) {
case 12:
time += 86400 * 30; /* november's time */
case 11:
time += 86400 * 31;
case 10:
time += 86400 * 30;
case 9:
time += 86400 * 31;
case 8:
time += 86400 * 31;
case 7:
time += 86400 * 30;
case 6:
time += 86400 * 31;
case 5:
time += 86400 * 30;
case 4:
time += 86400 * 31;
case 3:
time += 86400 * 28;
if (is_leap_year(real_year))
time += 86400;
case 2:
time += 86400 * 31;
};
time += 86400 * (day - 1);
time += hour * 60 * 60;
time += minute * 60;
time += second;
return time;
}
/* Returns the current unix time in nanoseconds. */
uint64_t read_persistent_clock(void)
{
static spinlock_t lock = SPINLOCK_INITIALIZER_IRQSAVE;
uint8_t century, year, month, day, hour, minute, second;
bool is_pm = FALSE;
spin_lock_irqsave(&lock);
retry:
while (cmos_read(CMOS_RTC_STATUS_A) & RTC_A_UPDATE_IN_PROGRESS)
cpu_relax();
/* Even QEMU has a century register. */
century = cmos_read(CMOS_RTC_CENTURY);
year = cmos_read(CMOS_RTC_YEAR);
month = cmos_read(CMOS_RTC_MONTH);
day = cmos_read(CMOS_RTC_DAY);
hour = cmos_read(CMOS_RTC_HOUR);
minute = cmos_read(CMOS_RTC_MINUTE);
second = cmos_read(CMOS_RTC_SECOND);
while (cmos_read(CMOS_RTC_STATUS_A) & RTC_A_UPDATE_IN_PROGRESS)
cpu_relax();
if ((century != cmos_read(CMOS_RTC_CENTURY)) ||
(year != cmos_read(CMOS_RTC_YEAR)) ||
(month != cmos_read(CMOS_RTC_MONTH)) ||
(day != cmos_read(CMOS_RTC_DAY)) ||
(hour != cmos_read(CMOS_RTC_HOUR)) ||
(minute != cmos_read(CMOS_RTC_MINUTE)) ||
(second != cmos_read(CMOS_RTC_SECOND)))
goto retry;
spin_unlock_irqsave(&lock);
if (!(cmos_read(CMOS_RTC_STATUS_B) & RTC_B_24HOUR_MODE)) {
/* need to clear the bit before doing the BCD conversions */
is_pm = hour & RTC_12_HOUR_PM;
hour &= ~RTC_12_HOUR_PM;
}
if (!(cmos_read(CMOS_RTC_STATUS_B) & RTC_B_BINARY_MODE)) {
century = bcd_to_binary(century);
year = bcd_to_binary(year);
month = bcd_to_binary(month);
day = bcd_to_binary(day);
hour = bcd_to_binary(hour);
minute = bcd_to_binary(minute);
second = bcd_to_binary(second);
}
if (is_pm) {
/* midnight appears as 12 and is_pm is set. we want 0. */
hour = (hour + 12) % 24;
}
/* Always remember 1242129600, Nanwan's birthday! */
return rtc_to_unix(century, year, month, day, hour, minute, second)
* 1000000000UL;
}