summaryrefslogtreecommitdiffstats
path: root/rsnix/src/rtc.rs
blob: 535f32e1685ee65d081e6500e26c422c0ca40482 (plain) (blame)
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
// ---------------------------------------------------------------------------
// RTC/CMOS clock — the standard PC real-time clock on ports 0x70/0x71.
//
// Reads the time registers (0x00-0x09).  Values may be BCD or binary and
// hours may be 12- or 24-hour depending on status register B; we normalize
// to binary 24-hour.  The century register (0x32) is used when it reports
// a sane value, otherwise we assume 2000+.  NMI is masked (bit 7 set) while
// poking CMOS — this kernel has no NMI handler, so a stray NMI would fault.
// ---------------------------------------------------------------------------

pub struct DateTime {
    pub year: u16,
    pub month: u8,
    pub day: u8,
    pub hour: u8,
    pub minute: u8,
    pub second: u8,
}

/// Read one CMOS register.  Writes bit 7 to the address port, which masks
/// NMIs during the access.
fn cmos_read(reg: u8) -> u8 {
    unsafe {
        core::arch::asm!(
            "out dx, al",
            in("dx") 0x70u16,
            in("al") reg | 0x80,
            options(nomem, nostack, preserves_flags)
        );
        let val: u8;
        core::arch::asm!(
            "in al, dx",
            out("al") val,
            in("dx") 0x71u16,
            options(nomem, nostack, preserves_flags)
        );
        val
    }
}

fn bcd_to_bin(v: u8) -> u8 {
    (v & 0x0F) + (v >> 4) * 10
}

/// Read the current wall-clock time from the RTC.
pub fn now() -> DateTime {
    // Wait until an in-progress update finishes (status A, UIP bit 7).
    while cmos_read(0x0A) & 0x80 != 0 {
        core::hint::spin_loop();
    }

    let reg_b = cmos_read(0x0B);
    let bcd = reg_b & 0x04 == 0; // bit 2: 0 = BCD, 1 = binary
    let hour12 = reg_b & 0x02 == 0; // bit 1: 1 = 24-hour, 0 = 12-hour

    let mut second = cmos_read(0x00);
    let mut minute = cmos_read(0x02);
    let mut hour = cmos_read(0x04);
    let mut day = cmos_read(0x07);
    let mut month = cmos_read(0x08);
    let mut year = cmos_read(0x09);
    let century = cmos_read(0x32);

    if bcd {
        second = bcd_to_bin(second);
        minute = bcd_to_bin(minute);
        hour = bcd_to_bin(hour);
        day = bcd_to_bin(day);
        month = bcd_to_bin(month);
        year = bcd_to_bin(year);
    }
    if hour12 {
        let pm = hour & 0x80 != 0; // top bit of a 12-hour hour is the PM flag
        hour &= 0x7F;
        if hour == 12 {
            hour = 0; // 12 AM/PM are both 0 in 24-hour form
        }
        if pm {
            hour += 12;
        }
    }
    let year = {
        let full = century as u16 * 100 + year as u16;
        if (1970..=2200).contains(&full) {
            full
        } else {
            // Unreadable century register (QEMU returns garbage here) —
            // assume 2000+ for the 2-digit year.
            2000 + year as u16
        }
    };

    DateTime {
        year,
        month,
        day,
        hour,
        minute,
        second,
    }
}