summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-07-31 16:10:40 +0200
committersalmonik <salmonik@tuta.io>2026-07-31 16:10:40 +0200
commit8b1c1a23382806fcdbd3dbc7336a6ac2da8a995b (patch)
tree5d7b039a1baf289638d34b6e2d66c81ba9117560
parentstyle: rustfmt field alignment in RuntimeServices (diff)
downloadrs-dos-8b1c1a23382806fcdbd3dbc7336a6ac2da8a995b.tar.gz
rs-dos-8b1c1a23382806fcdbd3dbc7336a6ac2da8a995b.zip
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
Diffstat (limited to '')
-rw-r--r--CONTEXT.md4
-rw-r--r--rsdos/src/main.rs13
-rw-r--r--rsnix/src/lib.rs2
-rw-r--r--rsnix/src/pci.rs125
-rw-r--r--rsnix/src/rtc.rs101
-rw-r--r--shell/src/lib.rs38
6 files changed, 282 insertions, 1 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 0074969..8ee7f6c 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -96,6 +96,8 @@ Single Rust crate (`x86_64-unknown-uefi` target). Module breakdown:
96 allocator), bitmap page allocator 96 allocator), bitmap page allocator
97- `paging.rs` — identity page-table helpers (map/unmap/query, large-page split) 97- `paging.rs` — identity page-table helpers (map/unmap/query, large-page split)
98- `power.rs` — `ResetSystem`-based shutdown, hlt loop fallback 98- `power.rs` — `ResetSystem`-based shutdown, hlt loop fallback
99- `rtc.rs` — CMOS/RTC clock (ports 0x70/0x71), BCD/12h normalization
100- `pci.rs` — legacy PCI config space (0xCF8/0xCFC), full enumeration
99- `gop.rs` — framebuffer access and text rendering 101- `gop.rs` — framebuffer access and text rendering
100- `font.rs` — 8×16 bitmap font data 102- `font.rs` — 8×16 bitmap font data
101- `ps2.rs` — PS/2 keyboard driver (polling) 103- `ps2.rs` — PS/2 keyboard driver (polling)
@@ -120,6 +122,8 @@ Single Rust crate (`x86_64-unknown-uefi` target). Module breakdown:
120✅ **Shell** — `mmap` dumps the UEFI map, `stress` exercises alloc/free + growth 122✅ **Shell** — `mmap` dumps the UEFI map, `stress` exercises alloc/free + growth
121✅ **Poweroff** — `poweroff`/`exit` shut down via UEFI `ResetSystem` 123✅ **Poweroff** — `poweroff`/`exit` shut down via UEFI `ResetSystem`
122 (ACPI S5; `halt` keeps the plain hlt loop) 124 (ACPI S5; `halt` keeps the plain hlt loop)
125✅ **RTC** — CMOS clock read at boot and via `date` (BCD/12h handled)
126✅ **PCI** — port-based config access, full bus enumeration via `pci`
123 127
124## Target audience 128## Target audience
125 129
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs
index d9578e2..c35d88f 100644
--- a/rsdos/src/main.rs
+++ b/rsdos/src/main.rs
@@ -141,6 +141,19 @@ pub extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemT
141 rsnix::serial_println!("pg: self-test FAILED"); 141 rsnix::serial_println!("pg: self-test FAILED");
142 } 142 }
143 143
144 let t = rsnix::rtc::now();
145 rsnix::serial_println!(
146 "rtc: {}-{:02}-{:02} {:02}:{:02}:{:02}",
147 t.year,
148 t.month,
149 t.day,
150 t.hour,
151 t.minute,
152 t.second
153 );
154 let devices = rsnix::pci::probe();
155 rsnix::serial_println!("pci: {} device(s) on all buses", devices.len());
156
144 rsnix::serial_println!("boot: entering shell"); 157 rsnix::serial_println!("boot: entering shell");
145 shell::run(); 158 shell::run();
146} 159}
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;
7pub mod interrupts; 7pub mod interrupts;
8pub mod memory; 8pub mod memory;
9pub mod paging; 9pub mod paging;
10pub mod pci;
10pub mod power; 11pub mod power;
11pub mod ps2; 12pub mod ps2;
13pub mod rtc;
12pub mod serial; 14pub mod serial;
13pub mod uefi; 15pub 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 @@
1// ---------------------------------------------------------------------------
2// PCI config space — legacy port-based access (0xCF8 / 0xCFC).
3//
4// Enumeration walks buses 0-255 × devices 0-31 × functions 0-7. Port I/O
5// stays usable after ExitBootServices, so the shell can list devices and
6// future drivers (AHCI, network, ...) can find their controllers.
7// ---------------------------------------------------------------------------
8
9const CONFIG_ADDRESS: u16 = 0xCF8;
10const CONFIG_DATA: u16 = 0xCFC;
11
12#[derive(Debug, Clone, Copy)]
13pub struct DeviceInfo {
14 pub bus: u8,
15 pub device: u8,
16 pub func: u8,
17 pub vendor: u16,
18 pub device_id: u16,
19 pub class: u8,
20 pub subclass: u8,
21 pub prog_if: u8,
22 pub header_type: u8,
23}
24
25fn outl(port: u16, val: u32) {
26 unsafe {
27 core::arch::asm!(
28 "out dx, eax",
29 in("dx") port,
30 in("eax") val,
31 options(nomem, nostack, preserves_flags)
32 );
33 }
34}
35
36fn inl(port: u16) -> u32 {
37 let val: u32;
38 unsafe {
39 core::arch::asm!(
40 "in eax, dx",
41 out("eax") val,
42 in("dx") port,
43 options(nomem, nostack, preserves_flags)
44 );
45 }
46 val
47}
48
49/// Read a dword from a device's config space (`reg` must be dword-aligned).
50pub fn config_read(bus: u8, device: u8, func: u8, reg: u8) -> u32 {
51 let addr = 0x8000_0000
52 | ((bus as u32) << 16)
53 | ((device as u32) << 11)
54 | ((func as u32) << 8)
55 | ((reg as u32) & 0xFC);
56 outl(CONFIG_ADDRESS, addr);
57 inl(CONFIG_DATA)
58}
59
60/// Enumerate every device/function present on all buses.
61pub fn probe() -> alloc::vec::Vec<DeviceInfo> {
62 let mut out = alloc::vec::Vec::new();
63 for bus in 0..=255u8 {
64 for dev in 0..32u8 {
65 for func in 0..8u8 {
66 let id = config_read(bus, dev, func, 0);
67 let vendor = (id & 0xFFFF) as u16;
68 if vendor == 0xFFFF {
69 if func == 0 {
70 break; // nothing on this device number
71 }
72 continue;
73 }
74 let hdr = config_read(bus, dev, func, 0x0C);
75 let header_type = (hdr & 0xFF) as u8;
76 let cc = config_read(bus, dev, func, 0x08);
77 out.push(DeviceInfo {
78 bus,
79 device: dev,
80 func,
81 vendor,
82 device_id: (id >> 16) as u16,
83 class: (cc >> 24) as u8,
84 subclass: (cc >> 16) as u8,
85 prog_if: (cc >> 8) as u8,
86 header_type,
87 });
88 // A non-multifunction device has no functions 1-7.
89 if func == 0 && header_type & 0x80 == 0 {
90 break;
91 }
92 }
93 }
94 }
95 out
96}
97
98/// Short human name for a (class, subclass) pair.
99pub fn class_name(class: u8, subclass: u8) -> &'static str {
100 match (class, subclass) {
101 (0x00, 0x01) => "VGA-compatible",
102 (0x00, _) => "Unclassified",
103 (0x01, 0x01) => "IDE controller",
104 (0x01, 0x06) => "SATA controller",
105 (0x01, 0x08) => "NVM controller",
106 (0x01, _) => "Mass storage",
107 (0x02, 0x00) => "Ethernet",
108 (0x02, _) => "Network",
109 (0x03, 0x00) => "VGA",
110 (0x03, _) => "Display",
111 (0x04, _) => "Multimedia",
112 (0x05, _) => "Memory controller",
113 (0x06, 0x00) => "Host bridge",
114 (0x06, 0x01) => "ISA bridge",
115 (0x06, 0x04) => "PCI-PCI bridge",
116 (0x06, _) => "Bridge",
117 (0x08, 0x80) => "PIC",
118 (0x0C, 0x03) => "USB controller",
119 (0x0C, 0x05) => "SMBus",
120 (0x0C, _) => "Serial bus",
121 (0x11, _) => "Signal processing",
122 (0xFF, _) => "Unassigned",
123 _ => "Other",
124 }
125}
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 @@
1// ---------------------------------------------------------------------------
2// RTC/CMOS clock — the standard PC real-time clock on ports 0x70/0x71.
3//
4// Reads the time registers (0x00-0x09). Values may be BCD or binary and
5// hours may be 12- or 24-hour depending on status register B; we normalize
6// to binary 24-hour. The century register (0x32) is used when it reports
7// a sane value, otherwise we assume 2000+. NMI is masked (bit 7 set) while
8// poking CMOS — this kernel has no NMI handler, so a stray NMI would fault.
9// ---------------------------------------------------------------------------
10
11pub struct DateTime {
12 pub year: u16,
13 pub month: u8,
14 pub day: u8,
15 pub hour: u8,
16 pub minute: u8,
17 pub second: u8,
18}
19
20/// Read one CMOS register. Writes bit 7 to the address port, which masks
21/// NMIs during the access.
22fn cmos_read(reg: u8) -> u8 {
23 unsafe {
24 core::arch::asm!(
25 "out dx, al",
26 in("dx") 0x70u16,
27 in("al") reg | 0x80,
28 options(nomem, nostack, preserves_flags)
29 );
30 let val: u8;
31 core::arch::asm!(
32 "in al, dx",
33 out("al") val,
34 in("dx") 0x71u16,
35 options(nomem, nostack, preserves_flags)
36 );
37 val
38 }
39}
40
41fn bcd_to_bin(v: u8) -> u8 {
42 (v & 0x0F) + (v >> 4) * 10
43}
44
45/// Read the current wall-clock time from the RTC.
46pub fn now() -> DateTime {
47 // Wait until an in-progress update finishes (status A, UIP bit 7).
48 while cmos_read(0x0A) & 0x80 != 0 {
49 core::hint::spin_loop();
50 }
51
52 let reg_b = cmos_read(0x0B);
53 let bcd = reg_b & 0x04 == 0; // bit 2: 0 = BCD, 1 = binary
54 let hour12 = reg_b & 0x02 == 0; // bit 1: 1 = 24-hour, 0 = 12-hour
55
56 let mut second = cmos_read(0x00);
57 let mut minute = cmos_read(0x02);
58 let mut hour = cmos_read(0x04);
59 let mut day = cmos_read(0x07);
60 let mut month = cmos_read(0x08);
61 let mut year = cmos_read(0x09);
62 let century = cmos_read(0x32);
63
64 if bcd {
65 second = bcd_to_bin(second);
66 minute = bcd_to_bin(minute);
67 hour = bcd_to_bin(hour);
68 day = bcd_to_bin(day);
69 month = bcd_to_bin(month);
70 year = bcd_to_bin(year);
71 }
72 if hour12 {
73 let pm = hour & 0x80 != 0; // top bit of a 12-hour hour is the PM flag
74 hour &= 0x7F;
75 if hour == 12 {
76 hour = 0; // 12 AM/PM are both 0 in 24-hour form
77 }
78 if pm {
79 hour += 12;
80 }
81 }
82 let year = {
83 let full = century as u16 * 100 + year as u16;
84 if (1970..=2200).contains(&full) {
85 full
86 } else {
87 // Unreadable century register (QEMU returns garbage here) —
88 // assume 2000+ for the 2-digit year.
89 2000 + year as u16
90 }
91 };
92
93 DateTime {
94 year,
95 month,
96 day,
97 hour,
98 minute,
99 second,
100 }
101}
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index 5cab122..500eaae 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -50,6 +50,8 @@ pub fn run() -> ! {
50 "mem" => cmd_mem(), 50 "mem" => cmd_mem(),
51 "mmap" => cmd_mmap(), 51 "mmap" => cmd_mmap(),
52 "stress" => cmd_stress(args), 52 "stress" => cmd_stress(args),
53 "date" => cmd_date(),
54 "pci" => cmd_pci(),
53 _ => { 55 _ => {
54 gop_println!("unknown command: {}", cmd); 56 gop_println!("unknown command: {}", cmd);
55 serial_println!("unknown command: {}", cmd); 57 serial_println!("unknown command: {}", cmd);
@@ -124,11 +126,13 @@ fn cmd_help() {
124 gop_println!(" mem — show memory info"); 126 gop_println!(" mem — show memory info");
125 gop_println!(" mmap — dump the UEFI memory map"); 127 gop_println!(" mmap — dump the UEFI memory map");
126 gop_println!(" stress — stress-test heap/page allocators"); 128 gop_println!(" stress — stress-test heap/page allocators");
129 gop_println!(" date — show current date/time (RTC)");
130 gop_println!(" pci — list PCI devices");
127 gop_println!(" halt — halt the system (hlt loop)"); 131 gop_println!(" halt — halt the system (hlt loop)");
128 gop_println!(" poweroff — shut down via ACPI (ResetSystem)"); 132 gop_println!(" poweroff — shut down via ACPI (ResetSystem)");
129 gop_println!(" exit — alias for poweroff"); 133 gop_println!(" exit — alias for poweroff");
130 serial_println!("available commands:"); 134 serial_println!("available commands:");
131 serial_println!(" help, echo, clear, hello, info, mem, mmap, stress, halt, poweroff, exit"); 135 serial_println!(" help, echo, clear, hello, info, mem, mmap, stress, date, pci, halt, poweroff, exit");
132} 136}
133 137
134fn cmd_echo(args: &str) { 138fn cmd_echo(args: &str) {
@@ -364,6 +368,38 @@ fn cmd_halt() -> ! {
364 rsnix::power::halt() 368 rsnix::power::halt()
365} 369}
366 370
371fn cmd_date() {
372 let t = rsnix::rtc::now();
373 let line = alloc::format!(
374 "{}-{:02}-{:02} {:02}:{:02}:{:02}",
375 t.year, t.month, t.day, t.hour, t.minute, t.second
376 );
377 gop_println!("{}", line);
378 serial_println!("{}", line);
379}
380
381fn cmd_pci() {
382 let devices = rsnix::pci::probe();
383 let mut out = alloc::string::String::new();
384 use alloc::fmt::Write;
385 let _ = writeln!(out, "pci: {} device(s)", devices.len());
386 let _ = writeln!(out, " bus dev fn class vendor:device");
387 for d in &devices {
388 let _ = writeln!(
389 out,
390 " {:02x}:{:02x}.{} {:<16} {:04x}:{:04x}",
391 d.bus,
392 d.device,
393 d.func,
394 rsnix::pci::class_name(d.class, d.subclass),
395 d.vendor,
396 d.device_id
397 );
398 }
399 gop_print!("{}", out);
400 serial_print!("{}", out);
401}
402
367fn cmd_poweroff() -> ! { 403fn cmd_poweroff() -> ! {
368 gop_println!("shutting down..."); 404 gop_println!("shutting down...");
369 serial_println!("shutting down..."); 405 serial_println!("shutting down...");