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/usb.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 'rsnix/src/usb.rs')
| -rw-r--r-- | rsnix/src/usb.rs | 147 |
1 files changed, 144 insertions, 3 deletions
diff --git a/rsnix/src/usb.rs b/rsnix/src/usb.rs index 71df781..3ed3a0c 100644 --- a/rsnix/src/usb.rs +++ b/rsnix/src/usb.rs | |||
| @@ -229,6 +229,15 @@ unsafe fn wr64(a: usize, v: u64) { | |||
| 229 | fn op(off: usize) -> usize { | 229 | fn op(off: usize) -> usize { |
| 230 | unsafe { OPB + off } | 230 | unsafe { OPB + off } |
| 231 | } | 231 | } |
| 232 | |||
| 233 | /// 16-bit I/O read (UHCI port status/control registers live in I/O space). | ||
| 234 | unsafe fn io_iow16(port: u16) -> u16 { | ||
| 235 | let v: u16; | ||
| 236 | unsafe { | ||
| 237 | core::arch::asm!("in ax, dx", out("ax") v, in("dx") port, options(nomem, nostack, preserves_flags)); | ||
| 238 | } | ||
| 239 | v | ||
| 240 | } | ||
| 232 | fn db(slot: u32) -> usize { | 241 | fn db(slot: u32) -> usize { |
| 233 | unsafe { DBB }.wrapping_add(slot as usize * 4) | 242 | unsafe { DBB }.wrapping_add(slot as usize * 4) |
| 234 | } | 243 | } |
| @@ -727,7 +736,13 @@ pub fn init() -> bool { | |||
| 727 | dev, | 736 | dev, |
| 728 | func, | 737 | func, |
| 729 | 0x04, | 738 | 0x04, |
| 730 | (cmd & 0xFFFF0000) | ((cmd & 0xFFFF) | 0x6), | 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), | ||
| 731 | ); | 746 | ); |
| 732 | let bar = crate::pci::bar64(b, dev, func, 0); | 747 | let bar = crate::pci::bar64(b, dev, func, 0); |
| 733 | let base = bar.unwrap() as usize; | 748 | let base = bar.unwrap() as usize; |
| @@ -745,6 +760,15 @@ pub fn init() -> bool { | |||
| 745 | MAXPORTS = (hcs1 >> 24) & 0xff; // MaxPorts is bits 31:24 (bits 23:16 are MaxIntrs) | 760 | MAXPORTS = (hcs1 >> 24) & 0xff; // MaxPorts is bits 31:24 (bits 23:16 are MaxIntrs) |
| 746 | let _ = hcc; | 761 | let _ = hcc; |
| 747 | CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 }; | 762 | CTRL_CS = if hcc & (1 << 2) != 0 { 64 } else { 32 }; |
| 763 | |||
| 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) { | ||
| 767 | 0 => crate::serial_println!("usb: xHCI USB legacy: already released (no claim needed)"), | ||
| 768 | 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)"), | ||
| 770 | _ => crate::serial_println!("usb: xHCI USB legacy: no capability found"), | ||
| 771 | } | ||
| 748 | } | 772 | } |
| 749 | 773 | ||
| 750 | // Reset the controller (stop, then reset). Wait for reset completion | 774 | // Reset the controller (stop, then reset). Wait for reset completion |
| @@ -802,8 +826,10 @@ pub fn init() -> bool { | |||
| 802 | wr(rt(RT_ERSTSZ), 1); | 826 | wr(rt(RT_ERSTSZ), 1); |
| 803 | wr64(rt(RT_ERSTBA), ERST as u64); | 827 | wr64(rt(RT_ERSTBA), ERST as u64); |
| 804 | wr64(rt(RT_ERDP), EV.raw as u64); | 828 | wr64(rt(RT_ERDP), EV.raw as u64); |
| 805 | // Interrupter 0: clear pending, enable. | 829 | // Interrupter 0: keep the interrupt DISABLED (IE=0, clear any |
| 806 | wr(rt(RT_IMAN), rd(rt(RT_IMAN)) | 0x2); | 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); | ||
| 807 | wr(rt(RT_IMOD), 0); | 833 | wr(rt(RT_IMOD), 0); |
| 808 | // Run. | 834 | // Run. |
| 809 | wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN); | 835 | wr(op(OP_USBCMD), rd(op(OP_USBCMD)) | CMD_RUN); |
| @@ -973,5 +999,120 @@ pub fn diagnose() -> alloc::string::String { | |||
| 973 | unsafe { QUEUED } | 999 | unsafe { QUEUED } |
| 974 | )); | 1000 | )); |
| 975 | } | 1001 | } |
| 1002 | |||
| 1003 | // Cross-controller attach scan: report which PCI USB controller(s) | ||
| 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"); | ||
| 1007 | unsafe { | ||
| 1008 | for d in crate::pci::probe() { | ||
| 1009 | if d.class != 0x0C || d.subclass != 0x03 { | ||
| 1010 | 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 | } | ||
| 1054 | 0x20 => { | ||
| 1055 | // EHCI: MMIO BAR. CAPLENGTH byte0, HcSParams (nports) @0x04. | ||
| 1056 | let cl = rd(base as usize) & 0xff; | ||
| 1057 | let sp = rd(base as usize + 0x04); | ||
| 1058 | let hc = rd(base as usize + 0x08); | ||
| 1059 | let np = (sp & 0xf) as usize; | ||
| 1060 | let opb = base as usize + cl as usize; | ||
| 1061 | let cmd = crate::pci::config_read(d.bus, d.device, d.func, 0x04); | ||
| 1062 | let eccp = crate::pci::config_read(d.bus, d.device, d.func, 0x34); | ||
| 1063 | s.push_str(&alloc::format!( | ||
| 1064 | " ehci nports={} hrs={} hcc={:#x} cmd={:#x} eccp={:#x}\n", | ||
| 1065 | np, sp, hc, cmd, (eccp >> 8) & 0xff | ||
| 1066 | )); | ||
| 1067 | let cfg = rd(opb + 0x40); | ||
| 1068 | s.push_str(&alloc::format!( | ||
| 1069 | " ehci cfgflag={:#x} ", cfg | ||
| 1070 | )); | ||
| 1071 | for p in 0..np.min(16) { | ||
| 1072 | let v = rd(opb + 0x44 + p * 0x04); | ||
| 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 | } | ||
| 1097 | 0x00 => { | ||
| 1098 | // UHCI: I/O BAR (bar64 refuses I/O bars — read raw). | ||
| 1099 | let lo = crate::pci::config_read(d.bus, d.device, d.func, 0x10); | ||
| 1100 | let io = (lo & 0xFFF0) as u16; | ||
| 1101 | if io == 0 { | ||
| 1102 | continue; | ||
| 1103 | } | ||
| 1104 | for p in 0..2 { | ||
| 1105 | let pr = io_iow16(io + 0x10 + (p as u16) * 2); | ||
| 1106 | s.push_str(&alloc::format!( | ||
| 1107 | "{}{:x}:", if pr & 1 != 0 { "*" } else { "" }, | ||
| 1108 | (pr & 0xff) | ||
| 1109 | )); | ||
| 1110 | } | ||
| 1111 | s.push('\n'); | ||
| 1112 | } | ||
| 1113 | _ => {} | ||
| 1114 | } | ||
| 1115 | } | ||
| 1116 | } | ||
| 976 | s | 1117 | s |
| 977 | } | 1118 | } |
