1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
#include <assert.h>
#include <cpu/idt.h>
#include <cpu/io.h>
#include <drivers/cmos.h>
/*
* Why the fuck would somebody go through the effort of converting their
* seconds counter to minutes, hours IN PM AND AM AND THE DAYS OF THE MONTH???
* It is obvious that the first thing any person reading these values
* will do is convert them into seconds SINCE THAT IS THE ONLY SANE WAY
* OF STORING TIME FOR COMPUTERS.
*
* Register Contents Range
* 0x00 Seconds 0–59
* 0x02 Minutes 0–59
* 0x04 Hours 0–23 in 24-hour mode,
* 1–12 in 12-hour mode, highest bit set if pm
* 0x06 Weekday 1–7, Sunday = 1
* 0x07 Day of Month 1–31
* 0x08 Month 1–12
* 0x09 Year 0–99
* 0x32 Century (maybe) 19–20?
* 0x0A Status Register A
* 0x0B Status Register B
*/
#define CMOS_SECONDS 0x00
#define CMOS_MINUTES 0x02
#define CMOS_HOURS 0x04
#define CMOS_WEEKDAY 0x06
#define CMOS_DAY_OF_MONTH 0x07
#define CMOS_MONTH 0x08
#define CMOS_YEAR 0x09
#define CMOS_CENTURY 0x32
#define CMOS_REG_B 0x0B
#define NMI_disable_bit 1
static u8 cmos_get_register(u8 reg) {
outb(0x70, (NMI_disable_bit << 7) | reg);
return inb(0x71);
}
static void cmos_set_register(u8 reg, u8 value) {
outb(0x70, (NMI_disable_bit << 7) | reg);
outb(0x71, value);
}
static u8 binary_to_bcd(int binary) {
int result = 0;
int shift = 0;
while (binary > 0) {
result |= (binary % 10) << (shift++ << 2);
binary /= 10;
}
return result;
}
static u8 bcd_to_binary(u8 bcd) {
return ((bcd / 16) * 10) + (bcd & 0xf);
}
u8 days_in_month[] = {
31, // January: 31 days
28, // February: 28 days and 29 in every leap year
31, // March: 31 days
30, // April: 30 days
31, // May: 31 days
30, // June: 30 days
31, // July: 31 days
31, // August: 31 days
30, // September: 30 days
31, // October: 31 days
30, // November: 30 days
31, // December: 31 days
};
static i64 cmos_get_time(void);
static void cmos_set_time(i64 time);
int cmos_has_command = 0;
int cmos_command_is_read = 0;
int *cmos_done_ptr = NULL;
i64 *cmos_time_ptr = NULL;
void cmos_handler(reg_t *reg) {
(void)reg;
if (!cmos_has_command) {
goto cmos_handler_exit;
}
if (cmos_command_is_read) {
*cmos_time_ptr = cmos_get_time();
*cmos_done_ptr = 1;
goto cmos_handler_exit;
}
if (!cmos_command_is_read) {
cmos_set_time(*cmos_time_ptr);
*cmos_done_ptr = 1;
goto cmos_handler_exit;
}
cmos_handler_exit:
cmos_has_command = 0;
u8 reg_b = cmos_get_register(CMOS_REG_B);
reg_b &= ~0x40; // Disable interrupts
cmos_set_register(CMOS_REG_B, reg_b);
EOI(0x8);
}
void cmos_init(void) {
install_handler((interrupt_handler)cmos_handler, INT_32_INTERRUPT_GATE(0x0),
0x28);
}
int cmos_start_call(int is_read, int *done, i64 *time) {
if (cmos_has_command) {
return 0;
}
*done = 0;
cmos_done_ptr = done;
cmos_time_ptr = time;
cmos_command_is_read = is_read;
cmos_has_command = 1;
u8 reg_b = cmos_get_register(CMOS_REG_B);
reg_b |= 0x40; // Enable interrupts
cmos_set_register(CMOS_REG_B, reg_b);
return 1;
}
// This function returns the current unix timestamp from the CMOS RTC
// TODO: It currently makes the assumption that time travel is not possible and
// as a result will not give negative values. This support should maybe be added
// to prepare ourselves for the past.
static i64 cmos_get_time(void) {
u8 seconds = cmos_get_register(CMOS_SECONDS);
u8 minutes = cmos_get_register(CMOS_MINUTES);
u8 reg_b = cmos_get_register(0x0B);
u8 hours = cmos_get_register(CMOS_HOURS);
u8 day_of_month = cmos_get_register(CMOS_DAY_OF_MONTH);
u8 month = cmos_get_register(CMOS_MONTH);
u8 incomplete_year = cmos_get_register(CMOS_YEAR);
u8 century = cmos_get_register(CMOS_CENTURY);
if (!(reg_b & (1 << 2))) {
hours = bcd_to_binary(hours);
day_of_month = bcd_to_binary(day_of_month);
month = bcd_to_binary(month);
incomplete_year = bcd_to_binary(incomplete_year);
century = bcd_to_binary(century);
}
if (!(reg_b & (1 << 1))) {
// TODO: Add support for AM/PM so the americans can use this
return 0;
}
u16 year = century * 100 + incomplete_year;
u64 second_sum = 0;
// TODO: There is probably a more clever algorithm for this
const u32 seconds_in_a_day = 24 * 60 * 60;
for (int i = 1970; i < year; i++) {
second_sum += 365 * seconds_in_a_day;
if (0 == i % 4) {
second_sum += seconds_in_a_day;
}
}
for (int i = 0; i < (int)month - 1
/*-1 to account for it being 1 indexed*/;
i++) {
second_sum += days_in_month[i] * seconds_in_a_day;
int is_leap_year = 0 == (year % 4);
if (is_leap_year && 1 == i /*February*/) {
second_sum += seconds_in_a_day;
}
}
second_sum += (day_of_month - 1) * seconds_in_a_day;
second_sum += hours * 60 * 60;
second_sum += minutes * 60;
second_sum += seconds;
return second_sum;
}
static void cmos_set_time(i64 time) {
assert(time > 0); // TODO: Add support for time travelers
u8 reg_b = cmos_get_register(0x0B);
u8 seconds = 0;
u8 minutes = 0;
u8 hours = 0;
const u32 seconds_in_a_day = 24 * 60 * 60;
// 1 January 1970 was a thursday, that is why there is a +4
u8 weekday = ((((time / seconds_in_a_day) + 4)) % 7) + 1;
u8 day_of_month = 0;
u8 month = 0;
u16 year = 1970;
if (!(reg_b & (1 << 1))) {
// TODO: Add support for AM/PM so the americans can use this
return;
}
// Keep adding years for as long as possible
for (;;) {
u32 s = 365 * seconds_in_a_day;
if (0 == year % 4) {
s += seconds_in_a_day;
if (0 == year % 100 && 0 != year % 400) {
s -= seconds_in_a_day;
}
}
if (s > time) {
break;
}
year++;
time -= s;
}
// Keep adding months
for (;;) {
u32 s = days_in_month[month] * seconds_in_a_day;
if (0 == (year % 4) && 1 == month && (0 != year % 100 || 0 == year % 400)) {
s += seconds_in_a_day;
}
if (s > time) {
break;
}
month++;
time -= s;
}
month++;
day_of_month = time / seconds_in_a_day;
day_of_month++;
time %= seconds_in_a_day;
hours = time / (60 * 60);
time %= 60 * 60;
minutes = time / (60);
time %= 60;
seconds = time;
u8 s_year = year % 100;
u8 century = (year - (year % 100)) / 100;
if (!(reg_b & (1 << 2))) {
seconds = binary_to_bcd(seconds);
minutes = binary_to_bcd(minutes);
hours = binary_to_bcd(hours);
day_of_month = binary_to_bcd(day_of_month);
month = binary_to_bcd(month);
weekday = binary_to_bcd(weekday);
s_year = binary_to_bcd(s_year);
century = binary_to_bcd(century);
}
cmos_set_register(CMOS_SECONDS, seconds);
cmos_set_register(CMOS_MINUTES, minutes);
cmos_set_register(CMOS_HOURS, hours);
cmos_set_register(CMOS_DAY_OF_MONTH, day_of_month);
cmos_set_register(CMOS_MONTH, month);
cmos_set_register(CMOS_YEAR, s_year);
cmos_set_register(CMOS_CENTURY, century);
cmos_set_register(CMOS_WEEKDAY, weekday);
}
|