From 8b1c1a23382806fcdbd3dbc7336a6ac2da8a995b Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 31 Jul 2026 16:10:40 +0200 Subject: rtc + pci: CMOS clock with date command, PCI bus enumeration - rtc.rs: read the PC RTC via CMOS ports 0x70/0x71 (UIP wait, BCD/12h normalization, century sanity-check with 2000+ fallback); boot prints the wall clock, shell gains 'date' - pci.rs: legacy config-space access (0xCF8/0xCFC) with full enumeration of buses 0-255; boot reports device count, shell gains 'pci' (lspci style listing with class names) - QEMU q35 verified: Q35 host bridge, stdvga, e1000, ICH9 LPC --- rsnix/src/lib.rs | 2 + rsnix/src/pci.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ rsnix/src/rtc.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 rsnix/src/pci.rs create mode 100644 rsnix/src/rtc.rs (limited to 'rsnix/src') diff --git a/rsnix/src/lib.rs b/rsnix/src/lib.rs index 8c69b35..f84fa73 100644 --- a/rsnix/src/lib.rs +++ b/rsnix/src/lib.rs @@ -7,7 +7,9 @@ pub mod gop; pub mod interrupts; pub mod memory; pub mod paging; +pub mod pci; pub mod power; pub mod ps2; +pub mod rtc; pub mod serial; pub mod uefi; diff --git a/rsnix/src/pci.rs b/rsnix/src/pci.rs new file mode 100644 index 0000000..2d201f3 --- /dev/null +++ b/rsnix/src/pci.rs @@ -0,0 +1,125 @@ +// --------------------------------------------------------------------------- +// PCI config space — legacy port-based access (0xCF8 / 0xCFC). +// +// Enumeration walks buses 0-255 × devices 0-31 × functions 0-7. Port I/O +// stays usable after ExitBootServices, so the shell can list devices and +// future drivers (AHCI, network, ...) can find their controllers. +// --------------------------------------------------------------------------- + +const CONFIG_ADDRESS: u16 = 0xCF8; +const CONFIG_DATA: u16 = 0xCFC; + +#[derive(Debug, Clone, Copy)] +pub struct DeviceInfo { + pub bus: u8, + pub device: u8, + pub func: u8, + pub vendor: u16, + pub device_id: u16, + pub class: u8, + pub subclass: u8, + pub prog_if: u8, + pub header_type: u8, +} + +fn outl(port: u16, val: u32) { + unsafe { + core::arch::asm!( + "out dx, eax", + in("dx") port, + in("eax") val, + options(nomem, nostack, preserves_flags) + ); + } +} + +fn inl(port: u16) -> u32 { + let val: u32; + unsafe { + core::arch::asm!( + "in eax, dx", + out("eax") val, + in("dx") port, + options(nomem, nostack, preserves_flags) + ); + } + val +} + +/// Read a dword from a device's config space (`reg` must be dword-aligned). +pub fn config_read(bus: u8, device: u8, func: u8, reg: u8) -> u32 { + let addr = 0x8000_0000 + | ((bus as u32) << 16) + | ((device as u32) << 11) + | ((func as u32) << 8) + | ((reg as u32) & 0xFC); + outl(CONFIG_ADDRESS, addr); + inl(CONFIG_DATA) +} + +/// Enumerate every device/function present on all buses. +pub fn probe() -> alloc::vec::Vec { + let mut out = alloc::vec::Vec::new(); + for bus in 0..=255u8 { + for dev in 0..32u8 { + for func in 0..8u8 { + let id = config_read(bus, dev, func, 0); + let vendor = (id & 0xFFFF) as u16; + if vendor == 0xFFFF { + if func == 0 { + break; // nothing on this device number + } + continue; + } + let hdr = config_read(bus, dev, func, 0x0C); + let header_type = (hdr & 0xFF) as u8; + let cc = config_read(bus, dev, func, 0x08); + out.push(DeviceInfo { + bus, + device: dev, + func, + vendor, + device_id: (id >> 16) as u16, + class: (cc >> 24) as u8, + subclass: (cc >> 16) as u8, + prog_if: (cc >> 8) as u8, + header_type, + }); + // A non-multifunction device has no functions 1-7. + if func == 0 && header_type & 0x80 == 0 { + break; + } + } + } + } + out +} + +/// Short human name for a (class, subclass) pair. +pub fn class_name(class: u8, subclass: u8) -> &'static str { + match (class, subclass) { + (0x00, 0x01) => "VGA-compatible", + (0x00, _) => "Unclassified", + (0x01, 0x01) => "IDE controller", + (0x01, 0x06) => "SATA controller", + (0x01, 0x08) => "NVM controller", + (0x01, _) => "Mass storage", + (0x02, 0x00) => "Ethernet", + (0x02, _) => "Network", + (0x03, 0x00) => "VGA", + (0x03, _) => "Display", + (0x04, _) => "Multimedia", + (0x05, _) => "Memory controller", + (0x06, 0x00) => "Host bridge", + (0x06, 0x01) => "ISA bridge", + (0x06, 0x04) => "PCI-PCI bridge", + (0x06, _) => "Bridge", + (0x08, 0x80) => "PIC", + (0x0C, 0x03) => "USB controller", + (0x0C, 0x05) => "SMBus", + (0x0C, _) => "Serial bus", + (0x11, _) => "Signal processing", + (0xFF, _) => "Unassigned", + _ => "Other", + } +} diff --git a/rsnix/src/rtc.rs b/rsnix/src/rtc.rs new file mode 100644 index 0000000..535f32e --- /dev/null +++ b/rsnix/src/rtc.rs @@ -0,0 +1,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, + } +} -- cgit v1.2.3