summaryrefslogtreecommitdiffstats
path: root/rsnix/src/ehci.rs
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 /rsnix/src/ehci.rs
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.
Diffstat (limited to '')
-rw-r--r--rsnix/src/ehci.rs1852
1 files changed, 1007 insertions, 845 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