summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 16:33:05 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 16:33:05 +0100
commit50b4d2f0ab05901a847d8e1318152308265e579b (patch)
treecd9fdf79e23e501e32646f9fb0af06523d48e169
parentusb: re-enable EHCI, share HID keyboard decoding, fix boot protocol (diff)
downloadrs-dos-50b4d2f0ab05901a847d8e1318152308265e579b.tar.gz
rs-dos-50b4d2f0ab05901a847d8e1318152308265e579b.zip
usb: real-hardware rework — hubs, both EHCI controllers, keyboard, xHCI hardening
EHCI (OptiPlex 7010 / Q77 and similar Intel PCH): - enumerate *both* EHCI controllers (00:1a.0 + 00:1d.0), not just the first - hub support: read the hub class descriptor, power/reset each port via hub class requests, read the child speed, then drive the child with split transactions carrying the hub's address and port (TT). On 7-series PCH the root port presents the integrated Rate Matching Hub (8087:0024, class 9) and the mouse/keyboard sit behind it; without this the driver stopped at "no HID mouse/keyboard endpoint in config". - HID mouse *and* keyboard, boot protocol + idle, per-device periodic QH chained into the frame list, per-device report buffers and counters - periodic re-arm follows the standard shape (qTD re-activated, Next-qTD re-linked); the HC owns the status/toggle overlay - DMA from the page allocator inside [16 MiB, 4 GiB): EHCI programs 32-bit addresses and the page bitmap spans all RAM - 2 ms recovery after SET_ADDRESS xHCI: - scratchpad buffers (HCSPARAMS2) and MaxSlotsEn > 1, with per-slot EP0 and interrupt rings plus per-slot device contexts - DMA from the page allocator instead of the heap allocator - boot protocol + idle, mouse + keyboard, first-report diagnostics - transfer events are routed to every device before matching a waiter, so a second device's enumeration cannot swallow an armed device's report shared: - rsnix/src/hid.rs: HID usage -> scancode-set-1 tables, one scancode FIFO feeding ps2::feed_scancode, per-device boot-keyboard report differ - WM input pump polls PS/2 -> UHCI -> EHCI -> xHCI - memory::alloc_pages_range(count, min, max) for bounded DMA allocations QEMU coverage: make test-usb (EHCI + xHCI, mouse + keyboard). QEMU's EHCI split path delivers only the first report for FS devices, so repeated-report delivery and the RMH path remain hardware-test items.
-rw-r--r--rsnix/src/ehci.rs1852
-rw-r--r--rsnix/src/hid.rs1
-rw-r--r--rsnix/src/memory.rs13
-rw-r--r--rsnix/src/usb.rs1102
-rw-r--r--rsnix/src/wm.rs6
5 files changed, 1644 insertions, 1330 deletions
diff --git a/rsnix/src/ehci.rs b/rsnix/src/ehci.rs
index 8baa0ad..5ee11cf 100644
--- a/rsnix/src/ehci.rs
+++ b/rsnix/src/ehci.rs
@@ -1,32 +1,37 @@
1// --------------------------------------------------------------------------- 1// ---------------------------------------------------------------------------
2// rsdos EHCI (USB2.0 host controller) driver — mouse only. 2// rsdos EHCI (USB 2.0 host controller) driver — HID mouse + keyboard.
3// 3//
4// Enumerates devices connected to an Intel-class EHCI (direct root-port 4// Real-hardware topology (Intel PCH, e.g. Q77/OptiPlex 7010): the USB2
5// connect), discovers a HID boot-mouse interrupt-IN endpoint, and drains its 5// controllers (00:1a.0, 00:1d.0) each present an *integrated Rate Matching
6// reports into ps2::MouseEvent. Uses the EHCI async schedule for control 6// Hub* on their root port (8087:0024, class 9). Full/low-speed devices
7// transfers and the periodic (frame-list) schedule for the interrupt IN 7// (mice, keyboards) sit behind that hub, so the driver must
8// endpoint. Polled: no IRQ/MSI — keep exactly one report outstanding and
9// drain it from the cursor task.
10// 8//
11// Why EHCI: on real hardware (e.g. a Q67/Q77-era OptiPlex) a USB mouse is a 9// 1. enumerate every EHCI controller (not just the first),
12// USB2 (full/low-speed) device attached to the EHCI USB2 controllers, not to 10// 2. enumerate a root-port hub, power/reset its ports and read the child
13// the xHCI's USB3/SS ports. QEMU only exercises the xHCI path, so this class 11// speed via hub class requests,
14// of real hardware requires its own driver. 12// 3. drive the child through split transactions using the hub's USB
13// address and the child port (TT),
14// 4. arm a periodic interrupt-IN QH per HID device and poll reports.
15// 15//
16// Identity map (phys==virt) means any kernel-heap pointer is a valid DMA 16// A device connected directly to a root port (QEMU's topology) uses the
17// address; data structures must be 32-byte aligned for the EHCI. 17// root-hub TT (hub address 0, port = root port + 1).
18//
19// Polled: no IRQ/MSI — one report outstanding per device, drained from the
20// cursor task (mouse) and the WM input pump (keyboard). DMA structures are
21// allocated from the page allocator (physical == CPU address, identity map)
22// below 4 GiB because EHCI programs 32-bit DMA pointers.
18// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
19 24
20use crate::ps2::MouseEvent; 25use crate::ps2::{KeyEvent, MouseEvent};
21use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; 26use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
22 27
23// ---- Registers (host controller registers; opbase = base + CAPLENGTH) ---- 28// ---- Registers (opbase = base + CAPLENGTH) ----
24const OPCMD: usize = 0x00; 29const OPCMD: usize = 0x00;
25const OPSTS: usize = 0x04; 30const OPSTS: usize = 0x04;
26const OPINTR: usize = 0x08; 31const OPINTR: usize = 0x08;
27const OPFRIDX: usize = 0x0C; 32const OPFRIDX: usize = 0x0C;
28const OPCFGFLAG: usize = 0x40; 33const OPCFGFLAG: usize = 0x40;
29const OP_PRTSC_BASE: usize = 0x44; // PORTSC #1 34const OP_PRTSC_BASE: usize = 0x44;
30const OP_PRTSC_STRIDE: usize = 0x04; 35const OP_PRTSC_STRIDE: usize = 0x04;
31 36
32const CMD_RUN: u32 = 1 << 0; 37const CMD_RUN: u32 = 1 << 0;
@@ -42,79 +47,131 @@ const PRT_CONNECT: u32 = 1 << 0;
42const PRT_PED: u32 = 1 << 1; 47const PRT_PED: u32 = 1 << 1;
43const PRT_RESET: u32 = 1 << 8; 48const PRT_RESET: u32 = 1 << 8;
44const PRT_POWER: u32 = 1 << 12; 49const PRT_POWER: u32 = 1 << 12;
45const PRT_RWC: u32 = (1 << 17) | (1 << 18) | (1 << 16) | (1 << 20); // CSC|PEC|CRC|OCC 50const PRT_RWC: u32 = (1 << 17) | (1 << 18) | (1 << 16) | (1 << 20);
46 51
47// ---- QH / QTD fields ---- 52// ---- QH / QTD fields ----
48const Q_TYPE_QH: u32 = 1 << 1; 53const Q_TYPE_QH: u32 = 1 << 1;
49const QT_TERMINATE: u32 = 1; // terminate = 1 in a link pointer 54const QT_TERMINATE: u32 = 1;
50const QH_HEAD: u32 = 1 << 15; 55const QH_HEAD: u32 = 1 << 15;
51const QH_CONTROL_EP: u32 = 1 << 27; 56const QH_CONTROL_EP: u32 = 1 << 27;
52const QH_DTC: u32 = 1 << 14; // data toggle comes from the qTDs (control endpoints) 57const QH_DTC: u32 = 1 << 14;
53const QH_RL: u32 = 4 << 28; // NAK reload count (Linux EHCI_TUNE_RL_{HS,TT}) 58const QH_RL: u32 = 4 << 28;
54const QH_EPS_HIGH: u32 = 2 << 12; 59const QH_EPS_HIGH: u32 = 2 << 12;
55const QH_EPS_FULL: u32 = 0 << 12; 60const QH_EPS_FULL: u32 = 0 << 12;
56const QH_EPS_LOW: u32 = 1 << 12; 61const QH_EPS_LOW: u32 = 1 << 12;
57const QH_MULT: u32 = 1 << 30; // MULT=1: one split transaction per uframe 62const QH_MULT: u32 = 1 << 30;
58 63
59const TOK_IOC: u32 = 1 << 15; 64const TOK_IOC: u32 = 1 << 15;
60const TOK_ACTIVE: u32 = 1 << 7; 65const TOK_ACTIVE: u32 = 1 << 7;
61const TOK_HALT: u32 = 1 << 6; 66const TOK_HALT: u32 = 1 << 6;
62const TOK_XACT: u32 = 1 << 3; 67const TOK_XACT: u32 = 1 << 3;
63const TOK_MMF: u32 = 1 << 2; // missed microframe: split did not complete in its window 68const TOK_MMF: u32 = 1 << 2;
64const PID_IN: u32 = 1; 69const PID_IN: u32 = 1;
65const PID_SETUP: u32 = 2; 70const PID_SETUP: u32 = 2;
66 71
67const CCERR_3: u32 = 3 << 10; 72const CCERR_3: u32 = 3 << 10;
68 73
69// ---- Static control state ---- 74// ---- Limits / DMA layout ----
70static EHCI_BASE: AtomicU32 = AtomicU32::new(0); 75const MAX_CTRL: usize = 2; // OptiPlex 7010: 00:1a.0 + 00:1d.0
71static N_PORTS: AtomicU32 = AtomicU32::new(0); 76const MAX_DEV: usize = 4; // mouse + keyboard on each controller
77const DMA_MIN: usize = 0x100_0000; // avoid <16 MiB (PCI-master hostile region)
78const DMA_MAX: usize = 0x1_0000_0000; // 32-bit EHCI DMA pointers
79
80const BUF_DEV_DESC: usize = 0x000; // 64 B
81const BUF_CFG: usize = 0x100; // 512 B (hubs can have long configs)
82const BUF_HUB_DESC: usize = 0x300; // 64 B
83const BUF_PORT_STATUS: usize = 0x340; // 16 B
84const BUF_REPORT: usize = 0x400; // 64 B per device
85
86/// One EHCI controller instance (addresses are per controller).
87#[derive(Clone, Copy)]
88struct Ctrl {
89 base: usize,
90 opb: usize,
91 nports: u32,
92 async_head: *mut u32,
93 xfer_qh: *mut u32,
94 framelist: *mut u32,
95 qh_page: *mut u32,
96 buf_page: *mut u8,
97}
98
99impl Ctrl {
100 const EMPTY: Ctrl = Ctrl {
101 base: 0,
102 opb: 0,
103 nports: 0,
104 async_head: core::ptr::null_mut(),
105 xfer_qh: core::ptr::null_mut(),
106 framelist: core::ptr::null_mut(),
107 qh_page: core::ptr::null_mut(),
108 buf_page: core::ptr::null_mut(),
109 };
110}
111
112#[derive(Clone, Copy, PartialEq)]
113enum Kind {
114 Mouse,
115 Keyboard,
116}
117
118/// Transaction translator for a device behind a hub (`None` = high-speed
119/// device or direct root-port high-speed).
120#[derive(Clone, Copy)]
121struct Tt {
122 hub: u8,
123 port: u8,
124}
125
126/// One armed HID device.
127#[derive(Clone, Copy)]
128struct Dev {
129 kind: Kind,
130 ctrl: u32,
131 qh: *mut u32,
132 iqtd: *mut u32,
133 report: *mut u8,
134 len: u32,
135 kbd: crate::hid::Keyboard,
136 armed: bool,
137 failures: u32,
138 reports: u32,
139}
140
141impl Dev {
142 const EMPTY: Dev = Dev {
143 kind: Kind::Mouse,
144 ctrl: 0,
145 qh: core::ptr::null_mut(),
146 iqtd: core::ptr::null_mut(),
147 report: core::ptr::null_mut(),
148 len: 0,
149 kbd: crate::hid::Keyboard::new(),
150 armed: false,
151 failures: 0,
152 reports: 0,
153 };
154}
155
156static mut CTRLS: [Ctrl; MAX_CTRL] = [Ctrl::EMPTY; MAX_CTRL];
157static mut DEVS: [Dev; MAX_DEV] = [Dev::EMPTY; MAX_DEV];
158static N_CTRL: AtomicU32 = AtomicU32::new(0);
159static N_DEV: AtomicU32 = AtomicU32::new(0);
160/// Controller currently being initialized (registers/pointers resolve here).
161static CUR: AtomicU32 = AtomicU32::new(0);
72static CTRL_UP: AtomicBool = AtomicBool::new(false); 162static CTRL_UP: AtomicBool = AtomicBool::new(false);
73static MOUSE_PRES: AtomicBool = AtomicBool::new(false); 163static LAST_SCAN: AtomicU32 = AtomicU32::new(0);
74static DEV_ADDR: AtomicU32 = AtomicU32::new(0);
75static HID_EP: AtomicU32 = AtomicU32::new(0); // EP number (1..15), IN
76static HID_MPS: AtomicU32 = AtomicU32::new(0);
77static ARMED: AtomicBool = AtomicBool::new(false);
78// When the last enumerate attempt ran (ms): a failed/glitched mouse must
79// never trigger a re-enumeration hot loop.
80static LAST_ENUM: AtomicU32 = AtomicU32::new(0);
81// Root ports observed in full/low-speed mode with a UHCI companion present.
82// Those ports are owned by the companion controller — the EHCI can never
83// talk to them, so they are skipped instead of failing a 5 s descriptor
84// timeout every second (the "stuck in a loop" the OptiPlex showed).
85static FS_LS_PORTS: AtomicU32 = AtomicU32::new(0);
86// One-shot: the first transfer failure renders a diagnostic QR on screen
87// (photograph it); retries keep dumping serial but must not re-render.
88static QR_SHOWN: AtomicBool = AtomicBool::new(false); 164static QR_SHOWN: AtomicBool = AtomicBool::new(false);
89// DEBUG: run the one-boot transfer variant matrix (real-hardware bisection 165
90// of the EHCI System Error trigger). MATRIX_DONE gates it to one pass; 166unsafe fn ctrl_mut(i: usize) -> &'static mut Ctrl {
91// XFER_TWEAK flips construction variables inside ctl_xfer. 167 unsafe { &mut *core::ptr::addr_of_mut!(CTRLS[i]) }
92// 168}
93// OFF in normal builds: with it on, `enumerate()` is replaced by the 169unsafe fn dev_mut(i: usize) -> &'static mut Dev {
94// diagnostic matrix and returns false, i.e. the EHCI driver never brings a 170 unsafe { &mut *core::ptr::addr_of_mut!(DEVS[i]) }
95// device up. Flip to true only when bisecting a hardware fault, and expect 171}
96// no USB input in that build. 172fn cur_ctrl() -> &'static mut Ctrl {
97static DEBUG_MATRIX: bool = false; 173 unsafe { ctrl_mut(CUR.load(Ordering::Relaxed) as usize) }
98static MATRIX_DONE: AtomicBool = AtomicBool::new(false); 174}
99static XFER_TWEAK: AtomicU32 = AtomicU32::new(0);
100// Low-region (<1 MB) control QH for the matrix's low-region variant.
101static mut LOW_XFER: *mut u32 = core::ptr::null_mut();
102
103// DMA buffers. Allocated once at init; base pointers recorded here.
104static mut ASYNC_HEAD: *mut u32 = core::ptr::null_mut(); // QH (32 bytes min, align 32)
105static mut XFER_QH: *mut u32 = core::ptr::null_mut(); // control QH
106static mut FRAMELIST: *mut u32 = core::ptr::null_mut(); // 1024 entries, 4K aligned
107static mut HID_QH: *mut u32 = core::ptr::null_mut();
108
109// One dedicated 4 KiB DMA page for every transfer buffer (device descriptor,
110// config descriptor, HID report). All DMA targets live in controller-visible
111// pages — never in .bss adjacent to kernel statics — so an overrun (report
112// longer than MPS, descriptor longer than requested, short-packet arithmetic
113// slip) can only clobber spare DMA space, on QEMU or on real silicon.
114static mut BUF_PAGE: *mut u8 = core::ptr::null_mut();
115const BUF_DEV_DESC: usize = 0x000; // 64 B incl. red zone
116const BUF_CFG: usize = 0x100; // 320 B incl. red zone
117const BUF_HID_REPORT: usize = 0x400; // 64 B incl. red zone
118 175
119unsafe fn rd(a: usize) -> u32 { 176unsafe fn rd(a: usize) -> u32 {
120 unsafe { (a as *const u32).read_volatile() } 177 unsafe { (a as *const u32).read_volatile() }
@@ -123,20 +180,17 @@ unsafe fn wr(a: usize, v: u32) {
123 unsafe { (a as *mut u32).write_volatile(v) } 180 unsafe { (a as *mut u32).write_volatile(v) }
124} 181}
125 182
126fn opbase() -> usize {
127 unsafe {
128 EHCI_BASE.load(Ordering::Relaxed) as usize
129 + (rd(EHCI_BASE.load(Ordering::Relaxed) as usize) & 0xff) as usize
130 }
131}
132fn op(off: usize) -> usize { 183fn op(off: usize) -> usize {
133 opbase() + off 184 cur_ctrl().opb + off
134} 185}
135fn prtsc(p: usize) -> usize { 186fn prtsc(p: usize) -> usize {
136 opbase() + OP_PRTSC_BASE + p * OP_PRTSC_STRIDE 187 op(OP_PRTSC_BASE + p * OP_PRTSC_STRIDE)
188}
189fn hostpc(p: usize) -> u32 {
190 unsafe { rd(cur_ctrl().base + 0x84 + p * 4) }
137} 191}
138 192
139// ---- QH/QTD helpers: all writes go through raw dword pointers ---- 193// ---- QH/QTD dword helpers ----
140fn qh_next(qh: *mut u32) -> *mut u32 { 194fn qh_next(qh: *mut u32) -> *mut u32 {
141 qh.wrapping_add(0) 195 qh.wrapping_add(0)
142} 196}
@@ -161,7 +215,6 @@ fn qh_token(qh: *mut u32) -> *mut u32 {
161fn qh_buf(qh: *mut u32) -> *mut u32 { 215fn qh_buf(qh: *mut u32) -> *mut u32 {
162 qh.wrapping_add(7) 216 qh.wrapping_add(7)
163} 217}
164
165fn link_of(ptr: usize) -> u32 { 218fn link_of(ptr: usize) -> u32 {
166 (ptr as u32) | Q_TYPE_QH 219 (ptr as u32) | Q_TYPE_QH
167} 220}
@@ -177,52 +230,64 @@ unsafe fn wait_recl(timeout: usize) {
177 } 230 }
178} 231}
179 232
180/// Issue a single control transfer on the async schedule. `dev` is the USB 233fn sleep_ms(ms: u64) {
181/// device address (0 for the first GET_DESCRIPTOR), `mps` the EP0 max packet, 234 let t0 = crate::interrupts::uptime_ms();
182/// `speed` the device speed (0=full, 1=low, 2=high), `port` the root port 235 while crate::interrupts::uptime_ms().saturating_sub(t0) < ms {
183/// index (0-based; used only for full/low-speed split QHs), `req` the 8-byte 236 core::hint::spin_loop();
184/// setup (bmRequestType,bRequest,..), `dir_in` true for a device->host data 237 }
185/// stage, `buf/len` the data buffer. Returns bytes transferred, or a 238}
186/// negative error code (-1 timeout, -2 HALT, -3 XactErr, -4 missed uframe). 239
240/// 4 KiB-aligned DMA page below 4 GiB (EHCI uses 32-bit pointers).
241unsafe fn dma_alloc(bytes: usize, max_addr: usize) -> *mut u8 {
242 unsafe {
243 let pages = bytes.div_ceil(4096).max(1);
244 let Some(phys) = crate::memory::alloc_pages_range(pages, DMA_MIN, max_addr) else {
245 return core::ptr::null_mut();
246 };
247 let p = phys as *mut u8;
248 core::ptr::write_bytes(p, 0, pages * 4096);
249 p
250 }
251}
252
253fn ctrl_req(bmreq: u8, breq: u8, wval: u16, widx: u16, wlen: u16) -> [u8; 8] {
254 [
255 bmreq,
256 breq,
257 (wval & 0xff) as u8,
258 (wval >> 8) as u8,
259 (widx & 0xff) as u8,
260 (widx >> 8) as u8,
261 (wlen & 0xff) as u8,
262 (wlen >> 8) as u8,
263 ]
264}
265
266/// One control transfer on the async schedule. `tt` selects split
267/// transactions (device behind a hub, or a full/low-speed root port).
268/// Returns bytes transferred or a negative error (-1 timeout, -2 halt,
269/// -3 setup halted, -4 data halted, -5 XactErr, -6 missed microframe).
187unsafe fn ctl_xfer( 270unsafe fn ctl_xfer(
188 dev: u32, 271 dev: u32,
189 mps: u32, 272 mps: u32,
190 speed: u32, 273 speed: u32,
191 port: usize, 274 tt: Option<Tt>,
192 req: &[u8; 8], 275 req: &[u8; 8],
193 dir_in: bool, 276 dir_in: bool,
194 buf: *mut u8, 277 buf: *mut u8,
195 len: u32, 278 len: u32,
279 diag_port: usize,
196) -> i32 { 280) -> i32 {
197 // DEBUG matrix hook: XFER_TWEAK bits flip construction variables.
198 // bit0: no-ASE arm; bit1: no-split fields; bit2: no qtd chain;
199 // bit3: zero-length (no DATA stage); bit6: short timeout (1.2 s).
200 let len = if XFER_TWEAK.load(Ordering::Relaxed) & 8 != 0 {
201 0
202 } else {
203 len
204 };
205 unsafe { 281 unsafe {
206 let head = ASYNC_HEAD; 282 let head = cur_ctrl().async_head;
207 let tw0 = XFER_TWEAK.load(Ordering::Relaxed); 283 let xfer = cur_ctrl().xfer_qh;
208 // DEBUG matrix: execute the same transfer from the low (<1 MB) page.
209 let xfer = if tw0 & 0x80 != 0 && !LOW_XFER.is_null() {
210 LOW_XFER
211 } else {
212 XFER_QH
213 };
214 284
215 // QH occupies dwords 0..11 (48 bytes) incl. the 5-dword buffer list. 285 let st = xfer.add(0x40 / 4); // SETUP qtd (32-byte aligned)
216 // QEMU's NLPTR_GET masks qtd addresses to 32-byte alignment 286 let dt = st.add(0x20 / 4); // DATA qtd
217 // (& 0xffffffe0), so every qtd must sit on a 32-byte boundary. 287 let sp = dt.add(0x20 / 4); // STATUS qtd
218 // Place them at +0x40 / +0x60 / +0x80 (each 8 dwords = 32 bytes). 288
219 let st = xfer.add(0x40 / 4); // SETUP qtd at QH+0x40 (32-aligned) 289 core::ptr::write_volatile(st.add(0), 0u32);
220 let dt = st.add(0x20 / 4); // DATA qtd at QH+0x60 (32-aligned) 290 core::ptr::write_volatile(st.add(1), QT_TERMINATE);
221 let sp = dt.add(0x20 / 4); // STATUS qtd at QH+0x80 (32-aligned)
222
223 // SETUP token: toggle 0, len 8, PID_SETUP, active, IOC off.
224 core::ptr::write_volatile(st.add(0), 0u32); // next (chained below)
225 core::ptr::write_volatile(st.add(1), QT_TERMINATE); // alt: terminate
226 core::ptr::write_volatile( 291 core::ptr::write_volatile(
227 st.add(2), 292 st.add(2),
228 (8 << 16) | CCERR_3 | (PID_SETUP << 8) | TOK_ACTIVE, 293 (8 << 16) | CCERR_3 | (PID_SETUP << 8) | TOK_ACTIVE,
@@ -230,61 +295,45 @@ unsafe fn ctl_xfer(
230 for i in 0..5 { 295 for i in 0..5 {
231 core::ptr::write_volatile(st.add(3 + i), 0u32); 296 core::ptr::write_volatile(st.add(3 + i), 0u32);
232 } 297 }
233 // Setup packet buffer: 8 bytes at +0x110 (clear of the QH + qtds).
234 let sbuf = xfer.add(0x110 / 4); 298 let sbuf = xfer.add(0x110 / 4);
235 let sb = core::slice::from_raw_parts_mut(sbuf.cast::<u8>(), 8); 299 let sb = core::slice::from_raw_parts_mut(sbuf.cast::<u8>(), 8);
236 sb.copy_from_slice(req); 300 sb.copy_from_slice(req);
237 core::ptr::write_volatile(st.add(3), sbuf as u32); 301 core::ptr::write_volatile(st.add(3), sbuf as u32);
238 302
239 // DATA qtd. EHCI token PID field (bits 9:8): OUT=0, IN=1, SETUP=2.
240 let data_pid_w = (if dir_in { 1u32 } else { 0u32 }) << 8; 303 let data_pid_w = (if dir_in { 1u32 } else { 0u32 }) << 8;
241 if len == 0 { 304 if len == 0 {
242 // no data stage
243 core::ptr::write_volatile(dt.add(2), 0u32); 305 core::ptr::write_volatile(dt.add(2), 0u32);
244 } else { 306 } else {
245 core::ptr::write_volatile(dt.add(0), sp as u32); 307 core::ptr::write_volatile(dt.add(0), sp as u32);
246 // altnext MUST be terminate (1): QEMU's ADVANCEQUEUE follows altnext
247 // whenever TBYTES != 0 after an IN (e.g. a short packet). altnext=0
248 // is treated as *valid* (bit0==0) so the controller would jump to
249 // qtd address 0 and hang. Terminate keeps short transfers chained
250 // to the STATUS qtd, as required for control transfers.
251 core::ptr::write_volatile(dt.add(1), QT_TERMINATE); 308 core::ptr::write_volatile(dt.add(1), QT_TERMINATE);
252 core::ptr::write_volatile( 309 core::ptr::write_volatile(
253 dt.add(2), 310 dt.add(2),
254 ((len & 0x7fff) << 16) | (1 << 31) | CCERR_3 | (data_pid_w) | TOK_ACTIVE, 311 ((len & 0x7fff) << 16) | (1 << 31) | CCERR_3 | data_pid_w | TOK_ACTIVE,
255 ); 312 );
256 for i in 0..5 { 313 for i in 0..5 {
257 core::ptr::write_volatile(dt.add(3 + i), 0u32); 314 core::ptr::write_volatile(dt.add(3 + i), 0u32);
258 } 315 }
259 // data buffer
260 core::ptr::write_volatile(dt.add(3), buf as u32); 316 core::ptr::write_volatile(dt.add(3), buf as u32);
261 } 317 }
262 318
263 // STATUS qtd: toggle 1, len 0, PID opposite of the data stage; for a
264 // zero-length control the status is opposite of the setup direction.
265 let setup_in = (req[0] & 0x80) != 0; 319 let setup_in = (req[0] & 0x80) != 0;
266 let status_pid_w = if len > 0 { 320 let status_pid_w = if len > 0 {
267 if dir_in { 321 if dir_in { 0u32 } else { 1u32 }
268 0u32
269 } else {
270 1u32
271 } // opposite of data
272 } else if setup_in { 322 } else if setup_in {
273 0u32 323 0u32
274 } else { 324 } else {
275 1u32 325 1u32
276 } << 8; 326 } << 8;
277 core::ptr::write_volatile(sp.add(0), 1u32); // terminate 327 core::ptr::write_volatile(sp.add(0), 1u32);
278 core::ptr::write_volatile(sp.add(1), QT_TERMINATE); 328 core::ptr::write_volatile(sp.add(1), QT_TERMINATE);
279 core::ptr::write_volatile( 329 core::ptr::write_volatile(
280 sp.add(2), 330 sp.add(2),
281 (1 << 31) | CCERR_3 | (status_pid_w) | TOK_ACTIVE | TOK_IOC, 331 (1 << 31) | CCERR_3 | status_pid_w | TOK_ACTIVE | TOK_IOC,
282 ); 332 );
283 for i in 0..5 { 333 for i in 0..5 {
284 core::ptr::write_volatile(sp.add(3 + i), 0u32); 334 core::ptr::write_volatile(sp.add(3 + i), 0u32);
285 } 335 }
286 336
287 // Chain the qtd list.
288 if len == 0 { 337 if len == 0 {
289 core::ptr::write_volatile(st.add(0), sp as u32); 338 core::ptr::write_volatile(st.add(0), sp as u32);
290 } else { 339 } else {
@@ -292,24 +341,18 @@ unsafe fn ctl_xfer(
292 core::ptr::write_volatile(dt.add(0), sp as u32); 341 core::ptr::write_volatile(dt.add(0), sp as u32);
293 } 342 }
294 343
295 // Configure the control QH (EP0).
296 let eps = match speed { 344 let eps = match speed {
297 2 => QH_EPS_HIGH, 345 2 => QH_EPS_HIGH,
298 1 => QH_EPS_LOW, 346 1 => QH_EPS_LOW,
299 _ => QH_EPS_FULL, 347 _ => QH_EPS_FULL,
300 }; 348 };
301 let control_flag = if speed == 2 { 0 } else { QH_CONTROL_EP }; 349 let control_flag = if speed == 2 { 0 } else { QH_CONTROL_EP };
302 // FS/LS split QH: DTC (toggles come from the qTDs), NAK reload, MULT=1, 350 let info1 = (mps & 0x7ff) << 16 | QH_RL | QH_DTC | control_flag | eps | dev;
303 // hub addr 0 (integrated root-hub TT) and the 1-based root port number
304 // in bits 29:23. The controller runs SSPLIT/CSPLIT pairs for every qTD
305 // on this QH; no S/C masks are needed on the async list (Linux and
306 // FreeBSD both leave them 0 for control/bulk).
307 let info1 = (mps & 0x7ff) << 16 | QH_RL | QH_DTC | control_flag | eps | dev; // devaddr in low 7 bits
308 *qh_info1(xfer) = info1; 351 *qh_info1(xfer) = info1;
309 *qh_info2(xfer) = if speed == 2 || XFER_TWEAK.load(Ordering::Relaxed) & 2 != 0 { 352 // Split fields: hub address bits 22:16, port bits 29:23, MULT bit 30.
310 0 353 *qh_info2(xfer) = match tt {
311 } else { 354 Some(t) if speed != 2 => QH_MULT | ((t.hub as u32) << 16) | ((t.port as u32) << 23),
312 QH_MULT | (((port as u32) + 1) << 23) 355 _ => 0,
313 }; 356 };
314 *qh_current(xfer) = 0; 357 *qh_current(xfer) = 0;
315 *qh_next_qtd(xfer) = st as u32; 358 *qh_next_qtd(xfer) = st as u32;
@@ -319,44 +362,20 @@ unsafe fn ctl_xfer(
319 *qh_buf(xfer).add(i) = 0; 362 *qh_buf(xfer).add(i) = 0;
320 } 363 }
321 364
322 // Link: head -> xfer -> head (reclamation cycle).
323 *qh_next(xfer) = link_of(head as usize); 365 *qh_next(xfer) = link_of(head as usize);
324 *qh_next(head) = link_of(xfer as usize); 366 *qh_next(head) = link_of(xfer as usize);
325 if XFER_TWEAK.load(Ordering::Relaxed) & 4 != 0 {
326 *qh_next_qtd(xfer) = 1; // no qtd chain: HC treats the QH as idle
327 }
328 367
329 // Ensure the controller is running and both schedules enabled.
330 // Re-assert every transfer: QEMU may drop RUNSTOP, and re-setting the
331 // bits is what makes it (re)schedule the async walk.
332 //
333 // Clear the W1C status bits first so a failure dump reflects only
334 // this transfer's window (USBINT|USBERR|PCD|FR|SE).
335 wr(op(OPSTS), 0x1F); 368 wr(op(OPSTS), 0x1F);
336 // Fence: make all DMA writes globally visible before the controller reads.
337 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); 369 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
338 let tw = XFER_TWEAK.load(Ordering::Relaxed); 370 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_ASE);
339 if tw & 0x20 != 0 { 371
340 // wire-off: disable the port so no transaction can reach the wire.
341 wr(prtsc(port), rd(prtsc(port)) & !(1 << 1)); // PED=0
342 }
343 wr(
344 op(OPCMD),
345 rd(op(OPCMD)) | CMD_RUN | (if tw & 1 == 0 { CMD_ASE } else { 0 }),
346 ); // no PSE — periodic not needed for control
347 let ctl_timeout = if tw & 0x40 != 0 { 1200u64 } else { 5000u64 };
348 // Poll for completion. Watch the whole chain: a HALT on the SETUP
349 // or DATA qtd stops the queue and the STATUS qtd would never clear
350 // (which previously read as a plain timeout). Exit early naming the
351 // failing stage, and on any failure dump the controller + queue state
352 // for real-hardware iteration.
353 let t0 = crate::interrupts::uptime_ms(); 372 let t0 = crate::interrupts::uptime_ms();
354 let mut stage: i32 = 0; // 0=ok, 1=setup halted, 2=data halted, 3=timeout, 4=hc halted/SE 373 let mut stage: i32 = 0;
355 loop { 374 loop {
356 let stok = core::ptr::read_volatile(st.add(2).cast::<u32>()); 375 let stok = core::ptr::read_volatile(st.add(2).cast::<u32>());
357 let ptok = core::ptr::read_volatile(sp.add(2).cast::<u32>()); 376 let ptok = core::ptr::read_volatile(sp.add(2).cast::<u32>());
358 if ptok & TOK_ACTIVE == 0 { 377 if ptok & TOK_ACTIVE == 0 {
359 break; // status completed 378 break;
360 } 379 }
361 if stok & TOK_HALT != 0 { 380 if stok & TOK_HALT != 0 {
362 stage = 1; 381 stage = 1;
@@ -369,15 +388,11 @@ unsafe fn ctl_xfer(
369 break; 388 break;
370 } 389 }
371 } 390 }
372 // Controller-level fault: the HC halted itself (HCH) or threw a
373 // system error (SE). Polling further is pointless — the qtds
374 // would sit ACTIVE forever and read back as a plain timeout.
375 // Catching it here names the window precisely.
376 if rd(op(OPSTS)) & (STS_HCH | STS_SE) != 0 { 391 if rd(op(OPSTS)) & (STS_HCH | STS_SE) != 0 {
377 stage = 4; 392 stage = 4;
378 break; 393 break;
379 } 394 }
380 if t0 + ctl_timeout < crate::interrupts::uptime_ms() { 395 if t0 + 5000 < crate::interrupts::uptime_ms() {
381 stage = 3; 396 stage = 3;
382 break; 397 break;
383 } 398 }
@@ -392,50 +407,34 @@ unsafe fn ctl_xfer(
392 }; 407 };
393 let ptok = core::ptr::read_volatile(sp.add(2).cast::<u32>()); 408 let ptok = core::ptr::read_volatile(sp.add(2).cast::<u32>());
394 crate::serial_println!( 409 crate::serial_println!(
395 "ehci: xfer fail stage={} (1=setup 2=data 3=timeout) speed={} dev={} dir_in={} len={}", 410 "ehci: xfer fail stage={} speed={} dev={} in={} len={} tt={:?}",
396 stage, 411 stage,
397 speed, 412 speed,
398 dev, 413 dev,
399 dir_in, 414 dir_in,
400 len 415 len,
416 tt.map(|t| (t.hub, t.port))
401 ); 417 );
402 let base = EHCI_BASE.load(Ordering::Relaxed) as usize;
403 crate::serial_println!( 418 crate::serial_println!(
404 "ehci: cmd={:#x} sts={:#x} prtsc{}={:#x} hostpc={:#x}", 419 "ehci: cmd={:#x} sts={:#x} prtsc{}={:#x} hostpc={:#x}",
405 rd(op(OPCMD)), 420 rd(op(OPCMD)),
406 rd(op(OPSTS)), 421 rd(op(OPSTS)),
407 port, 422 diag_port,
408 rd(prtsc(port)), 423 rd(prtsc(diag_port)),
409 rd(base + 0x84 + port * 4) 424 hostpc(diag_port)
410 ); 425 );
411 crate::serial_println!( 426 crate::serial_println!(
412 "ehci: qh i1={:#x} i2={:#x} cur={:#x} nqtd={:#x}", 427 "ehci: qh i1={:#x} i2={:#x} nqtd={:#x} setup={:#x} data={:#x} stat={:#x}",
413 core::ptr::read_volatile(qh_info1(xfer)), 428 core::ptr::read_volatile(qh_info1(xfer)),
414 core::ptr::read_volatile(qh_info2(xfer)), 429 core::ptr::read_volatile(qh_info2(xfer)),
415 core::ptr::read_volatile(qh_current(xfer)), 430 core::ptr::read_volatile(qh_next_qtd(xfer)),
416 core::ptr::read_volatile(qh_next_qtd(xfer))
417 );
418 crate::serial_println!(
419 "ehci: tok setup={:#x} data={:#x} stat={:#x}",
420 stok, 431 stok,
421 dtok, 432 dtok,
422 ptok 433 ptok
423 ); 434 );
424 if stage == 4 {
425 crate::serial_println!(
426 "ehci: HC halted/SE after {} ms since arm (frindex={:#x} asyncbase={:#x})",
427 crate::interrupts::uptime_ms().wrapping_sub(t0),
428 rd(op(OPFRIDX)),
429 rd(op(0x18))
430 );
431 }
432 // One-shot on-screen QR of the whole diagnostic: band text plus a
433 // scannable matrix below. Used on hardware with no serial pipe —
434 // point a phone at the screen. Lock the klog screen echo so the
435 // retry loop cannot overwrite it (serial keeps logging).
436 if !QR_SHOWN.swap(true, Ordering::Relaxed) { 435 if !QR_SHOWN.swap(true, Ordering::Relaxed) {
437 let l1 = alloc::format!( 436 let l1 = alloc::format!(
438 "EHCI ctl fail stage={} t={}ms speed={} dev={} in={} len={}", 437 "EHCI fail stage={} t={}ms sp={} dev={} in={} len={}",
439 stage, 438 stage,
440 crate::interrupts::uptime_ms().wrapping_sub(t0), 439 crate::interrupts::uptime_ms().wrapping_sub(t0),
441 speed, 440 speed,
@@ -446,27 +445,18 @@ unsafe fn ctl_xfer(
446 let l2 = alloc::format!("cmd={:08x} sts={:08x}", rd(op(OPCMD)), rd(op(OPSTS))); 445 let l2 = alloc::format!("cmd={:08x} sts={:08x}", rd(op(OPCMD)), rd(op(OPSTS)));
447 let l3 = alloc::format!( 446 let l3 = alloc::format!(
448 "prtsc{}=0x{:08x} hp=0x{:08x}", 447 "prtsc{}=0x{:08x} hp=0x{:08x}",
449 port, 448 diag_port,
450 rd(prtsc(port)), 449 rd(prtsc(diag_port)),
451 rd(base + 0x84 + port * 4) 450 hostpc(diag_port)
452 ); 451 );
453 let l4 = alloc::format!( 452 let l4 = alloc::format!(
454 "qh1={:08x} qh2={:08x} cur={:08x} nqtd={:08x}", 453 "qh1={:08x} qh2={:08x} nqtd={:08x}",
455 core::ptr::read_volatile(qh_info1(xfer)), 454 core::ptr::read_volatile(qh_info1(xfer)),
456 core::ptr::read_volatile(qh_info2(xfer)), 455 core::ptr::read_volatile(qh_info2(xfer)),
457 core::ptr::read_volatile(qh_current(xfer)),
458 core::ptr::read_volatile(qh_next_qtd(xfer)) 456 core::ptr::read_volatile(qh_next_qtd(xfer))
459 ); 457 );
460 let l5 = alloc::format!("setup={:08x} data={:08x} stat={:08x}", stok, dtok, ptok); 458 let l5 = alloc::format!("setup={:08x} data={:08x} stat={:08x}", stok, dtok, ptok);
461 let l6 = alloc::format!( 459 let all = [l1, l2, l3, l4, l5];
462 "dma a={:08x} x={:08x} f={:08x} h={:08x} b={:08x}",
463 ASYNC_HEAD as usize,
464 XFER_QH as usize,
465 FRAMELIST as usize,
466 HID_QH as usize,
467 BUF_PAGE as usize
468 );
469 let all = [l1, l2, l3, l4, l5, l6];
470 let refs: alloc::vec::Vec<&str> = all.iter().map(|s| s.as_str()).collect(); 460 let refs: alloc::vec::Vec<&str> = all.iter().map(|s| s.as_str()).collect();
471 crate::gop::dump_qr(&refs); 461 crate::gop::dump_qr(&refs);
472 crate::gop::qr_lock(); 462 crate::gop::qr_lock();
@@ -474,334 +464,720 @@ unsafe fn ctl_xfer(
474 *qh_next(head) = link_of(head as usize); 464 *qh_next(head) = link_of(head as usize);
475 wait_recl(100); 465 wait_recl(100);
476 return match stage { 466 return match stage {
477 1 => -3, // setup halted 467 1 => -3,
478 2 => -4, // data halted 468 2 => -4,
479 _ => -1, // timeout 469 _ => -1,
480 }; 470 };
481 } 471 }
482 // Status completed — check its error bits.
483 let tok = core::ptr::read_volatile(sp.add(2).cast::<u32>()); 472 let tok = core::ptr::read_volatile(sp.add(2).cast::<u32>());
484 if tok & TOK_HALT != 0 { 473 if tok & TOK_HALT != 0 {
485 *qh_next(head) = link_of(head as usize); 474 *qh_next(head) = link_of(head as usize);
486 wait_recl(100); 475 wait_recl(100);
487 return -2; // HALT — qtd halted on error (babble/DBE/...) 476 return -2;
488 } 477 }
489 if tok & TOK_XACT != 0 { 478 if tok & TOK_XACT != 0 {
490 *qh_next(head) = link_of(head as usize); 479 *qh_next(head) = link_of(head as usize);
491 wait_recl(100); 480 wait_recl(100);
492 return -5; // XactErr — CRC/timeout on the transaction 481 return -5;
493 } 482 }
494 if tok & TOK_MMF != 0 { 483 if tok & TOK_MMF != 0 {
495 *qh_next(head) = link_of(head as usize); 484 *qh_next(head) = link_of(head as usize);
496 wait_recl(100); 485 wait_recl(100);
497 return -6; // missed microframe — split window missed (retryable) 486 return -6;
498 } 487 }
499 // Bytes transferred = requested - remaining on the DATA qtd.
500 let mut got = len; 488 let mut got = len;
501 if len > 0 { 489 if len > 0 {
502 let dtok = core::ptr::read_volatile(dt.add(2).cast::<u32>()); 490 let dtok = core::ptr::read_volatile(dt.add(2).cast::<u32>());
503 let rem = ((dtok >> 16) & 0x7fff) as u32; 491 let rem = ((dtok >> 16) & 0x7fff) as u32;
504 got = len - rem.min(len); 492 got = len - rem.min(len);
505 } 493 }
506 // Unlink: idle self-loop must keep the QH type bit, otherwise QEMU
507 // parses it as an iTD and resets the HC ("non queue head request").
508 *qh_next(head) = link_of(head as usize); 494 *qh_next(head) = link_of(head as usize);
509 wait_recl(100); 495 wait_recl(100);
510 let _ = qh_next_qtd;
511 got as i32 496 got as i32
512 } 497 }
513} 498}
514 499
515// Control-transfer convenience using the standard control request layout. 500// ---------------------------------------------------------------------------
516fn ctrl_req(bmreq: u8, breq: u8, wval: u16, widx: u16, wlen: u16) -> [u8; 8] { 501// Descriptor parsing
517 [ 502// ---------------------------------------------------------------------------
518 bmreq, 503
519 breq, 504const HID_CLASS: u8 = 0x03;
520 (wval & 0xff) as u8, 505const HID_SUBCLASS_BOOT: u8 = 0x01;
521 (wval >> 8) as u8, 506const HID_PROTO_KEYBOARD: u8 = 1;
522 (widx & 0xff) as u8, 507const HID_PROTO_MOUSE: u8 = 2;
523 (widx >> 8) as u8, 508
524 (wlen & 0xff) as u8, 509/// Walk a config descriptor: find the first boot HID mouse/keyboard
525 (wlen >> 8) as u8, 510/// interface and its interrupt-IN endpoint.
526 ] 511/// Returns (kind, interface number, ep address, mps, interval).
512fn find_hid(cfg: &[u8]) -> Option<(Kind, u8, u8, u32, u32)> {
513 let mut i = 0usize;
514 let mut cand: Option<(Kind, u8)> = None;
515 while i + 2 <= cfg.len() {
516 let blen = cfg[i] as usize;
517 if blen < 2 {
518 break;
519 }
520 let bt = cfg[i + 1];
521 if bt == 4 && blen >= 9 && i + 9 <= cfg.len() {
522 // interface: class/subclass/protocol at +5/+6/+7
523 let iface = cfg[i + 2];
524 let class = cfg[i + 5];
525 let sub = cfg[i + 6];
526 let proto = cfg[i + 7];
527 cand = if class == HID_CLASS && sub == HID_SUBCLASS_BOOT {
528 match proto {
529 HID_PROTO_KEYBOARD => Some((Kind::Keyboard, iface)),
530 HID_PROTO_MOUSE => Some((Kind::Mouse, iface)),
531 _ => None,
532 }
533 } else {
534 None
535 };
536 } else if bt == 5 && blen >= 7 && i + 7 <= cfg.len() {
537 if let Some((kind, iface)) = cand {
538 let addr = cfg[i + 2];
539 let attrs = cfg[i + 3];
540 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8);
541 let interval = cfg[i + 6] as u32;
542 if (addr & 0x80) != 0 && (attrs & 0x03) == 0x03 {
543 return Some((kind, iface, addr, mps, interval));
544 }
545 }
546 }
547 i += blen;
548 }
549 None
550}
551
552/// Hub class descriptor: port count lives at offset 2.
553fn hub_ports(desc: &[u8]) -> u32 {
554 if desc.len() >= 3 && desc[1] == 0x29 {
555 desc[2] as u32
556 } else {
557 0
558 }
527} 559}
528 560
529// --------------------------------------------------------------------------- 561// ---------------------------------------------------------------------------
530// Controller bring-up 562// Hub port control (USB 2.0 hub class requests)
531// --------------------------------------------------------------------------- 563// ---------------------------------------------------------------------------
532 564
533/// Find an EHCI controller (class 0x0C class 0x03, prog_if 0x20). 565const HUB_GET_STATUS: u8 = 0x00;
534fn find_ehci() -> Option<(u8, u8, u8)> { 566const HUB_CLEAR_FEATURE: u8 = 0x01;
535 for d in crate::pci::probe() { 567const HUB_SET_FEATURE: u8 = 0x03;
536 if d.class == 0x0C && d.subclass == 0x03 && d.prog_if == 0x20 { 568
537 return Some((d.bus, d.device, d.func)); 569const PORT_RESET_FEAT: u16 = 4;
570const PORT_POWER_FEAT: u16 = 8;
571const C_PORT_RESET_FEAT: u16 = 20;
572
573const PS_CONNECTION: u32 = 1 << 0;
574const PS_ENABLE: u32 = 1 << 1;
575const PS_RESET: u32 = 1 << 4;
576const PS_LOW_SPEED: u32 = 1 << 9;
577const PS_HIGH_SPEED: u32 = 1 << 10;
578
579/// Read (status, change) for one hub port.
580unsafe fn hub_port_status(hub_addr: u8, hub_mps: u32, port: u16) -> Option<(u32, u32)> {
581 unsafe {
582 let buf = cur_ctrl().buf_page.add(BUF_PORT_STATUS);
583 core::ptr::write_bytes(buf, 0, 8);
584 let req = ctrl_req(0xA3, HUB_GET_STATUS, 0, port, 4);
585 let r = ctl_xfer(
586 hub_addr as u32,
587 hub_mps,
588 2,
589 None,
590 &req,
591 true,
592 buf,
593 4,
594 port as usize,
595 );
596 if r < 4 {
597 return None;
538 } 598 }
599 let b = core::slice::from_raw_parts(buf, 4);
600 let st = u16::from_le_bytes([b[0], b[1]]) as u32;
601 let ch = u16::from_le_bytes([b[2], b[3]]) as u32;
602 Some((st, ch))
539 } 603 }
540 None
541} 604}
542 605
543/// Return the number of the first root port with a device attached, or usize::MAX. 606unsafe fn hub_feature(hub_addr: u8, hub_mps: u32, set: bool, feat: u16, port: u16) {
544fn find_connected_port() -> usize { 607 unsafe {
545 let np = N_PORTS.load(Ordering::Relaxed) as usize; 608 let req = ctrl_req(
546 let skip = FS_LS_PORTS.load(Ordering::Relaxed); 609 if set { 0x23 } else { 0x23 },
547 for p in 0..np { 610 if set { HUB_SET_FEATURE } else { HUB_CLEAR_FEATURE },
548 if skip & (1 << p) != 0 { 611 feat,
549 continue; // full/low-speed — owned by the companion UHCI 612 port,
613 0,
614 );
615 let _ = ctl_xfer(
616 hub_addr as u32,
617 hub_mps,
618 2,
619 None,
620 &req,
621 false,
622 core::ptr::null_mut(),
623 0,
624 port as usize,
625 );
626 }
627}
628
629// ---------------------------------------------------------------------------
630// Enumeration
631// ---------------------------------------------------------------------------
632
633static NEXT_DEV_ADDR: AtomicU32 = AtomicU32::new(1);
634
635fn take_addr() -> u8 {
636 NEXT_DEV_ADDR.fetch_add(1, Ordering::Relaxed) as u8
637}
638
639/// Register + arm one HID device.
640unsafe fn register_hid(
641 ci: usize,
642 addr: u8,
643 kind: Kind,
644 ep: u8,
645 mps: u32,
646 interval: u32,
647 speed: u32,
648 tt: Option<Tt>,
649) -> bool {
650 unsafe {
651 let slot = N_DEV.load(Ordering::Relaxed) as usize;
652 if slot >= MAX_DEV {
653 return false;
654 }
655 let c = ctrl_mut(ci);
656 let qh = c.qh_page.add(slot * 0x100 / 4);
657 let iqtd = qh.add(0x40 / 4);
658 let report = c.buf_page.add(BUF_REPORT + slot * 64);
659 core::ptr::write_bytes(qh as *mut u8, 0, 0x100);
660 core::ptr::write_bytes(report, 0, 64);
661
662 let len = mps.min(8).max(1);
663 let info1 = QH_RL
664 | (speed << 12)
665 | ((mps.max(4) & 0x7ff) << 16)
666 | (addr as u32 & 0x7f)
667 | (((ep as u32) & 0x0f) << 8);
668 let info2 = match tt {
669 Some(t) if speed != 2 => {
670 let (smask, cmask) = (0x01u32, 0x1Cu32 << 8);
671 smask | cmask | QH_MULT | ((t.hub as u32) << 16) | ((t.port as u32) << 23)
672 }
673 _ => 0xFF, // high-speed: schedule every microframe
674 };
675 *qh_next(qh) = QT_TERMINATE;
676 *qh_info1(qh) = info1;
677 *qh_info2(qh) = info2;
678 *qh_current(qh) = 0;
679 *qh_alt_qtd(qh) = QT_TERMINATE;
680 *qh_token(qh) = 0;
681 for i in 0..5 {
682 *qh_buf(qh).add(i) = 0;
550 } 683 }
551 let v = unsafe { rd(prtsc(p)) }; 684 core::ptr::write_volatile(iqtd.add(0), QT_TERMINATE);
552 if v & PRT_CONNECT != 0 { 685 core::ptr::write_volatile(iqtd.add(1), QT_TERMINATE);
553 return p; 686 core::ptr::write_volatile(
687 iqtd.add(2),
688 ((len & 0x7fff) << 16) | (PID_IN << 8) | TOK_ACTIVE | TOK_IOC,
689 );
690 for i in 0..5 {
691 core::ptr::write_volatile(iqtd.add(3 + i), 0u32);
554 } 692 }
693 core::ptr::write_volatile(iqtd.add(3), report as u32);
694 *qh_next_qtd(qh) = iqtd as u32;
695
696 let d = dev_mut(slot);
697 d.kind = kind;
698 d.ctrl = ci as u32;
699 d.qh = qh;
700 d.iqtd = iqtd;
701 d.report = report;
702 d.len = len;
703 d.kbd = crate::hid::Keyboard::new();
704 d.armed = true;
705 d.failures = 0;
706 d.reports = 0;
707 N_DEV.fetch_add(1, Ordering::Relaxed);
708 crate::serial_println!(
709 "ehci: HID {} armed (addr {} ep{:#x} mps {} interval {})",
710 if kind == Kind::Mouse { "mouse" } else { "keyboard" },
711 addr,
712 ep,
713 mps,
714 interval
715 );
716 true
555 } 717 }
556 usize::MAX
557} 718}
558 719
559/// Clear the FS/LS port mask so discovery retries (shell `usb rescan`). 720/// Enumerate the device at `addr` (0 = unaddressed) on the current
560pub fn rescan() { 721/// controller. Handles HID devices and one level of USB hub.
561 FS_LS_PORTS.store(0, Ordering::Relaxed); 722/// Returns the number of HID devices registered.
723unsafe fn enumerate(addr: u8, speed: u32, tt: Option<Tt>, diag_port: usize) -> u32 {
724 unsafe {
725 let mut addr = addr;
726 let mut mps = if speed == 2 { 64u32 } else { 8u32 };
727 let cbuf = cur_ctrl().buf_page;
728
729 // 1. Device descriptor (learn bMaxPacketSize0 + class).
730 let dd = cbuf.add(BUF_DEV_DESC);
731 core::ptr::write_bytes(dd, 0, 64);
732 let req = ctrl_req(0x80, 6, 0x0100, 0, 18);
733 let mut got = ctl_xfer(
734 addr as u32,
735 mps,
736 speed,
737 tt,
738 &req,
739 true,
740 dd,
741 18,
742 diag_port,
743 );
744 if got < 8 {
745 crate::serial_println!(
746 "ehci: get-dev-desc failed (addr {} sp {} tt {:?}) code={}",
747 addr,
748 speed,
749 tt.map(|t| (t.hub, t.port)),
750 got
751 );
752 return 0;
753 }
754 let dd = core::slice::from_raw_parts(dd, 18);
755 let class = dd[4];
756 let mps0 = dd[7] as u32;
757 if mps0 != 0 {
758 mps = mps0;
759 }
760
761 // 2. Assign an address (root/unaddressed devices only).
762 if addr == 0 {
763 let new = take_addr();
764 let req = ctrl_req(0x00, 5, new as u16, 0, 0);
765 got = ctl_xfer(
766 0,
767 mps,
768 speed,
769 tt,
770 &req,
771 false,
772 core::ptr::null_mut(),
773 0,
774 diag_port,
775 );
776 if got < 0 {
777 crate::serial_println!("ehci: set-address failed code={}", got);
778 return 0;
779 }
780 addr = new;
781 sleep_ms(2); // USB 2.0 §9.2.6.3: 2 ms recovery
782 }
783
784 crate::serial_println!(
785 "ehci: dev {:04x}:{:04x} class={:#x} mps0={} addr={} sp={}{}",
786 u16::from_le_bytes([dd[8], dd[9]]),
787 u16::from_le_bytes([dd[10], dd[11]]),
788 class,
789 mps,
790 addr,
791 speed,
792 if tt.is_some() { " (split)" } else { "" }
793 );
794
795 // 3. Hub: configure it, then scan its ports for HID devices.
796 if class == 9 {
797 return scan_hub(addr, mps, diag_port);
798 }
799
800 // 4. HID: read the config descriptor.
801 let cfg = cbuf.add(BUF_CFG);
802 core::ptr::write_bytes(cfg, 0, 512);
803 let req = ctrl_req(0x80, 6, 0x0200, 0, 512);
804 got = ctl_xfer(addr as u32, mps, speed, tt, &req, true, cfg, 512, diag_port);
805 if got < 9 {
806 crate::serial_println!("ehci: get-config failed code={}", got);
807 return 0;
808 }
809 let cfgs = core::slice::from_raw_parts(cfg, got as usize);
810 let Some((kind, iface, ep, epmps, interval)) = find_hid(cfgs) else {
811 crate::serial_println!("ehci: no HID mouse/keyboard endpoint in config");
812 return 0;
813 };
814
815 // 5. SET_CONFIGURATION(config 1) then boot protocol for the interface.
816 let cfgval = cfgs[5];
817 if cfgval == 0 {
818 return 0;
819 }
820 let req = ctrl_req(0x00, 9, cfgval as u16, 0, 0);
821 if ctl_xfer(addr as u32, mps, speed, tt, &req, false, core::ptr::null_mut(), 0, diag_port)
822 < 0
823 {
824 crate::serial_println!("ehci: set-config failed");
825 return 0;
826 }
827 // HID class SET_PROTOCOL(boot) — report layout becomes fixed.
828 let req = ctrl_req(0x21, 0x0B, 0, iface as u16, 0);
829 if ctl_xfer(addr as u32, mps, speed, tt, &req, false, core::ptr::null_mut(), 0, diag_port)
830 < 0
831 {
832 crate::serial_println!("ehci: set-protocol(boot) stalled (continuing)");
833 }
834 // HID SET_IDLE(0) — only report on change; some devices stall it.
835 let req = ctrl_req(0x21, 0x0A, 0, iface as u16, 0);
836 let _ = ctl_xfer(addr as u32, mps, speed, tt, &req, false, core::ptr::null_mut(), 0, diag_port);
837
838 let ci = CUR.load(Ordering::Relaxed) as usize;
839 if register_hid(ci, addr, kind, ep, epmps, interval, speed, tt) {
840 1
841 } else {
842 0
843 }
844 }
562} 845}
563 846
564pub fn init() -> bool { 847/// Configure a USB hub, then enumerate every connected port's device.
565 let Some((b, d, f)) = find_ehci() else { 848unsafe fn scan_hub(hub_addr: u8, hub_mps: u32, diag_port: usize) -> u32 {
566 crate::serial_println!("ehci: no EHCI controller"); 849 unsafe {
567 return false; 850 let cbuf = cur_ctrl().buf_page;
568 }; 851 // Config + SET_CONFIGURATION.
569 crate::serial_println!("ehci: USB2 EHCI at {:02x}:{:02x}.{}", b, d, f); 852 let cfg = cbuf.add(BUF_CFG);
853 core::ptr::write_bytes(cfg, 0, 512);
854 let req = ctrl_req(0x80, 6, 0x0200, 0, 512);
855 let got = ctl_xfer(hub_addr as u32, hub_mps, 2, None, &req, true, cfg, 512, diag_port);
856 if got < 9 {
857 crate::serial_println!("ehci: hub get-config failed code={}", got);
858 return 0;
859 }
860 let cfgval = core::slice::from_raw_parts(cfg, got as usize)[5];
861 let req = ctrl_req(0x00, 9, cfgval as u16, 0, 0);
862 if ctl_xfer(hub_addr as u32, hub_mps, 2, None, &req, false, core::ptr::null_mut(), 0, diag_port)
863 < 0
864 {
865 crate::serial_println!("ehci: hub set-config failed");
866 return 0;
867 }
868
869 // Hub class descriptor -> port count.
870 let hd = cbuf.add(BUF_HUB_DESC);
871 core::ptr::write_bytes(hd, 0, 64);
872 let req = ctrl_req(0xA0, 6, 0x2900, 0, 9);
873 let got = ctl_xfer(hub_addr as u32, hub_mps, 2, None, &req, true, hd, 9, diag_port);
874 if got < 3 {
875 crate::serial_println!("ehci: hub descriptor failed code={}", got);
876 return 0;
877 }
878 let hd = core::slice::from_raw_parts(hd, got as usize);
879 let nports = hub_ports(hd);
880 crate::serial_println!("ehci: hub addr={} ports={}", hub_addr, nports);
570 881
882 // Power every port, then give the hub time to settle.
883 for p in 1..=nports as u16 {
884 hub_feature(hub_addr, hub_mps, true, PORT_POWER_FEAT, p);
885 }
886 sleep_ms(100);
887
888 let mut found = 0u32;
889 for p in 1..=nports as u16 {
890 let Some((st, _ch)) = hub_port_status(hub_addr, hub_mps, p) else {
891 continue;
892 };
893 if st & PS_CONNECTION == 0 {
894 continue;
895 }
896 // Reset the port and wait for the hub to finish.
897 hub_feature(hub_addr, hub_mps, true, PORT_RESET_FEAT, p);
898 let t0 = crate::interrupts::uptime_ms();
899 let mut reset_done = false;
900 while crate::interrupts::uptime_ms().saturating_sub(t0) < 500 {
901 if let Some((s, _)) = hub_port_status(hub_addr, hub_mps, p)
902 && s & PS_RESET == 0
903 {
904 reset_done = true;
905 break;
906 }
907 sleep_ms(10);
908 }
909 if !reset_done {
910 crate::serial_println!("ehci: hub port {} reset timeout", p);
911 continue;
912 }
913 hub_feature(hub_addr, hub_mps, false, C_PORT_RESET_FEAT, p);
914
915 let Some((st, _)) = hub_port_status(hub_addr, hub_mps, p) else {
916 continue;
917 };
918 if st & PS_ENABLE == 0 {
919 crate::serial_println!("ehci: hub port {} not enabled (st={:#x})", p, st);
920 continue;
921 }
922 let speed = if st & PS_HIGH_SPEED != 0 {
923 2
924 } else if st & PS_LOW_SPEED != 0 {
925 1
926 } else {
927 0
928 };
929 let child_tt = if speed == 2 {
930 None
931 } else {
932 Some(Tt {
933 hub: hub_addr,
934 port: p as u8,
935 })
936 };
937 crate::serial_println!(
938 "ehci: hub port {}: {} device{}",
939 p,
940 match speed {
941 2 => "high-speed",
942 1 => "low-speed",
943 _ => "full-speed",
944 },
945 if child_tt.is_some() { " (split)" } else { "" }
946 );
947 if st & PS_CONNECTION == 0 {
948 continue;
949 }
950 found += enumerate(0, speed, child_tt, diag_port);
951 }
952 found
953 }
954}
955
956/// Bring one controller up and scan it.
957unsafe fn init_one(bus: u8, dev: u8, func: u8) -> bool {
571 unsafe { 958 unsafe {
572 // Enable Memory Space + Bus Master, and disable INTx assertion 959 let ci = N_CTRL.load(Ordering::Relaxed) as usize;
573 // (bit 10) — the driver is polled (OPINTR=0), and an unhandled 960 if ci >= MAX_CTRL {
574 // EHCI interrupt on real hardware would fire a stray IRQ that the 961 return false;
575 // kernel's catch-all handler EOI's but cannot service. 962 }
576 let cmd = crate::pci::config_read(b, d, f, 0x04); 963 CUR.store(ci as u32, Ordering::Relaxed);
964 NEXT_DEV_ADDR.store(1, Ordering::Relaxed);
965
966 // Memory space + bus master, INTx disabled (polled).
967 let cmd = crate::pci::config_read(bus, dev, func, 0x04);
577 crate::pci::config_write( 968 crate::pci::config_write(
578 b, 969 bus,
579 d, 970 dev,
580 f, 971 func,
581 0x04, 972 0x04,
582 (cmd & 0xFFFF_0000) | (cmd & 0xFFFF) | 0x6 | 0x400, 973 (cmd & 0xFFFF_0000) | (cmd & 0xFFFF) | 0x6 | 0x400,
583 ); 974 );
584 let bar = crate::pci::bar64(b, d, f, 0); 975 let bar = crate::pci::bar64(bus, dev, func, 0);
585 let base = bar.unwrap_or(0) as usize; 976 let base = bar.unwrap_or(0) as usize;
586 EHCI_BASE.store(base as u32, Ordering::Relaxed);
587 let cap = rd(base);
588 let sp = rd(base + 4);
589 let hc = rd(base + 8);
590 let np = sp & 0xf;
591 N_PORTS.store(np, Ordering::Relaxed);
592 crate::serial_println!(
593 "ehci: mmio={:#x} caplen={:#x} ports={} 64-bit-caps={}",
594 base,
595 cap & 0xff,
596 np,
597 (hc & 1) != 0
598 );
599 if base == 0 { 977 if base == 0 {
978 crate::serial_println!("ehci: bad BAR");
600 return false; 979 return false;
601 } 980 }
981 let caplen = (rd(base) & 0xff) as usize;
982 let opb = base + caplen;
983 let nports = rd(base + 4) & 0xf;
984 crate::serial_println!(
985 "ehci: {:02x}:{:02x}.{} mmio={:#x} caplen={:#x} ports={} 64-bit-caps={}",
986 bus,
987 dev,
988 func,
989 base,
990 caplen,
991 nports,
992 (rd(base + 8) & 1) != 0
993 );
602 994
603 // Claim USB Legacy ownership from the BIOS before resetting the 995 // BIOS legacy ownership: resetting the controller while SMM owns it
604 // controller. The PCH BIOS drives USB keyboards through SMM 996 // kills the firmware keyboard path and can collide with our DMA.
605 // legacy emulation while it owns the EHCI; resetting/reprogramming 997 match crate::pci::claim_usb_legacy(bus, dev, func, base, 0x08, false) {
606 // the controller underneath it silently kills the legacy keyboard 998 0 => crate::serial_println!("ehci: USB legacy: already released"),
607 // (observed on the 7010) and the still-active SMI poll can collide
608 // with our async DMA — a Host System Error. The capability lives
609 // in PCI config space (EHCI spec §5.1); the claim walks it exactly
610 // like Linux/FreeBSD. Bounded, no-panic; reports what happened.
611 match crate::pci::claim_usb_legacy(b, d, f, base, 0x08, false) {
612 0 => crate::serial_println!("ehci: USB legacy: already released (no claim needed)"),
613 1 => crate::serial_println!("ehci: USB legacy handed over by BIOS"), 999 1 => crate::serial_println!("ehci: USB legacy handed over by BIOS"),
614 2 => crate::serial_println!("ehci: USB legacy claim timed out (BIOS sem stuck)"), 1000 2 => crate::serial_println!("ehci: USB legacy claim timed out"),
615 _ => crate::serial_println!("ehci: USB legacy: no capability found"), 1001 _ => crate::serial_println!("ehci: USB legacy: no capability"),
1002 }
1003
1004 {
1005 let c = ctrl_mut(ci);
1006 c.base = base;
1007 c.opb = opb;
1008 c.nports = nports;
616 } 1009 }
617 1010
618 // Halt the controller. 1011 // Halt, reset, wait for reset to clear.
619 wr(op(OPCMD), 0); 1012 wr(op(OPCMD), 0);
620 let mut t = 0; 1013 let mut t = 0;
621 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 { 1014 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
622 t += 1; 1015 t += 1;
623 core::hint::spin_loop(); 1016 core::hint::spin_loop();
624 } 1017 }
625 crate::serial_println!(
626 "ehci: halted {}",
627 if rd(op(OPSTS)) & STS_HCH != 0 {
628 "ok"
629 } else {
630 "TIMEOUT"
631 }
632 );
633 // Ensure not in reset already; then issue HC reset.
634 wr(op(OPCMD), CMD_HCRESET); 1018 wr(op(OPCMD), CMD_HCRESET);
635 t = 0; 1019 t = 0;
636 while t < 10_000_000 && (rd(op(OPCMD)) & CMD_HCRESET) != 0 { 1020 while t < 10_000_000 && (rd(op(OPCMD)) & CMD_HCRESET) != 0 {
637 t += 1; 1021 t += 1;
638 core::hint::spin_loop(); 1022 core::hint::spin_loop();
639 } 1023 }
640 crate::serial_println!(
641 "ehci: hcreset {}",
642 if rd(op(OPCMD)) & CMD_HCRESET == 0 {
643 "ok"
644 } else {
645 "TIMEOUT"
646 }
647 );
648 t = 0; 1024 t = 0;
649 while t < 10_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 { 1025 while t < 10_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
650 t += 1; 1026 t += 1;
651 core::hint::spin_loop(); 1027 core::hint::spin_loop();
652 } 1028 }
653 crate::serial_println!(
654 "ehci: post-reset halt {}",
655 if rd(op(OPSTS)) & STS_HCH != 0 {
656 "ok"
657 } else {
658 "TIMEOUT"
659 }
660 );
661 // Disable interrupts (polled).
662 wr(op(OPINTR), 0); 1029 wr(op(OPINTR), 0);
663 // Clear HCHalted? After reset the HC is halted; running is set later. 1030
664 1031 // DMA structures (<4 GiB, >=16 MiB).
665 // Allocate the async head QH + control QH + frame list + HID structures. 1032 let async_head = dma_alloc(0x400, DMA_MAX) as *mut u32;
666 ASYNC_HEAD = dma_alloc(0x400, 0x20) as *mut u32; // 1KB region 1033 let framelist = dma_alloc(0x1000, DMA_MAX) as *mut u32;
667 XFER_QH = ASYNC_HEAD.add(0x80 / 4); // control QH at +0x80 1034 let qh_page = dma_alloc(0x1000, DMA_MAX) as *mut u32;
668 // qtds live after XFER_QH's 11 dwords (44 bytes); space reserved. 1035 let buf_page = dma_alloc(0x1000, DMA_MAX);
669 FRAMELIST = dma_alloc(0x1000, 0x1000) as *mut u32; 1036 if async_head.is_null() || framelist.is_null() || qh_page.is_null() || buf_page.is_null() {
670 HID_QH = dma_alloc(0x400, 0x20) as *mut u32; 1037 crate::serial_println!("ehci: DMA allocation failed");
671 // DEBUG matrix: low-region control QH (sub-1MB page) to test whether
672 // the SE is tied to the 16 MB DMA region (write protection) or not.
673 if let Some(lp) = crate::memory::alloc_pages_above(2, 0) {
674 LOW_XFER = lp as *mut u32;
675 crate::serial_println!("ehci: low-xfer debug page at {:#x}", lp);
676 }
677 BUF_PAGE = dma_alloc(0x1000, 0x20);
678 if BUF_PAGE.is_null() {
679 crate::serial_println!("ehci: buffer page allocation failed");
680 return false; 1038 return false;
681 } 1039 }
1040 {
1041 let c = ctrl_mut(ci);
1042 c.async_head = async_head;
1043 c.xfer_qh = async_head.add(0x80 / 4);
1044 c.framelist = framelist;
1045 c.qh_page = qh_page;
1046 c.buf_page = buf_page;
1047 }
682 crate::serial_println!( 1048 crate::serial_println!(
683 "ehci: dma async={:#x} xfer={:#x} framelist={:#x} hid={:#x} buf={:#x}", 1049 "ehci: dma async={:#x} framelist={:#x} qh={:#x} buf={:#x}",
684 ASYNC_HEAD as usize, 1050 async_head as usize,
685 XFER_QH as usize, 1051 framelist as usize,
686 FRAMELIST as usize, 1052 qh_page as usize,
687 HID_QH as usize, 1053 buf_page as usize
688 BUF_PAGE as usize
689 ); 1054 );
690 1055
691 // Init async head QH: self-linked, marked as head of reclamation. 1056 let xfer_qh = cur_ctrl().xfer_qh;
692 *qh_next(ASYNC_HEAD) = link_of(XFER_QH as usize); // circular: head → control QH → head 1057 *qh_next(async_head) = link_of(cur_ctrl().xfer_qh as usize);
693 *qh_info1(ASYNC_HEAD) = QH_HEAD; 1058 *qh_info1(async_head) = QH_HEAD;
694 *qh_info2(ASYNC_HEAD) = 0; 1059 *qh_info2(async_head) = 0;
695 *qh_current(ASYNC_HEAD) = 0; 1060 *qh_current(async_head) = 0;
696 *qh_next_qtd(ASYNC_HEAD) = 0; 1061 *qh_next_qtd(async_head) = 0;
697 1062 *qh_next(xfer_qh) = link_of(async_head as usize);
698 // Configure CONTROL QH as an idle EP0 (filled per transfer). 1063 *qh_info1(xfer_qh) = QH_EPS_FULL | (64 << 16);
699 *qh_next(XFER_QH) = link_of(ASYNC_HEAD as usize); 1064 *qh_info2(xfer_qh) = 0;
700 *qh_info1(XFER_QH) = QH_EPS_FULL | (64 << 16); // HS mps 64 1065 *qh_current(xfer_qh) = 0;
701 *qh_info2(XFER_QH) = 0; 1066 *qh_next_qtd(xfer_qh) = 0;
702 *qh_current(XFER_QH) = 0; 1067 *qh_alt_qtd(xfer_qh) = QT_TERMINATE;
703 *qh_next_qtd(XFER_QH) = 0; // idle 1068 *qh_token(xfer_qh) = 0;
704 *qh_alt_qtd(XFER_QH) = QT_TERMINATE; 1069 for i in 0..5 {
705 *qh_token(XFER_QH) = 0; 1070 *qh_buf(xfer_qh).add(i) = 0;
706 1071 }
707 // Async schedule address. 1072
708 wr(op(OPFRIDX), 0); 1073 wr(op(OPFRIDX), 0);
709 // Set ASYNCLISTADDR (32-bit). Controller supports 64-bit (hcc&1) but we 1074 wr(op(0x10), 0); // CTRLDSSEGMENT: all our DMA is below 4 GiB
710 // keep everything below 4 GB; CTRLDSSEGMENT=0 suffices. 1075 wr(op(0x18), (async_head as usize as u32) & !0x1F);
711 // ASYNCLISTADDR low 32 bits: address of HEAD QH (lower 5 bits zero).
712 // With hcc 64-bit set, the controller reads the 64-bit address
713 // CTRLDSSEGMENT:ASYNCLISTADDR. Zero DSEG + low 32-bit pointer is fine
714 // for addresses < 4 GB.
715 let head_addr = ASYNC_HEAD as usize;
716 wr(op(0x10), 0); // CTRLDSSEGMENT
717 // ASYNCLISTADDR is at op+0x18
718 wr(op(0x18), (head_addr as u32) & !0x1F);
719 // Periodic frame list address.
720 // Zero the frame list to avoid stale QH/ITD pointers causing
721 // QEMU to reset the controller when PSE is later enabled.
722 let fl = FRAMELIST as usize;
723 for i in 0..1024 { 1076 for i in 0..1024 {
724 core::ptr::write_volatile(FRAMELIST.add(i), 0u32); 1077 core::ptr::write_volatile(framelist.add(i), 0u32);
725 } 1078 }
726 wr(op(0x14), (fl as u32) & !0x3FF); 1079 wr(op(0x14), (framelist as usize as u32) & !0x3FF);
727 // Frame list size: FLS field (bits 2-3) stays 0 → 1024 entries. 1080 wr(op(OPCMD), CMD_RUN);
728 // Intel/QEMU layout: PSE=bit4, ASE=bit5. 1081 t = 0;
729 // NOTE: do NOT enable PSE here — the frame list is empty (all zeros).
730 // Enabling PSE on an empty frame list is fine (QEMU skips zero entries),
731 // but we want to avoid any race with stale OVMF entries. PSE will be
732 // enabled later in poll_mouse when the HID QH is linked into the list.
733 wr(op(OPCMD), 0x01u32); // CMD_RUN only, no PSE
734 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN);
735 // Wait to leave halt.
736 let mut t = 0;
737 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) != 0 { 1082 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) != 0 {
738 t += 1; 1083 t += 1;
739 core::hint::spin_loop(); 1084 core::hint::spin_loop();
740 } 1085 }
741 crate::serial_println!( 1086 wr(op(OPCFGFLAG), 1); // route all ports to the host
742 "ehci: running {}", 1087 for p in 0..nports as usize {
743 if rd(op(OPSTS)) & STS_HCH == 0 {
744 "ok"
745 } else {
746 "STUCK HALTED"
747 }
748 );
749 // Configure flag: route all ports to the host (CF=1) and power them.
750 wr(op(OPCFGFLAG), 1);
751 for p in 0..np as usize {
752 let psc = prtsc(p); 1088 let psc = prtsc(p);
753 let v = rd(psc); 1089 let v = rd(psc);
754 if v & PRT_POWER == 0 { 1090 if v & PRT_POWER == 0 {
755 wr(psc, v | PRT_POWER); 1091 wr(psc, v | PRT_POWER);
756 } 1092 }
757 } 1093 }
1094 sleep_ms(20);
1095 crate::serial_println!("ehci: controller up ({} ports)", nports);
1096
1097 N_CTRL.store((ci + 1) as u32, Ordering::Relaxed);
758 CTRL_UP.store(true, Ordering::Relaxed); 1098 CTRL_UP.store(true, Ordering::Relaxed);
759 crate::serial_println!("ehci: controller up ({} ports)", np);
760 1099
761 // DIAGNOSTIC: run the async schedule with an idle head (self-loop, 1100 // ---- scan ports ----
762 // no qtds) for 150 ms. If this faults (HCH/SE), the SE happens on 1101 for p in 0..nports as usize {
763 // schedule/DMA mechanics alone — nothing to do with the split 1102 let v = rd(prtsc(p));
764 // transfer. If clean, the fault is specific to transfer execution. 1103 if v & PRT_CONNECT == 0 {
765 *qh_next(ASYNC_HEAD) = link_of(ASYNC_HEAD as usize); 1104 continue;
766 wr(op(OPSTS), 0x1F); 1105 }
767 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); 1106 let hs = reset_port(p);
768 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_ASE); 1107 if !hs && crate::uhci::controller_up() {
769 let pt0 = crate::interrupts::uptime_ms();
770 loop {
771 let s = rd(op(OPSTS));
772 if s & (STS_HCH | STS_SE) != 0 {
773 crate::serial_println!( 1108 crate::serial_println!(
774 "ehci: idle-probe FAULTED {}ms: cmd={:#x} sts={:#x} frindex={:#x} asyncbase={:#x} prtsc0={:#x}", 1109 "ehci: port {} full/low-speed — owned by companion UHCI",
775 crate::interrupts::uptime_ms().wrapping_sub(pt0), 1110 p
776 rd(op(OPCMD)),
777 s,
778 rd(op(OPFRIDX)),
779 rd(op(0x18)),
780 rd(prtsc(0))
781 ); 1111 );
782 break; 1112 continue;
783 } 1113 }
784 if crate::interrupts::uptime_ms().wrapping_sub(pt0) >= 150 { 1114 let speed = if hs { 2 } else { port_speed(p) };
785 crate::serial_println!("ehci: idle-probe ok (150ms, no fault)"); 1115 crate::serial_println!(
786 break; 1116 "ehci: port {} connected ({}{})",
1117 p,
1118 match speed {
1119 2 => "high-speed",
1120 1 => "low-speed",
1121 _ => "full-speed",
1122 },
1123 if hs { "" } else { ", root-port split" }
1124 );
1125 let tt = if hs {
1126 None
1127 } else {
1128 Some(Tt {
1129 hub: 0,
1130 port: (p + 1) as u8,
1131 })
1132 };
1133 enumerate(0, speed, tt, p);
1134 }
1135
1136 arm_periodic(ci);
1137 true
1138 }
1139}
1140
1141/// Chain every armed device QH of this controller into its frame list and
1142/// enable the periodic schedule.
1143unsafe fn arm_periodic(ci: usize) {
1144 unsafe {
1145 CUR.store(ci as u32, Ordering::Relaxed);
1146 let c = ctrl_mut(ci);
1147 let mut first: *mut u32 = core::ptr::null_mut();
1148 let mut prev: *mut u32 = core::ptr::null_mut();
1149 let n = N_DEV.load(Ordering::Relaxed) as usize;
1150 for i in 0..n {
1151 let d = dev_mut(i);
1152 if d.ctrl != ci as u32 || !d.armed {
1153 continue;
787 } 1154 }
788 core::hint::spin_loop(); 1155 if first.is_null() {
1156 first = d.qh;
1157 } else {
1158 *qh_next(prev) = link_of(d.qh as usize);
1159 }
1160 prev = d.qh;
1161 }
1162 if prev.is_null() {
1163 return;
789 } 1164 }
1165 *qh_next(prev) = QT_TERMINATE;
1166 for f in 0..1024 {
1167 core::ptr::write_volatile(c.framelist.add(f), link_of(first as usize));
1168 }
1169 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
1170 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_PSE);
1171 crate::serial_println!("ehci: periodic schedule enabled ({} device(s) on ctrl {})", n, ci);
790 } 1172 }
791 true
792} 1173}
793 1174
794/// Port reset on USB2 EHCI. Assert PR, hold it briefly (real EHCI clears 1175/// Reset a root port. Returns true when the port enabled (high-speed).
795/// PR itself once reset signaling finishes; QEMU needs the driver to
796/// deassert), then wait for the port to enable. Returns true if PED set
797/// (high-speed device). Full/low-speed stay PED=0 and need a TT path.
798unsafe fn reset_port(p: usize) -> bool { 1176unsafe fn reset_port(p: usize) -> bool {
799 unsafe { 1177 unsafe {
800 let psc = prtsc(p); 1178 let psc = prtsc(p);
801 let v = rd(psc); 1179 let v = rd(psc);
802 // Put port in reset.
803 wr(psc, (v | PRT_RESET) & !PRT_RWC); 1180 wr(psc, (v | PRT_RESET) & !PRT_RWC);
804 // Hold ~50 ms, bailing early if the HC cleared PR on its own.
805 let t0 = crate::interrupts::uptime_ms(); 1181 let t0 = crate::interrupts::uptime_ms();
806 while crate::interrupts::uptime_ms().saturating_sub(t0) < 50 { 1182 while crate::interrupts::uptime_ms().saturating_sub(t0) < 50 {
807 core::hint::spin_loop(); 1183 core::hint::spin_loop();
@@ -809,470 +1185,256 @@ unsafe fn reset_port(p: usize) -> bool {
809 break; 1185 break;
810 } 1186 }
811 } 1187 }
812 // Deassert explicitly (QEMU only completes the reset on deassert).
813 wr(psc, (rd(psc) & !PRT_RESET) & !PRT_RWC); 1188 wr(psc, (rd(psc) & !PRT_RESET) & !PRT_RWC);
814 // Wait briefly for the port to enable (PED). High-speed devices set
815 // PED on reset; full/low-speed stay PED=0 (they need split/TT). Do
816 // not spin forever — decide on the line state instead.
817 let mut t = 0; 1189 let mut t = 0;
818 while t < 2_000_000 && (rd(psc) & PRT_PED) == 0 { 1190 while t < 2_000_000 && (rd(psc) & PRT_PED) == 0 {
819 t += 1; 1191 t += 1;
820 core::hint::spin_loop(); 1192 core::hint::spin_loop();
821 } 1193 }
822 // A high-speed device gains PED. Full/low-speed won't (needs companion/TT).
823 rd(psc) & PRT_PED != 0 1194 rd(psc) & PRT_PED != 0
824 } 1195 }
825} 1196}
826 1197
827/// Distinguish full- vs low-speed on a connected root port (for the split QH 1198fn port_speed(p: usize) -> u32 {
828/// EPS field). Intel PCH EHCIs expose the speed in HOSTPC (base+0x84+4p, 1199 let hp = hostpc(p);
829/// bits 27:26: 0=full, 1=low, 2=high); other controllers fall back to the 1200 if hp != u32::MAX && hp != 0 {
830/// PORTSC bits 27:26. Only meaningful after the port has completed reset. 1201 let f = (hp >> 25) & 3;
831unsafe fn port_speed(p: usize) -> u32 { 1202 if f != 3 {
832 unsafe { 1203 return f;
833 let base = EHCI_BASE.load(Ordering::Relaxed) as usize;
834 let hp = rd(base + 0x84 + p * 4);
835 if hp != u32::MAX && hp != 0 {
836 let f = (hp >> 25) & 3;
837 if f != 3 {
838 return f;
839 }
840 } 1204 }
841 (rd(prtsc(p)) >> 26) & 3
842 } 1205 }
1206 unsafe { (rd(prtsc(p)) >> 26) & 3 }
843} 1207}
844 1208
845// --------------------------------------------------------------------------- 1209// ---------------------------------------------------------------------------
846// Enumeration + HID discovery 1210// Boot + polling
847// --------------------------------------------------------------------------- 1211// ---------------------------------------------------------------------------
848 1212
849const HID_CLASS: u8 = 0x03; 1213pub fn init() -> bool {
850 1214 let devs = crate::pci::probe();
851fn find_hid_ep(cfg: &[u8]) -> Option<(u8, u32, u32, u8)> { 1215 let mut up = false;
852 // returns (epaddr, mps, interval_bint, epnum) 1216 for d in devs {
853 let mut i = 0usize; 1217 if d.class == 0x0C && d.subclass == 0x03 && d.prog_if == 0x20 {
854 let mut in_iface = false; 1218 if N_CTRL.load(Ordering::Relaxed) as usize >= MAX_CTRL {
855 while i + 2 <= cfg.len() { 1219 break;
856 let blen = cfg[i] as usize;
857 if blen == 0 {
858 break;
859 }
860 let bt = cfg[i + 1];
861 if bt == 4 && i + 9 <= cfg.len() {
862 in_iface = cfg[i + 5] == HID_CLASS;
863 } else if bt == 5 && in_iface && i + 7 <= cfg.len() {
864 let addr = cfg[i + 2];
865 let attrs = cfg[i + 3];
866 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8);
867 let bint = cfg[i + 6] as u32;
868 if (addr & 0x80) != 0 && (attrs & 0x03) == 0x03 {
869 return Some((addr, mps, bint, addr & 0x0f));
870 }
871 }
872 i += blen;
873 }
874 None
875}
876
877fn enumerate() -> bool {
878 let port = find_connected_port();
879 if port == usize::MAX {
880 return false;
881 }
882 // DEBUG: one-boot transfer variant matrix replaces normal enumeration.
883 if DEBUG_MATRIX && !MATRIX_DONE.swap(true, Ordering::Relaxed) {
884 unsafe {
885 debug_xfer_matrix(port);
886 }
887 return false; // diagnostic build
888 }
889 unsafe {
890 // If a periodic schedule is live (a previous arm), stop it cleanly
891 // before touching the controller: point every frame at terminate,
892 // clear PSE and wait for the reclaim handshake. Re-enumerating while
893 // the old HID QH is still being walked interleaves the periodic walk
894 // with control transfers — fragile on any HC.
895 if rd(op(OPCMD)) & CMD_PSE != 0 {
896 let fl = FRAMELIST;
897 for f in 0..1024 {
898 core::ptr::write_volatile(fl.add(f), QT_TERMINATE);
899 } 1220 }
900 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); 1221 unsafe {
901 wr(op(OPCMD), rd(op(OPCMD)) & !CMD_PSE); 1222 if init_one(d.bus, d.device, d.func) {
902 wait_recl(1000); 1223 up = true;
903 } 1224 }
904 // Discovery state: nothing armed/present until the arm at the end.
905 ARMED.store(false, Ordering::Relaxed);
906 MOUSE_PRES.store(false, Ordering::Relaxed);
907 let hs = reset_port(port);
908 if !hs && crate::uhci::controller_up() {
909 // A real (non-QEMU) EHCI can never transfer to a full/low-speed
910 // device: the port is owned by the companion UHCI. Mark the
911 // port and stop — a descriptor timeout here costs 5 s and
912 // repeats every second, which is exactly the "stuck in a loop"
913 // the OptiPlex showed. (QEMU's usb-ehci accepts PED=0 with a
914 // high-speed device behind it, so this skip only triggers when
915 // a real UHCI companion is actually present.)
916 FS_LS_PORTS.store(
917 FS_LS_PORTS.load(Ordering::Relaxed) | (1 << port),
918 Ordering::Relaxed,
919 );
920 crate::serial_println!(
921 "ehci: port {} full/low-speed — owned by companion UHCI (uhci driver handles it)",
922 port
923 );
924 return false;
925 }
926 crate::serial_println!(
927 "ehci: port {} resolved{}",
928 port,
929 if hs {
930 " (high-speed)"
931 } else {
932 " (full/low — TT path)"
933 } 1225 }
934 );
935 // Speed for the QH EPS field: 2=HS, 1=LS, 0=FS. FS/LS on an EHCI
936 // root port must be serviced with split transactions (integrated TT).
937 let speed = if hs { 2 } else { port_speed(port) };
938 let speed_name = match speed {
939 2 => "high-speed",
940 1 => "low-speed (split)",
941 _ => "full-speed (split)",
942 };
943 let hostpc = unsafe { rd(EHCI_BASE.load(Ordering::Relaxed) as usize + 0x84 + port * 4) };
944 crate::serial_println!(
945 "ehci: port {} speed: {} (hostpc={:#x})",
946 port,
947 speed_name,
948 hostpc
949 );
950 let mps = if hs { 64u32 } else { 8u32 };
951
952 // 1. GET_DESCRIPTOR (device) — 18 bytes to learn idVendor etc.
953 let req = ctrl_req(0x80, 6, 0x0100, 0, 18);
954 let dd = BUF_PAGE.add(BUF_DEV_DESC);
955 let mut got = ctl_xfer(0, mps, speed, port, &req, true, dd, 18);
956 if got < 0 {
957 crate::serial_println!("ehci: get-dev-desc failed code={}", got);
958 return false;
959 }
960 if got < 8 {
961 crate::serial_println!("ehci: short dev-desc ({})", got);
962 return false;
963 }
964 let dd = dd as *const u8;
965 let mps0 = core::ptr::read_volatile(dd.add(7)) as u32; // bMaxPacketSize0
966 let _ = mps0;
967 crate::serial_println!(
968 "ehci: dev {:04x}:{:04x} class={:#x} mps0={}",
969 (core::ptr::read_volatile(dd.add(9)) as u16)
970 | ((core::ptr::read_volatile(dd.add(8)) as u16) << 8),
971 (core::ptr::read_volatile(dd.add(11)) as u16)
972 | ((core::ptr::read_volatile(dd.add(10)) as u16) << 8),
973 core::ptr::read_volatile(dd.add(4)),
974 core::ptr::read_volatile(dd.add(7))
975 );
976
977 // 2. SET_ADDRESS to 1.
978 let req = ctrl_req(0x00, 5, 1, 0, 0);
979 if ctl_xfer(0, mps, speed, port, &req, false, core::ptr::null_mut(), 0) < 0 {
980 crate::serial_println!("ehci: set-address failed");
981 return false;
982 }
983 DEV_ADDR.store(1, Ordering::Relaxed);
984
985 // 3. GET_CONFIG(0) — 256 bytes, enough for a HID descriptor.
986 let cfg = BUF_PAGE.add(BUF_CFG);
987 core::ptr::write_bytes(cfg, 0, 256);
988 let req = ctrl_req(0x80, 6, 0x0200, 0, 256);
989 got = ctl_xfer(1, mps, speed, port, &req, true, cfg, 256);
990 if got < 0 {
991 crate::serial_println!("ehci: get-config failed code={}", got);
992 return false;
993 } 1226 }
994 let Some((epaddr, epmps, bint, epnum)) = 1227 }
995 find_hid_ep(core::slice::from_raw_parts(cfg as *const u8, 256)) 1228 if !up {
996 else { 1229 crate::serial_println!("ehci: no EHCI controller");
997 crate::serial_println!("ehci: no HID mouse endpoint in config"); 1230 } else {
998 return false;
999 };
1000 crate::serial_println!( 1231 crate::serial_println!(
1001 "ehci: HID mouse IN ep{}({:#x}) mps={} interval={}", 1232 "ehci: {} controller(s), {} HID device(s)",
1002 epnum, 1233 N_CTRL.load(Ordering::Relaxed),
1003 epaddr, 1234 N_DEV.load(Ordering::Relaxed)
1004 epmps,
1005 bint
1006 );
1007
1008 // 4. SET_CONFIGURATION(1).
1009 let req = ctrl_req(0x00, 9, 1, 0, 0);
1010 if ctl_xfer(1, mps, speed, port, &req, false, core::ptr::null_mut(), 0) < 0 {
1011 crate::serial_println!("ehci: set-config failed");
1012 return false;
1013 }
1014
1015 // 5. Arm the HID interrupt-IN endpoint on the periodic schedule.
1016 HID_EP.store(epnum as u32, Ordering::Relaxed);
1017 HID_MPS.store(epmps, Ordering::Relaxed);
1018 // Build HID QH for EP<epnum> IN on device 1.
1019 let hq = HID_QH;
1020 // eps: speed (0/1/2) maps directly onto the QH EPS field (bits 13:12).
1021 let info1 = QH_RL
1022 | (speed << 12)
1023 | ((epmps.max(4) & 0x7ff) << 16)
1024 | (DEV_ADDR.load(Ordering::Relaxed) & 0x7f)
1025 | (((epnum as u32) & 0x0f) << 8);
1026 // Poll every frame. HS: SMASK covers every uframe. FS/LS: split —
1027 // SSPLIT at uframe 0, CSPLIT at uframes 2..4 (Linux NEWSCHED), hub
1028 // addr 0 (integrated root TT), 1-based root port in bits 29:23.
1029 let (smask, cmask, split) = if speed == 2 {
1030 (0xFFu32, 0u32, 0u32)
1031 } else {
1032 (0x01u32, 0x1Cu32 << 8, QH_MULT | (((port as u32) + 1) << 23))
1033 };
1034 *qh_next(hq) = QT_TERMINATE; // end of this frame's periodic list
1035 *qh_info1(hq) = info1;
1036 *qh_info2(hq) = smask | cmask | split;
1037 *qh_current(hq) = 0;
1038 let iqtd = hq.add(0x40 / 4); // qtd at +0x40 (must be 32-byte aligned)
1039 // HID report qtd: len = epmps, PID IN.
1040 core::ptr::write_volatile(iqtd.wrapping_add(0), QT_TERMINATE);
1041 core::ptr::write_volatile(iqtd.wrapping_add(1), QT_TERMINATE);
1042 core::ptr::write_volatile(
1043 iqtd.wrapping_add(2),
1044 ((epmps.max(4) & 0x7fff) << 16) | (PID_IN << 8) | TOK_ACTIVE | TOK_IOC,
1045 ); 1235 );
1046 for i in 0..5 {
1047 core::ptr::write_volatile(iqtd.add(3 + i), 0u32);
1048 }
1049 core::ptr::write_volatile(iqtd.wrapping_add(3), BUF_PAGE.add(BUF_HID_REPORT) as u32);
1050 *qh_next_qtd(hq) = iqtd as u32;
1051 *qh_alt_qtd(hq) = QT_TERMINATE;
1052 *qh_token(hq) = 0;
1053 // QEMU reads the report buffer from the qtd's bufptr (ehci_init_transfer),
1054 // so the QH overlay's buffer list need not be pre-populated.
1055 // Point every period frame entry at the HID QH.
1056 let fl = FRAMELIST;
1057 for f in 0..1024 {
1058 core::ptr::write_volatile(fl.add(f), link_of(hq as usize));
1059 }
1060 // Enable the periodic schedule so the frame list is actually walked.
1061 // PSE was deliberately off until now (empty frame list during init).
1062 core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
1063 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN | CMD_PSE);
1064 ARMED.store(true, Ordering::Relaxed);
1065 MOUSE_PRES.store(true, Ordering::Relaxed);
1066 crate::serial_println!("ehci: HID boot mouse armed (device 1)");
1067 true
1068 }
1069}
1070
1071// ---------------------------------------------------------------------------
1072// DEBUG: one-boot transfer variant matrix (real-hardware SE bisection).
1073// Each variant does a full HC reset + port reset, then one ctl_xfer with a
1074// single construction variable flipped (see XFER_TWEAK in ctl_xfer), and
1075// logs the outcome. A wedged port from an earlier SE cannot confound the
1076// comparisons because every variant restarts the controller.
1077// ---------------------------------------------------------------------------
1078unsafe fn hc_restart() {
1079 unsafe {
1080 wr(op(OPCMD), 0);
1081 let mut t = 0;
1082 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
1083 t += 1;
1084 core::hint::spin_loop();
1085 }
1086 wr(op(OPCMD), CMD_HCRESET);
1087 t = 0;
1088 while t < 10_000_000 && (rd(op(OPCMD)) & CMD_HCRESET) != 0 {
1089 t += 1;
1090 core::hint::spin_loop();
1091 }
1092 t = 0;
1093 while t < 10_000_000 && (rd(op(OPSTS)) & STS_HCH) == 0 {
1094 t += 1;
1095 core::hint::spin_loop();
1096 }
1097 wr(op(0x18), (ASYNC_HEAD as u32) & !0x1F);
1098 wr(op(0x14), (FRAMELIST as u32) & !0x3FF);
1099 wr(op(OPCFGFLAG), 1);
1100 let np = N_PORTS.load(Ordering::Relaxed);
1101 for p in 0..np as usize {
1102 let psc = prtsc(p);
1103 let v = rd(psc);
1104 if v & PRT_POWER == 0 {
1105 wr(psc, v | PRT_POWER);
1106 }
1107 }
1108 wr(op(OPSTS), 0x1F);
1109 wr(op(OPCMD), rd(op(OPCMD)) | CMD_RUN);
1110 t = 0;
1111 while t < 1_000_000 && (rd(op(OPSTS)) & STS_HCH) != 0 {
1112 t += 1;
1113 core::hint::spin_loop();
1114 }
1115 } 1236 }
1237 up
1116} 1238}
1117 1239
1118unsafe fn debug_xfer_matrix(port: usize) { 1240pub fn rescan() {
1119 unsafe { 1241 // Hotplug is only handled while nothing is armed: re-run the boot scan.
1120 QR_SHOWN.store(true, Ordering::Relaxed); // serial-only diagnostics 1242 if N_DEV.load(Ordering::Relaxed) == 0 {
1121 crate::serial_println!( 1243 let n = N_CTRL.load(Ordering::Relaxed) as usize;
1122 "ehci: ===== DEBUG xfer variant matrix (port {}) =====", 1244 for ci in 0..n {
1123 port 1245 unsafe {
1124 ); 1246 CUR.store(ci as u32, Ordering::Relaxed);
1125 let tweaks: [u32; 7] = [0, 1, 2, 4, 8, 0x20, 0x80]; 1247 let np = ctrl_mut(ci).nports as usize;
1126 let names = [ 1248 NEXT_DEV_ADDR.store((N_DEV.load(Ordering::Relaxed) + 1) as u32, Ordering::Relaxed);
1127 "base", 1249 for p in 0..np {
1128 "no-ASE", 1250 if rd(prtsc(p)) & PRT_CONNECT == 0 {
1129 "no-split", 1251 continue;
1130 "no-qtd", 1252 }
1131 "len0", 1253 let hs = reset_port(p);
1132 "wire-off", 1254 if !hs && crate::uhci::controller_up() {
1133 "low-region", 1255 continue;
1134 ]; 1256 }
1135 let ebase = EHCI_BASE.load(Ordering::Relaxed) as usize; 1257 let speed = if hs { 2 } else { port_speed(p) };
1136 for (i, &tw) in tweaks.iter().enumerate() { 1258 let tt = if hs {
1137 hc_restart(); 1259 None
1138 let _hs = reset_port(port); 1260 } else {
1139 let req: [u8; 8] = [0x80, 0x06, 0x00, 0x01, 0, 0, 0x12, 0]; // get_desc dev, 18B 1261 Some(Tt {
1140 let mut buf = [0u8; 18]; 1262 hub: 0,
1141 XFER_TWEAK.store(tw | 0x40, Ordering::Relaxed); // + short timeout 1263 port: (p + 1) as u8,
1142 let t0 = crate::interrupts::uptime_ms(); 1264 })
1143 let r = ctl_xfer(0, 8, 0, port, &req, true, buf.as_mut_ptr(), 18); 1265 };
1144 let dt = crate::interrupts::uptime_ms().wrapping_sub(t0); 1266 enumerate(0, speed, tt, p);
1145 crate::serial_println!( 1267 }
1146 "ehci: v{} {:<10}: ret={} t={}ms sts={:#x} cmd={:#x} prtsc0={:#x} hp={:#x}", 1268 arm_periodic(ci);
1147 i,
1148 names[i],
1149 r,
1150 dt,
1151 rd(op(OPSTS)),
1152 rd(op(OPCMD)),
1153 rd(prtsc(0)),
1154 rd(ebase + 0x84)
1155 );
1156 XFER_TWEAK.store(0, Ordering::Relaxed);
1157 let w0 = crate::interrupts::uptime_ms();
1158 while crate::interrupts::uptime_ms().wrapping_sub(w0) < 600 {
1159 core::hint::spin_loop();
1160 } 1269 }
1161 } 1270 }
1162 crate::serial_println!("ehci: ===== matrix done =====");
1163 } 1271 }
1164} 1272}
1165 1273
1166// ---------------------------------------------------------------------------
1167// Active interface (called by cursor task / shell)
1168// ---------------------------------------------------------------------------
1169
1170pub fn controller_up() -> bool { 1274pub fn controller_up() -> bool {
1171 CTRL_UP.load(Ordering::Relaxed) 1275 CTRL_UP.load(Ordering::Relaxed)
1172} 1276}
1277
1173pub fn mouse_present() -> bool { 1278pub fn mouse_present() -> bool {
1174 MOUSE_PRES.load(Ordering::Relaxed) 1279 let n = N_DEV.load(Ordering::Relaxed) as usize;
1280 for i in 0..n {
1281 if unsafe { dev_mut(i) }.kind == Kind::Mouse {
1282 return true;
1283 }
1284 }
1285 false
1175} 1286}
1176 1287
1177pub fn poll_mouse() -> Option<MouseEvent> { 1288pub fn poll_mouse() -> Option<MouseEvent> {
1178 unsafe { 1289 unsafe {
1179 // Hot path first. The qtd's ACTIVE bit (in controller-visible DMA 1290 let n = N_DEV.load(Ordering::Relaxed) as usize;
1180 // memory) is the ground truth for "report outstanding"; a kernel 1291 for i in 0..n {
1181 // static must never gate report delivery. 1292 let d = dev_mut(i);
1182 if ARMED.load(Ordering::Relaxed) { 1293 if d.kind != Kind::Mouse || !d.armed {
1183 let hq = HID_QH; 1294 continue;
1184 let iqtd = hq.add(0x40 / 4); // same 32-byte-aligned location as arming 1295 }
1185 let tok = core::ptr::read_volatile(iqtd.wrapping_add(2).cast::<u32>()); 1296 let tok = core::ptr::read_volatile(d.iqtd.add(2).cast::<u32>());
1186 if tok & TOK_ACTIVE == 0 { 1297 if tok & TOK_ACTIVE != 0 {
1187 if tok & (TOK_HALT | TOK_XACT) != 0 { 1298 continue;
1188 // Transfer error: drop the arm; a bounded re-enum follows. 1299 }
1189 ARMED.store(false, Ordering::Relaxed); 1300 if tok & (TOK_HALT | TOK_XACT) != 0 {
1190 return None; 1301 d.armed = false;
1191 } 1302 d.failures += 1;
1192 // Completed: decode the report from the dedicated DMA page. 1303 continue;
1193 let hb = BUF_PAGE.add(BUF_HID_REPORT) as *const u8; 1304 }
1194 let b0 = core::ptr::read_volatile(hb); 1305 let hb = d.report;
1195 let b1 = core::ptr::read_volatile(hb.add(1)); 1306 let b0 = core::ptr::read_volatile(hb);
1196 let b2 = core::ptr::read_volatile(hb.add(2)); 1307 let b1 = core::ptr::read_volatile(hb.add(1));
1197 let ev = Some(MouseEvent { 1308 let b2 = core::ptr::read_volatile(hb.add(2));
1198 dx: (b1 as i8) as i16, 1309 let ev = MouseEvent {
1199 dy: (b2 as i8) as i16, 1310 dx: (b1 as i8) as i16,
1200 left: b0 & 0x01 != 0, 1311 dy: (b2 as i8) as i16,
1201 right: b0 & 0x02 != 0, 1312 left: b0 & 0x01 != 0,
1202 middle: b0 & 0x04 != 0, 1313 right: b0 & 0x02 != 0,
1203 }); 1314 middle: b0 & 0x04 != 0,
1204 // Re-arm the periodic interrupt-IN qtd: restore its token to 1315 };
1205 // ACTIVE and re-link it as the QH's next qtd so the HC fetches 1316 d.reports += 1;
1206 // it again next frame (canonical one-qtd periodic re-arm). 1317 if d.reports == 1 {
1207 core::ptr::write_volatile( 1318 crate::serial_println!(
1208 iqtd.wrapping_add(2), 1319 "ehci: mouse report dx={} dy={} b={:#x}",
1209 ((HID_MPS.load(Ordering::Relaxed).max(4) & 0x7fff) << 16) 1320 ev.dx,
1210 | (PID_IN << 8) 1321 ev.dy,
1211 | TOK_ACTIVE 1322 b0
1212 | TOK_IOC,
1213 ); 1323 );
1214 *qh_next_qtd(hq) = iqtd as u32;
1215 return ev;
1216 } 1324 }
1217 return None; 1325 rearm(d);
1326 return Some(ev);
1218 } 1327 }
1219 // Not armed: bring the mouse up lazily, but never hot-loop — at most 1328 // Nothing armed: retry discovery at most once per second.
1220 // one enumerate attempt per second, and only while the controller is 1329 if N_DEV.load(Ordering::Relaxed) == 0 && CTRL_UP.load(Ordering::Relaxed) {
1221 // up. A transient glitch can delay a report; it must not wedge the
1222 // system in a re-enumeration loop.
1223 if CTRL_UP.load(Ordering::Relaxed) {
1224 let now = crate::interrupts::uptime_ms() as u32; 1330 let now = crate::interrupts::uptime_ms() as u32;
1225 if now.wrapping_sub(LAST_ENUM.load(Ordering::Relaxed)) >= 1000 { 1331 if now.wrapping_sub(LAST_SCAN.load(Ordering::Relaxed)) >= 1000 {
1226 LAST_ENUM.store(now, Ordering::Relaxed); 1332 LAST_SCAN.store(now, Ordering::Relaxed);
1227 enumerate(); 1333 rescan();
1228 } 1334 }
1229 } 1335 }
1336 None
1230 } 1337 }
1231 None
1232} 1338}
1233 1339
1234/// Allocate DMA-safe memory above 16 MB. Uses the page allocator 1340pub fn try_poll_key() -> Option<KeyEvent> {
1235/// (physical, identity-mapped addresses) instead of the heap allocator
1236/// (virtual addresses) because the EHCI controller's DMA engine needs
1237/// physical addresses that match what the CPU sees. The 16 MB floor
1238/// keeps the schedule structures out of the first 640 K, which some real
1239/// chipsets reject from PCI masters (controller System Error halt).
1240unsafe fn dma_alloc(bytes: usize, _align: usize) -> *mut u8 {
1241 unsafe { 1341 unsafe {
1242 let pages = bytes.div_ceil(4096); 1342 if let Some(b) = crate::hid::pop_byte() {
1243 let Some(phys) = crate::memory::alloc_pages_above(pages, 0x100_0000) else { 1343 return crate::ps2::feed_scancode(b);
1244 return core::ptr::null_mut(); 1344 }
1245 }; 1345 let n = N_DEV.load(Ordering::Relaxed) as usize;
1246 let p = phys as *mut u8; 1346 for i in 0..n {
1247 core::ptr::write_bytes(p, 0, pages * 4096); 1347 let d = dev_mut(i);
1248 p 1348 if d.kind != Kind::Keyboard || !d.armed {
1349 continue;
1350 }
1351 let tok = core::ptr::read_volatile(d.iqtd.add(2).cast::<u32>());
1352 if tok & TOK_ACTIVE != 0 {
1353 continue;
1354 }
1355 if tok & (TOK_HALT | TOK_XACT) != 0 {
1356 d.armed = false;
1357 d.failures += 1;
1358 continue;
1359 }
1360 let mut rep = [0u8; 8];
1361 let len = (d.len as usize).min(8);
1362 for (k, b) in rep.iter_mut().take(len).enumerate() {
1363 *b = core::ptr::read_volatile(d.report.add(k));
1364 }
1365 d.kbd.diff(&rep);
1366 d.reports += 1;
1367 if d.reports == 1 {
1368 crate::serial_println!(
1369 "ehci: kbd report mods={:#x} keys={:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
1370 rep[0], rep[2], rep[3], rep[4], rep[5], rep[6], rep[7]
1371 );
1372 }
1373 rearm(d);
1374 return crate::hid::pop_byte().and_then(crate::ps2::feed_scancode);
1375 }
1376 None
1249 } 1377 }
1250} 1378}
1251 1379
1252/// One-line status for the shell `usb` command. 1380/// Restore one device's periodic qtd to ACTIVE and re-link it.
1381///
1382/// Standard EHCI re-arm (same shape as Linux's `qh_append_tds`): the HC owns
1383/// the QH status/current-qTD/toggle overlay, so firmware must not rewrite it
1384/// — only re-activate the qTD and point Next-qTD at it.
1385unsafe fn rearm(d: &mut Dev) {
1386 unsafe {
1387 core::ptr::write_volatile(
1388 d.iqtd.add(2),
1389 ((d.len & 0x7fff) << 16) | (PID_IN << 8) | TOK_ACTIVE | TOK_IOC,
1390 );
1391 *qh_next_qtd(d.qh) = d.iqtd as u32;
1392 }
1393}
1394
1395// ---------------------------------------------------------------------------
1396// Diagnostics (shell `usb`)
1397// ---------------------------------------------------------------------------
1398
1253pub fn diagnose() -> alloc::string::String { 1399pub fn diagnose() -> alloc::string::String {
1254 let mut s = alloc::string::String::new(); 1400 let mut s = alloc::string::String::new();
1401 let nc = N_CTRL.load(Ordering::Relaxed);
1402 let nd = N_DEV.load(Ordering::Relaxed);
1255 s.push_str(&alloc::format!( 1403 s.push_str(&alloc::format!(
1256 "ehci mmio={:#x} ports={} mouse={} dev={} hid_ep={} armed={}\n", 1404 "ehci: {} controller(s), {} device(s) up={}\n",
1257 EHCI_BASE.load(Ordering::Relaxed), 1405 nc,
1258 N_PORTS.load(Ordering::Relaxed), 1406 nd,
1259 if MOUSE_PRES.load(Ordering::Relaxed) { 1407 CTRL_UP.load(Ordering::Relaxed)
1260 "yes"
1261 } else {
1262 "no"
1263 },
1264 DEV_ADDR.load(Ordering::Relaxed),
1265 HID_EP.load(Ordering::Relaxed),
1266 ARMED.load(Ordering::Relaxed)
1267 )); 1408 ));
1268 let np = N_PORTS.load(Ordering::Relaxed) as usize; 1409 for c in 0..nc as usize {
1269 for p in 0..np.min(8) { 1410 let ct = unsafe { &*core::ptr::addr_of!(CTRLS[c]) };
1270 let v = unsafe { rd(prtsc(p)) }; 1411 s.push_str(&alloc::format!(
1412 " ctrl{} mmio={:#x} ports={}\n",
1413 c,
1414 ct.base,
1415 ct.nports
1416 ));
1417 for p in 0..ct.nports.min(8) as usize {
1418 let v = unsafe { rd(ct.opb + OP_PRTSC_BASE + p * OP_PRTSC_STRIDE) };
1419 s.push_str(&alloc::format!(
1420 " port{}: {}{:#x}\n",
1421 p,
1422 if v & PRT_CONNECT != 0 { "*" } else { "" },
1423 v
1424 ));
1425 }
1426 }
1427 for i in 0..nd as usize {
1428 let d = unsafe { &*core::ptr::addr_of!(DEVS[i]) };
1271 s.push_str(&alloc::format!( 1429 s.push_str(&alloc::format!(
1272 " eport{}: {}{:#x}\n", 1430 " {} dev{} ctrl={} armed={} fails={} reports={} qh={:#x}\n",
1273 p, 1431 if d.kind == Kind::Mouse { "mouse" } else { "kbd" },
1274 if v & PRT_CONNECT != 0 { "*" } else { "" }, 1432 i,
1275 v & 0xffff 1433 d.ctrl,
1434 d.armed,
1435 d.failures,
1436 d.reports,
1437 d.qh as usize
1276 )); 1438 ));
1277 } 1439 }
1278 s 1440 s
diff --git a/rsnix/src/hid.rs b/rsnix/src/hid.rs
index 42c7602..13711e1 100644
--- a/rsnix/src/hid.rs
+++ b/rsnix/src/hid.rs
@@ -183,6 +183,7 @@ pub fn push_scancode(scan: u16, press: bool) {
183 183
184/// Per-device boot-keyboard report differ. Boot protocol reports are 184/// Per-device boot-keyboard report differ. Boot protocol reports are
185/// 8 bytes: modifiers, reserved, 6 usage codes. 185/// 8 bytes: modifiers, reserved, 6 usage codes.
186#[derive(Clone, Copy)]
186pub struct Keyboard { 187pub struct Keyboard {
187 prev: [u8; 8], 188 prev: [u8; 8],
188} 189}
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs
index 2a32da8..0bac169 100644
--- a/rsnix/src/memory.rs
+++ b/rsnix/src/memory.rs
@@ -1200,6 +1200,13 @@ pub fn alloc_pages(count: usize) -> Option<usize> {
1200/// chipsets is hostile to PCI masters and shows up as a controller 1200/// chipsets is hostile to PCI masters and shows up as a controller
1201/// "System Error" (SE) halt. Page-aligned like `alloc_pages`. 1201/// "System Error" (SE) halt. Page-aligned like `alloc_pages`.
1202pub fn alloc_pages_above(count: usize, min_addr: usize) -> Option<usize> { 1202pub fn alloc_pages_above(count: usize, min_addr: usize) -> Option<usize> {
1203 alloc_pages_range(count, min_addr, usize::MAX)
1204}
1205
1206/// Allocate `count` contiguous physical pages inside `[min_addr, max_addr)`.
1207/// The upper bound matters for 32-bit DMA engines (EHCI): a buffer above
1208/// 4 GiB is unreachable even though the kernel can address it.
1209pub fn alloc_pages_range(count: usize, min_addr: usize, max_addr: usize) -> Option<usize> {
1203 if count == 0 { 1210 if count == 0 {
1204 return None; 1211 return None;
1205 } 1212 }
@@ -1209,13 +1216,15 @@ pub fn alloc_pages_above(count: usize, min_addr: usize) -> Option<usize> {
1209 } 1216 }
1210 let span = SPAN_PAGES; 1217 let span = SPAN_PAGES;
1211 let min_page = min_addr.div_ceil(PAGE_SIZE).max(BASE_PAGE); 1218 let min_page = min_addr.div_ceil(PAGE_SIZE).max(BASE_PAGE);
1212 if min_page >= BASE_PAGE + span { 1219 let max_page = (max_addr / PAGE_SIZE).min(BASE_PAGE + span);
1220 if min_page >= max_page {
1213 return None; 1221 return None;
1214 } 1222 }
1215 let start_idx = min_page - BASE_PAGE; 1223 let start_idx = min_page - BASE_PAGE;
1224 let end_idx = max_page - BASE_PAGE;
1216 let mut run_start = 0usize; 1225 let mut run_start = 0usize;
1217 let mut run_len = 0usize; 1226 let mut run_len = 0usize;
1218 for idx in start_idx..span { 1227 for idx in start_idx..end_idx {
1219 let free = (*BITMAP.add(idx / 8) >> (idx % 8)) & 1 == 0; 1228 let free = (*BITMAP.add(idx / 8) >> (idx % 8)) & 1 == 0;
1220 if free { 1229 if free {
1221 if run_len == 0 { 1230 if run_len == 0 {
diff --git a/rsnix/src/usb.rs b/rsnix/src/usb.rs
index 3ed3a0c..5a4136f 100644
--- a/rsnix/src/usb.rs
+++ b/rsnix/src/usb.rs
@@ -1,33 +1,22 @@
1// --------------------------------------------------------------------------- 1// ---------------------------------------------------------------------------
2// USB host controller driver — xHCI (USB 2.0/3.x) + HID mouse support. 2// USB host controller driver — xHCI (USB 2.0/3.x), HID mouse + keyboard.
3// 3//
4// **Polled** driver: it runs the xHCI event ring from the cursor task loop 4// **Polled** driver: the event ring is drained from the cursor task loop (and
5// instead of wiring MSI/INTx into the PIC, matching how the kernel already 5// the WM input pump for keyboards) instead of wiring MSI/INTx into the PIC.
6// polls the PS/2 mouse (see `cursor`).
7// 6//
8// Minimal, mouse-only scope: 7// Scope:
9// • take the controller out of reset, set up the command + event rings 8// • controller reset, command + event rings, scratchpad buffers (§5.4.5)
10// • reset ports, run the slot/address/configure firmware sequence 9// • one slot per HID device (up to MAX_XDEV), address/configure sequence
11// • find the HID mouse boot interface, open its interrupt-IN endpoint, 10// • boot-protocol HID mouse and keyboard interfaces
12// and translate the standard report into `ps2::MouseEvent` (reusing 11// • reports decoded into ps2::MouseEvent / ps2::feed_scancode
13// the cursor task's consumption path).
14// 12//
15// DMA / MMIO: the kernel is identity-mapped (physical == virtual), so bus 13// DMA: rings/contexts come from the page allocator (physical == kernel
16// addresses are plain kernel pointers and every heap allocation is DMA-able. 14// address via the identity map), never the heap, and are 64-byte aligned as
17// Rings/contexts are allocated once at boot and handed to the controller 15// the xHCI requires. The controller is 64-bit capable, so no 4 GiB bound.
18// permanently.
19// 16//
20// Register/TRB/context layouts follow the Linux xHCI driver definitions 17// Register/TRB/context layouts follow the Linux xHCI driver definitions and
21// (drivers/usb/host/xhci*.h) and the xHCI 1.2 spec. All exact field 18// the xHCI 1.2 spec (TRB type bits 10-15, completion code bits 24-31 of event
22// positions were cross-checked against the Linux sources: 19// dword2, slot id bits 24-31 / ep id bits 16-20 of event dword3).
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// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
32#![allow(non_snake_case)] // macro-style helpers mirror the Linux xHCI headers 21#![allow(non_snake_case)] // macro-style helpers mirror the Linux xHCI headers
33use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; 22use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
@@ -36,7 +25,8 @@ use crate::ps2::MouseEvent;
36 25
37// ---- capability registers (offset from mmio base) ---- 26// ---- capability registers (offset from mmio base) ----
38const CAP_CAPLENGTH: usize = 0x00; 27const CAP_CAPLENGTH: usize = 0x00;
39const HCSPARAMS1: usize = 0x04; // MaxSlots[7:0] MaxIntrs[15:8] MaxPorts[23:16] 28const HCSPARAMS1: usize = 0x04; // MaxSlots[7:0] MaxIntrs[15:8] MaxPorts[31:24]
29const HCSPARAMS2: usize = 0x08; // scratchpad buffers, ERST max
40const HCCPARAMS1: usize = 0x10; // bit0=64-bit addressing; bit2=64-byte contexts 30const HCCPARAMS1: usize = 0x10; // bit0=64-bit addressing; bit2=64-byte contexts
41const DBOFF: usize = 0x14; 31const DBOFF: usize = 0x14;
42const RTSOFF: usize = 0x18; 32const RTSOFF: usize = 0x18;
@@ -47,7 +37,8 @@ const OP_USBSTS: usize = 0x04;
47const OP_CRCR: usize = 0x18; // 64-bit 37const OP_CRCR: usize = 0x18; // 64-bit
48const OP_DCBAAP: usize = 0x30; // 64-bit 38const OP_DCBAAP: usize = 0x30; // 64-bit
49const OP_CONFIG: usize = 0x38; 39const OP_CONFIG: usize = 0x38;
50const OP_PORTSC: usize = 0x400; // PORTSC port 1 (port set stride 0x10) 40const OP_PORTSC: usize = 0x400;
41const PORT_STRIDE: usize = 0x10;
51 42
52// ---- runtime registers (relative to mmio + RTSOFF), interrupter 0 ---- 43// ---- runtime registers (relative to mmio + RTSOFF), interrupter 0 ----
53const RT_IMAN: usize = 0x20; 44const RT_IMAN: usize = 0x20;
@@ -67,7 +58,7 @@ const CMD_RING_CYCLE: u32 = 1 << 0;
67const TRB_CYCLE: u32 = 1 << 0; 58const TRB_CYCLE: u32 = 1 << 0;
68const TRB_IOC: u32 = 1 << 5; 59const TRB_IOC: u32 = 1 << 5;
69const TRB_IDT: u32 = 1 << 6; 60const TRB_IDT: u32 = 1 << 6;
70const TRB_TYPE_BITMASK: u32 = 0xfc00; // bits 10..15 61const TRB_TYPE_BITMASK: u32 = 0xfc00;
71fn TYPE(t: u32) -> u32 { 62fn TYPE(t: u32) -> u32 {
72 t << 10 63 t << 10
73} 64}
@@ -148,7 +139,6 @@ fn DEV_SPEED(p: u32) -> u32 {
148} 139}
149 140
150// ---- USB meta protocol ---- 141// ---- USB meta protocol ----
151const USB_DIR_IN: u8 = 0x80;
152const USB_REQ_GET_DESCRIPTOR: u8 = 0x06; 142const USB_REQ_GET_DESCRIPTOR: u8 = 0x06;
153const USB_REQ_SET_CONFIGURATION: u8 = 0x09; 143const USB_REQ_SET_CONFIGURATION: u8 = 0x09;
154const DT_DEVICE: u8 = 0x01; 144const DT_DEVICE: u8 = 0x01;
@@ -156,6 +146,19 @@ const DT_CONFIG: u8 = 0x02;
156const DT_INTERFACE: u8 = 0x04; 146const DT_INTERFACE: u8 = 0x04;
157const DT_ENDPOINT: u8 = 0x05; 147const DT_ENDPOINT: u8 = 0x05;
158const HID_CLASS: u8 = 0x03; 148const HID_CLASS: u8 = 0x03;
149const HID_SUBCLASS_BOOT: u8 = 0x01;
150const HID_PROTO_KEYBOARD: u8 = 1;
151const HID_PROTO_MOUSE: u8 = 2;
152const REQ_SET_PROTOCOL: u8 = 0x0B;
153const REQ_SET_IDLE: u8 = 0x0A;
154
155const MAX_XDEV: usize = 4;
156
157#[derive(Clone, Copy, PartialEq)]
158enum Kind {
159 Mouse,
160 Keyboard,
161}
159 162
160// --------------------------------------------------------------------------- 163// ---------------------------------------------------------------------------
161// State (cooperative single-task kernel → unsafe statics) 164// State (cooperative single-task kernel → unsafe statics)
@@ -166,12 +169,14 @@ static mut DBB: usize = 0; // doorbell base
166static mut RTB: usize = 0; // runtime base 169static mut RTB: usize = 0; // runtime base
167static mut CTRL_CS: usize = 32; // context size 170static mut CTRL_CS: usize = 32; // context size
168static mut MAXPORTS: u32 = 0; 171static mut MAXPORTS: u32 = 0;
172static mut N_SLOTS: u32 = 1; // MaxSlotsEn we program
169 173
174#[derive(Clone, Copy)]
170struct Ring { 175struct Ring {
171 raw: *mut u32, 176 raw: *mut u32,
172 count: usize, // TRBs 177 count: usize,
173 idx: usize, // producer (command/transfer) or consumer (event) index 178 idx: usize,
174 cyc: u32, // software cycle 179 cyc: u32,
175} 180}
176const fn ri_empty(count: usize) -> Ring { 181const fn ri_empty(count: usize) -> Ring {
177 Ring { 182 Ring {
@@ -182,29 +187,60 @@ const fn ri_empty(count: usize) -> Ring {
182 } 187 }
183} 188}
184static mut CMD: Ring = ri_empty(64); 189static 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); 190static mut EV: Ring = ri_empty(32);
188 191
189static mut DCBAA: *mut u64 = core::ptr::null_mut(); 192static 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(); 193static mut INCTX: *mut u8 = core::ptr::null_mut();
192static mut ERST: *mut u8 = core::ptr::null_mut(); 194static mut ERST: *mut u8 = core::ptr::null_mut();
195static mut SPB_ARRAY: *mut u64 = core::ptr::null_mut();
196static mut NUM_SPB: u32 = 0;
197/// Report buffers (16 bytes per device) in one DMA page.
198static mut REPORTS: *mut u8 = core::ptr::null_mut();
193 199
194static SLOT: AtomicU32 = AtomicU32::new(0); 200/// One enumerated HID device (slot + interrupt-IN endpoint).
195static HID_EPID: AtomicU32 = AtomicU32::new(0); // device-context index of the IN endpoint 201#[derive(Clone, Copy)]
196static mut HID_BUF: [u8; 8] = [0; 8]; 202struct XDev {
197static mut HID_LEN: usize = 4; 203 slot: u32,
198static mut MPS0: u32 = 8; 204 dci: u32, // endpoint context index of the IN endpoint
199 205 ep0: Ring,
200static mut GOT_REPORT: bool = false; 206 epin: Ring,
201static mut QUEUED: bool = false; 207 devctx: *mut u8,
202static mut RBTN: u8 = 0; 208 kind: Kind,
203static mut RDX: i8 = 0; 209 len: u32,
204static mut RDY: i8 = 0; 210 kbd: crate::hid::Keyboard,
211 armed: bool,
212 ready: bool,
213 code: u32,
214 reports: u32,
215 failures: u32,
216}
217impl XDev {
218 const EMPTY: XDev = XDev {
219 slot: 0,
220 dci: 0,
221 ep0: ri_empty(8),
222 epin: ri_empty(8),
223 devctx: core::ptr::null_mut(),
224 kind: Kind::Mouse,
225 len: 0,
226 kbd: crate::hid::Keyboard::new(),
227 armed: false,
228 ready: false,
229 code: 0,
230 reports: 0,
231 failures: 0,
232 };
233}
234static mut XDEVS: [XDev; MAX_XDEV] = [XDev::EMPTY; MAX_XDEV];
235static NXDEV: AtomicU32 = AtomicU32::new(0);
205 236
206static INITOK: AtomicBool = AtomicBool::new(false); 237static INITOK: AtomicBool = AtomicBool::new(false);
207static MOUSEPRESENT: AtomicBool = AtomicBool::new(false); 238static DISCOVERED_TRIED: AtomicU32 = AtomicU32::new(0);
239static LAST_SCAN: AtomicU32 = AtomicU32::new(0);
240
241unsafe fn dev_mut(i: usize) -> &'static mut XDev {
242 unsafe { &mut *core::ptr::addr_of_mut!(XDEVS[i]) }
243}
208 244
209fn cs() -> usize { 245fn cs() -> usize {
210 unsafe { CTRL_CS } 246 unsafe { CTRL_CS }
@@ -245,14 +281,17 @@ fn rt(off: usize) -> usize {
245 unsafe { RTB + off } 281 unsafe { RTB + off }
246} 282}
247 283
248fn dma_alloc(bytes: usize, align: usize) -> *mut u8 { 284/// DMA page(s) from the page allocator (physical == CPU address). Always
249 unsafe { 285/// 4 KiB aligned, which satisfies every xHCI ring/context alignment rule.
250 let layout = alloc::alloc::Layout::from_size_align(bytes.max(1), align).unwrap(); 286fn dma_alloc(bytes: usize, _align: usize) -> *mut u8 {
251 let p = alloc::alloc::alloc(layout); 287 let pages = bytes.div_ceil(4096).max(1);
252 if !p.is_null() { 288 match crate::memory::alloc_pages_above(pages, 0x100_0000) {
253 core::ptr::write_bytes(p, 0, bytes.max(1)); 289 Some(p) => {
290 let ptr = p as *mut u8;
291 unsafe { core::ptr::write_bytes(ptr, 0, pages * 4096) };
292 ptr
254 } 293 }
255 p 294 None => core::ptr::null_mut(),
256 } 295 }
257} 296}
258 297
@@ -273,10 +312,8 @@ unsafe fn trb_push(r: &mut Ring, d0: u32, d1: u32, d2: u32, d3: u32) {
273 } 312 }
274} 313}
275 314
276// --------------------------------------------------------------------------- 315/// Drain produced events, calling `handle(d0,d1,d2,d3)` per event, then
277// Event ring: drain produced events, calling handle(d0,d1,d2,d3) per event, 316/// advance ERDP so the controller can reuse the segment entries.
278// then advance ERDP to the consumption point so the controller reuses it.
279// ---------------------------------------------------------------------------
280unsafe fn drain_events(handle: &mut dyn FnMut(u32, u32, u32, u32)) { 317unsafe fn drain_events(handle: &mut dyn FnMut(u32, u32, u32, u32)) {
281 unsafe { 318 unsafe {
282 let ev = &mut *core::ptr::addr_of_mut!(EV); 319 let ev = &mut *core::ptr::addr_of_mut!(EV);
@@ -309,18 +346,16 @@ fn wait_cmd() -> (u32, u32) {
309 let mut slot = 0u32; 346 let mut slot = 0u32;
310 let mut done = false; 347 let mut done = false;
311 let mut seen = 0u32; 348 let mut seen = 0u32;
312 let mut raw: [u32; 4] = [0; 4];
313 for _ in 0..4_000_000 { 349 for _ in 0..4_000_000 {
314 if done { 350 if done {
315 break; 351 break;
316 } 352 }
317 unsafe { 353 unsafe {
318 drain_events(&mut |d0, d1, d2, d3| { 354 drain_events(&mut |_d0, _d1, d2, d3| {
319 seen += 1; 355 seen += 1;
320 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_CMD_COMPLETE { 356 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_CMD_COMPLETE {
321 code = GET_COMP_CODE(d2); 357 code = GET_COMP_CODE(d2);
322 slot = TRB_TO_SLOT_ID(d3); 358 slot = TRB_TO_SLOT_ID(d3);
323 raw = [d0, d1, d2, d3];
324 done = true; 359 done = true;
325 } 360 }
326 }); 361 });
@@ -328,31 +363,20 @@ fn wait_cmd() -> (u32, u32) {
328 core::hint::spin_loop(); 363 core::hint::spin_loop();
329 } 364 }
330 if code != COMP_SUCCESS { 365 if code != COMP_SUCCESS {
331 unsafe { 366 crate::serial_println!(
332 let evidx = core::ptr::addr_of!(EV).cast::<Ring>().read().idx; 367 "usb: cmd-complete code={} slot={} seen={}",
333 crate::serial_println!( 368 code,
334 "usb: cmd-complete code={} slot={} seen={} evidx={}", 369 slot,
335 code, slot, seen, evidx 370 seen
336 ); 371 );
337 for i in 0..4 {
338 let s = EV.raw.add(i * 4);
339 crate::serial_println!(
340 " ev[{i}] {:08x} {:08x} {:08x} {:08x}",
341 s.add(0).read(), s.add(1).read(), s.add(2).read(), s.add(3).read()
342 );
343 }
344 }
345 } 372 }
346 (code, slot) 373 (code, slot)
347} 374}
348 375
349/// Enqueue a command TRB and ring the host command doorbell.
350fn issue_cmd(d3: u32) -> (u32, u32) { 376fn issue_cmd(d3: u32) -> (u32, u32) {
351 issue_cmd_ptr(d3, 0) 377 issue_cmd_ptr(d3, 0)
352} 378}
353 379
354/// Enqueue a command TRB that carries a 64-bit parameter (used by
355/// Address-Device / Configure-Endpoint for the input-context pointer).
356fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) { 380fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) {
357 unsafe { 381 unsafe {
358 trb_push( 382 trb_push(
@@ -368,19 +392,39 @@ fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) {
368 wait_cmd() 392 wait_cmd()
369} 393}
370 394
371/// Wait for a transfer-complete event on the given device-context endpoint id. 395/// Record a transfer event against the device it belongs to. Called for
372fn wait_xfer(epid: u32) -> u32 { 396/// *every* transfer event, including ones drained while waiting for a
373 let mut code = 0u32; 397/// control transfer: dropping them would lose an armed device's report and
398/// stall that device forever.
399fn route_event(d2: u32, d3: u32) {
400 if TRB_FIELD_TO_TYPE(d3) != TRB_EV_TRANSFER {
401 return;
402 }
403 let slot = TRB_TO_SLOT_ID(d3);
404 let epid = TRB_TO_EP_ID(d3);
405 let n = NXDEV.load(Ordering::Relaxed) as usize;
406 for i in 0..n {
407 let d = unsafe { dev_mut(i) };
408 if d.slot == slot && d.dci == epid {
409 d.ready = true;
410 d.code = GET_COMP_CODE(d2);
411 }
412 }
413}
414
415/// Wait for a transfer-complete event on (slot, endpoint id).
416fn wait_xfer(slot: u32, epid: u32) -> u32 {
417 let mut code = u32::MAX;
374 let mut done = false; 418 let mut done = false;
375 let sl = SLOT.load(Ordering::Relaxed);
376 for _ in 0..4_000_000 { 419 for _ in 0..4_000_000 {
377 if done { 420 if done {
378 break; 421 break;
379 } 422 }
380 unsafe { 423 unsafe {
381 drain_events(&mut |_, _, d2, d3| { 424 drain_events(&mut |_d0, _d1, d2, d3| {
425 route_event(d2, d3);
382 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER 426 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER
383 && TRB_TO_SLOT_ID(d3) == sl 427 && TRB_TO_SLOT_ID(d3) == slot
384 && TRB_TO_EP_ID(d3) == epid 428 && TRB_TO_EP_ID(d3) == epid
385 { 429 {
386 code = GET_COMP_CODE(d2); 430 code = GET_COMP_CODE(d2);
@@ -390,14 +434,14 @@ fn wait_xfer(epid: u32) -> u32 {
390 } 434 }
391 core::hint::spin_loop(); 435 core::hint::spin_loop();
392 } 436 }
393 code 437 if !done { 0 } else { code }
394} 438}
395 439
396// --------------------------------------------------------------------------- 440// ---------------------------------------------------------------------------
397// Control transfer on EP0 (setup + optional data + status), per Linux 441// Control transfer on a device's EP0 (setup + optional data + status).
398// xhci_queue_ctrl_tx. Returns OK(len) or the failure completion code.
399// --------------------------------------------------------------------------- 442// ---------------------------------------------------------------------------
400fn ctrl_xfer( 443fn ctrl_xfer(
444 di: usize,
401 bmreq: u8, 445 bmreq: u8,
402 breq: u8, 446 breq: u8,
403 wval: u16, 447 wval: u16,
@@ -406,10 +450,10 @@ fn ctrl_xfer(
406 buflen: u32, 450 buflen: u32,
407 dir_in: bool, 451 dir_in: bool,
408) -> Result<usize, u32> { 452) -> Result<usize, u32> {
409 let slot = SLOT.load(Ordering::Relaxed);
410 let e0 = unsafe { &mut *core::ptr::addr_of_mut!(EP0) };
411 unsafe { 453 unsafe {
412 // Setup stage (immediate data, 8-byte setup packet in d0/d1). 454 let d = dev_mut(di);
455 let slot = d.slot;
456 let e0 = &mut d.ep0;
413 let tx = if buflen > 0 { 457 let tx = if buflen > 0 {
414 TRB_TX_TYPE(if dir_in { TRB_DATA_IN } else { TRB_DATA_OUT }) 458 TRB_TX_TYPE(if dir_in { TRB_DATA_IN } else { TRB_DATA_OUT })
415 } else { 459 } else {
@@ -431,13 +475,12 @@ fn ctrl_xfer(
431 TYPE(TRB_DATA) | if dir_in { TRB_DIR_IN } else { 0 }, 475 TYPE(TRB_DATA) | if dir_in { TRB_DIR_IN } else { 0 },
432 ); 476 );
433 } 477 }
434 // Status stage.
435 let stat_dir = if buflen > 0 && dir_in { 0 } else { TRB_DIR_IN }; 478 let stat_dir = if buflen > 0 && dir_in { 0 } else { TRB_DIR_IN };
436 trb_push(e0, 0, 0, 0, TYPE(TRB_STATUS) | TRB_IOC | stat_dir); 479 trb_push(e0, 0, 0, 0, TYPE(TRB_STATUS) | TRB_IOC | stat_dir);
437 wr(db(slot), 1); // ring EP0 (doorbell target value 1) 480 wr(db(slot), 1);
438 let _ = rd(db(slot)); 481 let _ = rd(db(slot));
439 } 482 }
440 let code = wait_xfer(1); 483 let code = wait_xfer(unsafe { XDEVS[di].slot }, 1);
441 if code == COMP_SUCCESS || code == COMP_SHORT { 484 if code == COMP_SUCCESS || code == COMP_SHORT {
442 Ok(buflen as usize) 485 Ok(buflen as usize)
443 } else { 486 } else {
@@ -446,9 +489,9 @@ fn ctrl_xfer(
446} 489}
447 490
448// --------------------------------------------------------------------------- 491// ---------------------------------------------------------------------------
449// Context construction helpers 492// Context construction
450// --------------------------------------------------------------------------- 493// ---------------------------------------------------------------------------
451fn build_addr_ictx(port_speed: u32, port_num: u32) { 494fn build_addr_ictx(port_speed: u32, port_num: u32, ep0: *mut u32, mps0: u32) {
452 unsafe { 495 unsafe {
453 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs()); 496 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs());
454 let cc = INCTX as *mut u32; 497 let cc = INCTX as *mut u32;
@@ -459,8 +502,8 @@ fn build_addr_ictx(port_speed: u32, port_num: u32) {
459 *sc.add(1) = ROOTPORT(port_num + 1); 502 *sc.add(1) = ROOTPORT(port_num + 1);
460 let e0 = INCTX.add(32 + cs()) as *mut u32; 503 let e0 = INCTX.add(32 + cs()) as *mut u32;
461 *e0.add(0) = 0; 504 *e0.add(0) = 0;
462 *e0.add(1) = EP_TYPE(CTRL_EP) | MAX_PACKET(MPS0) | ERROR_COUNT(3); 505 *e0.add(1) = EP_TYPE(CTRL_EP) | MAX_PACKET(mps0) | ERROR_COUNT(3);
463 (INCTX.add(32 + cs() + 8) as *mut u64).write_volatile((EP0.raw as u64) | 1); 506 (INCTX.add(32 + cs() + 8) as *mut u64).write_volatile((ep0 as u64) | 1);
464 (INCTX.add(32 + cs() + 16) as *mut u32).write_volatile(EP_AVG(8)); 507 (INCTX.add(32 + cs() + 16) as *mut u32).write_volatile(EP_AVG(8));
465 } 508 }
466} 509}
@@ -470,13 +513,11 @@ fn build_addr_ictx(port_speed: u32, port_num: u32) {
470// --------------------------------------------------------------------------- 513// ---------------------------------------------------------------------------
471unsafe fn reset_port(port: u32) -> Result<u32, ()> { 514unsafe fn reset_port(port: u32) -> Result<u32, ()> {
472 unsafe { 515 unsafe {
473 let psc = op(OP_PORTSC) + (port as usize) * 0x10; 516 let psc = op(OP_PORTSC) + (port as usize) * PORT_STRIDE;
474 // Ensure powered.
475 wr(psc, rd(psc) | PORT_POWER); 517 wr(psc, rd(psc) | PORT_POWER);
476 for _ in 0..1000 { 518 for _ in 0..1000 {
477 core::hint::spin_loop(); 519 core::hint::spin_loop();
478 } 520 }
479 // Assert reset, wait for the hardware to clear it (reset done).
480 wr(psc, rd(psc) | PORT_RESET); 521 wr(psc, rd(psc) | PORT_RESET);
481 for _ in 0..4_000_000 { 522 for _ in 0..4_000_000 {
482 if rd(psc) & PORT_RESET == 0 { 523 if rd(psc) & PORT_RESET == 0 {
@@ -484,7 +525,6 @@ unsafe fn reset_port(port: u32) -> Result<u32, ()> {
484 } 525 }
485 core::hint::spin_loop(); 526 core::hint::spin_loop();
486 } 527 }
487 // Wait for the port to be enabled.
488 for _ in 0..4_000_000 { 528 for _ in 0..4_000_000 {
489 if rd(psc) & PORT_PE != 0 { 529 if rd(psc) & PORT_PE != 0 {
490 break; 530 break;
@@ -499,213 +539,253 @@ unsafe fn reset_port(port: u32) -> Result<u32, ()> {
499} 539}
500 540
501// --------------------------------------------------------------------------- 541// ---------------------------------------------------------------------------
502// Enumeration + HID setup 542// Descriptor parsing: first boot HID mouse/keyboard interface
503// --------------------------------------------------------------------------- 543// ---------------------------------------------------------------------------
504fn read_device_descriptor() -> Result<[u8; 18], u32> { 544/// Returns (kind, interface number, ep address, mps, interval).
505 let mut d = [0u8; 18]; 545fn find_hid(cfg: &[u8]) -> Option<(Kind, u8, u8, u32, u32)> {
506 ctrl_xfer(
507 USB_DIR_IN,
508 USB_REQ_GET_DESCRIPTOR,
509 (DT_DEVICE as u16) << 8,
510 0,
511 d.as_mut_ptr(),
512 18,
513 true,
514 )?;
515 Ok(d)
516}
517
518fn read_config_descriptor(buf: &mut [u8]) -> Result<usize, u32> {
519 let total = ctrl_xfer(
520 USB_DIR_IN,
521 USB_REQ_GET_DESCRIPTOR,
522 (DT_CONFIG as u16) << 8,
523 0,
524 buf.as_mut_ptr(),
525 buf.len() as u32,
526 true,
527 )?;
528 Ok(total)
529}
530
531/// Walk a config descriptor tree looking for a HID interrupt IN endpoint
532/// with an interrupt (transfer type 3). Returns (mps, interval, eepid).
533fn find_hid_mouse(cfg: &[u8]) -> Option<(u32, u32, u32)> {
534 let mut i = 0usize; 546 let mut i = 0usize;
535 let mut in_iface = false; 547 let mut cand: Option<(Kind, u8)> = None;
536 while i + 2 <= cfg.len() { 548 while i + 2 <= cfg.len() {
537 let bLen = cfg[i] as usize; 549 let blen = cfg[i] as usize;
538 if bLen == 0 { 550 if blen < 2 {
539 break; 551 break;
540 } 552 }
541 let btype = cfg[i + 1]; 553 let bt = cfg[i + 1];
542 if btype == DT_INTERFACE && i + 9 <= cfg.len() { 554 if bt == DT_INTERFACE && blen >= 9 && i + 9 <= cfg.len() {
543 // interface descriptor: +5 = bInterfaceClass 555 let iface = cfg[i + 2];
544 in_iface = cfg[i + 5] == HID_CLASS; 556 let class = cfg[i + 5];
545 } else if btype == DT_ENDPOINT && in_iface && i + 7 <= cfg.len() { 557 let sub = cfg[i + 6];
546 let addr = cfg[i + 2]; 558 let proto = cfg[i + 7];
547 let attrs = cfg[i + 3]; 559 cand = if class == HID_CLASS && sub == HID_SUBCLASS_BOOT {
548 let dir_in = addr & 0x80 != 0; 560 match proto {
549 let xfer = attrs & 0x03; 561 HID_PROTO_KEYBOARD => Some((Kind::Keyboard, iface)),
550 if dir_in && xfer == 3 { 562 HID_PROTO_MOUSE => Some((Kind::Mouse, iface)),
563 _ => None,
564 }
565 } else {
566 None
567 };
568 } else if bt == DT_ENDPOINT && blen >= 7 && i + 7 <= cfg.len() {
569 if let Some((kind, iface)) = cand {
570 let addr = cfg[i + 2];
571 let attrs = cfg[i + 3];
551 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8); 572 let mps = (cfg[i + 4] as u32) | ((cfg[i + 5] as u32) << 8);
552 let interval = cfg[i + 6] as u32; 573 let interval = cfg[i + 6] as u32;
553 let epnum = (addr & 0x0f) as u32; 574 if (addr & 0x80) != 0 && (attrs & 0x03) == 0x03 {
554 let euid = 2 * epnum + 1; // device-context index (IN) 575 return Some((kind, iface, addr, mps, interval));
555 return Some((mps, interval, euid)); 576 }
556 } 577 }
557 } 578 }
558 i += bLen; 579 i += blen;
559 } 580 }
560 None 581 None
561} 582}
562 583
563// --------------------------------------------------------------------------- 584// ---------------------------------------------------------------------------
564// Bring one port's device through address + configure to an HID mouse 585// Enumeration: slot + address + descriptors + configure IN endpoint
565// --------------------------------------------------------------------------- 586// ---------------------------------------------------------------------------
566fn enumerate(port: u32, speed: u32) -> bool { 587fn enumerate(port: u32, speed: u32) -> bool {
567 let _ = DEV_SPEED; 588 let idx = NXDEV.load(Ordering::Relaxed) as usize;
568 // 1. Enable slot. 589 if idx >= MAX_XDEV {
569 let (code, slotid) = issue_cmd(TYPE(TRB_ENABLE_SLOT)); 590 return false;
570 if code != COMP_SUCCESS || slotid == 0 { 591 }
571 crate::serial_println!("usb: enable-slot failed code={} slot={}", code, slotid); 592 // Per-device rings + output device context.
593 let ep0_raw = dma_alloc(8 * 16, 64) as *mut u32;
594 let epin_raw = dma_alloc(8 * 16, 64) as *mut u32;
595 let devctx = dma_alloc((1 + 8) * cs(), 64);
596 let report = unsafe { REPORTS.add(idx * 16) };
597 if ep0_raw.is_null() || epin_raw.is_null() || devctx.is_null() || report.is_null() {
598 crate::serial_println!("usb: DMA allocation failed");
599 return false;
600 }
601
602 // Enable slot.
603 let (code, slot) = issue_cmd(TYPE(TRB_ENABLE_SLOT));
604 if code != COMP_SUCCESS || slot == 0 {
605 crate::serial_println!("usb: enable-slot failed code={}", code);
572 return false; 606 return false;
573 } 607 }
574 SLOT.store(slotid, Ordering::Relaxed);
575 unsafe { 608 unsafe {
576 *DCBAA.add(slotid as usize) = DEVCTX as u64; 609 *DCBAA.add(slot as usize) = devctx as u64;
610 let d = dev_mut(idx);
611 d.slot = slot;
612 d.ep0 = Ring {
613 raw: ep0_raw,
614 count: 8,
615 idx: 0,
616 cyc: 1,
617 };
618 d.epin = Ring {
619 raw: epin_raw,
620 count: 8,
621 idx: 0,
622 cyc: 1,
623 };
624 d.devctx = devctx;
625 d.armed = false;
626 d.ready = false;
627 d.code = 0;
628 d.reports = 0;
629 d.failures = 0;
630 d.kbd = crate::hid::Keyboard::new();
577 } 631 }
578 // 2. Address device. 632
579 build_addr_ictx(speed, port); 633 // Address device with EP0 max packet 8 (first transactions are tiny).
580 let (code, _) = issue_cmd_ptr(TYPE(TRB_ADDR_DEV) | SLOT_ID(slotid), unsafe { INCTX } as usize); 634 build_addr_ictx(speed, port, ep0_raw, 8);
635 let (code, _) = issue_cmd_ptr(TYPE(TRB_ADDR_DEV) | SLOT_ID(slot), unsafe {
636 INCTX as usize
637 });
581 if code != COMP_SUCCESS { 638 if code != COMP_SUCCESS {
582 crate::serial_println!("usb: address-device failed code={}", code); 639 crate::serial_println!("usb: address-device failed code={}", code);
583 return false; 640 return false;
584 } 641 }
585 // 3. Read the device descriptor (picks up bMaxPacketSize0). 642
586 match read_device_descriptor() { 643 // Device descriptor.
644 match read_device_descriptor(idx) {
587 Ok(d) => { 645 Ok(d) => {
588 let mps = d[7];
589 if mps != 0 {
590 unsafe {
591 MPS0 = mps as u32;
592 }
593 }
594 crate::serial_println!( 646 crate::serial_println!(
595 "usb: dev {:04x}:{:04x} class={:#x} mps0={}", 647 "usb: dev {:04x}:{:04x} class={:#x} mps0={} slot={}",
596 u16::from_le_bytes([d[8], d[9]]), 648 u16::from_le_bytes([d[8], d[9]]),
597 u16::from_le_bytes([d[10], d[11]]), 649 u16::from_le_bytes([d[10], d[11]]),
598 d[4], 650 d[4],
599 d[7] 651 d[7],
652 slot
600 ); 653 );
601 } 654 }
602 Err(c) => crate::serial_println!("usb: get-device-desc failed code={}", c), 655 Err(c) => {
656 crate::serial_println!("usb: get-device-desc failed code={}", c);
657 return false;
658 }
603 } 659 }
604 // 4. Read the whole config descriptor and find the HID mouse IN ep. 660
605 let mut cfg = [0u8; 256]; 661 // Config descriptor -> HID interface.
606 let got = match read_config_descriptor(&mut cfg) { 662 let mut cfg = [0u8; 512];
663 let got = match read_config_descriptor(idx, &mut cfg) {
607 Ok(n) => n, 664 Ok(n) => n,
608 Err(c) => { 665 Err(c) => {
609 crate::serial_println!("usb: get-config failed code={}", c); 666 crate::serial_println!("usb: get-config failed code={}", c);
610 return false; 667 return false;
611 } 668 }
612 }; 669 };
613 let _ = got; 670 let Some((kind, iface, _epaddr, mps, interval)) = find_hid(&cfg[..got]) else {
614 match find_hid_mouse(&cfg) { 671 crate::serial_println!("usb: no HID mouse/keyboard endpoint in config");
615 Some((mps, interval, euid)) => { 672 return false;
616 crate::serial_println!( 673 };
617 "usb: HID mouse IN ep mps={} interval={} euid={}", 674 let cfgval = cfg[5];
618 mps, 675 if cfgval == 0 {
619 interval, 676 return false;
620 euid
621 );
622 // 5. Set configuration = 1 (first config value present).
623 let cfgval = cfg[5];
624 if cfgval == 0 {
625 return false;
626 }
627 if read_configuration(cfgval).is_err() {
628 return false;
629 }
630 // 6. Configure the IN endpoint.
631 if !configure_hid_endpoint(euid, mps, interval) {
632 return false;
633 }
634 HID_EPID.store(euid, Ordering::Relaxed);
635 unsafe {
636 HID_LEN = mps as usize;
637 QUEUED = false;
638 }
639 // 7. Arm the first IN read.
640 queue_report();
641 MOUSEPRESENT.store(true, Ordering::Relaxed);
642 crate::serial_println!(
643 "usb: HID boot mouse armed (slot {})",
644 SLOT.load(Ordering::Relaxed)
645 );
646 true
647 }
648 None => {
649 crate::serial_println!("usb: no HID mouse endpoint in config");
650 false
651 }
652 } 677 }
678 if ctrl_xfer(idx, 0x00, USB_REQ_SET_CONFIGURATION, cfgval as u16, 0, core::ptr::null_mut(), 0, false)
679 .is_err()
680 {
681 return false;
682 }
683 // Boot protocol: fixed 3-byte mouse / 8-byte keyboard reports.
684 if ctrl_xfer(idx, 0x21, REQ_SET_PROTOCOL, 0, iface as u16, core::ptr::null_mut(), 0, false)
685 .is_err()
686 {
687 crate::serial_println!("usb: set-protocol(boot) stalled (continuing)");
688 }
689 let _ = ctrl_xfer(idx, 0x21, REQ_SET_IDLE, 0, iface as u16, core::ptr::null_mut(), 0, false);
690
691 if !configure_hid_endpoint(idx, _epaddr, mps, interval) {
692 return false;
693 }
694 unsafe {
695 let d = dev_mut(idx);
696 d.kind = kind;
697 d.len = mps.min(8).max(1);
698 d.armed = true;
699 }
700 NXDEV.fetch_add(1, Ordering::Relaxed);
701 queue_report(idx);
702 crate::serial_println!(
703 "usb: HID {} armed (slot {} mps {} interval {})",
704 if kind == Kind::Mouse { "mouse" } else { "keyboard" },
705 slot,
706 mps,
707 interval
708 );
709 true
653} 710}
654 711
655fn read_configuration(val: u8) -> Result<(), u32> { 712fn read_device_descriptor(di: usize) -> Result<[u8; 18], u32> {
713 let mut d = [0u8; 18];
714 let b = d.as_mut_ptr();
656 ctrl_xfer( 715 ctrl_xfer(
716 di,
717 0x80,
718 USB_REQ_GET_DESCRIPTOR,
719 (DT_DEVICE as u16) << 8,
657 0, 720 0,
658 USB_REQ_SET_CONFIGURATION, 721 b,
659 val.into(), 722 18,
660 0, 723 true,
661 core::ptr::null_mut(), 724 )
725 .map(|_| d)
726}
727
728fn read_config_descriptor(di: usize, buf: &mut [u8; 512]) -> Result<usize, u32> {
729 ctrl_xfer(
730 di,
731 0x80,
732 USB_REQ_GET_DESCRIPTOR,
733 (DT_CONFIG as u16) << 8,
662 0, 734 0,
663 false, 735 buf.as_mut_ptr(),
736 buf.len() as u32,
737 true,
664 ) 738 )
665 .map(|_| ())
666} 739}
667 740
668fn configure_hid_endpoint(euid: u32, mps: u32, interval: u32) -> bool { 741/// Commit the interrupt-IN endpoint context and ring its doorbell.
742fn configure_hid_endpoint(di: usize, epaddr: u8, mps: u32, interval: u32) -> bool {
669 unsafe { 743 unsafe {
670 let slot = SLOT.load(Ordering::Relaxed); 744 let d = dev_mut(di);
671 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs()); 745 core::ptr::write_bytes(INCTX, 0, 32 + 8 * cs());
672 let cc = INCTX as *mut u32; 746 let cc = INCTX as *mut u32;
673 *cc.add(0) = 0; // Drop flags 747 // Add Context flags are DCI bits: bit0 = slot, bit(2*ep+1) = IN ep.
674 *cc.add(1) = (1 << 0) | (1 << euid); // Add slot + this EP 748 // Endpoint DCI for an IN endpoint is 2 * ep_number + 1 (EP1 IN -> 3).
675 // Input slot context: copy the output (post-address) slot context so 749 let euid = ((epaddr & 0x0f) as u32) * 2 + 1;
676 // route/speed/rootport are preserved, then bump LAST_CTX to the ep. 750 *cc.add(0) = 0;
677 let osc = DEVCTX.offset(0).cast::<u32>(); 751 *cc.add(1) = (1 << 0) | (1 << euid);
752 d.dci = euid;
753 let osc = d.devctx.cast::<u32>();
678 let sc = INCTX.offset(32).cast::<u32>(); 754 let sc = INCTX.offset(32).cast::<u32>();
679 *sc.add(0) = (*osc.add(0) & !LAST_CTX(31)) | LAST_CTX(euid); 755 *sc.add(0) = (*osc.add(0) & !LAST_CTX(31)) | LAST_CTX(euid);
680 *sc.add(1) = *osc.add(1); 756 *sc.add(1) = *osc.add(1);
681 // Endpoint IN context.
682 let ep = INCTX.add(32 + (euid as usize) * cs()).cast::<u32>(); 757 let ep = INCTX.add(32 + (euid as usize) * cs()).cast::<u32>();
683 *ep.add(0) = EP_INTERVAL(interval); 758 *ep.add(0) = EP_INTERVAL(interval);
684 *ep.add(1) = EP_TYPE(INT_IN_EP) | MAX_PACKET(mps) | ERROR_COUNT(3); 759 *ep.add(1) = EP_TYPE(INT_IN_EP) | MAX_PACKET(mps) | ERROR_COUNT(3);
685 *ep.add(4) = EP_AVG(mps); 760 *ep.add(4) = EP_AVG(mps);
686 let deq = EPIN.raw as u64 | 1; 761 let deq = d.epin.raw as u64 | 1;
687 *ep.offset(2).cast::<u64>() = deq; 762 *ep.offset(2).cast::<u64>() = deq;
763 let slot = d.slot;
688 let (code, _) = issue_cmd_ptr(TYPE(TRB_CONFIG_EP) | SLOT_ID(slot), INCTX as usize); 764 let (code, _) = issue_cmd_ptr(TYPE(TRB_CONFIG_EP) | SLOT_ID(slot), INCTX as usize);
689 code == COMP_SUCCESS 765 if code != COMP_SUCCESS {
766 crate::serial_println!("usb: configure-endpoint failed code={}", code);
767 return false;
768 }
769 true
690 } 770 }
691} 771}
692 772
693fn queue_report() { 773/// Arm one interrupt-IN transfer on the device's endpoint ring.
694 let slot = SLOT.load(Ordering::Relaxed); 774fn queue_report(di: usize) {
695 let epid = HID_EPID.load(Ordering::Relaxed);
696 let epin = unsafe { &mut *core::ptr::addr_of_mut!(EPIN) };
697 unsafe { 775 unsafe {
698 let b = core::ptr::addr_of_mut!(HID_BUF) as *mut u8; 776 let d = dev_mut(di);
777 let slot = d.slot;
778 let epid = d.dci;
699 trb_push( 779 trb_push(
700 epin, 780 &mut d.epin,
701 b as u32, 781 REPORTS.add(di * 16) as u32,
702 (b as usize >> 32) as u32, 782 (REPORTS.add(di * 16) as usize >> 32) as u32,
703 TRB_LEN(HID_LEN as u32), 783 TRB_LEN(d.len),
704 TYPE(TRB_NORMAL) | TRB_IOC, 784 TYPE(TRB_NORMAL) | TRB_IOC,
705 ); 785 );
786 d.ready = false;
706 wr(db(slot), epid); 787 wr(db(slot), epid);
707 let _ = rd(db(slot)); 788 let _ = rd(db(slot));
708 QUEUED = true;
709 } 789 }
710} 790}
711 791
@@ -728,7 +808,6 @@ pub fn init() -> bool {
728 }; 808 };
729 crate::serial_println!("usb: xHCI at {:02x}:{:02x}.{}", b, dev, func); 809 crate::serial_println!("usb: xHCI at {:02x}:{:02x}.{}", b, dev, func);
730 810
731 // Enable MMIO + bus-mastering on the PCI command register.
732 unsafe { 811 unsafe {
733 let cmd = crate::pci::config_read(b, dev, func, 0x04); 812 let cmd = crate::pci::config_read(b, dev, func, 0x04);
734 crate::pci::config_write( 813 crate::pci::config_write(
@@ -736,48 +815,55 @@ pub fn init() -> bool {
736 dev, 815 dev,
737 func, 816 func,
738 0x04, 817 0x04,
739 // Memory space + bus master + INTx disable (bit 10). The
740 // driver is polled (event ring drained from the cursor task),
741 // so the controller must never assert INTx: an unhandled
742 // xHCI interrupt would hit the kernel's catch-all handler
743 // and (being level-triggered) re-fire forever on real
744 // hardware.
745 (cmd & 0xFFFF_0000) | ((cmd & 0xFFFF) | 0x6 | 0x400), 818 (cmd & 0xFFFF_0000) | ((cmd & 0xFFFF) | 0x6 | 0x400),
746 ); 819 );
747 let bar = crate::pci::bar64(b, dev, func, 0); 820 let bar = crate::pci::bar64(b, dev, func, 0);
748 let base = bar.unwrap() as usize; 821 let base = bar.unwrap_or(0) as usize;
822 if base == 0 {
823 crate::serial_println!("usb: bad BAR");
824 return false;
825 }
749 M = base; 826 M = base;
750 let caplen = rd(base + CAP_CAPLENGTH); 827 let caplen = rd(base + CAP_CAPLENGTH);
751 let hcs1 = rd(base + HCSPARAMS1); 828 let hcs1 = rd(base + HCSPARAMS1);
829 let hcs2 = rd(base + HCSPARAMS2);
752 let hcc = rd(base + HCCPARAMS1); 830 let hcc = rd(base + HCCPARAMS1);
753 crate::serial_println!(
754 "usb: mmio={:#x} caplen={:#x} hcs1={:#x} hcc1={:#x}",
755 base, caplen, hcs1, hcc
756 );
757 OPB = base + (caplen & 0xff) as usize; 831 OPB = base + (caplen & 0xff) as usize;
758 DBB = base + rd(base + DBOFF) as usize; 832 DBB = base + rd(base + DBOFF) as usize;
759 RTB = base + rd(base + RTSOFF) as usize; 833 RTB = base + rd(base + RTSOFF) as usize;
760 MAXPORTS = (hcs1 >> 24) & 0xff; // MaxPorts is bits 31:24 (bits 23:16 are MaxIntrs) 834 MAXPORTS = (hcs1 >> 24) & 0xff;
761 let _ = hcc; 835 N_SLOTS = (hcs1 & 0xff).clamp(1, MAX_XDEV as u32);
762 CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 }; 836 CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 };
837 // HCSPARAMS2: Max Scratchpad Buffers = Hi[31:27] << 5 | Lo[25:21].
838 NUM_SPB = (((hcs2 >> 27) & 0x1f) << 5) | ((hcs2 >> 21) & 0x1f);
839 crate::serial_println!(
840 "usb: mmio={:#x} caplen={:#x} hcs1={:#x} hcs2={:#x} hcc1={:#x}",
841 base,
842 caplen,
843 hcs1,
844 hcs2,
845 hcc
846 );
847 let (maxports, nslots, csz, nspb) = (MAXPORTS, N_SLOTS, CTRL_CS, NUM_SPB);
848 crate::serial_println!(
849 "usb: ports={} slots={} context={}B scratchpad={}",
850 maxports,
851 nslots,
852 csz,
853 nspb
854 );
763 855
764 // Claim USB Legacy ownership from the BIOS before resetting the
765 // controller (same SMM-legacy rationale as the EHCI driver).
766 match crate::pci::claim_usb_legacy(b, dev, func, base, 0x04, true) { 856 match crate::pci::claim_usb_legacy(b, dev, func, base, 0x04, true) {
767 0 => crate::serial_println!("usb: xHCI USB legacy: already released (no claim needed)"), 857 0 => crate::serial_println!("usb: xHCI USB legacy: already released"),
768 1 => crate::serial_println!("usb: xHCI USB legacy handed over by BIOS"), 858 1 => crate::serial_println!("usb: xHCI USB legacy handed over by BIOS"),
769 2 => crate::serial_println!("usb: xHCI USB legacy claim timed out (BIOS sem stuck)"), 859 2 => crate::serial_println!("usb: xHCI USB legacy claim timed out"),
770 _ => crate::serial_println!("usb: xHCI USB legacy: no capability found"), 860 _ => crate::serial_println!("usb: xHCI USB legacy: no capability"),
771 } 861 }
772 } 862 }
773 863
774 // Reset the controller (stop, then reset). Wait for reset completion 864 // Reset: stop, then RESET, then wait for CNR to clear.
775 // on the CMD_RESET bit clearing (per Linux xhci_reset), then for the
776 // Controller-Not-Ready flag to clear before any doorbell/op-register
777 // activity.
778 unsafe { 865 unsafe {
779 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) & !CMD_RUN); 866 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) & !CMD_RUN);
780 // Wait for halt.
781 let mut t = 0; 867 let mut t = 0;
782 while t < 1_000_000 && rd(op(OP_USBSTS)) & STS_HALT == 0 { 868 while t < 1_000_000 && rd(op(OP_USBSTS)) & STS_HALT == 0 {
783 t += 1; 869 t += 1;
@@ -796,42 +882,80 @@ pub fn init() -> bool {
796 } 882 }
797 } 883 }
798 884
799 // Allocate rings / contexts. 885 // Rings, contexts, scratchpads.
800 unsafe { 886 unsafe {
801 CMD.raw = dma_alloc(64 * 16, 64) as *mut u32; 887 let cmd_ring = dma_alloc(64 * 16, 64) as *mut u32;
802 EV.raw = dma_alloc(32 * 16, 64) as *mut u32; 888 let ev_ring = dma_alloc(32 * 16, 64) as *mut u32;
803 EP0.raw = dma_alloc(8 * 16, 64) as *mut u32; 889 let erst = dma_alloc(16, 64);
804 EPIN.raw = dma_alloc(8 * 16, 64) as *mut u32; 890 let dcb = dma_alloc((N_SLOTS as usize + 1) * 8, 64) as *mut u64;
805 ERST = dma_alloc(16, 64); 891 let ictx = dma_alloc(32 + 8 * cs(), 64);
806 DCBAA = dma_alloc(64 * 8, 64) as *mut u64; 892 let reports = dma_alloc(4096, 64);
807 DEVCTX = dma_alloc((1 + 8) * cs(), 64); 893 if cmd_ring.is_null()
808 INCTX = dma_alloc(32 + 8 * cs(), 64); 894 || ev_ring.is_null()
809 // ERST segment 0 -> event ring. 895 || erst.is_null()
896 || dcb.is_null()
897 || ictx.is_null()
898 || reports.is_null()
899 {
900 crate::serial_println!("usb: DMA allocation failed");
901 return false;
902 }
903 CMD = Ring {
904 raw: cmd_ring,
905 count: 64,
906 idx: 0,
907 cyc: 1,
908 };
909 EV = Ring {
910 raw: ev_ring,
911 count: 32,
912 idx: 0,
913 cyc: 1,
914 };
915 ERST = erst;
916 DCBAA = dcb;
917 INCTX = ictx;
918 REPORTS = reports;
919
920 // Scratchpad buffers (§4.20): a 64-byte array of buffer addresses
921 // whose entries the controller requires to exist when HCSPARAMS2
922 // reports them. DCBAAP[0] must point at that array.
923 if NUM_SPB > 0 {
924 let arr = dma_alloc(NUM_SPB as usize * 8 + 64, 64) as *mut u64;
925 if !arr.is_null() {
926 for i in 0..NUM_SPB as usize {
927 let p = dma_alloc(4096, 64);
928 if p.is_null() {
929 crate::serial_println!("usb: scratchpad buffer {} failed", i);
930 return false;
931 }
932 arr.add(i).write_volatile(p as u64);
933 }
934 SPB_ARRAY = arr;
935 *DCBAA.add(0) = arr as u64;
936 let nspb = NUM_SPB;
937 crate::serial_println!("usb: allocated {} scratchpad buffer(s)", nspb);
938 }
939 }
940
810 let e = ERST as *mut u32; 941 let e = ERST as *mut u32;
811 *e.add(0) = EV.raw as u32; 942 *e.add(0) = ev_ring as u32;
812 *e.add(1) = (EV.raw as usize >> 32) as u32; 943 *e.add(1) = (ev_ring as usize >> 32) as u32;
813 *e.add(2) = 32; // segment size (TRBs) 944 *e.add(2) = 32;
814 } 945 }
815 946
816 // Program registers.
817 unsafe { 947 unsafe {
818 wr64(op(OP_DCBAAP), DCBAA as u64); 948 wr64(op(OP_DCBAAP), DCBAA as u64);
819 wr64( 949 wr64(
820 op(OP_CRCR), 950 op(OP_CRCR),
821 ((CMD.raw as u64) & !0x3f) | CMD_RING_CYCLE as u64, 951 ((CMD.raw as u64) & !0x3f) | CMD_RING_CYCLE as u64,
822 ); 952 );
823 // MaxSlotsEn = 1 (we use a single slot). 953 wr(op(OP_CONFIG), (rd(op(OP_CONFIG)) & !0xff) | N_SLOTS);
824 wr(op(OP_CONFIG), (rd(op(OP_CONFIG)) & !0xff) | 1);
825 // Event ring.
826 wr(rt(RT_ERSTSZ), 1); 954 wr(rt(RT_ERSTSZ), 1);
827 wr64(rt(RT_ERSTBA), ERST as u64); 955 wr64(rt(RT_ERSTBA), ERST as u64);
828 wr64(rt(RT_ERDP), EV.raw as u64); 956 wr64(rt(RT_ERDP), EV.raw as u64);
829 // Interrupter 0: keep the interrupt DISABLED (IE=0, clear any
830 // pending IP) — the driver is polled and the controller must not
831 // assert INTx (see the PCI command-register comment above).
832 wr(rt(RT_IMAN), (rd(rt(RT_IMAN)) & !0x2) | 0x1); 957 wr(rt(RT_IMAN), (rd(rt(RT_IMAN)) & !0x2) | 0x1);
833 wr(rt(RT_IMOD), 0); 958 wr(rt(RT_IMOD), 0);
834 // Run.
835 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN); 959 wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN);
836 let mut t = 0; 960 let mut t = 0;
837 while t < 4_000_000 && rd(op(OP_USBSTS)) & STS_HALT != 0 { 961 while t < 4_000_000 && rd(op(OP_USBSTS)) & STS_HALT != 0 {
@@ -845,36 +969,38 @@ pub fn init() -> bool {
845 } 969 }
846 INITOK.store(true, Ordering::Relaxed); 970 INITOK.store(true, Ordering::Relaxed);
847 crate::serial_println!( 971 crate::serial_println!(
848 "usb: controller up ({} ports) — mouse discovery deferred to cursor task", 972 "usb: controller up ({} ports) — HID discovery deferred to polling",
849 unsafe { MAXPORTS } 973 unsafe { MAXPORTS }
850 ); 974 );
851 true 975 true
852} 976}
853 977
854/// Bitmask of root ports whose attempt already failed; skip them so we don't 978/// Scan ports until all slots are used or every port has been tried.
855/// re-reset the port on every poll.
856static DISCOVERED_TRIED: AtomicU32 = AtomicU32::new(0);
857
858/// Periodic, cheap scan called from the cursor task until a HID mouse is
859/// found. A USB device can finish connecting long after the controller
860/// starts, so discovery is driven asynchronously rather than blocking boot.
861fn maybe_discover() { 979fn maybe_discover() {
862 if MOUSEPRESENT.load(Ordering::Relaxed) { 980 if !INITOK.load(Ordering::Relaxed) {
863 return; 981 return;
864 } 982 }
983 if NXDEV.load(Ordering::Relaxed) as usize >= MAX_XDEV {
984 return;
985 }
986 let now = crate::interrupts::uptime_ms() as u32;
987 if now.wrapping_sub(LAST_SCAN.load(Ordering::Relaxed)) < 200 {
988 return;
989 }
990 LAST_SCAN.store(now, Ordering::Relaxed);
865 let maxports = unsafe { MAXPORTS }; 991 let maxports = unsafe { MAXPORTS };
866 for p in 0..maxports { 992 for p in 0..maxports {
867 if DISCOVERED_TRIED.load(Ordering::Relaxed) & (1 << p) != 0 { 993 if DISCOVERED_TRIED.load(Ordering::Relaxed) & (1 << p) != 0 {
868 continue; 994 continue;
869 } 995 }
870 unsafe { 996 unsafe {
871 let psc = op(OP_PORTSC) + (p as usize) * 0x10; 997 let psc = op(OP_PORTSC) + (p as usize) * PORT_STRIDE;
872 // Only touch ports that are both connected and not yet enabled.
873 if rd(psc) & (PORT_CONNECT | PORT_PE) == PORT_CONNECT { 998 if rd(psc) & (PORT_CONNECT | PORT_PE) == PORT_CONNECT {
874 crate::serial_println!("usb: port {} connect (speed {})", p, DEV_SPEED(rd(psc))); 999 crate::serial_println!("usb: port {} connect (speed {})", p, DEV_SPEED(rd(psc)));
875 if let Ok(speed) = reset_port(p) 1000 if let Ok(speed) = reset_port(p) {
876 && enumerate(p, speed) { 1001 if enumerate(p, speed) {
877 return; 1002 continue; // keep scanning for the other HID device
1003 }
878 } 1004 }
879 DISCOVERED_TRIED.fetch_or(1 << p, Ordering::Relaxed); 1005 DISCOVERED_TRIED.fetch_or(1 << p, Ordering::Relaxed);
880 } 1006 }
@@ -882,237 +1008,249 @@ fn maybe_discover() {
882 } 1008 }
883} 1009}
884 1010
1011fn has_kind(kind: Kind) -> bool {
1012 let n = NXDEV.load(Ordering::Relaxed) as usize;
1013 for i in 0..n {
1014 if unsafe { XDEVS[i].kind } == kind {
1015 return true;
1016 }
1017 }
1018 false
1019}
1020
885pub fn mouse_present() -> bool { 1021pub fn mouse_present() -> bool {
886 MOUSEPRESENT.load(Ordering::Relaxed) 1022 has_kind(Kind::Mouse)
887} 1023}
888 1024
889/// True once the xHCI controller is brought up (regardless of whether a
890/// device has attached yet). The cursor task is spawned as long as this is
891/// set so async mouse discovery can run.
892pub fn controller_up() -> bool { 1025pub fn controller_up() -> bool {
893 INITOK.load(Ordering::Relaxed) 1026 INITOK.load(Ordering::Relaxed)
894} 1027}
895 1028
896/// Reset discovery state (used by the `usb` shell command to rescan).
897pub fn rescan() { 1029pub fn rescan() {
898 DISCOVERED_TRIED.store(0, Ordering::Relaxed); 1030 DISCOVERED_TRIED.store(0, Ordering::Relaxed);
1031 LAST_SCAN.store(0, Ordering::Relaxed);
1032}
1033
1034/// Drain completion events into the device table.
1035fn pump_reports() {
1036 unsafe {
1037 drain_events(&mut |_d0, _d1, d2, d3| route_event(d2, d3));
1038 }
899} 1039}
900 1040
901/// Non-blocking poll: drain the event ring, then decode one mouse report if
902/// one arrived (re-arming the IN transfer so reports keep flowing).
903/// Returns `None` when no report is pending (or no mouse).
904pub fn poll_mouse() -> Option<MouseEvent> { 1041pub fn poll_mouse() -> Option<MouseEvent> {
905 // Drive discovery while no device is attached, then drain reports. 1042 if !INITOK.load(Ordering::Relaxed) {
906 if !MOUSEPRESENT.load(Ordering::Relaxed) { 1043 return None;
907 if INITOK.load(Ordering::Relaxed) { 1044 }
908 maybe_discover(); 1045 if !has_kind(Kind::Mouse) {
1046 maybe_discover();
1047 }
1048 pump_reports();
1049 let n = NXDEV.load(Ordering::Relaxed) as usize;
1050 for i in 0..n {
1051 let d = unsafe { dev_mut(i) };
1052 if d.kind != Kind::Mouse || !d.armed || !d.ready {
1053 continue;
1054 }
1055 if d.code != COMP_SUCCESS && d.code != COMP_SHORT {
1056 d.armed = false;
1057 d.failures += 1;
1058 d.ready = false;
1059 continue;
1060 }
1061 let hb = unsafe { REPORTS.add(i * 16) };
1062 let (b0, b1, b2) = unsafe {
1063 (
1064 core::ptr::read_volatile(hb),
1065 core::ptr::read_volatile(hb.add(1)),
1066 core::ptr::read_volatile(hb.add(2)),
1067 )
1068 };
1069 let ev = MouseEvent {
1070 dx: (b1 as i8) as i16,
1071 dy: (b2 as i8) as i16,
1072 left: b0 & 0x01 != 0,
1073 right: b0 & 0x02 != 0,
1074 middle: b0 & 0x04 != 0,
1075 };
1076 d.reports += 1;
1077 if d.reports == 1 {
1078 crate::serial_println!("usb: mouse report dx={} dy={} b={:#x}", ev.dx, ev.dy, b0);
909 } 1079 }
1080 queue_report(i);
1081 return Some(ev);
1082 }
1083 None
1084}
1085
1086pub fn try_poll_key() -> Option<crate::ps2::KeyEvent> {
1087 if !INITOK.load(Ordering::Relaxed) {
910 return None; 1088 return None;
911 } 1089 }
912 let epid = HID_EPID.load(Ordering::Relaxed); 1090 if !has_kind(Kind::Keyboard) {
913 let sl = SLOT.load(Ordering::Relaxed); 1091 maybe_discover();
914 unsafe { 1092 }
915 drain_events(&mut |_, _, d2, d3| { 1093 if let Some(b) = crate::hid::pop_byte() {
916 if TRB_FIELD_TO_TYPE(d3) == TRB_EV_TRANSFER 1094 return crate::ps2::feed_scancode(b);
917 && TRB_TO_SLOT_ID(d3) == sl 1095 }
918 && TRB_TO_EP_ID(d3) == epid 1096 pump_reports();
919 { 1097 let n = NXDEV.load(Ordering::Relaxed) as usize;
920 let c = GET_COMP_CODE(d2); 1098 for i in 0..n {
921 if c == COMP_SUCCESS || c == COMP_SHORT { 1099 let d = unsafe { dev_mut(i) };
922 // A fresh report sits in HID_BUF. 1100 if d.kind != Kind::Keyboard || !d.armed || !d.ready {
923 let p = core::ptr::addr_of!(HID_BUF) as *const u8; 1101 continue;
924 RBTN = core::ptr::read_volatile(p); 1102 }
925 RDX = core::ptr::read_volatile(p.add(1)) as i8; 1103 if d.code != COMP_SUCCESS && d.code != COMP_SHORT {
926 RDY = core::ptr::read_volatile(p.add(2)) as i8; 1104 d.armed = false;
927 GOT_REPORT = true; 1105 d.failures += 1;
928 QUEUED = false; 1106 d.ready = false;
929 } 1107 continue;
930 }
931 });
932 if GOT_REPORT {
933 GOT_REPORT = false;
934 let ev = Some(MouseEvent {
935 dx: RDX as i16,
936 dy: RDY as i16,
937 left: RBTN & 0x01 != 0,
938 right: RBTN & 0x02 != 0,
939 middle: RBTN & 0x04 != 0,
940 });
941 queue_report(); // re-arm for the next report
942 return ev;
943 } 1108 }
944 if !QUEUED { 1109 let hb = unsafe { REPORTS.add(i * 16) };
945 queue_report(); // keep exactly one IN read outstanding 1110 let mut rep = [0u8; 8];
1111 let len = (d.len as usize).min(8);
1112 for (k, b) in rep.iter_mut().take(len).enumerate() {
1113 *b = unsafe { core::ptr::read_volatile(hb.add(k)) };
946 } 1114 }
1115 d.kbd.diff(&rep);
1116 d.reports += 1;
1117 if d.reports == 1 {
1118 crate::serial_println!(
1119 "usb: kbd report mods={:#x} keys={:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
1120 rep[0], rep[2], rep[3], rep[4], rep[5], rep[6], rep[7]
1121 );
1122 }
1123 queue_report(i);
1124 return crate::hid::pop_byte().and_then(crate::ps2::feed_scancode);
947 } 1125 }
948 None 1126 None
949} 1127}
950 1128
951/// One-line status for the shell's `usb` command. 1129// ---------------------------------------------------------------------------
1130// Diagnostics (shell `usb`)
1131// ---------------------------------------------------------------------------
952pub fn diagnose() -> alloc::string::String { 1132pub fn diagnose() -> alloc::string::String {
953 let mut s = alloc::string::String::new(); 1133 let mut s = alloc::string::String::new();
954 s.push_str(&alloc::format!( 1134 s.push_str(&alloc::format!(
955 "xhci mmio={:#x} mapped={} ports={} mouse={} slot={}\n", 1135 "xhci mmio={:#x} mapped={} ports={} slots={} scratchpad={}\n",
956 unsafe { M }, 1136 unsafe { M },
957 if crate::paging::is_mapped(unsafe { M }) { "yes" } else { "no" }, 1137 if crate::paging::is_mapped(unsafe { M }) {
958 unsafe { MAXPORTS },
959 if MOUSEPRESENT.load(Ordering::Relaxed) {
960 "yes" 1138 "yes"
961 } else { 1139 } else {
962 "no" 1140 "no"
963 }, 1141 },
964 SLOT.load(Ordering::Relaxed) 1142 unsafe { MAXPORTS },
1143 unsafe { N_SLOTS },
1144 unsafe { NUM_SPB }
965 )); 1145 ));
966 unsafe { 1146 let n = NXDEV.load(Ordering::Relaxed) as usize;
967 // Page-table walk for the MMIO base (physical == virtual here). 1147 s.push_str(&alloc::format!(" {} device(s):\n", n));
968 let phys = M; 1148 for i in 0..n {
969 let pml4 = crate::paging::cr3() as *const u64; 1149 let d = unsafe { &*core::ptr::addr_of!(XDEVS[i]) };
970 let l4 = (phys >> 39) & 0x1FF;
971 let l3 = (phys >> 30) & 0x1FF;
972 let l2 = (phys >> 21) & 0x1FF;
973 let l1 = (phys >> 12) & 0x1FF;
974 let pml4e = pml4.add(l4).read_volatile();
975 let pdpte = if pml4e & 1 != 0 {
976 ((pml4e & 0x000f_ffff_ffff_f000) as *const u64).add(l3).read_volatile()
977 } else { 0 };
978 let pde = if pdpte & 1 != 0 && pdpte & (1 << 7) == 0 {
979 ((pdpte & 0x000f_ffff_ffff_f000) as *const u64).add(l2).read_volatile()
980 } else { 0 };
981 let pte = if pde & 1 != 0 && pde & (1 << 7) == 0 {
982 ((pde & 0x000f_ffff_ffff_f000) as *const u64).add(l1).read_volatile()
983 } else { 0 };
984 s.push_str(&alloc::format!( 1150 s.push_str(&alloc::format!(
985 " pt: pml4e={:#018x} pdpte={:#018x} pde={:#018x} pte={:#018x}\n", 1151 " {} slot={} dci={} armed={} fails={} reports={} ring={:#x}\n",
986 pml4e, pdpte, pde, pte 1152 if d.kind == Kind::Mouse { "mouse" } else { "kbd" },
1153 d.slot,
1154 d.dci,
1155 d.armed,
1156 d.failures,
1157 d.reports,
1158 d.epin.raw as usize
987 )); 1159 ));
1160 }
1161 unsafe {
988 for p in 0..8 { 1162 for p in 0..8 {
989 let psc = op(OP_PORTSC) + (p as usize) * 0x10; 1163 let psc = op(OP_PORTSC) + (p as usize) * PORT_STRIDE;
990 let v = rd(psc); 1164 let v = rd(psc);
991 s.push_str(&alloc::format!(" port{}: {:#010x}\n", p, v)); 1165 s.push_str(&alloc::format!(" port{}: {:#010x}\n", p, v));
992 } 1166 }
993 } 1167 }
994 if MOUSEPRESENT.load(Ordering::Relaxed) {
995 s.push_str(&alloc::format!(
996 " hid ep-index={} mps={} armed={}\n",
997 HID_EPID.load(Ordering::Relaxed),
998 unsafe { HID_LEN },
999 unsafe { QUEUED }
1000 ));
1001 }
1002 1168
1003 // Cross-controller attach scan: report which PCI USB controller(s) 1169 // Cross-controller attach scan (read-only): locate devices on real hw.
1004 // currently have a device with bit0 (connect) set, across xHCI / EHCI /
1005 // UHCI / OHCI. Read-only diagnostic to locate the mouse on real hw.
1006 s.push_str("usbctl: scanning all USB controllers:\n"); 1170 s.push_str("usbctl: scanning all USB controllers:\n");
1007 unsafe { 1171 unsafe {
1008 for d in crate::pci::probe() { 1172 for d in crate::pci::probe() {
1009 if d.class != 0x0C || d.subclass != 0x03 { 1173 if d.class != 0x0C || d.subclass != 0x03 {
1010 continue; 1174 continue;
1011 }
1012 let lbl = match d.prog_if {
1013 0x00 => "UHCI",
1014 0x10 => "OHCI",
1015 0x20 => "EHCI",
1016 0x30 => "xHCI",
1017 _ => "USB?",
1018 };
1019 let bar = crate::pci::bar64(d.bus, d.device, d.func, 0);
1020 let base = bar.unwrap_or(0);
1021 s.push_str(&alloc::format!(
1022 " {} {:02x}:{:02x}.{} bar={:#x}\n",
1023 lbl, d.bus, d.device, d.func, base
1024 ));
1025 if base == 0 || base == u64::MAX {
1026 // BAR not decoded / not enabled — nothing to probe.
1027 continue;
1028 }
1029 match d.prog_if {
1030 0x30 => {
1031 if base == 0 {
1032 continue;
1033 }
1034 let caplen = rd(base as usize);
1035 let hcs1 = rd(base as usize + 0x04);
1036 let np = ((hcs1 >> 24) & 0xff) as usize;
1037 let opb = base as usize + (caplen & 0xff) as usize;
1038 s.push_str(&alloc::format!(
1039 " xhci ports={} sc=", np
1040 ));
1041 for p in 0..np.min(16) {
1042 let v = rd(opb + 0x400 + p * 0x10);
1043 s.push_str(&alloc::format!(
1044 "{}{:#x}",
1045 if v & 1 != 0 { "*" } else { "" },
1046 v & 0x3ff
1047 ));
1048 if p + 1 < (np.min(16)) {
1049 s.push(' ');
1050 }
1051 }
1052 s.push('\n');
1053 } 1175 }
1054 0x20 => { 1176 let lbl = match d.prog_if {
1055 // EHCI: MMIO BAR. CAPLENGTH byte0, HcSParams (nports) @0x04. 1177 0x00 => "UHCI",
1056 let cl = rd(base as usize) & 0xff; 1178 0x10 => "OHCI",
1057 let sp = rd(base as usize + 0x04); 1179 0x20 => "EHCI",
1058 let hc = rd(base as usize + 0x08); 1180 0x30 => "xHCI",
1059 let np = (sp & 0xf) as usize; 1181 _ => "USB?",
1060 let opb = base as usize + cl as usize; 1182 };
1061 let cmd = crate::pci::config_read(d.bus, d.device, d.func, 0x04); 1183 let bar = crate::pci::bar64(d.bus, d.device, d.func, 0);
1062 let eccp = crate::pci::config_read(d.bus, d.device, d.func, 0x34); 1184 let base = bar.unwrap_or(0);
1063 s.push_str(&alloc::format!( 1185 s.push_str(&alloc::format!(
1064 " ehci nports={} hrs={} hcc={:#x} cmd={:#x} eccp={:#x}\n", 1186 " {} {:02x}:{:02x}.{} bar={:#x}\n",
1065 np, sp, hc, cmd, (eccp >> 8) & 0xff 1187 lbl,
1066 )); 1188 d.bus,
1067 let cfg = rd(opb + 0x40); 1189 d.device,
1068 s.push_str(&alloc::format!( 1190 d.func,
1069 " ehci cfgflag={:#x} ", cfg 1191 base
1070 )); 1192 ));
1071 for p in 0..np.min(16) { 1193 if base == 0 || base == u64::MAX {
1072 let v = rd(opb + 0x44 + p * 0x04); 1194 continue;
1073 s.push_str(&alloc::format!(
1074 "{}{:#x},",
1075 if v & 1 != 0 { "*" } else { "" },
1076 v & 0xffff
1077 ));
1078 }
1079 s.push('\n');
1080 // Legacy BIOS handoff status (USBLEGSUP at EECP).
1081 let eo = ((eccp >> 8) & 0xff) as usize;
1082 if eo != 0 && (eccp & 0xff) != 0 {
1083 let hs = crate::pci::config_read(
1084 d.bus,
1085 d.device,
1086 d.func,
1087 (eo & 0xfc) as u8,
1088 );
1089 s.push_str(&alloc::format!(
1090 " ehci legacy=usblegsup={:#x} os-owner={} bios-owned={}\n",
1091 hs,
1092 (hs & (1 << 24)) != 0,
1093 (hs & (1 << 16)) != 0 || (hs & 0x100) != 0
1094 ));
1095 }
1096 } 1195 }
1097 0x00 => { 1196 match d.prog_if {
1098 // UHCI: I/O BAR (bar64 refuses I/O bars — read raw). 1197 0x30 => {
1099 let lo = crate::pci::config_read(d.bus, d.device, d.func, 0x10); 1198 let caplen = rd(base as usize);
1100 let io = (lo & 0xFFF0) as u16; 1199 let hcs1 = rd(base as usize + 0x04);
1101 if io == 0 { 1200 let np = ((hcs1 >> 24) & 0xff) as usize;
1102 continue; 1201 let opb = base as usize + (caplen & 0xff) as usize;
1202 s.push_str(" xhci ports=");
1203 for p in 0..np.min(16) {
1204 let v = rd(opb + 0x400 + p * 0x10);
1205 s.push_str(&alloc::format!(
1206 "{}{:#x} ",
1207 if v & 1 != 0 { "*" } else { "" },
1208 v & 0x3ff
1209 ));
1210 }
1211 s.push('\n');
1103 } 1212 }
1104 for p in 0..2 { 1213 0x20 => {
1105 let pr = io_iow16(io + 0x10 + (p as u16) * 2); 1214 let cl = rd(base as usize) & 0xff;
1215 let sp = rd(base as usize + 0x04);
1216 let hc = rd(base as usize + 0x08);
1217 let np = (sp & 0xf) as usize;
1218 let opb = base as usize + cl as usize;
1219 let cfg = rd(opb + 0x40);
1106 s.push_str(&alloc::format!( 1220 s.push_str(&alloc::format!(
1107 "{}{:x}:", if pr & 1 != 0 { "*" } else { "" }, 1221 " ehci nports={} hcc={:#x} cfgflag={:#x} ",
1108 (pr & 0xff) 1222 np, hc, cfg
1109 )); 1223 ));
1224 for p in 0..np.min(16) {
1225 let v = rd(opb + 0x44 + p * 0x04);
1226 s.push_str(&alloc::format!(
1227 "{}{:#x},",
1228 if v & 1 != 0 { "*" } else { "" },
1229 v & 0xffff
1230 ));
1231 }
1232 s.push('\n');
1233 }
1234 0x00 => {
1235 let lo = crate::pci::config_read(d.bus, d.device, d.func, 0x10);
1236 let io = (lo & 0xFFF0) as u16;
1237 if io == 0 {
1238 continue;
1239 }
1240 s.push_str(" uhci ports=");
1241 for p in 0..2 {
1242 let pr = io_iow16(io + 0x10 + (p as u16) * 2);
1243 s.push_str(&alloc::format!(
1244 "{}{:x},",
1245 if pr & 1 != 0 { "*" } else { "" },
1246 pr & 0xff
1247 ));
1248 }
1249 s.push('\n');
1110 } 1250 }
1111 s.push('\n'); 1251 _ => {}
1112 } 1252 }
1113 _ => {}
1114 } 1253 }
1115 } 1254 }
1116 }
1117 s 1255 s
1118} 1256}
diff --git a/rsnix/src/wm.rs b/rsnix/src/wm.rs
index 9f3806f..38cae47 100644
--- a/rsnix/src/wm.rs
+++ b/rsnix/src/wm.rs
@@ -647,7 +647,11 @@ fn pump_main() {
647 // global rings directly; when nothing is focused the keys drop. 647 // global rings directly; when nothing is focused the keys drop.
648 loop { 648 loop {
649 let o = focused().and_then(owner).unwrap_or(crate::sched::SHELL_TID); 649 let o = focused().and_then(owner).unwrap_or(crate::sched::SHELL_TID);
650 while let Some(ev) = crate::ps2::try_poll_key().or_else(crate::uhci::try_poll_key) { 650 while let Some(ev) = crate::ps2::try_poll_key()
651 .or_else(crate::uhci::try_poll_key)
652 .or_else(crate::ehci::try_poll_key)
653 .or_else(crate::usb::try_poll_key)
654 {
651 if !handle_key(ev) { 655 if !handle_key(ev) {
652 let _ = crate::sched::send(o, TaskMsg::Key(ev)); 656 let _ = crate::sched::send(o, TaskMsg::Key(ev));
653 } 657 }