diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-17 14:53:52 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-17 14:53:52 +0200 |
| commit | be868a43185826b02bf6d091019bb779678525bb (patch) | |
| tree | 586988c15f1d700b1621c7f0208584b30878a56c /rsnix/src/uhci.rs | |
| parent | v0.5.10: change em-dashes to dashes (diff) | |
| download | rs-dos-be868a43185826b02bf6d091019bb779678525bb.tar.gz rs-dos-be868a43185826b02bf6d091019bb779678525bb.zip | |
USB2/1.1 (EHCI/UHCI) drivers + real-hardware bring-up hardening
- Add polled EHCI (USB2) and UHCI (USB1.1) host-controller drivers with
HID boot mouse support, wired into cursor polling and the 'usb' shell cmd
- Add USB Legacy ownership handoff (claim_usb_legacy) plus config_write_byte
- Harden boot: verify the PIT/timer ticks before shell entry, EOI stray
interrupts in the LAPIC to stop interrupt storms, bound PS/2 controller
waits, and INTx-disable AHCI (polled driver)
- Add boot stage markers, klog screen echo, and QR lock; scheduler
mark_shell_running; NO_USB stopgap to keep BIOS USB-Legacy input alive
- alloc_pages_above for DMA buffers that must avoid low conventional memory
- Expand DEBUG_NOTES with EHCI/QEMU bring-up findings
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/uhci.rs | 1129 |
1 files changed, 1129 insertions, 0 deletions
diff --git a/rsnix/src/uhci.rs b/rsnix/src/uhci.rs new file mode 100644 index 0000000..87ec9c6 --- /dev/null +++ b/rsnix/src/uhci.rs | |||
| @@ -0,0 +1,1129 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // rsdos UHCI (USB 1.1) companion driver — HID boot keyboard + mouse. | ||
| 3 | // | ||
| 4 | // Real Intel PCH (e.g. OptiPlex 7010) USB2 ports are handled by an EHCI | ||
| 5 | // (high-speed devices) plus its companion UHCI pair (full/low-speed | ||
| 6 | // devices). A real USB keyboard/mouse is full- or low-speed, so it lives | ||
| 7 | // on the UHCI — the EHCI cannot talk to it without a root-hub TT (Intel | ||
| 8 | // PCH EHCI has none; the ports belong to the companion UHCI). QEMU's | ||
| 9 | // virtual usb-mouse/usb-kbd are high-speed when attached to usb-ehci, so | ||
| 10 | // this class of real hardware was never exercised by the xHCI/EHCI paths. | ||
| 11 | // | ||
| 12 | // This driver is polled (no IRQ), exactly like ehci.rs: the frame list is | ||
| 13 | // walked every 1 ms by the controller; we keep one armed interrupt-IN TD | ||
| 14 | // per device and drain reports from the shell task (keyboard) and cursor | ||
| 15 | // task (mouse). Both devices share one control pipeline, serialized by a | ||
| 16 | // spin lock so the shell and cursor tasks cannot corrupt each other's | ||
| 17 | // transfers. | ||
| 18 | // | ||
| 19 | // Data structures follow the UHCI spec as implemented by QEMU's hcd-uhci | ||
| 20 | // and Linux's uhci-hcd (they agree field-for-field): | ||
| 21 | // TD: link(0x00) ctrl(0x04) token(0x08) buffer(0x0C) | ||
| 22 | // QH: link(0x00) el_link(0x04) | ||
| 23 | // ctrl: ACTIVE=1<<23 IOC=1<<24 IOS=1<<25 LS=1<<26 C_ERR=3<<27 SPD=1<<29 | ||
| 24 | // ACTLEN=bits 10:0 (n-1, written by the HC) | ||
| 25 | // token: PID=bits 7:0 (full byte: SETUP=0x2D IN=0x69 OUT=0xE1) | ||
| 26 | // DEVADDR=bits 14:8, EP=bits 18:15, TOGGLE=bit 19, | ||
| 27 | // EXPLEN=bits 31:21 (n-1) | ||
| 28 | // link ptrs: bit0=terminate, bit1=QH type, bit2=depth-first | ||
| 29 | // --------------------------------------------------------------------------- | ||
| 30 | |||
| 31 | use crate::ps2::MouseEvent; | ||
| 32 | use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; | ||
| 33 | |||
| 34 | // ---- Registers (I/O space; 16-bit unless noted) ---- | ||
| 35 | const USBCMD: u16 = 0x00; | ||
| 36 | const USBSTS: u16 = 0x02; | ||
| 37 | const USBINTR: u16 = 0x04; | ||
| 38 | const FRNUM: u16 = 0x06; | ||
| 39 | const FRBASEADD: u16 = 0x08; // 32-bit | ||
| 40 | const SOFMOD: u16 = 0x0C; | ||
| 41 | const PORTSC_BASE: u16 = 0x10; // PORTSC1, PORTSC2 (16-bit each) | ||
| 42 | |||
| 43 | const CMD_RUN: u16 = 1 << 0; | ||
| 44 | const CMD_HCRESET: u16 = 1 << 1; | ||
| 45 | const CMD_CF: u16 = 1 << 6; | ||
| 46 | |||
| 47 | const STS_HCHALTED: u16 = 1 << 5; | ||
| 48 | |||
| 49 | const PSC_CCS: u16 = 1 << 0; // current connect status | ||
| 50 | const PSC_CSC: u16 = 1 << 1; // connect change (write 1 to clear) | ||
| 51 | const PSC_PE: u16 = 1 << 2; // port enabled | ||
| 52 | const PSC_PEDC: u16 = 1 << 3; // enable change (write 1 to clear) | ||
| 53 | const PSC_LSDA: u16 = 1 << 8; // low-speed device attached | ||
| 54 | const PSC_R: u16 = 1 << 9; // reset | ||
| 55 | const PSC_SUSP: u16 = 1 << 12; // suspend | ||
| 56 | |||
| 57 | // ---- TD / QH fields ---- | ||
| 58 | const TD_ACTIVE: u32 = 1 << 23; | ||
| 59 | const TD_IOC: u32 = 1 << 24; | ||
| 60 | const TD_LS: u32 = 1 << 26; | ||
| 61 | const TD_ERR: u32 = 3 << 27; // C_ERR = 3 retries | ||
| 62 | const TD_STALLED: u32 = 1 << 22; | ||
| 63 | const TD_BABBLE: u32 = 1 << 20; | ||
| 64 | const TD_TIMEO: u32 = 1 << 18; | ||
| 65 | const TD_ACTLEN: u32 = 0x7FF; // actual length, n-1 | ||
| 66 | |||
| 67 | const TOK_DEV_SHIFT: u32 = 8; | ||
| 68 | const TOK_EP_SHIFT: u32 = 15; | ||
| 69 | const TOK_TOGGLE: u32 = 1 << 19; | ||
| 70 | const TOK_EXPLEN_SHIFT: u32 = 21; | ||
| 71 | |||
| 72 | const PID_SETUP: u32 = 0x2D; | ||
| 73 | const PID_IN: u32 = 0x69; | ||
| 74 | const PID_OUT: u32 = 0xE1; | ||
| 75 | |||
| 76 | const PTR_TERM: u32 = 0x1; | ||
| 77 | const PTR_QH: u32 = 0x2; | ||
| 78 | |||
| 79 | // ---- HID boot requests ---- | ||
| 80 | const RT_CLASS_IFACE: u8 = 0x21; | ||
| 81 | const REQ_SET_IDLE: u8 = 0x0A; | ||
| 82 | const REQ_SET_PROTOCOL: u8 = 0x0B; | ||
| 83 | |||
| 84 | // ---- Static state ---- | ||
| 85 | static IO_BASE: AtomicU32 = AtomicU32::new(0); | ||
| 86 | static N_PORTS: AtomicU32 = AtomicU32::new(0); | ||
| 87 | static CTRL_UP: AtomicBool = AtomicBool::new(false); | ||
| 88 | static MOUSE_PRES: AtomicBool = AtomicBool::new(false); | ||
| 89 | static KBD_ARMED: AtomicBool = AtomicBool::new(false); | ||
| 90 | static MOUSE_ARMED: AtomicBool = AtomicBool::new(false); | ||
| 91 | // Port index each device was found on (usize::MAX = none). Used so the | ||
| 92 | // keyboard scan skips the mouse's port and vice versa. | ||
| 93 | static KBD_PORT: AtomicU32 = AtomicU32::new(u32::MAX); | ||
| 94 | static MOUSE_PORT: AtomicU32 = AtomicU32::new(u32::MAX); | ||
| 95 | static DEV_KBD: AtomicU32 = AtomicU32::new(0); | ||
| 96 | static DEV_MOUSE: AtomicU32 = AtomicU32::new(0); | ||
| 97 | static KBD_EP: AtomicU32 = AtomicU32::new(1); | ||
| 98 | static KBD_MPS: AtomicU32 = AtomicU32::new(8); | ||
| 99 | static MOUSE_EP: AtomicU32 = AtomicU32::new(1); | ||
| 100 | static MOUSE_MPS: AtomicU32 = AtomicU32::new(4); | ||
| 101 | // Low-speed flag per device (TD_LS needed for 1.5 Mb/s). | ||
| 102 | static KBD_LS: AtomicBool = AtomicBool::new(false); | ||
| 103 | static MOUSE_LS: AtomicBool = AtomicBool::new(false); | ||
| 104 | // Next USB device address to assign (1, 2, ...). | ||
| 105 | static DEV_SEQ: AtomicU32 = AtomicU32::new(1); | ||
| 106 | // Throttles for the lazy re-enumeration paths (ms). | ||
| 107 | static LAST_KBD_ENUM: AtomicU32 = AtomicU32::new(0); | ||
| 108 | static LAST_MOUSE_ENUM: AtomicU32 = AtomicU32::new(0); | ||
| 109 | // Serializes control transfers between the shell task (keyboard) and the | ||
| 110 | // cursor task (mouse). Bounded spin; on timeout the transfer is skipped | ||
| 111 | // and retried on the next throttled pass. | ||
| 112 | static CTRL_LOCK: AtomicBool = AtomicBool::new(false); | ||
| 113 | // Toggle bit the device expects next (alternates after each report). | ||
| 114 | static KBD_TOGGLE: AtomicU32 = AtomicU32::new(0); | ||
| 115 | static MOUSE_TOGGLE: AtomicU32 = AtomicU32::new(0); | ||
| 116 | |||
| 117 | // ---- DMA: frame list + one struct page ---- | ||
| 118 | static mut FRAMELIST: *mut u32 = core::ptr::null_mut(); | ||
| 119 | static mut P: *mut u32 = core::ptr::null_mut(); // QHs, TDs, buffers | ||
| 120 | |||
| 121 | // Offsets within the struct page (16-byte-aligned QHs/TDs). | ||
| 122 | const OFF_CTRL_QH: usize = 0x000; | ||
| 123 | const OFF_KBD_QH: usize = 0x010; | ||
| 124 | const OFF_MOUSE_QH: usize = 0x020; | ||
| 125 | const OFF_SETUP_TD: usize = 0x030; | ||
| 126 | const OFF_DATA_TD: usize = 0x040; | ||
| 127 | const OFF_STAT_TD: usize = 0x050; | ||
| 128 | const OFF_KBD_TD: usize = 0x060; | ||
| 129 | const OFF_MOUSE_TD: usize = 0x070; | ||
| 130 | const OFF_REQ: usize = 0x100; // 8-byte setup packet | ||
| 131 | const OFF_DESC: usize = 0x200; // device descriptor (64 B) | ||
| 132 | const OFF_CFG: usize = 0x300; // config descriptor (256 B) | ||
| 133 | const OFF_KBD_REPORT: usize = 0x500; // 8 B | ||
| 134 | const OFF_MOUSE_REPORT: usize = 0x520; // 8 B | ||
| 135 | |||
| 136 | // Keyboard: previous report + pending scancode FIFO (fed to ps2 decoder). | ||
| 137 | static mut KBD_PREV: [u8; 8] = [0; 8]; | ||
| 138 | const KBDQ_SIZE: usize = 64; | ||
| 139 | static mut KBDQ: [u8; KBDQ_SIZE] = [0; KBDQ_SIZE]; | ||
| 140 | static KBDQ_HEAD: AtomicU32 = AtomicU32::new(0); | ||
| 141 | static KBDQ_LEN: AtomicU32 = AtomicU32::new(0); | ||
| 142 | |||
| 143 | // ---- port I/O ---- | ||
| 144 | #[inline] | ||
| 145 | unsafe fn inw(port: u16) -> u16 { | ||
| 146 | let v: u16; | ||
| 147 | unsafe { | ||
| 148 | core::arch::asm!("in ax, dx", out("ax") v, in("dx") port, options(nostack, preserves_flags)); | ||
| 149 | } | ||
| 150 | v | ||
| 151 | } | ||
| 152 | #[inline] | ||
| 153 | unsafe fn outw(port: u16, v: u16) { | ||
| 154 | unsafe { | ||
| 155 | core::arch::asm!("out dx, ax", in("dx") port, in("ax") v, options(nostack, preserves_flags)); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | #[inline] | ||
| 159 | unsafe fn outl(port: u16, v: u32) { | ||
| 160 | unsafe { | ||
| 161 | core::arch::asm!("out dx, eax", in("dx") port, in("eax") v, options(nostack, preserves_flags)); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | fn io() -> u16 { | ||
| 166 | IO_BASE.load(Ordering::Relaxed) as u16 | ||
| 167 | } | ||
| 168 | /// Absolute I/O port of root port `p`'s PORTSC register. | ||
| 169 | fn prtsc(p: usize) -> u16 { | ||
| 170 | io() + PORTSC_BASE + (p as u16) * 2 | ||
| 171 | } | ||
| 172 | |||
| 173 | // ---- DMA helpers (mirror ehci.rs) ---- | ||
| 174 | unsafe fn dma_alloc(bytes: usize) -> *mut u8 { | ||
| 175 | unsafe { | ||
| 176 | let pages = bytes.div_ceil(4096); | ||
| 177 | let Some(phys) = crate::memory::alloc_pages(pages) else { | ||
| 178 | return core::ptr::null_mut(); | ||
| 179 | }; | ||
| 180 | let p = phys as *mut u8; | ||
| 181 | core::ptr::write_bytes(p, 0, pages * 4096); | ||
| 182 | p | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | unsafe fn rd(addr: *const u32) -> u32 { | ||
| 187 | unsafe { addr.read_volatile() } | ||
| 188 | } | ||
| 189 | unsafe fn wr(addr: *mut u32, v: u32) { | ||
| 190 | unsafe { addr.write_volatile(v) } | ||
| 191 | } | ||
| 192 | |||
| 193 | fn td_addr(o: usize) -> *mut u32 { | ||
| 194 | unsafe { P.add(o / 4) } | ||
| 195 | } | ||
| 196 | fn buf_addr(o: usize) -> *mut u8 { | ||
| 197 | unsafe { P.add(o / 4) as *mut u8 } | ||
| 198 | } | ||
| 199 | |||
| 200 | #[inline] | ||
| 201 | fn ms_now() -> u64 { | ||
| 202 | crate::interrupts::uptime_ms() | ||
| 203 | } | ||
| 204 | |||
| 205 | /// Busy-wait for `ms` milliseconds (interrupts stay enabled; the tick keeps | ||
| 206 | /// the scheduler alive). | ||
| 207 | fn ms_spin(ms: u64) { | ||
| 208 | let t0 = ms_now(); | ||
| 209 | while ms_now().wrapping_sub(t0) < ms { | ||
| 210 | core::hint::spin_loop(); | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | /// Build an 8-byte USB setup packet. | ||
| 215 | fn ctrl_req(bm: u8, breq: u8, val: u16, idx: u16, len: u16) -> [u8; 8] { | ||
| 216 | let mut r = [0u8; 8]; | ||
| 217 | r[0] = bm; | ||
| 218 | r[1] = breq; | ||
| 219 | r[2] = val as u8; | ||
| 220 | r[3] = (val >> 8) as u8; | ||
| 221 | r[4] = idx as u8; | ||
| 222 | r[5] = (idx >> 8) as u8; | ||
| 223 | r[6] = len as u8; | ||
| 224 | r[7] = (len >> 8) as u8; | ||
| 225 | r | ||
| 226 | } | ||
| 227 | |||
| 228 | // --------------------------------------------------------------------------- | ||
| 229 | // Control transfer pipeline (shared, lock-protected) | ||
| 230 | // --------------------------------------------------------------------------- | ||
| 231 | |||
| 232 | fn lock_ctrl() -> bool { | ||
| 233 | for _ in 0..200_000 { | ||
| 234 | if !CTRL_LOCK.swap(true, Ordering::Acquire) { | ||
| 235 | return true; | ||
| 236 | } | ||
| 237 | core::hint::spin_loop(); | ||
| 238 | } | ||
| 239 | false | ||
| 240 | } | ||
| 241 | fn unlock_ctrl() { | ||
| 242 | CTRL_LOCK.store(false, Ordering::Release); | ||
| 243 | } | ||
| 244 | |||
| 245 | /// Issue one control transfer on EP0 of `dev`. `mps` is the device's EP0 | ||
| 246 | /// max packet, `low_speed` selects the TD_CTRL_LS bit, `req` the setup | ||
| 247 | /// packet, `dir_in` a device->host data phase, `buf/len` the data buffer. | ||
| 248 | /// Returns bytes received (IN) or 0 (OUT/setup-only), or a negative error. | ||
| 249 | /// Completion is polled (STATUS TD active bit) with a 5 s timeout. | ||
| 250 | unsafe fn ctl_xfer( | ||
| 251 | dev: u32, | ||
| 252 | _mps: u32, | ||
| 253 | low_speed: bool, | ||
| 254 | req: &[u8; 8], | ||
| 255 | dir_in: bool, | ||
| 256 | buf: *mut u8, | ||
| 257 | len: usize, | ||
| 258 | ) -> i32 { | ||
| 259 | unsafe { | ||
| 260 | // The controller DMA's the setup packet straight from the request | ||
| 261 | // buffer — copy it there first. | ||
| 262 | core::ptr::copy_nonoverlapping(req.as_ptr(), buf_addr(OFF_REQ), 8); | ||
| 263 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 264 | let ls = if low_speed { TD_LS } else { 0 }; | ||
| 265 | |||
| 266 | // SETUP TD (toggle 0, 8 bytes, always from the request buffer). | ||
| 267 | let st = td_addr(OFF_SETUP_TD); | ||
| 268 | wr(st.add(0), 0); // link: filled below | ||
| 269 | wr(st.add(1), TD_ACTIVE | TD_ERR | ls); | ||
| 270 | wr( | ||
| 271 | st.add(2), | ||
| 272 | PID_SETUP | (dev << TOK_DEV_SHIFT) | (7 << TOK_EXPLEN_SHIFT), // 8 bytes | ||
| 273 | ); | ||
| 274 | wr(st.add(3), buf_addr(OFF_REQ) as u32); | ||
| 275 | |||
| 276 | // STATUS TD (toggle 1, opposite direction of the data phase). | ||
| 277 | let sp = td_addr(OFF_STAT_TD); | ||
| 278 | let status_pid = if dir_in { PID_OUT } else { PID_IN }; | ||
| 279 | wr(sp.add(0), PTR_TERM); | ||
| 280 | wr(sp.add(1), TD_ACTIVE | TD_ERR | ls); | ||
| 281 | wr( | ||
| 282 | sp.add(2), | ||
| 283 | status_pid | (dev << TOK_DEV_SHIFT) | TOK_TOGGLE | (0 << TOK_EXPLEN_SHIFT), | ||
| 284 | ); | ||
| 285 | wr(sp.add(3), buf_addr(OFF_REQ) as u32); | ||
| 286 | |||
| 287 | if len == 0 { | ||
| 288 | // Setup-only transfer: SETUP -> STATUS. | ||
| 289 | wr(st.add(0), td_addr(OFF_STAT_TD) as u32); | ||
| 290 | } else { | ||
| 291 | // DATA TD (toggle 1). | ||
| 292 | let dt = td_addr(OFF_DATA_TD); | ||
| 293 | let dpid = if dir_in { PID_IN } else { PID_OUT }; | ||
| 294 | wr(dt.add(0), td_addr(OFF_STAT_TD) as u32); | ||
| 295 | wr(dt.add(1), TD_ACTIVE | TD_ERR | ls); | ||
| 296 | wr( | ||
| 297 | dt.add(2), | ||
| 298 | dpid | (dev << TOK_DEV_SHIFT) | ||
| 299 | | TOK_TOGGLE | ||
| 300 | | (((len as u32).saturating_sub(1) & 0x7FF) << TOK_EXPLEN_SHIFT), | ||
| 301 | ); | ||
| 302 | wr(dt.add(3), buf as u32); | ||
| 303 | wr(st.add(0), dt as u32); | ||
| 304 | } | ||
| 305 | |||
| 306 | // Point the control QH at the new chain and let the HC run it. | ||
| 307 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 308 | wr(td_addr(OFF_CTRL_QH).add(1), td_addr(OFF_SETUP_TD) as u32); | ||
| 309 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 310 | |||
| 311 | // Wait for completion (STATUS TD clears ACTIVE). | ||
| 312 | let t0 = ms_now(); | ||
| 313 | loop { | ||
| 314 | let c = rd(sp.add(1)); | ||
| 315 | if c & TD_ACTIVE == 0 { | ||
| 316 | // Error checks: the status TD mirrors the chain's fate. | ||
| 317 | if c & (TD_STALLED | TD_BABBLE | TD_TIMEO) != 0 { | ||
| 318 | return -2; | ||
| 319 | } | ||
| 320 | let got = if len > 0 && dir_in { | ||
| 321 | ((rd(td_addr(OFF_DATA_TD).add(1)) & TD_ACTLEN) + 1) as i32 | ||
| 322 | } else { | ||
| 323 | 0 | ||
| 324 | }; | ||
| 325 | return got.min(len as i32); | ||
| 326 | } | ||
| 327 | if ms_now().wrapping_sub(t0) >= 5000 { | ||
| 328 | return -1; // timeout | ||
| 329 | } | ||
| 330 | core::hint::spin_loop(); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | // --------------------------------------------------------------------------- | ||
| 336 | // Port management | ||
| 337 | // --------------------------------------------------------------------------- | ||
| 338 | |||
| 339 | /// Reset a connected root port and enable it. Returns the low-speed flag | ||
| 340 | /// (false = full-speed) on success. | ||
| 341 | unsafe fn reset_port(p: usize) -> Option<bool> { | ||
| 342 | unsafe { | ||
| 343 | let r = prtsc(p); | ||
| 344 | // Clear connect/enable change bits (write 1s). | ||
| 345 | outw(r, PSC_CSC | PSC_PEDC); | ||
| 346 | // Start reset. | ||
| 347 | outw(r, PSC_R); | ||
| 348 | ms_spin(50); | ||
| 349 | // End reset. | ||
| 350 | outw(r, 0); | ||
| 351 | // Enable the port (device must be connected). | ||
| 352 | outw(r, PSC_PE); | ||
| 353 | let t0 = ms_now(); | ||
| 354 | while ms_now().wrapping_sub(t0) < 100 { | ||
| 355 | let psc = inw(r); | ||
| 356 | if psc & PSC_PE != 0 { | ||
| 357 | return Some(psc & PSC_LSDA != 0); | ||
| 358 | } | ||
| 359 | ms_spin(1); | ||
| 360 | } | ||
| 361 | None | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | // --------------------------------------------------------------------------- | ||
| 366 | // Config-descriptor parsing: find a HID boot interface with the wanted | ||
| 367 | // protocol and its first interrupt IN endpoint. | ||
| 368 | // --------------------------------------------------------------------------- | ||
| 369 | |||
| 370 | struct HidEp { | ||
| 371 | epnum: u8, | ||
| 372 | mps: u16, | ||
| 373 | iface: u8, | ||
| 374 | } | ||
| 375 | |||
| 376 | /// Walk a config descriptor for a boot-protocol HID interface (class 3, | ||
| 377 | /// subclass 1) whose protocol matches `want` (1 = keyboard, 2 = mouse), | ||
| 378 | /// returning its first interrupt IN endpoint. | ||
| 379 | fn find_hid_ep(cfg: &[u8], want: u8) -> Option<HidEp> { | ||
| 380 | let mut i = 0; | ||
| 381 | while i + 1 < cfg.len() && cfg[i] != 0 { | ||
| 382 | let blen = cfg[i] as usize; | ||
| 383 | let btype = cfg[i + 1]; | ||
| 384 | if blen < 2 || i + blen > cfg.len() { | ||
| 385 | return None; | ||
| 386 | } | ||
| 387 | if btype == 4 { | ||
| 388 | // Interface descriptor. | ||
| 389 | if blen >= 9 | ||
| 390 | && cfg[i + 5] == 3 // bInterfaceClass = HID | ||
| 391 | && cfg[i + 6] == 1 // bInterfaceSubClass = boot | ||
| 392 | && cfg[i + 7] == want | ||
| 393 | // bInterfaceProtocol | ||
| 394 | { | ||
| 395 | let iface = cfg[i + 2]; | ||
| 396 | // Scan this interface's endpoints. | ||
| 397 | let mut j = i + blen; | ||
| 398 | while j + 1 < cfg.len() && cfg[j] != 0 { | ||
| 399 | let elen = cfg[j] as usize; | ||
| 400 | let etype = cfg[j + 1]; | ||
| 401 | if etype == 4 { | ||
| 402 | break; // next interface | ||
| 403 | } | ||
| 404 | if etype == 5 && elen >= 7 { | ||
| 405 | let epa = cfg[j + 2]; | ||
| 406 | let attrs = cfg[j + 3]; | ||
| 407 | if epa & 0x80 != 0 && attrs & 0x03 == 0x03 { | ||
| 408 | let mps = (cfg[j + 4] as u16) | ((cfg[j + 5] as u16) << 8); | ||
| 409 | return Some(HidEp { | ||
| 410 | epnum: epa & 0x0F, | ||
| 411 | mps: mps.max(1), | ||
| 412 | iface, | ||
| 413 | }); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | if elen < 2 { | ||
| 417 | return None; | ||
| 418 | } | ||
| 419 | j += elen; | ||
| 420 | } | ||
| 421 | } | ||
| 422 | } | ||
| 423 | i += blen; | ||
| 424 | } | ||
| 425 | None | ||
| 426 | } | ||
| 427 | |||
| 428 | // --------------------------------------------------------------------------- | ||
| 429 | // Device enumeration (one root port) | ||
| 430 | // --------------------------------------------------------------------------- | ||
| 431 | |||
| 432 | /// Enumerate the device on root port `p` and arm the interrupt-IN endpoint | ||
| 433 | /// if it is a HID boot device of protocol `want` (1 = keyboard, 2 = mouse). | ||
| 434 | /// Returns true when armed. | ||
| 435 | unsafe fn enumerate_port(p: usize, want: u8) -> bool { | ||
| 436 | unsafe { | ||
| 437 | let Some(low_speed) = reset_port(p) else { | ||
| 438 | return false; | ||
| 439 | }; | ||
| 440 | |||
| 441 | // 1. GET_DESCRIPTOR(device) at address 0 — 8 bytes for EP0 MPS. | ||
| 442 | let dd = buf_addr(OFF_DESC); | ||
| 443 | core::ptr::write_bytes(dd, 0, 64); | ||
| 444 | let req = ctrl_req(0x80, 6, 0x0100, 0, 8); | ||
| 445 | if ctl_xfer(0, 8, low_speed, &req, true, dd, 8) < 0 { | ||
| 446 | crate::serial_println!("uhci: port {} get-dev-desc failed (want {})", p, want); | ||
| 447 | return false; | ||
| 448 | } | ||
| 449 | let mps0 = core::ptr::read_volatile(dd.add(7)).max(8) as u32; | ||
| 450 | |||
| 451 | // 2. SET_ADDRESS. | ||
| 452 | let dev = DEV_SEQ.fetch_add(1, Ordering::Relaxed); | ||
| 453 | let req = ctrl_req(0x00, 5, dev as u16, 0, 0); | ||
| 454 | if ctl_xfer(0, mps0, low_speed, &req, false, core::ptr::null_mut(), 0) < 0 { | ||
| 455 | crate::serial_println!("uhci: set-address failed on port {}", p); | ||
| 456 | return false; | ||
| 457 | } | ||
| 458 | |||
| 459 | // 3. Full device descriptor at the new address. | ||
| 460 | core::ptr::write_bytes(dd, 0, 64); | ||
| 461 | let req = ctrl_req(0x80, 6, 0x0100, 0, 18); | ||
| 462 | let got = ctl_xfer(dev, mps0, low_speed, &req, true, dd, 18); | ||
| 463 | if got < 0 { | ||
| 464 | crate::serial_println!("uhci: re-get dev-desc failed on port {}", p); | ||
| 465 | return false; | ||
| 466 | } | ||
| 467 | let class = core::ptr::read_volatile(dd.add(4)); | ||
| 468 | let _ = class; // HID devices have device class 0x00; the HID class | ||
| 469 | // lives in the interface descriptor (checked below). | ||
| 470 | |||
| 471 | // 4. Full config descriptor. | ||
| 472 | let cfg = buf_addr(OFF_CFG); | ||
| 473 | core::ptr::write_bytes(cfg, 0, 256); | ||
| 474 | let req = ctrl_req(0x80, 6, 0x0200, 0, 256); | ||
| 475 | let got = ctl_xfer(dev, mps0, low_speed, &req, true, cfg, 256); | ||
| 476 | if got < 0 { | ||
| 477 | crate::serial_println!( | ||
| 478 | "uhci: get-config failed on port {} code={} (data ctrl={:#x} stat ctrl={:#x})", | ||
| 479 | p, | ||
| 480 | got, | ||
| 481 | rd(td_addr(OFF_DATA_TD).add(1)), | ||
| 482 | rd(td_addr(OFF_STAT_TD).add(1)) | ||
| 483 | ); | ||
| 484 | return false; | ||
| 485 | } | ||
| 486 | let Some(ep) = find_hid_ep(core::slice::from_raw_parts(cfg, got as usize), want) else { | ||
| 487 | return false; // not the device we're looking for | ||
| 488 | }; | ||
| 489 | |||
| 490 | // 5. SET_CONFIGURATION(1). | ||
| 491 | let req = ctrl_req(0x00, 9, 1, 0, 0); | ||
| 492 | if ctl_xfer(dev, mps0, low_speed, &req, false, core::ptr::null_mut(), 0) < 0 { | ||
| 493 | crate::serial_println!("uhci: set-config failed on port {}", p); | ||
| 494 | return false; | ||
| 495 | } | ||
| 496 | |||
| 497 | // 6. Boot protocol + no idle. Some devices stall these; tolerate. | ||
| 498 | let req = ctrl_req(RT_CLASS_IFACE, REQ_SET_PROTOCOL, 1, ep.iface as u16, 0); | ||
| 499 | if ctl_xfer(dev, mps0, low_speed, &req, false, core::ptr::null_mut(), 0) < 0 { | ||
| 500 | crate::serial_println!("uhci: set-protocol stalled on port {}", p); | ||
| 501 | } | ||
| 502 | let req = ctrl_req(RT_CLASS_IFACE, REQ_SET_IDLE, 0, ep.iface as u16, 0); | ||
| 503 | if ctl_xfer(dev, mps0, low_speed, &req, false, core::ptr::null_mut(), 0) < 0 { | ||
| 504 | crate::serial_println!("uhci: set-idle stalled on port {}", p); | ||
| 505 | } | ||
| 506 | |||
| 507 | // 7. Arm the interrupt-IN TD. | ||
| 508 | arm_in_td(want, dev, &ep, low_speed, p); | ||
| 509 | crate::serial_println!( | ||
| 510 | "uhci: HID boot {} armed (port {} dev {} ep{}({:#x}) mps={} {})", | ||
| 511 | if want == 1 { "keyboard" } else { "mouse" }, | ||
| 512 | p, | ||
| 513 | dev, | ||
| 514 | ep.epnum, | ||
| 515 | if want == 1 { 0x81 } else { 0x81 }, | ||
| 516 | ep.mps, | ||
| 517 | if low_speed { "low-speed" } else { "full-speed" } | ||
| 518 | ); | ||
| 519 | true | ||
| 520 | } | ||
| 521 | } | ||
| 522 | |||
| 523 | /// Build the interrupt-IN TD for a device and point its QH element at it. | ||
| 524 | unsafe fn arm_in_td(want: u8, dev: u32, ep: &HidEp, low_speed: bool, port: usize) { | ||
| 525 | unsafe { | ||
| 526 | let (qho, tdo, buf_off, mps, is_kbd) = if want == 1 { | ||
| 527 | ( | ||
| 528 | OFF_KBD_QH, | ||
| 529 | OFF_KBD_TD, | ||
| 530 | OFF_KBD_REPORT, | ||
| 531 | (ep.mps as u32).max(8), | ||
| 532 | true, | ||
| 533 | ) | ||
| 534 | } else { | ||
| 535 | ( | ||
| 536 | OFF_MOUSE_QH, | ||
| 537 | OFF_MOUSE_TD, | ||
| 538 | OFF_MOUSE_REPORT, | ||
| 539 | (ep.mps as u32).max(4), | ||
| 540 | false, | ||
| 541 | ) | ||
| 542 | }; | ||
| 543 | let qh = td_addr(qho); | ||
| 544 | let td = td_addr(tdo); | ||
| 545 | let ls = if low_speed { TD_LS } else { 0 }; | ||
| 546 | let explen = if is_kbd { 8 } else { 3 }; | ||
| 547 | let toggle = 0; // endpoint toggles start at 0 after SET_CONFIG | ||
| 548 | |||
| 549 | wr(td.add(0), PTR_TERM); | ||
| 550 | wr(td.add(1), TD_ACTIVE | TD_ERR | TD_IOC | ls); | ||
| 551 | wr( | ||
| 552 | td.add(2), | ||
| 553 | PID_IN | ||
| 554 | | (dev << TOK_DEV_SHIFT) | ||
| 555 | | ((ep.epnum as u32) << TOK_EP_SHIFT) | ||
| 556 | | (toggle << 19) | ||
| 557 | | (explen << TOK_EXPLEN_SHIFT), | ||
| 558 | ); | ||
| 559 | wr(td.add(3), buf_addr(buf_off) as u32); | ||
| 560 | // QH: el_link points at the TD (link/el_link written last). | ||
| 561 | wr(qh.add(0), PTR_TERM); // no next QH (per-device QH at chain end) | ||
| 562 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 563 | wr(qh.add(1), td as u32); | ||
| 564 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 565 | |||
| 566 | match is_kbd { | ||
| 567 | true => { | ||
| 568 | KBD_ARMED.store(true, Ordering::Relaxed); | ||
| 569 | KBD_PORT.store(port as u32, Ordering::Relaxed); | ||
| 570 | DEV_KBD.store(dev, Ordering::Relaxed); | ||
| 571 | KBD_EP.store(ep.epnum as u32, Ordering::Relaxed); | ||
| 572 | KBD_MPS.store(mps, Ordering::Relaxed); | ||
| 573 | KBD_LS.store(low_speed, Ordering::Relaxed); | ||
| 574 | KBD_TOGGLE.store(0, Ordering::Relaxed); | ||
| 575 | } | ||
| 576 | false => { | ||
| 577 | MOUSE_ARMED.store(true, Ordering::Relaxed); | ||
| 578 | MOUSE_PORT.store(port as u32, Ordering::Relaxed); | ||
| 579 | DEV_MOUSE.store(dev, Ordering::Relaxed); | ||
| 580 | MOUSE_EP.store(ep.epnum as u32, Ordering::Relaxed); | ||
| 581 | MOUSE_MPS.store(mps, Ordering::Relaxed); | ||
| 582 | MOUSE_LS.store(low_speed, Ordering::Relaxed); | ||
| 583 | MOUSE_TOGGLE.store(0, Ordering::Relaxed); | ||
| 584 | MOUSE_PRES.store(true, Ordering::Relaxed); | ||
| 585 | } | ||
| 586 | } | ||
| 587 | } | ||
| 588 | } | ||
| 589 | |||
| 590 | /// Scan all root ports and arm the first device of the wanted protocol. | ||
| 591 | unsafe fn arm_device(want: u8) -> bool { | ||
| 592 | unsafe { | ||
| 593 | if !lock_ctrl() { | ||
| 594 | return false; | ||
| 595 | } | ||
| 596 | let np = N_PORTS.load(Ordering::Relaxed) as usize; | ||
| 597 | let other = if want == 1 { | ||
| 598 | MOUSE_PORT.load(Ordering::Relaxed) | ||
| 599 | } else { | ||
| 600 | KBD_PORT.load(Ordering::Relaxed) | ||
| 601 | }; | ||
| 602 | for p in 0..np { | ||
| 603 | if p as u32 == other { | ||
| 604 | continue; // claimed by the other device | ||
| 605 | } | ||
| 606 | let psc = inw(prtsc(p)); | ||
| 607 | if psc & PSC_CCS == 0 { | ||
| 608 | continue; | ||
| 609 | } | ||
| 610 | if enumerate_port(p, want) { | ||
| 611 | unlock_ctrl(); | ||
| 612 | return true; | ||
| 613 | } | ||
| 614 | } | ||
| 615 | unlock_ctrl(); | ||
| 616 | false | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | // --------------------------------------------------------------------------- | ||
| 621 | // Keyboard: HID report -> scancode-set-1 make/break bytes -> ps2 decoder | ||
| 622 | // --------------------------------------------------------------------------- | ||
| 623 | |||
| 624 | /// HID usage -> scancode set 1 (make code). High byte 0xE0 = E0-prefixed. | ||
| 625 | /// 0 = unmapped. Table covers the common keyboard usages (0x04..0x63). | ||
| 626 | static HID_TO_SCAN: [u16; 0x64] = { | ||
| 627 | let mut t = [0u16; 0x64]; | ||
| 628 | let mut i = 0; | ||
| 629 | while i < 0x64 { | ||
| 630 | t[i] = 0; | ||
| 631 | i += 1; | ||
| 632 | } | ||
| 633 | // Letters a-z | ||
| 634 | t[0x04] = 0x1E; | ||
| 635 | t[0x05] = 0x30; | ||
| 636 | t[0x06] = 0x2E; | ||
| 637 | t[0x07] = 0x20; | ||
| 638 | t[0x08] = 0x12; | ||
| 639 | t[0x09] = 0x21; | ||
| 640 | t[0x0A] = 0x22; | ||
| 641 | t[0x0B] = 0x23; | ||
| 642 | t[0x0C] = 0x17; | ||
| 643 | t[0x0D] = 0x24; | ||
| 644 | t[0x0E] = 0x25; | ||
| 645 | t[0x0F] = 0x26; | ||
| 646 | t[0x10] = 0x32; | ||
| 647 | t[0x11] = 0x31; | ||
| 648 | t[0x12] = 0x18; | ||
| 649 | t[0x13] = 0x19; | ||
| 650 | t[0x14] = 0x10; | ||
| 651 | t[0x15] = 0x13; | ||
| 652 | t[0x16] = 0x1F; | ||
| 653 | t[0x17] = 0x14; | ||
| 654 | t[0x18] = 0x16; | ||
| 655 | t[0x19] = 0x2F; | ||
| 656 | t[0x1A] = 0x11; | ||
| 657 | t[0x1B] = 0x2D; | ||
| 658 | t[0x1C] = 0x15; | ||
| 659 | t[0x1D] = 0x2C; | ||
| 660 | // Digits + punctuation | ||
| 661 | t[0x1E] = 0x02; | ||
| 662 | t[0x1F] = 0x03; | ||
| 663 | t[0x20] = 0x04; | ||
| 664 | t[0x21] = 0x05; | ||
| 665 | t[0x22] = 0x06; | ||
| 666 | t[0x23] = 0x07; | ||
| 667 | t[0x24] = 0x08; | ||
| 668 | t[0x25] = 0x09; | ||
| 669 | t[0x26] = 0x0A; | ||
| 670 | t[0x27] = 0x0B; | ||
| 671 | t[0x28] = 0x1C; // Enter | ||
| 672 | t[0x29] = 0x01; // Esc | ||
| 673 | t[0x2A] = 0x0E; // Backspace | ||
| 674 | t[0x2B] = 0x0F; // Tab | ||
| 675 | t[0x2C] = 0x39; // Space | ||
| 676 | t[0x2D] = 0x0C; // - | ||
| 677 | t[0x2E] = 0x0D; // = | ||
| 678 | t[0x2F] = 0x1A; // [ | ||
| 679 | t[0x30] = 0x1B; // ] | ||
| 680 | t[0x31] = 0x2B; // backslash | ||
| 681 | t[0x33] = 0x27; // ; | ||
| 682 | t[0x34] = 0x28; // ' | ||
| 683 | t[0x35] = 0x29; // ` | ||
| 684 | t[0x36] = 0x33; // , | ||
| 685 | t[0x37] = 0x34; // . | ||
| 686 | t[0x38] = 0x35; // / | ||
| 687 | t[0x39] = 0x3A; // CapsLock | ||
| 688 | // F-keys | ||
| 689 | t[0x3A] = 0x3B; // F1 | ||
| 690 | t[0x3B] = 0x3C; | ||
| 691 | t[0x3C] = 0x3D; | ||
| 692 | t[0x3D] = 0x3E; | ||
| 693 | t[0x3E] = 0x3F; | ||
| 694 | t[0x3F] = 0x40; | ||
| 695 | t[0x40] = 0x41; | ||
| 696 | t[0x41] = 0x42; | ||
| 697 | t[0x42] = 0x43; | ||
| 698 | t[0x43] = 0x44; // F10 | ||
| 699 | t[0x44] = 0xE057; // F11 | ||
| 700 | t[0x45] = 0xE058; // F12 | ||
| 701 | // Nav cluster | ||
| 702 | t[0x49] = 0xE052; // Insert | ||
| 703 | t[0x4A] = 0xE047; // Home | ||
| 704 | t[0x4B] = 0xE049; // PageUp | ||
| 705 | t[0x4C] = 0xE053; // Delete | ||
| 706 | t[0x4D] = 0xE04F; // End | ||
| 707 | t[0x4E] = 0xE051; // PageDown | ||
| 708 | t[0x4F] = 0xE04D; // Right | ||
| 709 | t[0x50] = 0xE04B; // Left | ||
| 710 | t[0x51] = 0xE050; // Down | ||
| 711 | t[0x52] = 0xE048; // Up | ||
| 712 | // Keypad | ||
| 713 | t[0x53] = 0x45; // NumLock | ||
| 714 | t[0x54] = 0xE035; // / | ||
| 715 | t[0x55] = 0x37; // * | ||
| 716 | t[0x56] = 0x4A; // - | ||
| 717 | t[0x57] = 0x4E; // + | ||
| 718 | t[0x58] = 0xE01C; // Enter | ||
| 719 | t[0x59] = 0x4F; // 1 | ||
| 720 | t[0x5A] = 0x50; // 2 | ||
| 721 | t[0x5B] = 0x51; // 3 | ||
| 722 | t[0x5C] = 0x4B; // 4 | ||
| 723 | t[0x5D] = 0x4C; // 5 | ||
| 724 | t[0x5E] = 0x4D; // 6 | ||
| 725 | t[0x5F] = 0x47; // 7 | ||
| 726 | t[0x60] = 0x48; // 8 | ||
| 727 | t[0x61] = 0x49; // 9 | ||
| 728 | t[0x62] = 0x52; // 0 | ||
| 729 | t[0x63] = 0x53; // . | ||
| 730 | t | ||
| 731 | }; | ||
| 732 | |||
| 733 | /// Modifier bit -> scancode (set 1, high byte 0xE0 = E0-prefixed). | ||
| 734 | static MOD_TO_SCAN: [u16; 8] = [ | ||
| 735 | 0x1D, // 0 LCtrl | ||
| 736 | 0x2A, // 1 LShift | ||
| 737 | 0x38, // 2 LAlt | ||
| 738 | 0xE05B, // 3 LGui | ||
| 739 | 0xE01D, // 4 RCtrl | ||
| 740 | 0x36, // 5 RShift | ||
| 741 | 0xE038, // 6 RAlt | ||
| 742 | 0xE05C, // 7 RGui | ||
| 743 | ]; | ||
| 744 | |||
| 745 | fn kbdq_push(b: u8) { | ||
| 746 | unsafe { | ||
| 747 | let h = KBDQ_HEAD.load(Ordering::Relaxed) as usize; | ||
| 748 | let l = KBDQ_LEN.load(Ordering::Relaxed) as usize; | ||
| 749 | if l >= KBDQ_SIZE { | ||
| 750 | return; | ||
| 751 | } | ||
| 752 | KBDQ[(h + l) % KBDQ_SIZE] = b; | ||
| 753 | KBDQ_LEN.store((l + 1) as u32, Ordering::Relaxed); | ||
| 754 | } | ||
| 755 | } | ||
| 756 | fn kbdq_pop() -> Option<u8> { | ||
| 757 | unsafe { | ||
| 758 | let l = KBDQ_LEN.load(Ordering::Relaxed) as usize; | ||
| 759 | if l == 0 { | ||
| 760 | return None; | ||
| 761 | } | ||
| 762 | let h = KBDQ_HEAD.load(Ordering::Relaxed) as usize; | ||
| 763 | let b = KBDQ[h]; | ||
| 764 | KBDQ_HEAD.store(((h + 1) % KBDQ_SIZE) as u32, Ordering::Relaxed); | ||
| 765 | KBDQ_LEN.store((l - 1) as u32, Ordering::Relaxed); | ||
| 766 | Some(b) | ||
| 767 | } | ||
| 768 | } | ||
| 769 | |||
| 770 | /// Push the make (press) or break (release) scancode sequence for a | ||
| 771 | /// scancode-map entry (0 = unmapped, 0x00XX = plain, 0xE0XX = E0-prefixed). | ||
| 772 | fn push_scancode(scan: u16, press: bool) { | ||
| 773 | if scan == 0 { | ||
| 774 | return; | ||
| 775 | } | ||
| 776 | if scan & 0xFF00 == 0xE000 { | ||
| 777 | kbdq_push(0xE0); | ||
| 778 | let code = (scan & 0xFF) as u8; | ||
| 779 | kbdq_push(if press { code } else { code | 0x80 }); | ||
| 780 | } else { | ||
| 781 | let code = scan as u8; | ||
| 782 | kbdq_push(if press { code } else { code | 0x80 }); | ||
| 783 | } | ||
| 784 | } | ||
| 785 | |||
| 786 | /// Diff the completed HID report against the previous one and queue the | ||
| 787 | /// scancode transitions (make/break) for the shared PS/2 decoder. | ||
| 788 | unsafe fn diff_report(report: &[u8]) { | ||
| 789 | unsafe { | ||
| 790 | let prev = &mut *core::ptr::addr_of_mut!(KBD_PREV); | ||
| 791 | let new_mods = report[0]; | ||
| 792 | let old_mods = prev[0]; | ||
| 793 | for bit in 0..8 { | ||
| 794 | let new_set = new_mods & (1 << bit) != 0; | ||
| 795 | let old_set = old_mods & (1 << bit) != 0; | ||
| 796 | if new_set != old_set { | ||
| 797 | push_scancode(MOD_TO_SCAN[bit], new_set); | ||
| 798 | } | ||
| 799 | } | ||
| 800 | let new_keys = &report[2..8]; | ||
| 801 | let old_keys = &prev[2..8]; | ||
| 802 | // Releases first (old keys not in new set). | ||
| 803 | for &k in old_keys { | ||
| 804 | if k == 0 || new_keys.contains(&k) { | ||
| 805 | continue; | ||
| 806 | } | ||
| 807 | if k as usize >= HID_TO_SCAN.len() { | ||
| 808 | continue; | ||
| 809 | } | ||
| 810 | push_scancode(HID_TO_SCAN[k as usize], false); | ||
| 811 | } | ||
| 812 | // Then presses (new keys not in the old set). | ||
| 813 | for &k in new_keys { | ||
| 814 | if k == 0 || old_keys.contains(&k) { | ||
| 815 | continue; | ||
| 816 | } | ||
| 817 | if k as usize >= HID_TO_SCAN.len() { | ||
| 818 | continue; | ||
| 819 | } | ||
| 820 | push_scancode(HID_TO_SCAN[k as usize], true); | ||
| 821 | } | ||
| 822 | prev.copy_from_slice(report); | ||
| 823 | } | ||
| 824 | } | ||
| 825 | |||
| 826 | /// Re-arm a completed interrupt-IN TD (one-outstanding report pattern). | ||
| 827 | unsafe fn rearm_td(qho: usize, tdo: usize, toggle: u32, ls: bool, mps: usize, explen: u32) { | ||
| 828 | unsafe { | ||
| 829 | let qh = td_addr(qho); | ||
| 830 | let td = td_addr(tdo); | ||
| 831 | let t = rd(td.add(2)); | ||
| 832 | let dev = (t >> TOK_DEV_SHIFT) & 0x7F; | ||
| 833 | let ep = (t >> TOK_EP_SHIFT) & 0xF; | ||
| 834 | wr( | ||
| 835 | td.add(1), | ||
| 836 | TD_ACTIVE | TD_ERR | TD_IOC | if ls { TD_LS } else { 0 }, | ||
| 837 | ); | ||
| 838 | wr( | ||
| 839 | td.add(2), | ||
| 840 | PID_IN | ||
| 841 | | (dev << TOK_DEV_SHIFT) | ||
| 842 | | (ep << TOK_EP_SHIFT) | ||
| 843 | | (toggle << 19) | ||
| 844 | | (explen << TOK_EXPLEN_SHIFT), | ||
| 845 | ); | ||
| 846 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 847 | wr(qh.add(1), td as u32); | ||
| 848 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 849 | let _ = mps; | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | // --------------------------------------------------------------------------- | ||
| 854 | // Public interface | ||
| 855 | // --------------------------------------------------------------------------- | ||
| 856 | |||
| 857 | pub fn controller_up() -> bool { | ||
| 858 | CTRL_UP.load(Ordering::Relaxed) | ||
| 859 | } | ||
| 860 | pub fn mouse_present() -> bool { | ||
| 861 | MOUSE_PRES.load(Ordering::Relaxed) | ||
| 862 | } | ||
| 863 | |||
| 864 | /// Find the first UHCI companion controller (class 0x0C, subclass 0x03, | ||
| 865 | /// prog_if 0x00). | ||
| 866 | fn find_uhci() -> Option<(u8, u8, u8)> { | ||
| 867 | for d in crate::pci::probe() { | ||
| 868 | if d.class == 0x0C && d.subclass == 0x03 && d.prog_if == 0x00 { | ||
| 869 | return Some((d.bus, d.device, d.func)); | ||
| 870 | } | ||
| 871 | } | ||
| 872 | None | ||
| 873 | } | ||
| 874 | |||
| 875 | /// Bring up the first UHCI controller: claim it, reset it, run the frame | ||
| 876 | /// list, and enumerate any connected boot HID devices. | ||
| 877 | pub fn init() -> bool { | ||
| 878 | let Some((b, d, f)) = find_uhci() else { | ||
| 879 | crate::serial_println!("uhci: no UHCI controller"); | ||
| 880 | return false; | ||
| 881 | }; | ||
| 882 | crate::serial_println!("uhci: USB1.1 UHCI at {:02x}:{:02x}.{}", b, d, f); | ||
| 883 | |||
| 884 | unsafe { | ||
| 885 | // I/O space + bus master (polled: no INTx assertion needed, but | ||
| 886 | // clear it anyway so a stray legacy IRQ can never storm). | ||
| 887 | let cmd = crate::pci::config_read(b, d, f, 0x04); | ||
| 888 | crate::pci::config_write( | ||
| 889 | b, | ||
| 890 | d, | ||
| 891 | f, | ||
| 892 | 0x04, | ||
| 893 | (cmd & 0xFFFF_0000) | (cmd & 0xFFFF) | 0x5 | 0x400, | ||
| 894 | ); | ||
| 895 | let mut base: u32 = 0; | ||
| 896 | // BAR0 first (Intel ICH), then BAR4 (PIIX3/PIIX4-style). Take the | ||
| 897 | // first nonzero I/O BAR. | ||
| 898 | for bar_idx in [0u8, 4] { | ||
| 899 | let bar = crate::pci::config_read(b, d, f, 0x10 + bar_idx * 4); | ||
| 900 | let cand = bar & !0x3; | ||
| 901 | if cand != 0 { | ||
| 902 | base = cand; | ||
| 903 | break; | ||
| 904 | } | ||
| 905 | } | ||
| 906 | if base == 0 { | ||
| 907 | crate::serial_println!("uhci: no I/O BAR"); | ||
| 908 | return false; | ||
| 909 | } | ||
| 910 | IO_BASE.store(base, Ordering::Relaxed); | ||
| 911 | |||
| 912 | // Controller reset: stop, then HCRESET (UHCI clears HCRESET itself | ||
| 913 | // within microseconds). | ||
| 914 | outw(io(), 0); | ||
| 915 | outw(io() + USBCMD, CMD_HCRESET); | ||
| 916 | ms_spin(5); | ||
| 917 | let mut t = 0; | ||
| 918 | while t < 1_000_000 && inw(io() + USBCMD) & CMD_HCRESET != 0 { | ||
| 919 | t += 1; | ||
| 920 | core::hint::spin_loop(); | ||
| 921 | } | ||
| 922 | outw(io() + USBINTR, 0); | ||
| 923 | |||
| 924 | // DMA: 4 KiB frame list (1024 entries) + struct page. | ||
| 925 | FRAMELIST = dma_alloc(4096) as *mut u32; | ||
| 926 | P = dma_alloc(4096) as *mut u32; | ||
| 927 | if FRAMELIST.is_null() || P.is_null() { | ||
| 928 | crate::serial_println!("uhci: DMA alloc failed"); | ||
| 929 | return false; | ||
| 930 | } | ||
| 931 | |||
| 932 | // Wire the schedule: every frame entry -> CTRL_QH -> KBD_QH -> | ||
| 933 | // MOUSE_QH -> terminate. Each QH's element is set when its device | ||
| 934 | // arms; the control QH element is set per transfer. | ||
| 935 | let fl = FRAMELIST; | ||
| 936 | for i in 0..1024 { | ||
| 937 | wr(fl.add(i), (td_addr(OFF_CTRL_QH) as u32) | PTR_QH); | ||
| 938 | } | ||
| 939 | wr( | ||
| 940 | td_addr(OFF_CTRL_QH).add(0), | ||
| 941 | (td_addr(OFF_KBD_QH) as u32) | PTR_QH, | ||
| 942 | ); | ||
| 943 | wr(td_addr(OFF_CTRL_QH).add(1), PTR_TERM); | ||
| 944 | wr( | ||
| 945 | td_addr(OFF_KBD_QH).add(0), | ||
| 946 | (td_addr(OFF_MOUSE_QH) as u32) | PTR_QH, | ||
| 947 | ); | ||
| 948 | wr(td_addr(OFF_KBD_QH).add(1), PTR_TERM); | ||
| 949 | wr(td_addr(OFF_MOUSE_QH).add(0), PTR_TERM); | ||
| 950 | wr(td_addr(OFF_MOUSE_QH).add(1), PTR_TERM); | ||
| 951 | core::sync::atomic::fence(Ordering::SeqCst); | ||
| 952 | |||
| 953 | // Frame list base, configure, run. | ||
| 954 | outl(io() + FRBASEADD, fl as u32); | ||
| 955 | outw(io() + SOFMOD, 64); | ||
| 956 | outw(io() + FRNUM, 0); | ||
| 957 | outw(io() + USBCMD, CMD_CF); | ||
| 958 | outw(io() + USBCMD, CMD_CF | CMD_RUN); | ||
| 959 | ms_spin(2); | ||
| 960 | if inw(io() + USBSTS) & STS_HCHALTED != 0 { | ||
| 961 | crate::serial_println!("uhci: controller failed to run"); | ||
| 962 | return false; | ||
| 963 | } | ||
| 964 | |||
| 965 | // Port count: UHCI has 2 ports at 0x10/0x12; a few have more. | ||
| 966 | let mut np = 0; | ||
| 967 | while np < 4 { | ||
| 968 | let psc = inw(prtsc(np)); | ||
| 969 | if psc == 0xFFFF || psc == 0xff7f { | ||
| 970 | break; | ||
| 971 | } | ||
| 972 | np += 1; | ||
| 973 | } | ||
| 974 | N_PORTS.store(np as u32, Ordering::Relaxed); | ||
| 975 | CTRL_UP.store(true, Ordering::Relaxed); | ||
| 976 | crate::serial_println!("uhci: controller up (io={:#x} ports={})", base, np); | ||
| 977 | |||
| 978 | // Enumerate a boot keyboard first (input priority), then a mouse. | ||
| 979 | crate::serial_println!( | ||
| 980 | "uhci: dbg P={:#x} FL={:#x} ctrlqh={:#x} el={:#x} fl0={:#x} setup_td={:#x} setup_link={:#x}", | ||
| 981 | P as usize, | ||
| 982 | FRAMELIST as usize, | ||
| 983 | td_addr(OFF_CTRL_QH) as usize, | ||
| 984 | rd(td_addr(OFF_CTRL_QH).add(1)), | ||
| 985 | rd(FRAMELIST.add(0)), | ||
| 986 | td_addr(OFF_SETUP_TD) as usize, | ||
| 987 | rd(td_addr(OFF_SETUP_TD).add(0)) | ||
| 988 | ); | ||
| 989 | let _ = arm_device(1); | ||
| 990 | let _ = arm_device(2); | ||
| 991 | true | ||
| 992 | } | ||
| 993 | } | ||
| 994 | |||
| 995 | /// Non-blocking keyboard poll: drains the scancode FIFO (fed by completed | ||
| 996 | /// HID reports) through the shared PS/2 decoder. Lazy re-enumeration is | ||
| 997 | /// throttled to one attempt per second. | ||
| 998 | pub fn try_poll_key() -> Option<crate::ps2::KeyEvent> { | ||
| 999 | unsafe { | ||
| 1000 | // Fast path: pending scancodes first. | ||
| 1001 | if let Some(b) = kbdq_pop() { | ||
| 1002 | return crate::ps2::feed_scancode(b); | ||
| 1003 | } | ||
| 1004 | if KBD_ARMED.load(Ordering::Relaxed) { | ||
| 1005 | let td = td_addr(OFF_KBD_TD); | ||
| 1006 | let c = rd(td.add(1)); | ||
| 1007 | if c & TD_ACTIVE == 0 { | ||
| 1008 | if c & (TD_STALLED | TD_BABBLE | TD_TIMEO) != 0 { | ||
| 1009 | // Transfer error: drop the arm; bounded re-enum follows. | ||
| 1010 | KBD_ARMED.store(false, Ordering::Relaxed); | ||
| 1011 | return None; | ||
| 1012 | } | ||
| 1013 | // Completed: diff the report, re-arm, then drain the FIFO. | ||
| 1014 | let rep = buf_addr(OFF_KBD_REPORT); | ||
| 1015 | let mut report = [0u8; 8]; | ||
| 1016 | for (i, b) in report.iter_mut().enumerate() { | ||
| 1017 | *b = core::ptr::read_volatile(rep.add(i)); | ||
| 1018 | } | ||
| 1019 | diff_report(&report); | ||
| 1020 | let tog = KBD_TOGGLE.load(Ordering::Relaxed) ^ 1; | ||
| 1021 | KBD_TOGGLE.store(tog, Ordering::Relaxed); | ||
| 1022 | rearm_td( | ||
| 1023 | OFF_KBD_QH, | ||
| 1024 | OFF_KBD_TD, | ||
| 1025 | tog, | ||
| 1026 | KBD_LS.load(Ordering::Relaxed), | ||
| 1027 | KBD_MPS.load(Ordering::Relaxed) as usize, | ||
| 1028 | 8, | ||
| 1029 | ); | ||
| 1030 | return kbdq_pop().and_then(crate::ps2::feed_scancode); | ||
| 1031 | } | ||
| 1032 | return None; | ||
| 1033 | } | ||
| 1034 | // Not armed: bring the keyboard up lazily, never hot-loop. | ||
| 1035 | if CTRL_UP.load(Ordering::Relaxed) { | ||
| 1036 | let now = crate::interrupts::uptime_ms() as u32; | ||
| 1037 | if now.wrapping_sub(LAST_KBD_ENUM.load(Ordering::Relaxed)) >= 1000 { | ||
| 1038 | LAST_KBD_ENUM.store(now, Ordering::Relaxed); | ||
| 1039 | let _ = arm_device(1); | ||
| 1040 | } | ||
| 1041 | } | ||
| 1042 | kbdq_pop().and_then(crate::ps2::feed_scancode) | ||
| 1043 | } | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | /// Poll for a mouse report (mirrors ehci::poll_mouse): hot path reads the | ||
| 1047 | /// armed TD's ACTIVE bit in DMA memory; completion decodes the report and | ||
| 1048 | /// re-arms; a stalled/TD-error transfer drops the arm for a bounded | ||
| 1049 | /// re-enumeration. | ||
| 1050 | pub fn poll_mouse() -> Option<MouseEvent> { | ||
| 1051 | unsafe { | ||
| 1052 | if MOUSE_ARMED.load(Ordering::Relaxed) { | ||
| 1053 | let td = td_addr(OFF_MOUSE_TD); | ||
| 1054 | let c = rd(td.add(1)); | ||
| 1055 | if c & TD_ACTIVE == 0 { | ||
| 1056 | if c & (TD_STALLED | TD_BABBLE | TD_TIMEO) != 0 { | ||
| 1057 | MOUSE_ARMED.store(false, Ordering::Relaxed); | ||
| 1058 | return None; | ||
| 1059 | } | ||
| 1060 | let hb = buf_addr(OFF_MOUSE_REPORT); | ||
| 1061 | let b0 = core::ptr::read_volatile(hb); | ||
| 1062 | let b1 = core::ptr::read_volatile(hb.add(1)); | ||
| 1063 | let b2 = core::ptr::read_volatile(hb.add(2)); | ||
| 1064 | let ev = Some(MouseEvent { | ||
| 1065 | dx: (b1 as i8) as i16, | ||
| 1066 | dy: (b2 as i8) as i16, | ||
| 1067 | left: b0 & 0x01 != 0, | ||
| 1068 | right: b0 & 0x02 != 0, | ||
| 1069 | middle: b0 & 0x04 != 0, | ||
| 1070 | }); | ||
| 1071 | let tog = MOUSE_TOGGLE.load(Ordering::Relaxed) ^ 1; | ||
| 1072 | MOUSE_TOGGLE.store(tog, Ordering::Relaxed); | ||
| 1073 | rearm_td( | ||
| 1074 | OFF_MOUSE_QH, | ||
| 1075 | OFF_MOUSE_TD, | ||
| 1076 | tog, | ||
| 1077 | MOUSE_LS.load(Ordering::Relaxed), | ||
| 1078 | MOUSE_MPS.load(Ordering::Relaxed) as usize, | ||
| 1079 | 3, | ||
| 1080 | ); | ||
| 1081 | return ev; | ||
| 1082 | } | ||
| 1083 | return None; | ||
| 1084 | } | ||
| 1085 | if CTRL_UP.load(Ordering::Relaxed) { | ||
| 1086 | let now = crate::interrupts::uptime_ms() as u32; | ||
| 1087 | if now.wrapping_sub(LAST_MOUSE_ENUM.load(Ordering::Relaxed)) >= 1000 { | ||
| 1088 | LAST_MOUSE_ENUM.store(now, Ordering::Relaxed); | ||
| 1089 | let _ = arm_device(2); | ||
| 1090 | } | ||
| 1091 | } | ||
| 1092 | None | ||
| 1093 | } | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | /// One-line status for the shell `usb` command. | ||
| 1097 | pub fn diagnose() -> alloc::string::String { | ||
| 1098 | let mut s = alloc::string::String::new(); | ||
| 1099 | s.push_str(&alloc::format!( | ||
| 1100 | "uhci io={:#x} ports={} kbd={} mouse={} kbd_dev={} mouse_dev={}\n", | ||
| 1101 | IO_BASE.load(Ordering::Relaxed), | ||
| 1102 | N_PORTS.load(Ordering::Relaxed), | ||
| 1103 | if KBD_ARMED.load(Ordering::Relaxed) { | ||
| 1104 | "armed" | ||
| 1105 | } else { | ||
| 1106 | "-" | ||
| 1107 | }, | ||
| 1108 | if MOUSE_ARMED.load(Ordering::Relaxed) { | ||
| 1109 | "armed" | ||
| 1110 | } else { | ||
| 1111 | "-" | ||
| 1112 | }, | ||
| 1113 | DEV_KBD.load(Ordering::Relaxed), | ||
| 1114 | DEV_MOUSE.load(Ordering::Relaxed) | ||
| 1115 | )); | ||
| 1116 | let np = N_PORTS.load(Ordering::Relaxed) as usize; | ||
| 1117 | for p in 0..np.min(4) { | ||
| 1118 | let v = unsafe { inw(prtsc(p)) }; | ||
| 1119 | s.push_str(&alloc::format!( | ||
| 1120 | " uport{}: {}{}{}{:#x}\n", | ||
| 1121 | p, | ||
| 1122 | if v & PSC_CCS != 0 { "conn " } else { "none " }, | ||
| 1123 | if v & PSC_PE != 0 { "en " } else { "" }, | ||
| 1124 | if v & PSC_LSDA != 0 { "low " } else { "" }, | ||
| 1125 | v | ||
| 1126 | )); | ||
| 1127 | } | ||
| 1128 | s | ||
| 1129 | } | ||
