summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-04 03:06:06 +0200
committersalmonik <salmonik@tuta.io>2026-08-04 03:06:06 +0200
commit5e1bdc12bd298a9751da4285b7e93c68b1314332 (patch)
treecc4ae9f256dda100477784a47cbc53373fce79b1
parentv0.5.9: fix OptiPlex heap crash — EBS retry must not rebuild the free list (diff)
downloadrs-dos-5e1bdc12bd298a9751da4285b7e93c68b1314332.tar.gz
rs-dos-5e1bdc12bd298a9751da4285b7e93c68b1314332.zip
v0.5.10: USB HID mouse via polled xHCI (rsnix/src/usb.rs)
Polled xHCI driver (mouse-only): controller bring-up, port reset, enable-slot -> address-device -> config-descriptor walk -> HID boot mouse IN endpoint, one outstanding IN TRB drained on poll. Cursor task polls usb first, PS/2 second. New 'usb'/'usb rescan' shell cmds. Verified in QEMU (q35 + OVMF + qemu-xhci + usb-mouse): movement and all three buttons tracked exactly; heap stays clean. Fixes en route: MaxPorts=HCSPARAMS1[31:24]; operational regs CRCR=0x18 DCBAAP=0x30 CONFIG=0x38; pass input-context ptr in Address-Device; Input Control Context DW0=Drop DW1=Add; HID walker bInterfaceClass at +5; async discovery from cursor task.
-rw-r--r--Cargo.lock6
-rw-r--r--Cargo.toml2
-rw-r--r--DEBUG_NOTES.md55
-rw-r--r--rsdos/src/main.rs6
-rw-r--r--rsnix/src/cursor.rs2
-rw-r--r--rsnix/src/lib.rs1
-rw-r--r--rsnix/src/usb.rs977
-rw-r--r--shell/src/lib.rs19
8 files changed, 1061 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ba46b62..515158d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,7 +4,7 @@ version = 4
4 4
5[[package]] 5[[package]]
6name = "rsdos" 6name = "rsdos"
7version = "0.5.9" 7version = "0.5.10"
8dependencies = [ 8dependencies = [
9 "rsnix", 9 "rsnix",
10 "shell", 10 "shell",
@@ -19,11 +19,11 @@ dependencies = [
19 19
20[[package]] 20[[package]]
21name = "rsnix" 21name = "rsnix"
22version = "0.5.9" 22version = "0.5.10"
23 23
24[[package]] 24[[package]]
25name = "shell" 25name = "shell"
26version = "0.5.9" 26version = "0.5.10"
27dependencies = [ 27dependencies = [
28 "rsdosedit", 28 "rsdosedit",
29 "rsnix", 29 "rsnix",
diff --git a/Cargo.toml b/Cargo.toml
index f9c5923..704d86c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ resolver = "2"
3members = ["rsnix", "shell", "rsdos", "rsdosedit"] 3members = ["rsnix", "shell", "rsdos", "rsdosedit"]
4 4
5[workspace.package] 5[workspace.package]
6version = "0.5.9" 6version = "0.5.10"
7edition = "2024" 7edition = "2024"
8 8
9# no_std UEFI kernel: no unwinder available, so panic must abort. 9# no_std UEFI kernel: no unwinder available, so panic must abort.
diff --git a/DEBUG_NOTES.md b/DEBUG_NOTES.md
index a1f2d75..19fe5cf 100644
--- a/DEBUG_NOTES.md
+++ b/DEBUG_NOTES.md
@@ -281,3 +281,58 @@ with the `klog` shell command.
281(QEMU control arm is green with and without a forced EBS failure: 281(QEMU control arm is green with and without a forced EBS failure:
282`verify: ok`, probe NOT free, shadow base NOT in free list, full shell 282`verify: ok`, probe NOT free, shadow base NOT in free list, full shell
283sequence clean. `0x00AAAAAA` = gray text colour; shadow far rows stay 0.) 283sequence clean. `0x00AAAAAA` = gray text colour; shadow far rows stay 0.)
284
285---
286
287## ✅ v0.5.10 — USB HID mouse driver (polled xHCI, `rsnix/src/usb.rs`)
288
289Handoff: HEAD = `v0.5.10` (workspace Cargo.toml + boot banner). Adds a
290polled xHCI controller driver (mouse-only) announced via `usb: xHCI at
291<bus.dev.fn>` during boot; `cursor.rs` polls `usb::poll_mouse().or_else(ps2)`
292so a USB mouse supersedes PS/2. New shell cmd: `usb` (diagnose), `usb rescan`.
293
294### How it works
295- Bring up controller (`OPC=0`, set `RCS`+`CRR`, `CNR`→0), discover a
296 mouse asynchronously from the cursor task (`maybe_discover`), reset the
297 port, enable slot, address device, read device + config descriptors,
298 find the HID boot-mouse IN endpoint, SET_CONFIGURATION, configure EP,
299 arm an IN TRB and drain transfer-completions into `ps2::MouseEvent`.
300- Kernel is identity-mapped + PIC-only (no MSI): DMA is plain kernel
301 heap pointers; no IRQ — keep one IN report armed and drain on each poll.
302
303### QEMU bring-up observations / gotchas that bit us
304- **MaxPorts = bits 31:24 of `HCSPARAMS1`**, not 16:23 (`MaxIntrs`). Using
305 the wrong nibble silently yielded 0 ports ("ports=0", every PORTSC read 0).
306- **Operational registers** (xHCI >=1.1, matches QEMU/Linux): `CRCR=0x18`,
307 `DCBAAP=0x30`, `CONFIG=0x38`. Wrong offsets made Enable-Slot never
308 complete (only Port-Status-Change events → "Enable-Slot never completed").
309- **`issue_cmd` must pass the input-context pointer** in TRB parameter
310 (never drop it); Address-Device failed with code 5 (`CC_TRB_ERROR`) until
311 `code5issue_cmd_ptr(d3, ptr)` passed `INCTX`, and
312- **Input Context is DW0=Drop, DW1=Add** (reverse of what looks natural).
313 QEMU validates `ictl[0]==0 && ictl[1]==0x3`, else `CC_TRB_ERROR`.
314- **HID walker: `bInterfaceClass` is `+5` in the interface descriptor**,
315 not `+2` (`cfg[i+5]`). A reverse-engineering of the mouse's config dump
316 (`09 04 00 00 01 03 01 02 00 … 07 05 81 03 04 00 07`) made it obvious.
317- QEMU monitor **`mouse_button` help ("1=L 2=M 4=R") is wrong for the
318 usb-mouse path**: the value is a direct HID bitmask — `1`=bit0 L,
319 `2`=bit1 R, `4`=bit2 M (matches a real boot-mouse report byte). Bump monitors
320 semantics: transport `mouse_move dx dy`; button via `mouse_button 1|2|4`.
321- A long-lived QEMU session sometimes stopped forwarding `mouse_move`
322 after heavy use; a fresh relaunch always revived it — not a driver bug.
323- Diagnose cmd `usb` prints `xhci mmio=… mapped=… ports=… mouse=… slot=…`
324 plus per-port `PORTS` dwords and a ` hid ep-index=… mps=… armed=true` line.
325
326### Boot log (good path)
327```
328usb: xHCI at 00:03.0
329usb: controller up (8 ports) — mouse discovery deferred to cursor task
330usb: port 4 connect (speed 3)
331usb: dev 0627:0001 class=0x0 mps0=64
332usb: HID mouse IN ep mps=4 interval=7 euid=3
333usb: HID boot mouse armed (slot 1)
334```
335
336### Known limitations
337- Mouse-only, no HID descriptor protocol selection (`SET_PROTOCOL` boot),
338 no high-rate intervals, no keyboard/tablet, no hot-unplug handling, no MSI.
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs
index e40098d..27dab06 100644
--- a/rsdos/src/main.rs
+++ b/rsdos/src/main.rs
@@ -185,7 +185,7 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut
185 } else { 185 } else {
186 rsnix::serial_println!("gop: not found (0x{:x})", gop_result); 186 rsnix::serial_println!("gop: not found (0x{:x})", gop_result);
187 } 187 }
188 rsnix::gop::boot_marker("rsnix v0.5.9 booting.."); 188 rsnix::gop::boot_marker("rsnix v0.5.10 booting..");
189 rsnix::vt::init(); 189 rsnix::vt::init();
190 let (rows, cols) = rsnix::vt::size(); 190 let (rows, cols) = rsnix::vt::size();
191 rsnix::serial_println!("vt: terminal ready ({}x{})", rows, cols); 191 rsnix::serial_println!("vt: terminal ready ({}x{})", rows, cols);
@@ -347,7 +347,9 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut
347 347
348 rsnix::serial_println!("boot: entering shell"); 348 rsnix::serial_println!("boot: entering shell");
349 rsnix::gop::boot_marker("rsnix: boot done — shell"); 349 rsnix::gop::boot_marker("rsnix: boot done — shell");
350 if rsnix::ps2::mouse_present() { 350 // USB (xHCI) mouse is polled; fall back to PS/2 if no USB mouse.
351 rsnix::usb::init();
352 if rsnix::ps2::mouse_present() || rsnix::usb::controller_up() {
351 rsnix::cursor::spawn(); 353 rsnix::cursor::spawn();
352 } else { 354 } else {
353 rsnix::serial_println!("cursor: no mouse — overlay disabled"); 355 rsnix::serial_println!("cursor: no mouse — overlay disabled");
diff --git a/rsnix/src/cursor.rs b/rsnix/src/cursor.rs
index 18c9a1d..d9d97bc 100644
--- a/rsnix/src/cursor.rs
+++ b/rsnix/src/cursor.rs
@@ -50,7 +50,7 @@ fn forward_target() -> Option<usize> {
50 50
51fn task_main() { 51fn task_main() {
52 loop { 52 loop {
53 while let Some(ev) = crate::ps2::try_poll_mouse() { 53 while let Some(ev) = crate::usb::poll_mouse().or_else(crate::ps2::try_poll_mouse) {
54 crate::gop::cursor_move(ev.dx, ev.dy); 54 crate::gop::cursor_move(ev.dx, ev.dy);
55 crate::gop::cursor_set_buttons(btn_bits(&ev)); 55 crate::gop::cursor_set_buttons(btn_bits(&ev));
56 if let Some(id) = forward_target() 56 if let Some(id) = forward_target()
diff --git a/rsnix/src/lib.rs b/rsnix/src/lib.rs
index dbb327c..bc2553d 100644
--- a/rsnix/src/lib.rs
+++ b/rsnix/src/lib.rs
@@ -17,6 +17,7 @@ pub mod pci;
17pub mod qr; 17pub mod qr;
18pub mod power; 18pub mod power;
19pub mod ps2; 19pub mod ps2;
20pub mod usb;
20pub mod rtc; 21pub mod rtc;
21pub mod sched; 22pub mod sched;
22pub mod serial; 23pub mod serial;
diff --git a/rsnix/src/usb.rs b/rsnix/src/usb.rs
new file mode 100644
index 0000000..71df781
--- /dev/null
+++ b/rsnix/src/usb.rs
@@ -0,0 +1,977 @@
1// ---------------------------------------------------------------------------
2// USB host controller driver — xHCI (USB 2.0/3.x) + HID mouse support.
3//
4// **Polled** driver: it runs the xHCI event ring from the cursor task loop
5// instead of wiring MSI/INTx into the PIC, matching how the kernel already
6// polls the PS/2 mouse (see `cursor`).
7//
8// Minimal, mouse-only scope:
9// • take the controller out of reset, set up the command + event rings
10// • reset ports, run the slot/address/configure firmware sequence
11// • find the HID mouse boot interface, open its interrupt-IN endpoint,
12// and translate the standard report into `ps2::MouseEvent` (reusing
13// the cursor task's consumption path).
14//
15// DMA / MMIO: the kernel is identity-mapped (physical == virtual), so bus
16// addresses are plain kernel pointers and every heap allocation is DMA-able.
17// Rings/contexts are allocated once at boot and handed to the controller
18// permanently.
19//
20// Register/TRB/context layouts follow the Linux xHCI driver definitions
21// (drivers/usb/host/xhci*.h) and the xHCI 1.2 spec. All exact field
22// positions were cross-checked against the Linux sources:
23// * TRB type field is bits 10-15 (TRB_TYPE(p) = p << 10)
24// * transfer/command completion code is bits 24-31 of event dword2
25// * slot id = bits 24-31, ep id = bits 16-20 of event dword3
26// * endpoint context: type at bits 3-5, interval at 16-23,
27// max packet size at bits 16-31 of dword1, TR dequeue is a 64-bit
28// field at bytes 8-15 with DCS in bit 0
29// * slot context: route/LAST_CTX in dword0, root-hub-port at 16-23 of
30// dword1, speed at bits 20-23
31// ---------------------------------------------------------------------------
32#![allow(non_snake_case)] // macro-style helpers mirror the Linux xHCI headers
33use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
34
35use crate::ps2::MouseEvent;
36
37// ---- capability registers (offset from mmio base) ----
38const CAP_CAPLENGTH: usize = 0x00;
39const HCSPARAMS1: usize = 0x04; // MaxSlots[7:0] MaxIntrs[15:8] MaxPorts[23:16]
40const HCCPARAMS1: usize = 0x10; // bit0=64-bit addressing; bit2=64-byte contexts
41const DBOFF: usize = 0x14;
42const RTSOFF: usize = 0x18;
43
44// ---- operational registers (relative to op = mmio + CAPLENGTH) ----
45const OP_USBCMD: usize = 0x00;
46const OP_USBSTS: usize = 0x04;
47const OP_CRCR: usize = 0x18; // 64-bit
48const OP_DCBAAP: usize = 0x30; // 64-bit
49const OP_CONFIG: usize = 0x38;
50const OP_PORTSC: usize = 0x400; // PORTSC port 1 (port set stride 0x10)
51
52// ---- runtime registers (relative to mmio + RTSOFF), interrupter 0 ----
53const RT_IMAN: usize = 0x20;
54const RT_IMOD: usize = 0x24;
55const RT_ERSTSZ: usize = 0x28;
56const RT_ERSTBA: usize = 0x30; // 64-bit
57const RT_ERDP: usize = 0x38; // 64-bit
58
59const CMD_RUN: u32 = 1 << 0;
60const CMD_RESET: u32 = 1 << 1;
61const STS_HALT: u32 = 1 << 0;
62const STS_CNR: u32 = 1 << 11;
63
64const CMD_RING_CYCLE: u32 = 1 << 0;
65
66// ---- TRB control-word bit layout ----
67const TRB_CYCLE: u32 = 1 << 0;
68const TRB_IOC: u32 = 1 << 5;
69const TRB_IDT: u32 = 1 << 6;
70const TRB_TYPE_BITMASK: u32 = 0xfc00; // bits 10..15
71fn TYPE(t: u32) -> u32 {
72 t << 10
73}
74fn SLOT_ID(p: u32) -> u32 {
75 (p & 0xff) << 24
76}
77fn TRB_LEN(p: u32) -> u32 {
78 p & 0x1fffff
79}
80const TRB_DIR_IN: u32 = 1 << 16;
81fn TRB_TX_TYPE(p: u32) -> u32 {
82 p << 16
83}
84const TRB_DATA_OUT: u32 = 2;
85const TRB_DATA_IN: u32 = 3;
86
87const TRB_NORMAL: u32 = 1;
88const TRB_SETUP: u32 = 2;
89const TRB_DATA: u32 = 3;
90const TRB_STATUS: u32 = 4;
91const TRB_ENABLE_SLOT: u32 = 9;
92const TRB_ADDR_DEV: u32 = 11;
93const TRB_CONFIG_EP: u32 = 12;
94const TRB_EV_TRANSFER: u32 = 0x20;
95const TRB_EV_CMD_COMPLETE: u32 = 0x21;
96
97const COMP_SUCCESS: u32 = 1;
98const COMP_SHORT: u32 = 13;
99
100fn GET_COMP_CODE(d2: u32) -> u32 {
101 (d2 >> 24) & 0xff
102}
103fn TRB_TO_SLOT_ID(d3: u32) -> u32 {
104 (d3 >> 24) & 0xff
105}
106fn TRB_TO_EP_ID(d3: u32) -> u32 {
107 (d3 >> 16) & 0x1f
108}
109fn TRB_FIELD_TO_TYPE(d3: u32) -> u32 {
110 (d3 & TRB_TYPE_BITMASK) >> 10
111}
112
113// ---- context fields ----
114fn LAST_CTX(p: u32) -> u32 {
115 p << 27
116}
117fn ROOTPORT(p: u32) -> u32 {
118 (p & 0xff) << 16
119}
120fn SLOT_SPEED_X(p: u32) -> u32 {
121 (p & 0xf) << 20
122}
123fn EP_TYPE(p: u32) -> u32 {
124 p << 3
125}
126fn MAX_PACKET(p: u32) -> u32 {
127 (p & 0xffff) << 16
128}
129fn ERROR_COUNT(p: u32) -> u32 {
130 (p & 0x3) << 1
131}
132fn EP_INTERVAL(p: u32) -> u32 {
133 (p & 0xff) << 16
134}
135fn EP_AVG(p: u32) -> u32 {
136 p & 0xffff
137}
138const CTRL_EP: u32 = 4;
139const INT_IN_EP: u32 = 7;
140
141// ---- PORTSC ----
142const PORT_CONNECT: u32 = 1 << 0;
143const PORT_PE: u32 = 1 << 1;
144const PORT_RESET: u32 = 1 << 4;
145const PORT_POWER: u32 = 1 << 9;
146fn DEV_SPEED(p: u32) -> u32 {
147 (p >> 10) & 0xf
148}
149
150// ---- USB meta protocol ----
151const USB_DIR_IN: u8 = 0x80;
152const USB_REQ_GET_DESCRIPTOR: u8 = 0x06;
153const USB_REQ_SET_CONFIGURATION: u8 = 0x09;
154const DT_DEVICE: u8 = 0x01;
155const DT_CONFIG: u8 = 0x02;
156const DT_INTERFACE: u8 = 0x04;
157const DT_ENDPOINT: u8 = 0x05;
158const HID_CLASS: u8 = 0x03;
159
160// ---------------------------------------------------------------------------
161// State (cooperative single-task kernel → unsafe statics)
162// ---------------------------------------------------------------------------
163static mut M: usize = 0; // mmio base
164static mut OPB: usize = 0; // operational base
165static mut DBB: usize = 0; // doorbell base
166static mut RTB: usize = 0; // runtime base
167static mut CTRL_CS: usize = 32; // context size
168static mut MAXPORTS: u32 = 0;
169
170struct Ring {
171 raw: *mut u32,
172 count: usize, // TRBs
173 idx: usize, // producer (command/transfer) or consumer (event) index
174 cyc: u32, // software cycle
175}
176const fn ri_empty(count: usize) -> Ring {
177 Ring {
178 raw: core::ptr::null_mut(),
179 count,
180 idx: 0,
181 cyc: 1,
182 }
183}
184static mut CMD: Ring = ri_empty(64);
185static mut EP0: Ring = ri_empty(8);
186static mut EPIN: Ring = ri_empty(8);
187static mut EV: Ring = ri_empty(32);
188
189static mut DCBAA: *mut u64 = core::ptr::null_mut();
190static mut DEVCTX: *mut u8 = core::ptr::null_mut();
191static mut INCTX: *mut u8 = core::ptr::null_mut();
192static mut ERST: *mut u8 = core::ptr::null_mut();
193
194static SLOT: AtomicU32 = AtomicU32::new(0);
195static HID_EPID: AtomicU32 = AtomicU32::new(0); // device-context index of the IN endpoint
196static mut HID_BUF: [u8; 8] = [0; 8];
197static mut HID_LEN: usize = 4;
198static mut MPS0: u32 = 8;
199
200static mut GOT_REPORT: bool = false;
201static mut QUEUED: bool = false;
202static mut RBTN: u8 = 0;
203static mut RDX: i8 = 0;
204static mut RDY: i8 = 0;
205
206static INITOK: AtomicBool = AtomicBool::new(false);
207static MOUSEPRESENT: AtomicBool = AtomicBool::new(false);
208
209fn cs() -> usize {
210 unsafe { CTRL_CS }
211}
212
213// ---------------------------------------------------------------------------
214// MMIO accessors
215// ---------------------------------------------------------------------------
216unsafe fn rd(a: usize) -> u32 {
217 unsafe { (a as *const u32).read_volatile() }
218}
219unsafe fn wr(a: usize, v: u32) {
220 unsafe { (a as *mut u32).write_volatile(v) }
221}
222unsafe fn wr64(a: usize, v: u64) {
223 unsafe {
224 (a as *mut u32).write_volatile(v as u32);
225 (a as *mut u32).add(1).write_volatile((v >> 32) as u32);
226 let _ = rd(a);
227 }
228}
229fn op(off: usize) -> usize {
230 unsafe { OPB + off }
231}
232fn db(slot: u32) -> usize {
233 unsafe { DBB }.wrapping_add(slot as usize * 4)
234}
235fn rt(off: usize) -> usize {
236 unsafe { RTB + off }
237}
238
239fn dma_alloc(bytes: usize, align: usize) -> *mut u8 {
240 unsafe {
241 let layout = alloc::alloc::Layout::from_size_align(bytes.max(1), align).unwrap();
242 let p = alloc::alloc::alloc(layout);
243 if !p.is_null() {
244 core::ptr::write_bytes(p, 0, bytes.max(1));
245 }
246 p
247 }
248}
249
250// ---------------------------------------------------------------------------
251// Ring primitives
252// ---------------------------------------------------------------------------
253unsafe fn trb_push(r: &mut Ring, d0: u32, d1: u32, d2: u32, d3: u32) {
254 unsafe {
255 let s = r.raw.add((r.idx % r.count) * 4);
256 *s.add(0) = d0;
257 *s.add(1) = d1;
258 *s.add(2) = d2;
259 *s.add(3) = (d3 & !TRB_CYCLE) | r.cyc;
260 r.idx += 1;
261 if r.idx.is_multiple_of(r.count) {
262 r.cyc ^= 1;
263 }
264 }
265}
266
267// ---------------------------------------------------------------------------
268// Event ring: drain produced events, calling handle(d0,d1,d2,d3) per event,
269// then advance ERDP to the consumption point so the controller reuses it.
270// ---------------------------------------------------------------------------
271unsafe fn drain_events(handle: &mut dyn FnMut(u32, u32, u32, u32)) {
272 unsafe {
273 let ev = &mut *core::ptr::addr_of_mut!(EV);
274 let mut consumed = 0usize;
275 loop {
276 let idx = ev.idx % ev.count;
277 let s = ev.raw.add(idx * 4);
278 let d3 = s.add(3).read();
279 if (d3 & 1) != ev.cyc {
280 break;
281 }
282 let (d0, d1, d2) = (s.add(0).read(), s.add(1).read(), s.add(2).read());
283 handle(d0, d1, d2, d3);
284 ev.idx += 1;
285 consumed += 1;
286 if ev.idx.is_multiple_of(ev.count) {
287 ev.cyc ^= 1;
288 }
289 }
290 if consumed > 0 {
291 let idx = ev.idx % ev.count;
292 wr64(rt(RT_ERDP), ev.raw.add(idx * 4) as u64);
293 }
294 }
295}
296
297/// Wait for a command-completion event; returns (code, slot).
298fn wait_cmd() -> (u32, u32) {
299 let mut code = 0u32;
300 let mut slot = 0u32;
301 let mut done = false;
302 let mut seen = 0u32;
303 let mut raw: [u32; 4] = [0; 4];
304 for _ in 0..4_000_000 {
305 if done {
306 break;
307 }
308 unsafe {
309 drain_events(&mut |d0, d1, d2, d3| {
310 seen += 1;
311 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_CMD_COMPLETE {
312 code = GET_COMP_CODE(d2);
313 slot = TRB_TO_SLOT_ID(d3);
314 raw = [d0, d1, d2, d3];
315 done = true;
316 }
317 });
318 }
319 core::hint::spin_loop();
320 }
321 if code != COMP_SUCCESS {
322 unsafe {
323 let evidx = core::ptr::addr_of!(EV).cast::<Ring>().read().idx;
324 crate::serial_println!(
325 "usb: cmd-complete code={} slot={} seen={} evidx={}",
326 code, slot, seen, evidx
327 );
328 for i in 0..4 {
329 let s = EV.raw.add(i * 4);
330 crate::serial_println!(
331 " ev[{i}] {:08x} {:08x} {:08x} {:08x}",
332 s.add(0).read(), s.add(1).read(), s.add(2).read(), s.add(3).read()
333 );
334 }
335 }
336 }
337 (code, slot)
338}
339
340/// Enqueue a command TRB and ring the host command doorbell.
341fn issue_cmd(d3: u32) -> (u32, u32) {
342 issue_cmd_ptr(d3, 0)
343}
344
345/// Enqueue a command TRB that carries a 64-bit parameter (used by
346/// Address-Device / Configure-Endpoint for the input-context pointer).
347fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) {
348 unsafe {
349 trb_push(
350 &mut *core::ptr::addr_of_mut!(CMD),
351 ptr as u32,
352 (ptr as u64 >> 32) as u32,
353 0,
354 d3,
355 );
356 wr(db(0), 0);
357 let _ = rd(db(0));
358 }
359 wait_cmd()
360}
361
362/// Wait for a transfer-complete event on the given device-context endpoint id.
363fn wait_xfer(epid: u32) -> u32 {
364 let mut code = 0u32;
365 let mut done = false;
366 let sl = SLOT.load(Ordering::Relaxed);
367 for _ in 0..4_000_000 {
368 if done {
369 break;
370 }
371 unsafe {
372 drain_events(&mut |_, _, d2, d3| {
373 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER
374 && TRB_TO_SLOT_ID(d3) == sl
375 && TRB_TO_EP_ID(d3) == epid
376 {
377 code = GET_COMP_CODE(d2);
378 done = true;
379 }
380 });
381 }
382 core::hint::spin_loop();
383 }
384 code
385}
386
387// ---------------------------------------------------------------------------
388// Control transfer on EP0 (setup + optional data + status), per Linux
389// xhci_queue_ctrl_tx. Returns OK(len) or the failure completion code.
390// ---------------------------------------------------------------------------
391fn ctrl_xfer(
392 bmreq: u8,
393 breq: u8,
394 wval: u16,
395 widx: u16,
396 buf: *mut u8,
397 buflen: u32,
398 dir_in: bool,
399) -> Result<usize, u32> {
400 let slot = SLOT.load(Ordering::Relaxed);
401 let e0 = unsafe { &mut *core::ptr::addr_of_mut!(EP0) };
402 unsafe {
403 // Setup stage (immediate data, 8-byte setup packet in d0/d1).
404 let tx = if buflen > 0 {
405 TRB_TX_TYPE(if dir_in { TRB_DATA_IN } else { TRB_DATA_OUT })
406 } else {
407 0
408 };
409 trb_push(
410 e0,
411 (wval as u32) << 16 | (breq as u32) << 8 | bmreq as u32,
412 (widx as u32) | buflen << 16,
413 TRB_LEN(8),
414 TYPE(TRB_SETUP) | TRB_IDT | tx,
415 );
416 if buflen > 0 {
417 trb_push(
418 e0,
419 buf as u32,
420 (buf as usize >> 32) as u32,
421 TRB_LEN(buflen),
422 TYPE(TRB_DATA) | if dir_in { TRB_DIR_IN } else { 0 },
423 );
424 }
425 // Status stage.
426 let stat_dir = if buflen > 0 && dir_in { 0 } else { TRB_DIR_IN };
427 trb_push(e0, 0, 0, 0, TYPE(TRB_STATUS) | TRB_IOC | stat_dir);
428 wr(db(slot), 1); // ring EP0 (doorbell target value 1)
429 let _ = rd(db(slot));
430 }
431 let code = wait_xfer(1);
432 if code == COMP_SUCCESS || code == COMP_SHORT {
433 Ok(buflen as usize)
434 } else {
435 Err(code)
436 }
437}
438
439// ---------------------------------------------------------------------------
440// Context construction helpers
441// ---------------------------------------------------------------------------
442fn build_addr_ictx(port_speed: u32, port_num: u32) {
443 unsafe {
444 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs());
445 let cc = INCTX as *mut u32;
446 *cc.add(0) = 0; // Drop flags
447 *cc.add(1) = (1 << 0) | (1 << 1); // Add slot + EP0
448 let sc = INCTX.offset(32) as *mut u32;
449 *sc.add(0) = LAST_CTX(1) | SLOT_SPEED_X(port_speed);
450 *sc.add(1) = ROOTPORT(port_num + 1);
451 let e0 = INCTX.add(32 + cs()) as *mut u32;
452 *e0.add(0) = 0;
453 *e0.add(1) = EP_TYPE(CTRL_EP) | MAX_PACKET(MPS0) | ERROR_COUNT(3);
454 (INCTX.add(32 + cs() + 8) as *mut u64).write_volatile((EP0.raw as u64) | 1);
455 (INCTX.add(32 + cs() + 16) as *mut u32).write_volatile(EP_AVG(8));
456 }
457}
458
459// ---------------------------------------------------------------------------
460// Port reset
461// ---------------------------------------------------------------------------
462unsafe fn reset_port(port: u32) -> Result<u32, ()> {
463 unsafe {
464 let psc = op(OP_PORTSC) + (port as usize) * 0x10;
465 // Ensure powered.
466 wr(psc, rd(psc) | PORT_POWER);
467 for _ in 0..1000 {
468 core::hint::spin_loop();
469 }
470 // Assert reset, wait for the hardware to clear it (reset done).
471 wr(psc, rd(psc) | PORT_RESET);
472 for _ in 0..4_000_000 {
473 if rd(psc) & PORT_RESET == 0 {
474 break;
475 }
476 core::hint::spin_loop();
477 }
478 // Wait for the port to be enabled.
479 for _ in 0..4_000_000 {
480 if rd(psc) & PORT_PE != 0 {
481 break;
482 }
483 core::hint::spin_loop();
484 }
485 if rd(psc) & PORT_PE == 0 {
486 return Err(());
487 }
488 Ok(DEV_SPEED(rd(psc)))
489 }
490}
491
492// ---------------------------------------------------------------------------
493// Enumeration + HID setup
494// ---------------------------------------------------------------------------
495fn read_device_descriptor() -> Result<[u8; 18], u32> {
496 let mut d = [0u8; 18];
497 ctrl_xfer(
498 USB_DIR_IN,
499 USB_REQ_GET_DESCRIPTOR,
500 (DT_DEVICE as u16) << 8,
501 0,
502 d.as_mut_ptr(),
503 18,
504 true,
505 )?;
506 Ok(d)
507}
508
509fn read_config_descriptor(buf: &mut [u8]) -> Result<usize, u32> {
510 let total = ctrl_xfer(
511 USB_DIR_IN,
512 USB_REQ_GET_DESCRIPTOR,
513 (DT_CONFIG as u16) << 8,
514 0,
515 buf.as_mut_ptr(),
516 buf.len() as u32,
517 true,
518 )?;
519 Ok(total)
520}
521
522/// Walk a config descriptor tree looking for a HID interrupt IN endpoint
523/// with an interrupt (transfer type 3). Returns (mps, interval, eepid).
524fn find_hid_mouse(cfg: &[u8]) -> Option<(u32, u32, u32)> {
525 let mut i = 0usize;
526 let mut in_iface = false;
527 while i + 2 <= cfg.len() {
528 let bLen = cfg[i] as usize;
529 if bLen == 0 {
530 break;
531 }
532 let btype = cfg[i + 1];
533 if btype == DT_INTERFACE && i + 9 <= cfg.len() {
534 // interface descriptor: +5 = bInterfaceClass
535 in_iface = cfg[i + 5] == HID_CLASS;
536 } else if btype == DT_ENDPOINT && in_iface && i + 7 <= cfg.len() {
537 let addr = cfg[i + 2];
538 let attrs = cfg[i + 3];
539 let dir_in = addr & 0x80 != 0;
540 let xfer = attrs & 0x03;
541 if dir_in && xfer == 3 {
542 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8);
543 let interval = cfg[i + 6] as u32;
544 let epnum = (addr & 0x0f) as u32;
545 let euid = 2 * epnum + 1; // device-context index (IN)
546 return Some((mps, interval, euid));
547 }
548 }
549 i += bLen;
550 }
551 None
552}
553
554// ---------------------------------------------------------------------------
555// Bring one port's device through address + configure to an HID mouse
556// ---------------------------------------------------------------------------
557fn enumerate(port: u32, speed: u32) -> bool {
558 let _ = DEV_SPEED;
559 // 1. Enable slot.
560 let (code, slotid) = issue_cmd(TYPE(TRB_ENABLE_SLOT));
561 if code != COMP_SUCCESS || slotid == 0 {
562 crate::serial_println!("usb: enable-slot failed code={} slot={}", code, slotid);
563 return false;
564 }
565 SLOT.store(slotid, Ordering::Relaxed);
566 unsafe {
567 *DCBAA.add(slotid as usize) = DEVCTX as u64;
568 }
569 // 2. Address device.
570 build_addr_ictx(speed, port);
571 let (code, _) = issue_cmd_ptr(TYPE(TRB_ADDR_DEV) | SLOT_ID(slotid), unsafe { INCTX } as usize);
572 if code != COMP_SUCCESS {
573 crate::serial_println!("usb: address-device failed code={}", code);
574 return false;
575 }
576 // 3. Read the device descriptor (picks up bMaxPacketSize0).
577 match read_device_descriptor() {
578 Ok(d) => {
579 let mps = d[7];
580 if mps != 0 {
581 unsafe {
582 MPS0 = mps as u32;
583 }
584 }
585 crate::serial_println!(
586 "usb: dev {:04x}:{:04x} class={:#x} mps0={}",
587 u16::from_le_bytes([d[8], d[9]]),
588 u16::from_le_bytes([d[10], d[11]]),
589 d[4],
590 d[7]
591 );
592 }
593 Err(c) => crate::serial_println!("usb: get-device-desc failed code={}", c),
594 }
595 // 4. Read the whole config descriptor and find the HID mouse IN ep.
596 let mut cfg = [0u8; 256];
597 let got = match read_config_descriptor(&mut cfg) {
598 Ok(n) => n,
599 Err(c) => {
600 crate::serial_println!("usb: get-config failed code={}", c);
601 return false;
602 }
603 };
604 let _ = got;
605 match find_hid_mouse(&cfg) {
606 Some((mps, interval, euid)) => {
607 crate::serial_println!(
608 "usb: HID mouse IN ep mps={} interval={} euid={}",
609 mps,
610 interval,
611 euid
612 );
613 // 5. Set configuration = 1 (first config value present).
614 let cfgval = cfg[5];
615 if cfgval == 0 {
616 return false;
617 }
618 if read_configuration(cfgval).is_err() {
619 return false;
620 }
621 // 6. Configure the IN endpoint.
622 if !configure_hid_endpoint(euid, mps, interval) {
623 return false;
624 }
625 HID_EPID.store(euid, Ordering::Relaxed);
626 unsafe {
627 HID_LEN = mps as usize;
628 QUEUED = false;
629 }
630 // 7. Arm the first IN read.
631 queue_report();
632 MOUSEPRESENT.store(true, Ordering::Relaxed);
633 crate::serial_println!(
634 "usb: HID boot mouse armed (slot {})",
635 SLOT.load(Ordering::Relaxed)
636 );
637 true
638 }
639 None => {
640 crate::serial_println!("usb: no HID mouse endpoint in config");
641 false
642 }
643 }
644}
645
646fn read_configuration(val: u8) -> Result<(), u32> {
647 ctrl_xfer(
648 0,
649 USB_REQ_SET_CONFIGURATION,
650 val.into(),
651 0,
652 core::ptr::null_mut(),
653 0,
654 false,
655 )
656 .map(|_| ())
657}
658
659fn configure_hid_endpoint(euid: u32, mps: u32, interval: u32) -> bool {
660 unsafe {
661 let slot = SLOT.load(Ordering::Relaxed);
662 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs());
663 let cc = INCTX as *mut u32;
664 *cc.add(0) = 0; // Drop flags
665 *cc.add(1) = (1 << 0) | (1 << euid); // Add slot + this EP
666 // Input slot context: copy the output (post-address) slot context so
667 // route/speed/rootport are preserved, then bump LAST_CTX to the ep.
668 let osc = DEVCTX.offset(0).cast::<u32>();
669 let sc = INCTX.offset(32).cast::<u32>();
670 *sc.add(0) = (*osc.add(0) & !LAST_CTX(31)) | LAST_CTX(euid);
671 *sc.add(1) = *osc.add(1);
672 // Endpoint IN context.
673 let ep = INCTX.add(32 + (euid as usize) * cs()).cast::<u32>();
674 *ep.add(0) = EP_INTERVAL(interval);
675 *ep.add(1) = EP_TYPE(INT_IN_EP) | MAX_PACKET(mps) | ERROR_COUNT(3);
676 *ep.add(4) = EP_AVG(mps);
677 let deq = EPIN.raw as u64 | 1;
678 *ep.offset(2).cast::<u64>() = deq;
679 let (code, _) = issue_cmd_ptr(TYPE(TRB_CONFIG_EP) | SLOT_ID(slot), INCTX as usize);
680 code == COMP_SUCCESS
681 }
682}
683
684fn queue_report() {
685 let slot = SLOT.load(Ordering::Relaxed);
686 let epid = HID_EPID.load(Ordering::Relaxed);
687 let epin = unsafe { &mut *core::ptr::addr_of_mut!(EPIN) };
688 unsafe {
689 let b = core::ptr::addr_of_mut!(HID_BUF) as *mut u8;
690 trb_push(
691 epin,
692 b as u32,
693 (b as usize >> 32) as u32,
694 TRB_LEN(HID_LEN as u32),
695 TYPE(TRB_NORMAL) | TRB_IOC,
696 );
697 wr(db(slot), epid);
698 let _ = rd(db(slot));
699 QUEUED = true;
700 }
701}
702
703// ---------------------------------------------------------------------------
704// Controller bring-up
705// ---------------------------------------------------------------------------
706fn find_xhci() -> Option<(u8, u8, u8)> {
707 for d in crate::pci::probe() {
708 if d.class == 0x0C && d.subclass == 0x03 && d.prog_if == 0x30 {
709 return Some((d.bus, d.device, d.func));
710 }
711 }
712 None
713}
714
715pub fn init() -> bool {
716 let Some((b, dev, func)) = find_xhci() else {
717 crate::serial_println!("usb: no xHCI controller");
718 return false;
719 };
720 crate::serial_println!("usb: xHCI at {:02x}:{:02x}.{}", b, dev, func);
721
722 // Enable MMIO + bus-mastering on the PCI command register.
723 unsafe {
724 let cmd = crate::pci::config_read(b, dev, func, 0x04);
725 crate::pci::config_write(
726 b,
727 dev,
728 func,
729 0x04,
730 (cmd & 0xFFFF0000) | ((cmd & 0xFFFF) | 0x6),
731 );
732 let bar = crate::pci::bar64(b, dev, func, 0);
733 let base = bar.unwrap() as usize;
734 M = base;
735 let caplen = rd(base + CAP_CAPLENGTH);
736 let hcs1 = rd(base + HCSPARAMS1);
737 let hcc = rd(base + HCCPARAMS1);
738 crate::serial_println!(
739 "usb: mmio={:#x} caplen={:#x} hcs1={:#x} hcc1={:#x}",
740 base, caplen, hcs1, hcc
741 );
742 OPB = base + (caplen & 0xff) as usize;
743 DBB = base + rd(base + DBOFF) as usize;
744 RTB = base + rd(base + RTSOFF) as usize;
745 MAXPORTS = (hcs1 >> 24) & 0xff; // MaxPorts is bits 31:24 (bits 23:16 are MaxIntrs)
746 let _ = hcc;
747 CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 };
748 }
749
750 // Reset the controller (stop, then reset). Wait for reset completion
751 // on the CMD_RESET bit clearing (per Linux xhci_reset), then for the
752 // Controller-Not-Ready flag to clear before any doorbell/op-register
753 // activity.
754 unsafe {
755 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) & !CMD_RUN);
756 // Wait for halt.
757 let mut t = 0;
758 while t < 1_000_000 && rd(op(OP_USBSTS)) & STS_HALT == 0 {
759 t += 1;
760 core::hint::spin_loop();
761 }
762 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RESET);
763 t = 0;
764 while t < 10_000_000 && rd(op(OP_USBCMD)) & CMD_RESET != 0 {
765 t += 1;
766 core::hint::spin_loop();
767 }
768 t = 0;
769 while t < 10_000_000 && rd(op(OP_USBSTS)) & STS_CNR != 0 {
770 t += 1;
771 core::hint::spin_loop();
772 }
773 }
774
775 // Allocate rings / contexts.
776 unsafe {
777 CMD.raw = dma_alloc(64 * 16, 64) as *mut u32;
778 EV.raw = dma_alloc(32 * 16, 64) as *mut u32;
779 EP0.raw = dma_alloc(8 * 16, 64) as *mut u32;
780 EPIN.raw = dma_alloc(8 * 16, 64) as *mut u32;
781 ERST = dma_alloc(16, 64);
782 DCBAA = dma_alloc(64 * 8, 64) as *mut u64;
783 DEVCTX = dma_alloc((1 + 8) * cs(), 64);
784 INCTX = dma_alloc(32 + 8 * cs(), 64);
785 // ERST segment 0 -> event ring.
786 let e = ERST as *mut u32;
787 *e.add(0) = EV.raw as u32;
788 *e.add(1) = (EV.raw as usize >> 32) as u32;
789 *e.add(2) = 32; // segment size (TRBs)
790 }
791
792 // Program registers.
793 unsafe {
794 wr64(op(OP_DCBAAP), DCBAA as u64);
795 wr64(
796 op(OP_CRCR),
797 ((CMD.raw as u64) & !0x3f) | CMD_RING_CYCLE as u64,
798 );
799 // MaxSlotsEn = 1 (we use a single slot).
800 wr(op(OP_CONFIG), (rd(op(OP_CONFIG)) & !0xff) | 1);
801 // Event ring.
802 wr(rt(RT_ERSTSZ), 1);
803 wr64(rt(RT_ERSTBA), ERST as u64);
804 wr64(rt(RT_ERDP), EV.raw as u64);
805 // Interrupter 0: clear pending, enable.
806 wr(rt(RT_IMAN), rd(rt(RT_IMAN)) | 0x2);
807 wr(rt(RT_IMOD), 0);
808 // Run.
809 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN);
810 let mut t = 0;
811 while t < 4_000_000 && rd(op(OP_USBSTS)) & STS_HALT != 0 {
812 t += 1;
813 core::hint::spin_loop();
814 }
815 if rd(op(OP_USBSTS)) & STS_HALT != 0 {
816 crate::serial_println!("usb: controller failed to start");
817 return false;
818 }
819 }
820 INITOK.store(true, Ordering::Relaxed);
821 crate::serial_println!(
822 "usb: controller up ({} ports) — mouse discovery deferred to cursor task",
823 unsafe { MAXPORTS }
824 );
825 true
826}
827
828/// Bitmask of root ports whose attempt already failed; skip them so we don't
829/// re-reset the port on every poll.
830static DISCOVERED_TRIED: AtomicU32 = AtomicU32::new(0);
831
832/// Periodic, cheap scan called from the cursor task until a HID mouse is
833/// found. A USB device can finish connecting long after the controller
834/// starts, so discovery is driven asynchronously rather than blocking boot.
835fn maybe_discover() {
836 if MOUSEPRESENT.load(Ordering::Relaxed) {
837 return;
838 }
839 let maxports = unsafe { MAXPORTS };
840 for p in 0..maxports {
841 if DISCOVERED_TRIED.load(Ordering::Relaxed) & (1 << p) != 0 {
842 continue;
843 }
844 unsafe {
845 let psc = op(OP_PORTSC) + (p as usize) * 0x10;
846 // Only touch ports that are both connected and not yet enabled.
847 if rd(psc) & (PORT_CONNECT | PORT_PE) == PORT_CONNECT {
848 crate::serial_println!("usb: port {} connect (speed {})", p, DEV_SPEED(rd(psc)));
849 if let Ok(speed) = reset_port(p)
850 && enumerate(p, speed) {
851 return;
852 }
853 DISCOVERED_TRIED.fetch_or(1 << p, Ordering::Relaxed);
854 }
855 }
856 }
857}
858
859pub fn mouse_present() -> bool {
860 MOUSEPRESENT.load(Ordering::Relaxed)
861}
862
863/// True once the xHCI controller is brought up (regardless of whether a
864/// device has attached yet). The cursor task is spawned as long as this is
865/// set so async mouse discovery can run.
866pub fn controller_up() -> bool {
867 INITOK.load(Ordering::Relaxed)
868}
869
870/// Reset discovery state (used by the `usb` shell command to rescan).
871pub fn rescan() {
872 DISCOVERED_TRIED.store(0, Ordering::Relaxed);
873}
874
875/// Non-blocking poll: drain the event ring, then decode one mouse report if
876/// one arrived (re-arming the IN transfer so reports keep flowing).
877/// Returns `None` when no report is pending (or no mouse).
878pub fn poll_mouse() -> Option<MouseEvent> {
879 // Drive discovery while no device is attached, then drain reports.
880 if !MOUSEPRESENT.load(Ordering::Relaxed) {
881 if INITOK.load(Ordering::Relaxed) {
882 maybe_discover();
883 }
884 return None;
885 }
886 let epid = HID_EPID.load(Ordering::Relaxed);
887 let sl = SLOT.load(Ordering::Relaxed);
888 unsafe {
889 drain_events(&mut |_, _, d2, d3| {
890 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER
891 && TRB_TO_SLOT_ID(d3) == sl
892 && TRB_TO_EP_ID(d3) == epid
893 {
894 let c = GET_COMP_CODE(d2);
895 if c == COMP_SUCCESS || c == COMP_SHORT {
896 // A fresh report sits in HID_BUF.
897 let p = core::ptr::addr_of!(HID_BUF) as *const u8;
898 RBTN = core::ptr::read_volatile(p);
899 RDX = core::ptr::read_volatile(p.add(1)) as i8;
900 RDY = core::ptr::read_volatile(p.add(2)) as i8;
901 GOT_REPORT = true;
902 QUEUED = false;
903 }
904 }
905 });
906 if GOT_REPORT {
907 GOT_REPORT = false;
908 let ev = Some(MouseEvent {
909 dx: RDX as i16,
910 dy: RDY as i16,
911 left: RBTN & 0x01 != 0,
912 right: RBTN & 0x02 != 0,
913 middle: RBTN & 0x04 != 0,
914 });
915 queue_report(); // re-arm for the next report
916 return ev;
917 }
918 if !QUEUED {
919 queue_report(); // keep exactly one IN read outstanding
920 }
921 }
922 None
923}
924
925/// One-line status for the shell's `usb` command.
926pub fn diagnose() -> alloc::string::String {
927 let mut s = alloc::string::String::new();
928 s.push_str(&alloc::format!(
929 "xhci mmio={:#x} mapped={} ports={} mouse={} slot={}\n",
930 unsafe { M },
931 if crate::paging::is_mapped(unsafe { M }) { "yes" } else { "no" },
932 unsafe { MAXPORTS },
933 if MOUSEPRESENT.load(Ordering::Relaxed) {
934 "yes"
935 } else {
936 "no"
937 },
938 SLOT.load(Ordering::Relaxed)
939 ));
940 unsafe {
941 // Page-table walk for the MMIO base (physical == virtual here).
942 let phys = M;
943 let pml4 = crate::paging::cr3() as *const u64;
944 let l4 = (phys >> 39) & 0x1FF;
945 let l3 = (phys >> 30) & 0x1FF;
946 let l2 = (phys >> 21) & 0x1FF;
947 let l1 = (phys >> 12) & 0x1FF;
948 let pml4e = pml4.add(l4).read_volatile();
949 let pdpte = if pml4e & 1 != 0 {
950 ((pml4e & 0x000f_ffff_ffff_f000) as *const u64).add(l3).read_volatile()
951 } else { 0 };
952 let pde = if pdpte & 1 != 0 && pdpte & (1 << 7) == 0 {
953 ((pdpte & 0x000f_ffff_ffff_f000) as *const u64).add(l2).read_volatile()
954 } else { 0 };
955 let pte = if pde & 1 != 0 && pde & (1 << 7) == 0 {
956 ((pde & 0x000f_ffff_ffff_f000) as *const u64).add(l1).read_volatile()
957 } else { 0 };
958 s.push_str(&alloc::format!(
959 " pt: pml4e={:#018x} pdpte={:#018x} pde={:#018x} pte={:#018x}\n",
960 pml4e, pdpte, pde, pte
961 ));
962 for p in 0..8 {
963 let psc = op(OP_PORTSC) + (p as usize) * 0x10;
964 let v = rd(psc);
965 s.push_str(&alloc::format!(" port{}: {:#010x}\n", p, v));
966 }
967 }
968 if MOUSEPRESENT.load(Ordering::Relaxed) {
969 s.push_str(&alloc::format!(
970 " hid ep-index={} mps={} armed={}\n",
971 HID_EPID.load(Ordering::Relaxed),
972 unsafe { HID_LEN },
973 unsafe { QUEUED }
974 ));
975 }
976 s
977}
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index 0f5ac4b..c9b7cdd 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -721,6 +721,7 @@ write `{} <path> TIL <marker>`",
721 "stress" => cmd_stress(&parsed), 721 "stress" => cmd_stress(&parsed),
722 "date" => cmd_date(&parsed), 722 "date" => cmd_date(&parsed),
723 "pci" => cmd_pci(&parsed), 723 "pci" => cmd_pci(&parsed),
724 "usb" => cmd_usb(&parsed),
724 "rescan" => cmd_rescan(&parsed), 725 "rescan" => cmd_rescan(&parsed),
725 "mouse" => cmd_mouse(&parsed), 726 "mouse" => cmd_mouse(&parsed),
726 "cursor" => cmd_cursor(&parsed), 727 "cursor" => cmd_cursor(&parsed),
@@ -2217,6 +2218,24 @@ fn cmd_pci(p: &Parsed) {
2217 pipe.end(); 2218 pipe.end();
2218} 2219}
2219 2220
2221fn cmd_usb(p: &Parsed) {
2222 // `usb rescan` clears the failed-port mask so discovery retries.
2223 if p.args.first().is_some_and(|w| w == "rescan") {
2224 rsnix::usb::rescan();
2225 let mut pipe = Pipeline::new(&p.filters, &p.output);
2226 let _ = pipe.line("usb: rescanning ports");
2227 pipe.end();
2228 return;
2229 }
2230 let mut pipe = Pipeline::new(&p.filters, &p.output);
2231 for l in rsnix::usb::diagnose().lines() {
2232 if !pipe.line(l) {
2233 break;
2234 }
2235 }
2236 pipe.end();
2237}
2238
2220fn cmd_poweroff() -> ! { 2239fn cmd_poweroff() -> ! {
2221 term_println("shutting down..."); 2240 term_println("shutting down...");
2222 rsnix::power::poweroff() 2241 rsnix::power::poweroff()