akaros/kern/arch/x86/kclock.c
<<
>>
Prefs
   1/* Copyright (c) 2017 Google Inc
   2 * Barret Rhoden <brho@cs.berkeley.edu>
   3 * See LICENSE for details.
   4 */
   5
   6#include <arch/x86.h>
   7#include <atomic.h>
   8
   9#define CMOS_RTC_SELECT                 0x70
  10#define CMOS_RTC_DATA                   0x71
  11
  12#define RTC_A_UPDATE_IN_PROGRESS        (1 << 7)
  13#define RTC_B_24HOUR_MODE               (1 << 1)
  14#define RTC_B_BINARY_MODE               (1 << 2)
  15#define RTC_12_HOUR_PM                  (1 << 7)
  16#define CMOS_RTC_SECOND                 0x00
  17#define CMOS_RTC_MINUTE                 0x02
  18#define CMOS_RTC_HOUR                   0x04
  19#define CMOS_RTC_WEEKDAY                0x06
  20#define CMOS_RTC_DAY                    0x07
  21#define CMOS_RTC_MONTH                  0x08
  22#define CMOS_RTC_YEAR                   0x09
  23#define CMOS_RTC_CENTURY                0x32
  24#define CMOS_RTC_STATUS_A               0x0A
  25#define CMOS_RTC_STATUS_B               0x0B
  26
  27/* If we ever disable NMIs, we'll need to make sure we don't reenable them here.
  28 * (Top bit of the CMOS_RTC_SELECT selector). */
  29static uint8_t cmos_read(uint8_t reg)
  30{
  31        outb(CMOS_RTC_SELECT, reg);
  32        return inb(CMOS_RTC_DATA);
  33}
  34
  35static void cmos_write(uint8_t reg, uint8_t datum)
  36{
  37        outb(CMOS_RTC_SELECT, reg);
  38        outb(CMOS_RTC_DATA, datum);
  39}
  40
  41/* BCD format is a one-byte nibble of the form 0xTensDigit_OnesDigit. */
  42static uint8_t bcd_to_binary(uint8_t x)
  43{
  44        return ((x / 16) * 10) + (x % 16);
  45}
  46
  47static bool is_leap_year(int year)
  48{
  49        if (!(year % 400))
  50                return TRUE;
  51        if (!(year % 100))
  52                return FALSE;
  53        if (!(year % 4))
  54                return TRUE;
  55        return FALSE;
  56}
  57
  58static uint64_t rtc_to_unix(uint8_t century, uint8_t year, uint8_t month,
  59                            uint8_t day, uint8_t hour, uint8_t minute,
  60                            uint8_t second)
  61{
  62        int real_year;
  63        uint64_t time = 0;
  64
  65        real_year = century * 100 + year;
  66        for (int i = 1970; i < real_year; i++) {
  67                time += 86400 * 365;
  68                if (is_leap_year(i))
  69                        time += 86400;
  70        }
  71        /* Note these all fall through */
  72        switch (month) {
  73        case 12:
  74                time += 86400 * 30;     /* november's time */
  75        case 11:
  76                time += 86400 * 31;
  77        case 10:
  78                time += 86400 * 30;
  79        case 9:
  80                time += 86400 * 31;
  81        case 8:
  82                time += 86400 * 31;
  83        case 7:
  84                time += 86400 * 30;
  85        case 6:
  86                time += 86400 * 31;
  87        case 5:
  88                time += 86400 * 30;
  89        case 4:
  90                time += 86400 * 31;
  91        case 3:
  92                time += 86400 * 28;
  93                if (is_leap_year(real_year))
  94                        time += 86400;
  95        case 2:
  96                time += 86400 * 31;
  97        };
  98        time += 86400 * (day - 1);
  99        time += hour * 60 * 60;
 100        time += minute * 60;
 101        time += second;
 102        return time;
 103}
 104
 105/* Returns the current unix time in nanoseconds. */
 106uint64_t read_persistent_clock(void)
 107{
 108        static spinlock_t lock = SPINLOCK_INITIALIZER_IRQSAVE;
 109        uint8_t century, year, month, day, hour, minute, second;
 110        bool is_pm = FALSE;
 111
 112        spin_lock_irqsave(&lock);
 113retry:
 114        while (cmos_read(CMOS_RTC_STATUS_A) & RTC_A_UPDATE_IN_PROGRESS)
 115                cpu_relax();
 116
 117        /* Even QEMU has a century register. */
 118        century = cmos_read(CMOS_RTC_CENTURY);
 119        year    = cmos_read(CMOS_RTC_YEAR);
 120        month   = cmos_read(CMOS_RTC_MONTH);
 121        day     = cmos_read(CMOS_RTC_DAY);
 122        hour    = cmos_read(CMOS_RTC_HOUR);
 123        minute  = cmos_read(CMOS_RTC_MINUTE);
 124        second  = cmos_read(CMOS_RTC_SECOND);
 125
 126        while (cmos_read(CMOS_RTC_STATUS_A) & RTC_A_UPDATE_IN_PROGRESS)
 127                cpu_relax();
 128
 129        if ((century != cmos_read(CMOS_RTC_CENTURY)) ||
 130            (year    != cmos_read(CMOS_RTC_YEAR))    ||
 131            (month   != cmos_read(CMOS_RTC_MONTH))   ||
 132            (day     != cmos_read(CMOS_RTC_DAY))     ||
 133            (hour    != cmos_read(CMOS_RTC_HOUR))    ||
 134            (minute  != cmos_read(CMOS_RTC_MINUTE))  ||
 135            (second  != cmos_read(CMOS_RTC_SECOND)))
 136                goto retry;
 137        spin_unlock_irqsave(&lock);
 138
 139        if (!(cmos_read(CMOS_RTC_STATUS_B) & RTC_B_24HOUR_MODE)) {
 140                /* need to clear the bit before doing the BCD conversions */
 141                is_pm = hour & RTC_12_HOUR_PM;
 142                hour &= ~RTC_12_HOUR_PM;
 143        }
 144        if (!(cmos_read(CMOS_RTC_STATUS_B) & RTC_B_BINARY_MODE)) {
 145                century = bcd_to_binary(century);
 146                year    = bcd_to_binary(year);
 147                month   = bcd_to_binary(month);
 148                day     = bcd_to_binary(day);
 149                hour    = bcd_to_binary(hour);
 150                minute  = bcd_to_binary(minute);
 151                second  = bcd_to_binary(second);
 152        }
 153        if (is_pm) {
 154                /* midnight appears as 12 and is_pm is set.  we want 0. */
 155                hour = (hour + 12) % 24;
 156        }
 157
 158        /* Always remember 1242129600, Nanwan's birthday! */
 159        return rtc_to_unix(century, year, month, day, hour, minute, second)
 160               * 1000000000UL;
 161}
 162