diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-01 16:33:05 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-01 16:33:05 +0100 |
| commit | 50b4d2f0ab05901a847d8e1318152308265e579b (patch) | |
| tree | cd9fdf79e23e501e32646f9fb0af06523d48e169 | |
| parent | usb: re-enable EHCI, share HID keyboard decoding, fix boot protocol (diff) | |
| download | rs-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.rs | 1852 | ||||
| -rw-r--r-- | rsnix/src/hid.rs | 1 | ||||
| -rw-r--r-- | rsnix/src/memory.rs | 13 | ||||
| -rw-r--r-- | rsnix/src/usb.rs | 1102 | ||||
| -rw-r--r-- | rsnix/src/wm.rs | 6 |
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 | ||
| 20 | use crate::ps2::MouseEvent; | 25 | use crate::ps2::{KeyEvent, MouseEvent}; |
| 21 | use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; | 26 | use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; |
| 22 | 27 | ||
| 23 | // ---- Registers (host controller registers; opbase = base + CAPLENGTH) ---- | 28 | // ---- Registers (opbase = base + CAPLENGTH) ---- |
| 24 | const OPCMD: usize = 0x00; | 29 | const OPCMD: usize = 0x00; |
| 25 | const OPSTS: usize = 0x04; | 30 | const OPSTS: usize = 0x04; |
| 26 | const OPINTR: usize = 0x08; | 31 | const OPINTR: usize = 0x08; |
| 27 | const OPFRIDX: usize = 0x0C; | 32 | const OPFRIDX: usize = 0x0C; |
| 28 | const OPCFGFLAG: usize = 0x40; | 33 | const OPCFGFLAG: usize = 0x40; |
| 29 | const OP_PRTSC_BASE: usize = 0x44; // PORTSC #1 | 34 | const OP_PRTSC_BASE: usize = 0x44; |
| 30 | const OP_PRTSC_STRIDE: usize = 0x04; | 35 | const OP_PRTSC_STRIDE: usize = 0x04; |
| 31 | 36 | ||
| 32 | const CMD_RUN: u32 = 1 << 0; | 37 | const CMD_RUN: u32 = 1 << 0; |
| @@ -42,79 +47,131 @@ const PRT_CONNECT: u32 = 1 << 0; | |||
| 42 | const PRT_PED: u32 = 1 << 1; | 47 | const PRT_PED: u32 = 1 << 1; |
| 43 | const PRT_RESET: u32 = 1 << 8; | 48 | const PRT_RESET: u32 = 1 << 8; |
| 44 | const PRT_POWER: u32 = 1 << 12; | 49 | const PRT_POWER: u32 = 1 << 12; |
| 45 | const PRT_RWC: u32 = (1 << 17) | (1 << 18) | (1 << 16) | (1 << 20); // CSC|PEC|CRC|OCC | 50 | const PRT_RWC: u32 = (1 << 17) | (1 << 18) | (1 << 16) | (1 << 20); |
| 46 | 51 | ||
| 47 | // ---- QH / QTD fields ---- | 52 | // ---- QH / QTD fields ---- |
| 48 | const Q_TYPE_QH: u32 = 1 << 1; | 53 | const Q_TYPE_QH: u32 = 1 << 1; |
| 49 | const QT_TERMINATE: u32 = 1; // terminate = 1 in a link pointer | 54 | const QT_TERMINATE: u32 = 1; |
| 50 | const QH_HEAD: u32 = 1 << 15; | 55 | const QH_HEAD: u32 = 1 << 15; |
| 51 | const QH_CONTROL_EP: u32 = 1 << 27; | 56 | const QH_CONTROL_EP: u32 = 1 << 27; |
| 52 | const QH_DTC: u32 = 1 << 14; // data toggle comes from the qTDs (control endpoints) | 57 | const QH_DTC: u32 = 1 << 14; |
| 53 | const QH_RL: u32 = 4 << 28; // NAK reload count (Linux EHCI_TUNE_RL_{HS,TT}) | 58 | const QH_RL: u32 = 4 << 28; |
| 54 | const QH_EPS_HIGH: u32 = 2 << 12; | 59 | const QH_EPS_HIGH: u32 = 2 << 12; |
| 55 | const QH_EPS_FULL: u32 = 0 << 12; | 60 | const QH_EPS_FULL: u32 = 0 << 12; |
| 56 | const QH_EPS_LOW: u32 = 1 << 12; | 61 | const QH_EPS_LOW: u32 = 1 << 12; |
| 57 | const QH_MULT: u32 = 1 << 30; // MULT=1: one split transaction per uframe | 62 | const QH_MULT: u32 = 1 << 30; |
| 58 | 63 | ||
| 59 | const TOK_IOC: u32 = 1 << 15; | 64 | const TOK_IOC: u32 = 1 << 15; |
| 60 | const TOK_ACTIVE: u32 = 1 << 7; | 65 | const TOK_ACTIVE: u32 = 1 << 7; |
| 61 | const TOK_HALT: u32 = 1 << 6; | 66 | const TOK_HALT: u32 = 1 << 6; |
| 62 | const TOK_XACT: u32 = 1 << 3; | 67 | const TOK_XACT: u32 = 1 << 3; |
| 63 | const TOK_MMF: u32 = 1 << 2; // missed microframe: split did not complete in its window | 68 | const TOK_MMF: u32 = 1 << 2; |
| 64 | const PID_IN: u32 = 1; | 69 | const PID_IN: u32 = 1; |
| 65 | const PID_SETUP: u32 = 2; | 70 | const PID_SETUP: u32 = 2; |
| 66 | 71 | ||
| 67 | const CCERR_3: u32 = 3 << 10; | 72 | const CCERR_3: u32 = 3 << 10; |
| 68 | 73 | ||
| 69 | // ---- Static control state ---- | 74 | // ---- Limits / DMA layout ---- |
| 70 | static EHCI_BASE: AtomicU32 = AtomicU32::new(0); | 75 | const MAX_CTRL: usize = 2; // OptiPlex 7010: 00:1a.0 + 00:1d.0 |
| 71 | static N_PORTS: AtomicU32 = AtomicU32::new(0); | 76 | const MAX_DEV: usize = 4; // mouse + keyboard on each controller |
| 77 | const DMA_MIN: usize = 0x100_0000; // avoid <16 MiB (PCI-master hostile region) | ||
| 78 | const DMA_MAX: usize = 0x1_0000_0000; // 32-bit EHCI DMA pointers | ||
| 79 | |||
| 80 | const BUF_DEV_DESC: usize = 0x000; // 64 B | ||
| 81 | const BUF_CFG: usize = 0x100; // 512 B (hubs can have long configs) | ||
| 82 | const BUF_HUB_DESC: usize = 0x300; // 64 B | ||
| 83 | const BUF_PORT_STATUS: usize = 0x340; // 16 B | ||
| 84 | const BUF_REPORT: usize = 0x400; // 64 B per device | ||
| 85 | |||
| 86 | /// One EHCI controller instance (addresses are per controller). | ||
| 87 | #[derive(Clone, Copy)] | ||
| 88 | struct 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 | |||
| 99 | impl 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)] | ||
| 113 | enum 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)] | ||
| 121 | struct Tt { | ||
| 122 | hub: u8, | ||
| 123 | port: u8, | ||
| 124 | } | ||
| 125 | |||
| 126 | /// One armed HID device. | ||
| 127 | #[derive(Clone, Copy)] | ||
| 128 | struct 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 | |||
| 141 | impl 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 | |||
| 156 | static mut CTRLS: [Ctrl; MAX_CTRL] = [Ctrl::EMPTY; MAX_CTRL]; | ||
| 157 | static mut DEVS: [Dev; MAX_DEV] = [Dev::EMPTY; MAX_DEV]; | ||
| 158 | static N_CTRL: AtomicU32 = AtomicU32::new(0); | ||
| 159 | static N_DEV: AtomicU32 = AtomicU32::new(0); | ||
| 160 | /// Controller currently being initialized (registers/pointers resolve here). | ||
| 161 | static CUR: AtomicU32 = AtomicU32::new(0); | ||
| 72 | static CTRL_UP: AtomicBool = AtomicBool::new(false); | 162 | static CTRL_UP: AtomicBool = AtomicBool::new(false); |
| 73 | static MOUSE_PRES: AtomicBool = AtomicBool::new(false); | 163 | static LAST_SCAN: AtomicU32 = AtomicU32::new(0); |
| 74 | static DEV_ADDR: AtomicU32 = AtomicU32::new(0); | ||
| 75 | static HID_EP: AtomicU32 = AtomicU32::new(0); // EP number (1..15), IN | ||
| 76 | static HID_MPS: AtomicU32 = AtomicU32::new(0); | ||
| 77 | static 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. | ||
| 80 | static 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). | ||
| 85 | static 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. | ||
| 88 | static QR_SHOWN: AtomicBool = AtomicBool::new(false); | 164 | static 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; | 166 | unsafe 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 | 169 | unsafe 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. | 172 | fn cur_ctrl() -> &'static mut Ctrl { |
| 97 | static DEBUG_MATRIX: bool = false; | 173 | unsafe { ctrl_mut(CUR.load(Ordering::Relaxed) as usize) } |
| 98 | static MATRIX_DONE: AtomicBool = AtomicBool::new(false); | 174 | } |
| 99 | static XFER_TWEAK: AtomicU32 = AtomicU32::new(0); | ||
| 100 | // Low-region (<1 MB) control QH for the matrix's low-region variant. | ||
| 101 | static mut LOW_XFER: *mut u32 = core::ptr::null_mut(); | ||
| 102 | |||
| 103 | // DMA buffers. Allocated once at init; base pointers recorded here. | ||
| 104 | static mut ASYNC_HEAD: *mut u32 = core::ptr::null_mut(); // QH (32 bytes min, align 32) | ||
| 105 | static mut XFER_QH: *mut u32 = core::ptr::null_mut(); // control QH | ||
| 106 | static mut FRAMELIST: *mut u32 = core::ptr::null_mut(); // 1024 entries, 4K aligned | ||
| 107 | static 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. | ||
| 114 | static mut BUF_PAGE: *mut u8 = core::ptr::null_mut(); | ||
| 115 | const BUF_DEV_DESC: usize = 0x000; // 64 B incl. red zone | ||
| 116 | const BUF_CFG: usize = 0x100; // 320 B incl. red zone | ||
| 117 | const BUF_HID_REPORT: usize = 0x400; // 64 B incl. red zone | ||
| 118 | 175 | ||
| 119 | unsafe fn rd(a: usize) -> u32 { | 176 | unsafe 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 | ||
| 126 | fn 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 | } | ||
| 132 | fn op(off: usize) -> usize { | 183 | fn op(off: usize) -> usize { |
| 133 | opbase() + off | 184 | cur_ctrl().opb + off |
| 134 | } | 185 | } |
| 135 | fn prtsc(p: usize) -> usize { | 186 | fn prtsc(p: usize) -> usize { |
| 136 | opbase() + OP_PRTSC_BASE + p * OP_PRTSC_STRIDE | 187 | op(OP_PRTSC_BASE + p * OP_PRTSC_STRIDE) |
| 188 | } | ||
| 189 | fn 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 ---- |
| 140 | fn qh_next(qh: *mut u32) -> *mut u32 { | 194 | fn 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 { | |||
| 161 | fn qh_buf(qh: *mut u32) -> *mut u32 { | 215 | fn qh_buf(qh: *mut u32) -> *mut u32 { |
| 162 | qh.wrapping_add(7) | 216 | qh.wrapping_add(7) |
| 163 | } | 217 | } |
| 164 | |||
| 165 | fn link_of(ptr: usize) -> u32 { | 218 | fn 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 | 233 | fn 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). | ||
| 241 | unsafe 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 | |||
| 253 | fn 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). | ||
| 187 | unsafe fn ctl_xfer( | 270 | unsafe 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 | // --------------------------------------------------------------------------- |
| 516 | fn ctrl_req(bmreq: u8, breq: u8, wval: u16, widx: u16, wlen: u16) -> [u8; 8] { | 501 | // Descriptor parsing |
| 517 | [ | 502 | // --------------------------------------------------------------------------- |
| 518 | bmreq, | 503 | |
| 519 | breq, | 504 | const HID_CLASS: u8 = 0x03; |
| 520 | (wval & 0xff) as u8, | 505 | const HID_SUBCLASS_BOOT: u8 = 0x01; |
| 521 | (wval >> 8) as u8, | 506 | const HID_PROTO_KEYBOARD: u8 = 1; |
| 522 | (widx & 0xff) as u8, | 507 | const 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). |
| 512 | fn 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. | ||
| 553 | fn 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). | 565 | const HUB_GET_STATUS: u8 = 0x00; |
| 534 | fn find_ehci() -> Option<(u8, u8, u8)> { | 566 | const HUB_CLEAR_FEATURE: u8 = 0x01; |
| 535 | for d in crate::pci::probe() { | 567 | const 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)); | 569 | const PORT_RESET_FEAT: u16 = 4; |
| 570 | const PORT_POWER_FEAT: u16 = 8; | ||
| 571 | const C_PORT_RESET_FEAT: u16 = 20; | ||
| 572 | |||
| 573 | const PS_CONNECTION: u32 = 1 << 0; | ||
| 574 | const PS_ENABLE: u32 = 1 << 1; | ||
| 575 | const PS_RESET: u32 = 1 << 4; | ||
| 576 | const PS_LOW_SPEED: u32 = 1 << 9; | ||
| 577 | const PS_HIGH_SPEED: u32 = 1 << 10; | ||
| 578 | |||
| 579 | /// Read (status, change) for one hub port. | ||
| 580 | unsafe 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. | 606 | unsafe fn hub_feature(hub_addr: u8, hub_mps: u32, set: bool, feat: u16, port: u16) { |
| 544 | fn 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 | |||
| 633 | static NEXT_DEV_ADDR: AtomicU32 = AtomicU32::new(1); | ||
| 634 | |||
| 635 | fn take_addr() -> u8 { | ||
| 636 | NEXT_DEV_ADDR.fetch_add(1, Ordering::Relaxed) as u8 | ||
| 637 | } | ||
| 638 | |||
| 639 | /// Register + arm one HID device. | ||
| 640 | unsafe 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 |
| 560 | pub 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. |
| 723 | unsafe 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 | ||
| 564 | pub fn init() -> bool { | 847 | /// Configure a USB hub, then enumerate every connected port's device. |
| 565 | let Some((b, d, f)) = find_ehci() else { | 848 | unsafe 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. | ||
| 957 | unsafe 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. | ||
| 1143 | unsafe 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. | ||
| 798 | unsafe fn reset_port(p: usize) -> bool { | 1176 | unsafe 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 | 1198 | fn 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; |
| 831 | unsafe 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 | ||
| 849 | const HID_CLASS: u8 = 0x03; | 1213 | pub fn init() -> bool { |
| 850 | 1214 | let devs = crate::pci::probe(); | |
| 851 | fn 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 | |||
| 877 | fn 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 | // --------------------------------------------------------------------------- | ||
| 1078 | unsafe 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 | ||
| 1118 | unsafe fn debug_xfer_matrix(port: usize) { | 1240 | pub 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 | |||
| 1170 | pub fn controller_up() -> bool { | 1274 | pub fn controller_up() -> bool { |
| 1171 | CTRL_UP.load(Ordering::Relaxed) | 1275 | CTRL_UP.load(Ordering::Relaxed) |
| 1172 | } | 1276 | } |
| 1277 | |||
| 1173 | pub fn mouse_present() -> bool { | 1278 | pub 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 | ||
| 1177 | pub fn poll_mouse() -> Option<MouseEvent> { | 1288 | pub 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 | 1340 | pub 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). | ||
| 1240 | unsafe 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. | ||
| 1385 | unsafe 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 | |||
| 1253 | pub fn diagnose() -> alloc::string::String { | 1399 | pub 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)] | ||
| 186 | pub struct Keyboard { | 187 | pub 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`. |
| 1202 | pub fn alloc_pages_above(count: usize, min_addr: usize) -> Option<usize> { | 1202 | pub 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. | ||
| 1209 | pub 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 |
| 33 | use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; | 22 | use 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) ---- |
| 38 | const CAP_CAPLENGTH: usize = 0x00; | 27 | const CAP_CAPLENGTH: usize = 0x00; |
| 39 | const HCSPARAMS1: usize = 0x04; // MaxSlots[7:0] MaxIntrs[15:8] MaxPorts[23:16] | 28 | const HCSPARAMS1: usize = 0x04; // MaxSlots[7:0] MaxIntrs[15:8] MaxPorts[31:24] |
| 29 | const HCSPARAMS2: usize = 0x08; // scratchpad buffers, ERST max | ||
| 40 | const HCCPARAMS1: usize = 0x10; // bit0=64-bit addressing; bit2=64-byte contexts | 30 | const HCCPARAMS1: usize = 0x10; // bit0=64-bit addressing; bit2=64-byte contexts |
| 41 | const DBOFF: usize = 0x14; | 31 | const DBOFF: usize = 0x14; |
| 42 | const RTSOFF: usize = 0x18; | 32 | const RTSOFF: usize = 0x18; |
| @@ -47,7 +37,8 @@ const OP_USBSTS: usize = 0x04; | |||
| 47 | const OP_CRCR: usize = 0x18; // 64-bit | 37 | const OP_CRCR: usize = 0x18; // 64-bit |
| 48 | const OP_DCBAAP: usize = 0x30; // 64-bit | 38 | const OP_DCBAAP: usize = 0x30; // 64-bit |
| 49 | const OP_CONFIG: usize = 0x38; | 39 | const OP_CONFIG: usize = 0x38; |
| 50 | const OP_PORTSC: usize = 0x400; // PORTSC port 1 (port set stride 0x10) | 40 | const OP_PORTSC: usize = 0x400; |
| 41 | const PORT_STRIDE: usize = 0x10; | ||
| 51 | 42 | ||
| 52 | // ---- runtime registers (relative to mmio + RTSOFF), interrupter 0 ---- | 43 | // ---- runtime registers (relative to mmio + RTSOFF), interrupter 0 ---- |
| 53 | const RT_IMAN: usize = 0x20; | 44 | const RT_IMAN: usize = 0x20; |
| @@ -67,7 +58,7 @@ const CMD_RING_CYCLE: u32 = 1 << 0; | |||
| 67 | const TRB_CYCLE: u32 = 1 << 0; | 58 | const TRB_CYCLE: u32 = 1 << 0; |
| 68 | const TRB_IOC: u32 = 1 << 5; | 59 | const TRB_IOC: u32 = 1 << 5; |
| 69 | const TRB_IDT: u32 = 1 << 6; | 60 | const TRB_IDT: u32 = 1 << 6; |
| 70 | const TRB_TYPE_BITMASK: u32 = 0xfc00; // bits 10..15 | 61 | const TRB_TYPE_BITMASK: u32 = 0xfc00; |
| 71 | fn TYPE(t: u32) -> u32 { | 62 | fn 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 ---- |
| 151 | const USB_DIR_IN: u8 = 0x80; | ||
| 152 | const USB_REQ_GET_DESCRIPTOR: u8 = 0x06; | 142 | const USB_REQ_GET_DESCRIPTOR: u8 = 0x06; |
| 153 | const USB_REQ_SET_CONFIGURATION: u8 = 0x09; | 143 | const USB_REQ_SET_CONFIGURATION: u8 = 0x09; |
| 154 | const DT_DEVICE: u8 = 0x01; | 144 | const DT_DEVICE: u8 = 0x01; |
| @@ -156,6 +146,19 @@ const DT_CONFIG: u8 = 0x02; | |||
| 156 | const DT_INTERFACE: u8 = 0x04; | 146 | const DT_INTERFACE: u8 = 0x04; |
| 157 | const DT_ENDPOINT: u8 = 0x05; | 147 | const DT_ENDPOINT: u8 = 0x05; |
| 158 | const HID_CLASS: u8 = 0x03; | 148 | const HID_CLASS: u8 = 0x03; |
| 149 | const HID_SUBCLASS_BOOT: u8 = 0x01; | ||
| 150 | const HID_PROTO_KEYBOARD: u8 = 1; | ||
| 151 | const HID_PROTO_MOUSE: u8 = 2; | ||
| 152 | const REQ_SET_PROTOCOL: u8 = 0x0B; | ||
| 153 | const REQ_SET_IDLE: u8 = 0x0A; | ||
| 154 | |||
| 155 | const MAX_XDEV: usize = 4; | ||
| 156 | |||
| 157 | #[derive(Clone, Copy, PartialEq)] | ||
| 158 | enum 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 | |||
| 166 | static mut RTB: usize = 0; // runtime base | 169 | static mut RTB: usize = 0; // runtime base |
| 167 | static mut CTRL_CS: usize = 32; // context size | 170 | static mut CTRL_CS: usize = 32; // context size |
| 168 | static mut MAXPORTS: u32 = 0; | 171 | static mut MAXPORTS: u32 = 0; |
| 172 | static mut N_SLOTS: u32 = 1; // MaxSlotsEn we program | ||
| 169 | 173 | ||
| 174 | #[derive(Clone, Copy)] | ||
| 170 | struct Ring { | 175 | struct 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 | } |
| 176 | const fn ri_empty(count: usize) -> Ring { | 181 | const 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 | } |
| 184 | static mut CMD: Ring = ri_empty(64); | 189 | static mut CMD: Ring = ri_empty(64); |
| 185 | static mut EP0: Ring = ri_empty(8); | ||
| 186 | static mut EPIN: Ring = ri_empty(8); | ||
| 187 | static mut EV: Ring = ri_empty(32); | 190 | static mut EV: Ring = ri_empty(32); |
| 188 | 191 | ||
| 189 | static mut DCBAA: *mut u64 = core::ptr::null_mut(); | 192 | static mut DCBAA: *mut u64 = core::ptr::null_mut(); |
| 190 | static mut DEVCTX: *mut u8 = core::ptr::null_mut(); | ||
| 191 | static mut INCTX: *mut u8 = core::ptr::null_mut(); | 193 | static mut INCTX: *mut u8 = core::ptr::null_mut(); |
| 192 | static mut ERST: *mut u8 = core::ptr::null_mut(); | 194 | static mut ERST: *mut u8 = core::ptr::null_mut(); |
| 195 | static mut SPB_ARRAY: *mut u64 = core::ptr::null_mut(); | ||
| 196 | static mut NUM_SPB: u32 = 0; | ||
| 197 | /// Report buffers (16 bytes per device) in one DMA page. | ||
| 198 | static mut REPORTS: *mut u8 = core::ptr::null_mut(); | ||
| 193 | 199 | ||
| 194 | static SLOT: AtomicU32 = AtomicU32::new(0); | 200 | /// One enumerated HID device (slot + interrupt-IN endpoint). |
| 195 | static HID_EPID: AtomicU32 = AtomicU32::new(0); // device-context index of the IN endpoint | 201 | #[derive(Clone, Copy)] |
| 196 | static mut HID_BUF: [u8; 8] = [0; 8]; | 202 | struct XDev { |
| 197 | static mut HID_LEN: usize = 4; | 203 | slot: u32, |
| 198 | static mut MPS0: u32 = 8; | 204 | dci: u32, // endpoint context index of the IN endpoint |
| 199 | 205 | ep0: Ring, | |
| 200 | static mut GOT_REPORT: bool = false; | 206 | epin: Ring, |
| 201 | static mut QUEUED: bool = false; | 207 | devctx: *mut u8, |
| 202 | static mut RBTN: u8 = 0; | 208 | kind: Kind, |
| 203 | static mut RDX: i8 = 0; | 209 | len: u32, |
| 204 | static 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 | } | ||
| 217 | impl 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 | } | ||
| 234 | static mut XDEVS: [XDev; MAX_XDEV] = [XDev::EMPTY; MAX_XDEV]; | ||
| 235 | static NXDEV: AtomicU32 = AtomicU32::new(0); | ||
| 205 | 236 | ||
| 206 | static INITOK: AtomicBool = AtomicBool::new(false); | 237 | static INITOK: AtomicBool = AtomicBool::new(false); |
| 207 | static MOUSEPRESENT: AtomicBool = AtomicBool::new(false); | 238 | static DISCOVERED_TRIED: AtomicU32 = AtomicU32::new(0); |
| 239 | static LAST_SCAN: AtomicU32 = AtomicU32::new(0); | ||
| 240 | |||
| 241 | unsafe fn dev_mut(i: usize) -> &'static mut XDev { | ||
| 242 | unsafe { &mut *core::ptr::addr_of_mut!(XDEVS[i]) } | ||
| 243 | } | ||
| 208 | 244 | ||
| 209 | fn cs() -> usize { | 245 | fn 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 | ||
| 248 | fn 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(); | 286 | fn 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 | // --------------------------------------------------------------------------- | ||
| 280 | unsafe fn drain_events(handle: &mut dyn FnMut(u32, u32, u32, u32)) { | 317 | unsafe 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. | ||
| 350 | fn issue_cmd(d3: u32) -> (u32, u32) { | 376 | fn 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). | ||
| 356 | fn issue_cmd_ptr(d3: u32, ptr: usize) -> (u32, u32) { | 380 | fn 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 |
| 372 | fn 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. | ||
| 399 | fn 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). | ||
| 416 | fn 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 | // --------------------------------------------------------------------------- |
| 400 | fn ctrl_xfer( | 443 | fn 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 | // --------------------------------------------------------------------------- |
| 451 | fn build_addr_ictx(port_speed: u32, port_num: u32) { | 494 | fn 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 | // --------------------------------------------------------------------------- |
| 471 | unsafe fn reset_port(port: u32) -> Result<u32, ()> { | 514 | unsafe 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 | // --------------------------------------------------------------------------- |
| 504 | fn read_device_descriptor() -> Result<[u8; 18], u32> { | 544 | /// Returns (kind, interface number, ep address, mps, interval). |
| 505 | let mut d = [0u8; 18]; | 545 | fn 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 | |||
| 518 | fn 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). | ||
| 533 | fn 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 | // --------------------------------------------------------------------------- |
| 566 | fn enumerate(port: u32, speed: u32) -> bool { | 587 | fn 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 | ||
| 655 | fn read_configuration(val: u8) -> Result<(), u32> { | 712 | fn 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 | |||
| 728 | fn 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 | ||
| 668 | fn configure_hid_endpoint(euid: u32, mps: u32, interval: u32) -> bool { | 741 | /// Commit the interrupt-IN endpoint context and ring its doorbell. |
| 742 | fn 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 | ||
| 693 | fn queue_report() { | 773 | /// Arm one interrupt-IN transfer on the device's endpoint ring. |
| 694 | let slot = SLOT.load(Ordering::Relaxed); | 774 | fn 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. | ||
| 856 | static 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. | ||
| 861 | fn maybe_discover() { | 979 | fn 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 | ||
| 1011 | fn 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 | |||
| 885 | pub fn mouse_present() -> bool { | 1021 | pub 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. | ||
| 892 | pub fn controller_up() -> bool { | 1025 | pub 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). | ||
| 897 | pub fn rescan() { | 1029 | pub 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. | ||
| 1035 | fn 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). | ||
| 904 | pub fn poll_mouse() -> Option<MouseEvent> { | 1041 | pub 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 | |||
| 1086 | pub 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 | // --------------------------------------------------------------------------- | ||
| 952 | pub fn diagnose() -> alloc::string::String { | 1132 | pub 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 | } |
