kconfig: use pkg-config for ncurses detection
[akaros.git] / kern / arch / x86 / kclock.c
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). */
29 static uint8_t cmos_read(uint8_t reg)
30 {
31         outb(CMOS_RTC_SELECT, reg);
32         return inb(CMOS_RTC_DATA);
33 }
34
35 static 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. */
42 static uint8_t bcd_to_binary(uint8_t x)
43 {
44         return ((x / 16) * 10) + (x % 16);
45 }
46
47 static 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
58 static 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. */
106 uint64_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);
113 retry:
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 }