Skip to main content

strat9_kernel/hardware/timer/
rtc.rs

1// RTC (Real Time Clock) Driver
2// Reference: MC146818A RTC chip
3//
4// Features:
5// - Current time reading (BCD and binary modes)
6// - CMOS RAM access
7// - Update interrupt handling
8// - Alarm support
9
10#![allow(dead_code)]
11
12use crate::arch::x86_64::io::{inb, outb};
13use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
14use spin::Mutex;
15
16// CMOS ports
17const CMOS_ADDR_PORT: u16 = 0x70;
18const CMOS_DATA_PORT: u16 = 0x71;
19
20// CMOS registers
21const CMOS_REG_SECOND: u8 = 0x00;
22const CMOS_REG_MINUTE: u8 = 0x02;
23const CMOS_REG_HOUR: u8 = 0x04;
24const CMOS_REG_WEEKDAY: u8 = 0x06;
25const CMOS_REG_DAY: u8 = 0x07;
26const CMOS_REG_MONTH: u8 = 0x08;
27const CMOS_REG_YEAR: u8 = 0x09;
28const CMOS_REG_STATUS_A: u8 = 0x0A;
29const CMOS_REG_STATUS_B: u8 = 0x0B;
30const CMOS_REG_STATUS_C: u8 = 0x0C;
31
32// Status register A bits
33const STATUS_A_UIP: u8 = 0x80; // Update In Progress
34
35// Status register B bits
36const STATUS_B_PIE: u8 = 0x40; // Periodic interrupt enable
37const STATUS_B_UIE: u8 = 0x10; // Update interrupt enable
38const STATUS_B_DM: u8 = 0x04; // Data mode (1=binary, 0=BCD)
39const STATUS_B_24H: u8 = 0x02; // 24-hour mode
40
41// Status register C bits (read-only, clears interrupts)
42// RTC frequency options (Hz)
43const RTC_FREQ_1024: u8 = 6; // 1024 Hz
44const RTC_FREQ_256: u8 = 8; // 256 Hz
45const RTC_FREQ_64: u8 = 10; // 64 Hz
46const RTC_FREQ_16: u8 = 12; // 16 Hz
47const RTC_FREQ_4: u8 = 14; // 4 Hz
48const RTC_FREQ_1: u8 = 15; // 1 Hz
49
50const CENTURY_REG_CANDIDATES: &[u8] = &[0x32, 0x37];
51
52/// RTC date/time structure
53#[derive(Clone, Copy, Debug)]
54pub struct RtcDateTime {
55    pub second: u8,
56    pub minute: u8,
57    pub hour: u8,
58    pub weekday: u8, // 0=Sunday, 6=Saturday
59    pub day: u8,
60    pub month: u8,
61    pub year: u16,
62    pub century: u16,
63}
64
65impl RtcDateTime {
66    /// Creates a new instance.
67    pub fn new() -> Self {
68        Self {
69            second: 0,
70            minute: 0,
71            hour: 0,
72            weekday: 0,
73            day: 0,
74            month: 0,
75            year: 2000,
76            century: 20,
77        }
78    }
79
80    /// Convert to Unix timestamp (seconds since 1970-01-01 00:00:00 UTC)
81    pub fn to_timestamp(&self) -> u64 {
82        let mut year = self.year as i64;
83        let mut month = self.month as i64;
84        let day = self.day as i64;
85        let hour = self.hour as i64;
86        let minute = self.minute as i64;
87        let second = self.second as i64;
88
89        // Adjust for months starting from March
90        if month <= 2 {
91            year -= 1;
92            month += 12;
93        }
94
95        // Calculate days since epoch (Howard Hinnant algorithm)
96        let days =
97            365 * year + year / 4 - year / 100 + year / 400 + (153 * (month - 3) + 2) / 5 + day
98                - 1
99                - 719468;
100
101        // Convert to seconds
102        (days * 86400 + hour * 3600 + minute * 60 + second) as u64
103    }
104
105    /// Format as ISO 8601 string (simplified)
106    pub fn to_string(&self) -> [u8; 19] {
107        let mut buf = [0u8; 19];
108        let year = self.year;
109
110        // YYYY-MM-DDTHH:MM:SS
111        buf[0] = b'0' + ((year / 1000) % 10) as u8;
112        buf[1] = b'0' + ((year / 100) % 10) as u8;
113        buf[2] = b'0' + ((year / 10) % 10) as u8;
114        buf[3] = b'0' + (year % 10) as u8;
115        buf[4] = b'-';
116        buf[5] = b'0' + (self.month / 10);
117        buf[6] = b'0' + (self.month % 10);
118        buf[7] = b'-';
119        buf[8] = b'0' + (self.day / 10);
120        buf[9] = b'0' + (self.day % 10);
121        buf[10] = b'T';
122        buf[11] = b'0' + (self.hour / 10);
123        buf[12] = b'0' + (self.hour % 10);
124        buf[13] = b':';
125        buf[14] = b'0' + (self.minute / 10);
126        buf[15] = b'0' + (self.minute % 10);
127        buf[16] = b':';
128        buf[17] = b'0' + (self.second / 10);
129        buf[18] = b'0' + (self.second % 10);
130
131        buf
132    }
133}
134
135/// RTC driver state
136pub struct RtcDriver {
137    cmos_century_reg: u8,
138    use_binary: bool,
139    last_update_tick: AtomicU64,
140}
141
142unsafe impl Send for RtcDriver {}
143unsafe impl Sync for RtcDriver {}
144
145static RTC_DRIVER: Mutex<Option<RtcDriver>> = Mutex::new(None);
146static RTC_INITIALIZED: AtomicBool = AtomicBool::new(false);
147static RTC_LAST_TICK: AtomicU64 = AtomicU64::new(0);
148
149/// Read CMOS register
150fn cmos_read(reg: u8) -> u8 {
151    unsafe {
152        outb(CMOS_ADDR_PORT, reg | 0x80); // NMI disable
153        inb(CMOS_DATA_PORT)
154    }
155}
156
157/// Write CMOS register
158fn cmos_write(reg: u8, value: u8) {
159    unsafe {
160        outb(CMOS_ADDR_PORT, reg | 0x80); // NMI disable
161        outb(CMOS_DATA_PORT, value);
162    }
163}
164
165/// Convert BCD to binary
166fn bcd_to_binary(bcd: u8) -> u8 {
167    (bcd & 0x0F) + ((bcd / 16) * 10)
168}
169
170/// Performs the detect century register operation.
171fn detect_century_register(use_binary: bool) -> u8 {
172    if let Some(fadt) = crate::acpi::get_fadt() {
173        let reg = fadt.century;
174        if reg != 0 {
175            let raw = cmos_read(reg);
176            let val = if use_binary { raw } else { bcd_to_binary(raw) };
177            if (19..=21).contains(&val) {
178                return reg;
179            }
180        }
181    }
182
183    for &reg in CENTURY_REG_CANDIDATES {
184        let raw = cmos_read(reg);
185        let val = if use_binary { raw } else { bcd_to_binary(raw) };
186        if (19..=21).contains(&val) {
187            return reg;
188        }
189    }
190
191    0
192}
193
194/// Check if RTC update is in progress
195fn is_update_in_progress() -> bool {
196    cmos_read(CMOS_REG_STATUS_A) & STATUS_A_UIP != 0
197}
198
199/// Read time from RTC registers
200fn read_rtc_time() -> RtcDateTime {
201    #[derive(Clone, Copy, PartialEq, Eq)]
202    struct RawRtc {
203        second: u8,
204        minute: u8,
205        hour: u8,
206        weekday: u8,
207        day: u8,
208        month: u8,
209        year: u8,
210        century: u8,
211        status_b: u8,
212    }
213
214    /// Reads raw once.
215    fn read_raw_once(century_reg: u8) -> RawRtc {
216        RawRtc {
217            second: cmos_read(CMOS_REG_SECOND),
218            minute: cmos_read(CMOS_REG_MINUTE),
219            hour: cmos_read(CMOS_REG_HOUR),
220            weekday: cmos_read(CMOS_REG_WEEKDAY),
221            day: cmos_read(CMOS_REG_DAY),
222            month: cmos_read(CMOS_REG_MONTH),
223            year: cmos_read(CMOS_REG_YEAR),
224            century: if century_reg != 0 {
225                cmos_read(century_reg)
226            } else {
227                0
228            },
229            status_b: cmos_read(CMOS_REG_STATUS_B),
230        }
231    }
232
233    let (century_reg, default_binary) = {
234        let driver = RTC_DRIVER.lock();
235        if let Some(ref drv) = *driver {
236            (drv.cmos_century_reg, drv.use_binary)
237        } else {
238            (0, false)
239        }
240    };
241
242    let mut raw = loop {
243        while is_update_in_progress() {
244            core::hint::spin_loop();
245        }
246        let a = read_raw_once(century_reg);
247        while is_update_in_progress() {
248            core::hint::spin_loop();
249        }
250        let b = read_raw_once(century_reg);
251        if a == b {
252            break b;
253        }
254    };
255
256    let use_binary = if raw.status_b == 0 {
257        default_binary
258    } else {
259        (raw.status_b & STATUS_B_DM) != 0
260    };
261    let pm_bit = raw.hour & 0x80;
262    if !use_binary {
263        raw.second = bcd_to_binary(raw.second);
264        raw.minute = bcd_to_binary(raw.minute);
265        raw.hour = bcd_to_binary(raw.hour & 0x7F);
266        raw.weekday = bcd_to_binary(raw.weekday);
267        raw.day = bcd_to_binary(raw.day);
268        raw.month = bcd_to_binary(raw.month);
269        raw.year = bcd_to_binary(raw.year);
270        if raw.century != 0 {
271            raw.century = bcd_to_binary(raw.century);
272        }
273    }
274
275    let is_24h = (raw.status_b & STATUS_B_24H) != 0;
276    if !is_24h {
277        let pm = pm_bit != 0;
278        raw.hour &= 0x7F;
279        if pm {
280            if raw.hour != 12 {
281                raw.hour = raw.hour.saturating_add(12);
282            }
283        } else if raw.hour == 12 {
284            raw.hour = 0;
285        }
286    }
287
288    let full_year = if raw.century != 0 {
289        (raw.century as u16) * 100 + raw.year as u16
290    } else if raw.year >= 70 {
291        1900 + raw.year as u16
292    } else {
293        2000 + raw.year as u16
294    };
295
296    RtcDateTime {
297        second: raw.second,
298        minute: raw.minute,
299        hour: raw.hour,
300        weekday: raw.weekday.saturating_sub(1),
301        day: raw.day,
302        month: raw.month,
303        year: full_year,
304        century: (full_year / 100) as u16,
305    }
306}
307
308/// Initialize RTC
309pub fn init() -> Result<(), &'static str> {
310    log::info!("[RTC] Initializing RTC...");
311
312    // Read status B to check mode
313    let status_b = cmos_read(CMOS_REG_STATUS_B);
314    let use_binary = (status_b & STATUS_B_DM) != 0;
315
316    let cmos_century_reg = detect_century_register(use_binary);
317
318    let driver = RtcDriver {
319        cmos_century_reg,
320        use_binary,
321        last_update_tick: AtomicU64::new(0),
322    };
323
324    *RTC_DRIVER.lock() = Some(driver);
325
326    // Enable update interrupt (IRQ8)
327    // This would be done in IDT setup
328    // For now, just enable the interrupt in RTC
329    let mut status_b = cmos_read(CMOS_REG_STATUS_B);
330    status_b |= STATUS_B_UIE;
331    cmos_write(CMOS_REG_STATUS_B, status_b);
332
333    // Clear any pending interrupts
334    let _ = cmos_read(CMOS_REG_STATUS_C);
335
336    RTC_INITIALIZED.store(true, Ordering::SeqCst);
337
338    let time = RtcDriver::get_time();
339    log::info!(
340        "[RTC] Initialized: {:04}-{:02}-{:02} {:02}:{:02}:{:02}",
341        time.year,
342        time.month,
343        time.day,
344        time.hour,
345        time.minute,
346        time.second
347    );
348
349    Ok(())
350}
351
352impl RtcDriver {
353    /// Get current time from RTC
354    pub fn get_time() -> RtcDateTime {
355        read_rtc_time()
356    }
357
358    /// Get Unix timestamp
359    pub fn get_timestamp() -> u64 {
360        Self::get_time().to_timestamp()
361    }
362
363    /// Get last update tick
364    pub fn last_update_tick() -> u64 {
365        RTC_LAST_TICK.load(Ordering::Relaxed)
366    }
367}
368
369/// RTC interrupt handler (IRQ8)
370pub fn rtc_interrupt_handler() {
371    // Read status C to clear interrupt
372    let _status_c = cmos_read(CMOS_REG_STATUS_C);
373
374    // Update tick counter
375    RTC_LAST_TICK.fetch_add(1, Ordering::Relaxed);
376
377    // Notify driver
378    let driver = RTC_DRIVER.lock();
379    if let Some(ref drv) = *driver {
380        drv.last_update_tick.fetch_add(1, Ordering::Relaxed);
381    }
382}
383
384/// Check if RTC is available
385pub fn is_available() -> bool {
386    RTC_INITIALIZED.load(Ordering::Relaxed)
387}
388
389/// Get current date/time
390pub fn get_datetime() -> RtcDateTime {
391    RtcDriver::get_time()
392}
393
394/// Get Unix timestamp
395pub fn get_timestamp() -> u64 {
396    RtcDriver::get_timestamp()
397}
398
399/// Set periodic interrupt frequency
400pub fn set_periodic_frequency(freq_hz: u16) {
401    // Rate is encoded as: rate = 32768 / (2^(rate-1))
402    // Valid rates: 6-15 (1024 Hz to 1 Hz)
403    let rate = match freq_hz {
404        1024 => RTC_FREQ_1024,
405        256 => RTC_FREQ_256,
406        64 => RTC_FREQ_64,
407        16 => RTC_FREQ_16,
408        4 => RTC_FREQ_4,
409        1 => RTC_FREQ_1,
410        _ => {
411            log::warn!(
412                "[RTC] Unsupported periodic frequency {} Hz, falling back to 1 Hz",
413                freq_hz
414            );
415            RTC_FREQ_1
416        }
417    };
418
419    let mut status_a = cmos_read(CMOS_REG_STATUS_A);
420    status_a = (status_a & 0xF0) | (rate & 0x0F);
421    cmos_write(CMOS_REG_STATUS_A, status_a);
422}
423
424/// Enable/disable periodic interrupt
425pub fn set_periodic_interrupt(enable: bool) {
426    let mut status_b = cmos_read(CMOS_REG_STATUS_B);
427    if enable {
428        status_b |= STATUS_B_PIE;
429    } else {
430        status_b &= !STATUS_B_PIE;
431    }
432    cmos_write(CMOS_REG_STATUS_B, status_b);
433}
434
435/// Get seconds since boot (approximate, based on RTC updates)
436pub fn uptime_secs() -> u64 {
437    RTC_LAST_TICK.load(Ordering::Relaxed)
438}