summaryrefslogtreecommitdiffstats
path: root/rsnix/src/uhci.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-17 14:53:52 +0200
committersalmonik <salmonik@tuta.io>2026-08-17 14:53:52 +0200
commitbe868a43185826b02bf6d091019bb779678525bb (patch)
tree586988c15f1d700b1621c7f0208584b30878a56c /rsnix/src/uhci.rs
parentv0.5.10: change em-dashes to dashes (diff)
downloadrs-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/uhci.rs')
-rw-r--r--rsnix/src/uhci.rs1129
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
31use crate::ps2::MouseEvent;
32use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
33
34// ---- Registers (I/O space; 16-bit unless noted) ----
35const USBCMD: u16 = 0x00;
36const USBSTS: u16 = 0x02;
37const USBINTR: u16 = 0x04;
38const FRNUM: u16 = 0x06;
39const FRBASEADD: u16 = 0x08; // 32-bit
40const SOFMOD: u16 = 0x0C;
41const PORTSC_BASE: u16 = 0x10; // PORTSC1, PORTSC2 (16-bit each)
42
43const CMD_RUN: u16 = 1 << 0;
44const CMD_HCRESET: u16 = 1 << 1;
45const CMD_CF: u16 = 1 << 6;
46
47const STS_HCHALTED: u16 = 1 << 5;
48
49const PSC_CCS: u16 = 1 << 0; // current connect status
50const PSC_CSC: u16 = 1 << 1; // connect change (write 1 to clear)
51const PSC_PE: u16 = 1 << 2; // port enabled
52const PSC_PEDC: u16 = 1 << 3; // enable change (write 1 to clear)
53const PSC_LSDA: u16 = 1 << 8; // low-speed device attached
54const PSC_R: u16 = 1 << 9; // reset
55const PSC_SUSP: u16 = 1 << 12; // suspend
56
57// ---- TD / QH fields ----
58const TD_ACTIVE: u32 = 1 << 23;
59const TD_IOC: u32 = 1 << 24;
60const TD_LS: u32 = 1 << 26;
61const TD_ERR: u32 = 3 << 27; // C_ERR = 3 retries
62const TD_STALLED: u32 = 1 << 22;
63const TD_BABBLE: u32 = 1 << 20;
64const TD_TIMEO: u32 = 1 << 18;
65const TD_ACTLEN: u32 = 0x7FF; // actual length, n-1
66
67const TOK_DEV_SHIFT: u32 = 8;
68const TOK_EP_SHIFT: u32 = 15;
69const TOK_TOGGLE: u32 = 1 << 19;
70const TOK_EXPLEN_SHIFT: u32 = 21;
71
72const PID_SETUP: u32 = 0x2D;
73const PID_IN: u32 = 0x69;
74const PID_OUT: u32 = 0xE1;
75
76const PTR_TERM: u32 = 0x1;
77const PTR_QH: u32 = 0x2;
78
79// ---- HID boot requests ----
80const RT_CLASS_IFACE: u8 = 0x21;
81const REQ_SET_IDLE: u8 = 0x0A;
82const REQ_SET_PROTOCOL: u8 = 0x0B;
83
84// ---- Static state ----
85static IO_BASE: AtomicU32 = AtomicU32::new(0);
86static N_PORTS: AtomicU32 = AtomicU32::new(0);
87static CTRL_UP: AtomicBool = AtomicBool::new(false);
88static MOUSE_PRES: AtomicBool = AtomicBool::new(false);
89static KBD_ARMED: AtomicBool = AtomicBool::new(false);
90static 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.
93static KBD_PORT: AtomicU32 = AtomicU32::new(u32::MAX);
94static MOUSE_PORT: AtomicU32 = AtomicU32::new(u32::MAX);
95static DEV_KBD: AtomicU32 = AtomicU32::new(0);
96static DEV_MOUSE: AtomicU32 = AtomicU32::new(0);
97static KBD_EP: AtomicU32 = AtomicU32::new(1);
98static KBD_MPS: AtomicU32 = AtomicU32::new(8);
99static MOUSE_EP: AtomicU32 = AtomicU32::new(1);
100static MOUSE_MPS: AtomicU32 = AtomicU32::new(4);
101// Low-speed flag per device (TD_LS needed for 1.5 Mb/s).
102static KBD_LS: AtomicBool = AtomicBool::new(false);
103static MOUSE_LS: AtomicBool = AtomicBool::new(false);
104// Next USB device address to assign (1, 2, ...).
105static DEV_SEQ: AtomicU32 = AtomicU32::new(1);
106// Throttles for the lazy re-enumeration paths (ms).
107static LAST_KBD_ENUM: AtomicU32 = AtomicU32::new(0);
108static 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.
112static CTRL_LOCK: AtomicBool = AtomicBool::new(false);
113// Toggle bit the device expects next (alternates after each report).
114static KBD_TOGGLE: AtomicU32 = AtomicU32::new(0);
115static MOUSE_TOGGLE: AtomicU32 = AtomicU32::new(0);
116
117// ---- DMA: frame list + one struct page ----
118static mut FRAMELIST: *mut u32 = core::ptr::null_mut();
119static mut P: *mut u32 = core::ptr::null_mut(); // QHs, TDs, buffers
120
121// Offsets within the struct page (16-byte-aligned QHs/TDs).
122const OFF_CTRL_QH: usize = 0x000;
123const OFF_KBD_QH: usize = 0x010;
124const OFF_MOUSE_QH: usize = 0x020;
125const OFF_SETUP_TD: usize = 0x030;
126const OFF_DATA_TD: usize = 0x040;
127const OFF_STAT_TD: usize = 0x050;
128const OFF_KBD_TD: usize = 0x060;
129const OFF_MOUSE_TD: usize = 0x070;
130const OFF_REQ: usize = 0x100; // 8-byte setup packet
131const OFF_DESC: usize = 0x200; // device descriptor (64 B)
132const OFF_CFG: usize = 0x300; // config descriptor (256 B)
133const OFF_KBD_REPORT: usize = 0x500; // 8 B
134const OFF_MOUSE_REPORT: usize = 0x520; // 8 B
135
136// Keyboard: previous report + pending scancode FIFO (fed to ps2 decoder).
137static mut KBD_PREV: [u8; 8] = [0; 8];
138const KBDQ_SIZE: usize = 64;
139static mut KBDQ: [u8; KBDQ_SIZE] = [0; KBDQ_SIZE];
140static KBDQ_HEAD: AtomicU32 = AtomicU32::new(0);
141static KBDQ_LEN: AtomicU32 = AtomicU32::new(0);
142
143// ---- port I/O ----
144#[inline]
145unsafe 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]
153unsafe 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]
159unsafe 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
165fn io() -> u16 {
166 IO_BASE.load(Ordering::Relaxed) as u16
167}
168/// Absolute I/O port of root port `p`'s PORTSC register.
169fn prtsc(p: usize) -> u16 {
170 io() + PORTSC_BASE + (p as u16) * 2
171}
172
173// ---- DMA helpers (mirror ehci.rs) ----
174unsafe 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
186unsafe fn rd(addr: *const u32) -> u32 {
187 unsafe { addr.read_volatile() }
188}
189unsafe fn wr(addr: *mut u32, v: u32) {
190 unsafe { addr.write_volatile(v) }
191}
192
193fn td_addr(o: usize) -> *mut u32 {
194 unsafe { P.add(o / 4) }
195}
196fn buf_addr(o: usize) -> *mut u8 {
197 unsafe { P.add(o / 4) as *mut u8 }
198}
199
200#[inline]
201fn 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).
207fn 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.
215fn 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
232fn 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}
241fn 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.
250unsafe 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.
341unsafe 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
370struct 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.
379fn 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.
435unsafe 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.
524unsafe 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.
591unsafe 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).
626static 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).
734static 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
745fn 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}
756fn 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).
772fn 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.
788unsafe 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).
827unsafe 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
857pub fn controller_up() -> bool {
858 CTRL_UP.load(Ordering::Relaxed)
859}
860pub 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).
866fn 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.
877pub 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.
998pub 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.
1050pub 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.
1097pub 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}