Rewrite the system clock to use timestamps instead of calendar time
[monolithium.git] / kernel / src / clock.c
1 /*
2  * clock.c
3  *
4  * Copyright (C) 2018 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <clock.h>
21 #include <exception.h>
22 #include <syscalls.h>
23 #include <irq.h>
24 #include <user.h>
25 #include <timer.h>
26 #include <log.h>
27
28 #define RTC_IRQ 8
29
30 #define RTC_STA_UPDATING (1 << 7)
31
32 #define RTC_HOURS_PM_BIT (1 << 7)
33
34 #define RTC_STB_DST          (1 << 0)
35 #define RTC_STB_24HOUR       (1 << 1)
36 #define RTC_STB_BINARY       (1 << 2)
37 #define RTC_STB_SQUARE_WAVE  (1 << 3)
38 #define RTC_STB_UPDATE_INT   (1 << 4)
39 #define RTC_STB_ALARM_INT    (1 << 5)
40 #define RTC_STB_PERIODIC_INT (1 << 6)
41 #define RTC_STB_DISABLE      (1 << 7)
42
43 #define RTC_SECONDS_REG 0x00
44 #define RTC_MINUTES_REG 0x02
45 #define RTC_HOURS_REG 0x04
46 #define RTC_DAY_REG 0x07
47 #define RTC_MONTH_REG 0x08
48 #define RTC_YEAR_REG 0x09
49 #define RTC_STA_REG 0x0A
50 #define RTC_STB_REG 0x0B
51 #define RTC_STC_REG 0x0C
52
53 #define BCD_TO_BINARY(x) (((x) >> 4) * 10 + ((x) & 0x0F))
54
55 typedef struct
56 {
57     byte_t second, minute, hour, day, month, year;
58 } rtc_time_t;
59
60 static clock_time_t clock_startup_timestamp = 0;
61 static clock_time_t clock_adjustment = 0;
62 static byte_t rtc_settings;
63
64 static void read_rtc(rtc_time_t *time)
65 {
66     critical_t critical;
67     enter_critical(&critical);
68
69     cpu_write_port_byte(CMOS_CMD_PORT, RTC_STA_REG);
70     for (;;) if (!(cpu_read_port_byte(CMOS_DATA_PORT) & RTC_STA_UPDATING)) break;
71
72     cpu_write_port_byte(CMOS_CMD_PORT, RTC_SECONDS_REG);
73     time->second = cpu_read_port_byte(CMOS_DATA_PORT);
74     cpu_write_port_byte(CMOS_CMD_PORT, RTC_MINUTES_REG);
75     time->minute = cpu_read_port_byte(CMOS_DATA_PORT);
76     cpu_write_port_byte(CMOS_CMD_PORT, RTC_HOURS_REG);
77     time->hour = cpu_read_port_byte(CMOS_DATA_PORT);
78     cpu_write_port_byte(CMOS_CMD_PORT, RTC_DAY_REG);
79     time->day = cpu_read_port_byte(CMOS_DATA_PORT);
80     cpu_write_port_byte(CMOS_CMD_PORT, RTC_MONTH_REG);
81     time->month = cpu_read_port_byte(CMOS_DATA_PORT);
82     cpu_write_port_byte(CMOS_CMD_PORT, RTC_YEAR_REG);
83     time->year = cpu_read_port_byte(CMOS_DATA_PORT);
84
85     leave_critical(&critical);
86 }
87
88 sysret_t syscall_clock_get_time(clock_time_t *time)
89 {
90     if (get_previous_mode() != KERNEL_MODE && !check_usermode(time, sizeof(clock_time_t))) return ERR_BADPTR;
91     int64_t microseconds = (int64_t)(timer_get_nanoseconds() / 1000ULL);
92
93     EH_TRY
94     {
95         *time = clock_startup_timestamp + clock_adjustment + microseconds;
96     }
97     EH_CATCH
98     {
99         EH_ESCAPE(return ERR_BADPTR);
100     }
101     EH_DONE;
102
103     return ERR_SUCCESS;
104 }
105
106 sysret_t syscall_clock_set_time(clock_time_t *time)
107 {
108     clock_time_t safe_time;
109
110     if (get_previous_mode() == USER_MODE)
111     {
112         if (!check_usermode(time, sizeof(clock_time_t))) return ERR_BADPTR;
113
114         EH_TRY safe_time = *time;
115         EH_CATCH EH_ESCAPE(return ERR_BADPTR);
116         EH_DONE;
117
118         if (!check_privileges(PRIVILEGE_SET_TIME)) return ERR_FORBIDDEN;
119     }
120     else
121     {
122         safe_time = *time;
123     }
124
125     int64_t microseconds = (int64_t)(timer_get_nanoseconds() / 1000ULL);
126     clock_time_t new_timestamp = safe_time;
127     clock_time_t old_timestamp = clock_startup_timestamp + clock_adjustment + microseconds;
128     clock_adjustment = new_timestamp - old_timestamp;
129
130     // TODO: Update the RTC
131     return ERR_SUCCESS;
132 }
133
134 void clock_init()
135 {
136     cpu_write_port_byte(CMOS_CMD_PORT, RTC_STB_REG);
137     rtc_settings = cpu_read_port_byte(CMOS_DATA_PORT);
138
139     rtc_time_t time;
140     clock_time_t adjustment;
141
142     for (;;)
143     {
144         rtc_time_t check;
145         read_rtc(&check);
146         adjustment = (clock_time_t)(timer_get_nanoseconds() / 1000ULL);
147         read_rtc(&time);
148         if (memcmp(&time, &check, sizeof(rtc_time_t))) break;
149     }
150
151     if (!(rtc_settings & RTC_STB_BINARY))
152     {
153         time.second = BCD_TO_BINARY(time.second);
154         time.minute = BCD_TO_BINARY(time.minute);
155         time.hour = BCD_TO_BINARY(time.hour & ~RTC_HOURS_PM_BIT) | (time.hour & RTC_HOURS_PM_BIT);
156         time.day = BCD_TO_BINARY(time.day);
157         time.month = BCD_TO_BINARY(time.month);
158         time.year = BCD_TO_BINARY(time.year);
159     }
160
161     if (!(rtc_settings & RTC_STB_24HOUR))
162     {
163         time.hour = (time.hour & RTC_HOURS_PM_BIT) ? ((time.hour & ~RTC_HOURS_PM_BIT) % 12) + 12 : time.hour % 12;
164     }
165
166     TRACE("Current time: 20%02u-%02u-%02u %02u:%02u:%02u\n",
167           time.year,
168           time.month,
169           time.day,
170           time.hour,
171           time.minute,
172           time.second);
173
174     int i;
175     clock_startup_timestamp = 0;
176
177     for (i = 1980; i < (int)time.year + 2000; i++)
178     {
179         clock_startup_timestamp += (!(i % 400) || (!(i % 4) && (i % 100))) ? 31622400000000LL : 31536000000000LL;
180     }
181
182     int month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
183     if (!(i % 400) || (!(i % 4) && (i % 100))) month_days[1]++;
184
185     for (i = 1; i < time.month; i++)
186     {
187         clock_startup_timestamp += month_days[i - 1] * 86400000000LL;
188     }
189
190     clock_startup_timestamp += (time.day - 1) * 86400000000LL;
191     clock_startup_timestamp += time.hour * 3600000000LL;
192     clock_startup_timestamp += time.minute * 60000000LL;
193     clock_startup_timestamp += time.second * 1000000LL;
194     clock_startup_timestamp -= adjustment;
195     TRACE("Startup timestamp: %lld\n", clock_startup_timestamp);
196 }