kernel/devices/clock/
rtc.rs

1use core::fmt;
2
3use crate::{cpu, testing};
4
5pub const CURRENT_CENTURY: u16 = 2000 / 100;
6
7pub const RTC_ADDRESS: u16 = 0x70;
8pub const RTC_DATA: u16 = 0x71;
9
10pub const RTC_SECONDS: u8 = 0x00;
11pub const RTC_MINUTES: u8 = 0x02;
12pub const RTC_HOURS: u8 = 0x04;
13pub const RTC_DAY_OF_MONTH: u8 = 0x07;
14pub const RTC_MONTH: u8 = 0x08;
15pub const RTC_YEAR: u8 = 0x09;
16
17pub const RTC_STATUS_A: u8 = 0x0A;
18pub const RTC_STATUS_B: u8 = 0x0B;
19
20pub const SECONDS_PER_MINUTE: u64 = 60;
21pub const SECONDS_PER_HOUR: u64 = 60 * SECONDS_PER_MINUTE;
22pub const SECONDS_PER_DAY: u64 = 24 * SECONDS_PER_HOUR;
23pub const DAYS_PER_MONTH_ARRAY: [u64; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
24
25/// This is used to offset the calculated seconds for all days from unix time
26const UNIX_EPOCH_IN_SECONDS: u64 = 62135596800;
27
28#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
29pub struct RtcTime {
30    pub seconds: u8,
31    pub minutes: u8,
32    pub hours: u8,
33    pub day_of_month: u8,
34    pub month: u8,
35    pub year: u16,
36}
37
38impl RtcTime {
39    pub fn seconds_since_unix_epoch(&self) -> Option<u64> {
40        // unix starts at 1970-01-01 00:00:00
41        if self.year < 1970 {
42            return None;
43        }
44
45        let is_year_leap = self.month > 2
46            && self.year.is_multiple_of(4)
47            && (!self.year.is_multiple_of(100) || self.year.is_multiple_of(400));
48
49        let last_year = (self.year - 1) as u64;
50        let days_in_last_years =
51            (last_year * 365) + (last_year / 4) - (last_year / 100) + (last_year / 400);
52        let this_year_days = DAYS_PER_MONTH_ARRAY[self.month as usize - 1]
53            + self.day_of_month as u64
54            - !is_year_leap as u64;
55
56        let total_days = days_in_last_years + this_year_days;
57
58        let timestamp_since_unix = total_days * SECONDS_PER_DAY
59            + self.hours as u64 * SECONDS_PER_HOUR
60            + self.minutes as u64 * SECONDS_PER_MINUTE
61            + self.seconds as u64;
62
63        Some(timestamp_since_unix - UNIX_EPOCH_IN_SECONDS)
64    }
65}
66
67pub struct Rtc {
68    century_reg: Option<u8>,
69}
70
71impl fmt::Display for RtcTime {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(
74            f,
75            "{year:04}-{month:02}-{day_of_month:02} {hours:02}:{minutes:02}:{seconds:02}",
76            year = self.year,
77            month = self.month,
78            day_of_month = self.day_of_month,
79            hours = self.hours,
80            minutes = self.minutes,
81            seconds = self.seconds,
82        )
83    }
84}
85
86impl Rtc {
87    pub const fn new(century_reg: Option<u8>) -> Self {
88        let century_reg = if let Some(century_reg) = century_reg {
89            if century_reg == 0 {
90                None
91            } else {
92                Some(century_reg)
93            }
94        } else {
95            None
96        };
97        Self { century_reg }
98    }
99
100    fn read_register(&self, reg: u8) -> u8 {
101        unsafe {
102            cpu::io_out(RTC_ADDRESS, reg);
103            cpu::io_in(RTC_DATA)
104        }
105    }
106
107    fn is_updating(&self) -> bool {
108        self.read_register(RTC_STATUS_A) & 0x80 != 0
109    }
110
111    fn is_bcd(&self) -> bool {
112        self.read_register(RTC_STATUS_B) & 0x04 == 0
113    }
114
115    fn get_time_sync(&self) -> (RtcTime, u8) {
116        // keep getting until we get a consistent time
117        let mut t = RtcTime::default();
118        let mut century = 0;
119
120        loop {
121            while self.is_updating() {}
122            let mut century_new = century;
123            let t_new = RtcTime {
124                seconds: self.read_register(RTC_SECONDS),
125                minutes: self.read_register(RTC_MINUTES),
126                hours: self.read_register(RTC_HOURS),
127                day_of_month: self.read_register(RTC_DAY_OF_MONTH),
128                month: self.read_register(RTC_MONTH),
129                year: self.read_register(RTC_YEAR) as u16,
130            };
131
132            if let Some(century_reg) = self.century_reg {
133                century_new = self.read_register(century_reg);
134            }
135
136            // if we get a consistent time, break
137            if t_new == t && century_new == century {
138                break;
139            }
140            t = t_new;
141            century = century_new;
142        }
143        (t, century)
144    }
145
146    pub fn get_time(&self) -> RtcTime {
147        let (mut t, mut century) = self.get_time_sync();
148
149        if self.is_bcd() {
150            t.seconds = (t.seconds & 0x0F) + ((t.seconds / 16) * 10);
151            t.minutes = (t.minutes & 0x0F) + ((t.minutes / 16) * 10);
152            t.hours = ((t.hours & 0x0F) + (((t.hours & 0x70) / 16) * 10)) | (t.hours & 0x80);
153            t.day_of_month = (t.day_of_month & 0x0F) + ((t.day_of_month / 16) * 10);
154            t.month = (t.month & 0x0F) + ((t.month / 16) * 10);
155            t.year = (t.year & 0x0F) + ((t.year / 16) * 10);
156            if self.century_reg.is_some() {
157                century = (century & 0x0F) + ((century / 16) * 10);
158            }
159        }
160        if t.hours & 0x80 != 0 {
161            t.hours = ((t.hours & 0x7F) + 12) % 24;
162        }
163        let century = if self.century_reg.is_some() {
164            century as u16
165        } else {
166            CURRENT_CENTURY
167        };
168        t.year += century * 100;
169
170        t
171    }
172}
173
174#[macro_rules_attribute::apply(testing::test)]
175fn test_seconds_since_unix_epoch() {
176    // to silence clippy type warning
177    type RtcTestData = (u16, u8, u8, u8, u8, u8);
178    const TESTS: [(RtcTestData, u64); 5] = [
179        ((2024, 1, 1, 12, 3, 45), 1704110625),
180        ((1987, 11, 28, 0, 0, 0), 565056000),
181        ((5135, 3, 4, 9, 33, 45), 99883100025),
182        ((2811, 3, 4, 9, 33, 45), 26544792825),
183        ((2404, 2, 29, 9, 33, 45), 13700828025),
184    ];
185
186    for ((year, month, day, hours, minutes, seconds), expected) in TESTS {
187        let t = RtcTime {
188            seconds,
189            minutes,
190            hours,
191            day_of_month: day,
192            month,
193            year,
194        };
195        assert_eq!(t.seconds_since_unix_epoch(), Some(expected));
196    }
197}