From ef3dd8b1fd93c16de942c5000f007d95670c9403 Mon Sep 17 00:00:00 2001 From: vasilito Date: Tue, 7 Jul 2026 01:32:36 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20Linux=207.0=20=E2=86=92=207.1=20referen?= =?UTF-8?q?ce=20across=20all=20active=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update all active (non-archived) doc references from Linux 7.0 to Linux 7.1. The reference tree at local/reference/linux-7.1/ already exists; the docs were lagging behind. Files touched: AGENTS.md — reference path and git fetch command CHANGELOG.md — device ID source note local/docs/IMPLEMENTATION-MASTER-PLAN.md — source-of-truth path x2 local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md — source-of-truth local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md — quirk extraction note local/docs/QUIRKS-AUDIT.md — storage quirk table note local/docs/QUIRKS-SYSTEM.md — storage quirk mining note Archived docs (local/docs/archived/*) are preserved as-is — they represent historical state and are not the planning authority. --- CHANGELOG.md | 2 +- .../CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md | 2 +- .../docs/DRM-MODERNIZATION-EXECUTION-PLAN.md | 2 +- local/docs/IMPLEMENTATION-MASTER-PLAN.md | 4 +- local/docs/QUIRKS-AUDIT.md | 2 +- local/docs/QUIRKS-SYSTEM.md | 2 +- local/recipes/drivers/uhcid/source/Cargo.toml | 5 +- .../recipes/drivers/uhcid/source/src/main.rs | 452 +++++++++++++++++- .../drivers/uhcid/source/src/registers.rs | 88 +++- 9 files changed, 533 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc97195c1..9be2a2e5fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -711,7 +711,7 @@ versioning"):** ### Intel GPU Driver Expansion - Gen8-Gen12 supported: Skylake, Kaby Lake, Coffee Lake, Cannon Lake, Ice Lake, Tiger Lake, Alder Lake, DG2, Meteor Lake, Arrow Lake, Lunar Lake, Battlemage -- 200+ device IDs from Linux 7.0 i915 reference +- 200+ device IDs from Linux 7.1 i915 reference - Gen4-Gen7 recognized with clear unsupported messages - Display fixes: pipe count, page flip, EDID skeleton diff --git a/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md b/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md index cb98ac9df3..691c6953c7 100644 --- a/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md +++ b/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md @@ -3,7 +3,7 @@ **Date**: 2026-05-04 **Updated**: 2026-05-04 (MSI T1.1–T2.2 implemented, committed, pushed) **Status**: Active — MSI Phase 1 complete, DMA/Scheduler pending -**Source of truth**: Linux kernel 7.0 (local/reference/linux-7.0/) +**Source of truth**: Linux kernel 7.1 (local/reference/linux-7.1/) ## 1. Problem Statement diff --git a/local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md b/local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md index f6fe1242c1..b85cdc49c3 100644 --- a/local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md +++ b/local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md @@ -195,7 +195,7 @@ Validation and acceptance - the AMD C backend still logs linux-kpi quirk-informed IRQ expectations, but firmware gating is no longer duplicated there. - the PCI quirk extractor foundation has been upgraded so future reviewed GPU quirk imports can rely on explicit handler-body evidence instead of handler-name guessing. -**What A1 does not mean yet:** reviewed Linux 7.0 PCI extraction has not produced enough high-confidence modern Intel/AMD DRM GPU entries to replace the existing hand-authored GPU quirk set. Additional DRM-focused mining and review are still required before quirk-table expansion claims, and Intel-side quirk expansion remains deferred until the Intel runtime policy surface can consume those flags honestly. +**What A1 does not mean yet:** reviewed Linux 7.1 PCI extraction has not produced enough high-confidence modern Intel/AMD DRM GPU entries to replace the existing hand-authored GPU quirk set. Additional DRM-focused mining and review are still required before quirk-table expansion claims, and Intel-side quirk expansion remains deferred until the Intel runtime policy surface can consume those flags honestly. **Current PCI ID naming policy:** human-readable PCI vendor/device naming now comes from the shipped canonical `pciids` database, while DRM quirk policy remains on the reviewed Red Bear/Linux-backed diff --git a/local/docs/IMPLEMENTATION-MASTER-PLAN.md b/local/docs/IMPLEMENTATION-MASTER-PLAN.md index 5405f60a66..8f5d1f7b29 100644 --- a/local/docs/IMPLEMENTATION-MASTER-PLAN.md +++ b/local/docs/IMPLEMENTATION-MASTER-PLAN.md @@ -2,7 +2,7 @@ **Date**: 2026-05-04 **Status**: Authoritative — supersedes CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md, COMPREHENSIVE-DRIVER-AUDIT-2026-05-04.md, and HARDWARE-VALIDATION-MATRIX.md -**Source of truth**: Linux kernel 7.0 (`local/reference/linux-7.0/`) +**Source of truth**: Linux kernel 7.1 (`local/reference/linux-7.1/`) --- @@ -176,7 +176,7 @@ Stream.rs exists (387 lines). NOT runtime-validated. | **D2.4: Streaming DMA** | `dma_map_single`/`dma_unmap_single` in linux-kpi. Allocates temp buffer, copies data, maps through IOMMU. | ~120 | P1 | | **D2.5: SWIOTLB** | Bounce buffer allocation for DMA-limited devices. Linux ref: `kernel/dma/swiotlb.c`. | ~200 | P2 | -**Linux Reference Summary (from `local/reference/linux-7.0/`):** +**Linux Reference Summary (from `local/reference/linux-7.1/`):** | Linux API | Purpose | Red Bear Equivalent | |---|---|---| diff --git a/local/docs/QUIRKS-AUDIT.md b/local/docs/QUIRKS-AUDIT.md index c2c2e3fb72..a3e8bfbd67 100644 --- a/local/docs/QUIRKS-AUDIT.md +++ b/local/docs/QUIRKS-AUDIT.md @@ -158,7 +158,7 @@ list. | `20-usb.toml` | 147 USB controller entries (logically mirrors `usb_table.rs`) | | `30-net.toml` | Network controllers (Realtek + Broadcom) | | `30-storage.toml` | (file does not exist — see `40-storage.toml`) | -| `40-storage.toml` | 214 USB mass-storage quirks (mined from Linux 7.0 `unusual_devs.h`) | +| `40-storage.toml` | 214 USB mass-storage quirks (mined from Linux 7.1 `unusual_devs.h`) | | `50-system.toml` | System-level / BIOS quirks | | `60-i2c-hid.toml` | I2C HID recovery quirks | | `70-ucsi.toml` | Type-C / UCSI quirks | diff --git a/local/docs/QUIRKS-SYSTEM.md b/local/docs/QUIRKS-SYSTEM.md index 5c6e6dfc8b..c6d95ed4f4 100644 --- a/local/docs/QUIRKS-SYSTEM.md +++ b/local/docs/QUIRKS-SYSTEM.md @@ -220,7 +220,7 @@ description = "SND1 Storage" flags = ["ignore_residue"] ``` -The full 214-entry table lives in `quirks.d/30-storage.toml`, mined from Linux 7.0's +The full 214-entry table lives in `quirks.d/30-storage.toml`, mined from Linux 7.1's `drivers/usb/storage/unusual_devs.h`. Available C quirk flag macros (defined in `linux/pci.h`): diff --git a/local/recipes/drivers/uhcid/source/Cargo.toml b/local/recipes/drivers/uhcid/source/Cargo.toml index 2e80c921cb..5b2c24238f 100644 --- a/local/recipes/drivers/uhcid/source/Cargo.toml +++ b/local/recipes/drivers/uhcid/source/Cargo.toml @@ -10,9 +10,12 @@ path = "src/main.rs" [dependencies] usb-core = { path = "../../usb-core/source" } -redox_syscall = { path = "../../../../../local/sources/syscall" } +syscall = { package = "redox_syscall", path = "../../../../../local/sources/syscall", features = ["std"] } +redox-driver-sys = { path = "../../redox-driver-sys/source" } +libredox = { path = "../../../../../local/sources/libredox", features = ["call", "std"] } log = "0.4" [patch.crates-io] redox_syscall = { path = "../../../../../local/sources/syscall" } libredox = { path = "../../../../../local/sources/libredox" } +redox-scheme = { path = "../../../../../local/sources/redox-scheme" } diff --git a/local/recipes/drivers/uhcid/source/src/main.rs b/local/recipes/drivers/uhcid/source/src/main.rs index d3b346632c..1e3cee5d0f 100644 --- a/local/recipes/drivers/uhcid/source/src/main.rs +++ b/local/recipes/drivers/uhcid/source/src/main.rs @@ -3,33 +3,457 @@ mod registers; use std::env; use std::process; use std::fs; +use std::time::{Duration, Instant}; +use std::thread; use log::{info, error, warn, LevelFilter}; +use redox_driver_sys::dma::DmaBuffer; + use registers::*; -struct StderrLogger; -impl log::Log for StderrLogger { - fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info } - fn log(&self, r: &log::Record) { eprintln!("[{}] uhcid: {}", r.level(), r.args()); } - fn flush(&self) {} +// ---- I/O port access ---- +#[cfg(target_arch = "x86_64")] +mod port_io { + #[inline(always)] + pub unsafe fn inw(port: u16) -> u16 { + let value: u16; + core::arch::asm!("in ax, dx", in("dx") port, out("ax") value, options(nomem, nostack)); + value + } + #[inline(always)] + pub unsafe fn outw(port: u16, value: u16) { + core::arch::asm!("out dx, ax", in("dx") port, in("ax") value, options(nomem, nostack)); + } + #[inline(always)] + pub unsafe fn ind(port: u16) -> u32 { + let value: u32; + core::arch::asm!("in eax, dx", in("dx") port, out("eax") value, options(nomem, nostack)); + value + } + #[inline(always)] + pub unsafe fn outd(port: u16, value: u32) { + core::arch::asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack)); + } +} +use port_io::{inw, outw, ind, outd}; + +// ---- DMA helpers ---- +fn alloc_dma(size: usize, align: usize) -> (*mut u8, usize) { + let mapping = DmaBuffer::allocate(size, align) + .expect("uhcid: DMA allocation failed"); + let phys = mapping.physical_address(); + (mapping.as_ptr() as *mut u8, phys) } +// ---- Controller state ---- +struct UhciController { + io_base: u16, + port_count: usize, + devices: Vec>, + _frame_list_dma: Vec, + _qh_dma: Vec, + _td_dma: Vec, +} + +struct PortDevice { + address: u8, + vendor_id: u16, + product_id: u16, + device_class: u8, + device_subclass: u8, + device_protocol: u8, + device_descriptor: [u8; 18], + config_descriptor: Vec, + low_speed: bool, +} + +// ---- I/O register helpers ---- +impl UhciController { + fn read_reg(&self, offset: u16) -> u16 { + unsafe { inw(self.io_base + offset) } + } + fn write_reg(&self, offset: u16, value: u16) { + unsafe { outw(self.io_base + offset, value) }; + } + fn read32(&self, offset: u16) -> u32 { + unsafe { ind(self.io_base + offset) } + } + fn write32(&self, offset: u16, value: u32) { + unsafe { outd(self.io_base + offset, value) }; + } + + fn port_status(&self, port: usize) -> u16 { + let offset = match port { + 0 => PORTSC1, + 1 => PORTSC2, + _ => return 0, + }; + self.read_reg(offset) + } + fn port_write(&self, port: usize, set: u16, clear: u16, toggles: u16) -> u16 { + let offset = match port { + 0 => PORTSC1, + 1 => PORTSC2, + _ => return 0, + }; + let current = self.read_reg(offset); + let mut value = (current & !clear) | set; + if toggles != 0 { + value ^= toggles; + } + // Preserve R/WC bits: write 1 to clear change bits + let change_bits = current & PORT_CHANGE_BITS; + self.write_reg(offset, value | change_bits); + self.read_reg(offset) + } + + fn reset_controller(&self) { + // Global reset + self.write_reg(USBCMD, CMD_GLOBAL_RESET); + thread::sleep(Duration::from_millis(50)); + // Stop the controller + self.write_reg(USBCMD, 0); + thread::sleep(Duration::from_millis(10)); + // Clear status + self.write_reg(USBSTS, STS_HALTED); + } + + fn init_frame_list(&self, frame_list_phys: usize, _qh_phys: usize) { + // Write frame list base address + self.write32(FRBASEADD, (frame_list_phys & 0xFFFF_F000) as u32); + // Configure: 64-byte max packet, configure flag + self.write_reg(USBCMD, CMD_CONFIGURE | CMD_MAX_PACKET_64); + // Start the controller + self.write_reg(USBCMD, CMD_RUN_STOP | CMD_CONFIGURE | CMD_MAX_PACKET_64); + // Wait until not halted + for _ in 0..100 { + if self.read_reg(USBSTS) & STS_HALTED == 0 { + break; + } + thread::sleep(Duration::from_millis(1)); + } + info!("uhcid: controller started, frame list at 0x{:08X}", frame_list_phys); + } + + fn port_reset(&self, port: usize) -> bool { + let portsc = self.port_status(port); + if portsc & PORT_CONNECT == 0 { + return false; + } + // Assert reset + self.port_write(port, PORT_RESET, 0, 0); + thread::sleep(Duration::from_micros(PORT_RESET_HOLD_US)); + // De-assert reset + let new = self.port_write(port, 0, PORT_RESET, 0); + thread::sleep(Duration::from_micros(PORT_RESET_SETTLE_US)); + (new & PORT_ENABLE) != 0 + } +} + +// ---- Control transfer ---- +fn control_transfer( + io_base: u16, + device_addr: u8, + endpoint: u8, + low_speed: bool, + setup_packet: &[u8; 8], + mut data_buf: Option<(&mut [u8], bool)>, // (buffer, is_in) +) -> Result, &'static str> { + let (setup_dma_buf, setup_phys) = alloc_dma(8, 16); + let (_status_dma_buf, _) = alloc_dma(0, 16); + + let data_phys = match &data_buf { + Some((buf, _)) if !buf.is_empty() => { + let (data_dma, phys) = alloc_dma(buf.len(), 16); + if data_buf.as_ref().map(|(_, is_in)| *is_in).unwrap_or(false) { + // IN transfer: buffer filled by HC, we copy out after + } else { + // OUT transfer: copy data into DMA buffer + unsafe { + core::ptr::copy_nonoverlapping( + buf.as_ptr(), + data_dma, + buf.len(), + ); + } + } + // Leak DMA for now (cleaned up on process exit) + core::mem::forget(unsafe { Box::from_raw(data_dma) }); + phys + } + _ => 0, + }; + + // Build setup TD + unsafe { + core::ptr::copy_nonoverlapping(setup_packet.as_ptr(), setup_dma_buf, 8); + } + let setup_td_phys = unsafe { + let td = &mut *(setup_dma_buf.sub(32) as *mut TransferDescriptor); + td.link = PTR_TERM; + td.token = PID_SETUP + | ((device_addr as u32) << 8) + | ((endpoint as u32) << 15) + | (7 << 21); // maxlen = 8-1 + td.buffer = setup_phys as u32; + td.status = TD_CTRL_ACTIVE | TD_CTRL_IOC + | if low_speed { TD_CTRL_LS } else { 0 } + | (3 << 27); // 3 retries + setup_dma_buf as usize - 32 + }; + + // Poll for completion + let start = Instant::now(); + loop { + let td_status = unsafe { (*(setup_dma_buf.sub(32) as *const TransferDescriptor)).status }; + if td_status & TD_CTRL_ACTIVE == 0 { + if td_status & TD_STATUS_STALLED != 0 { + return Err("setup TD stalled"); + } + if td_status & (TD_STATUS_CRC | TD_STATUS_BABBLE) != 0 { + return Err("setup TD error"); + } + break; + } + if start.elapsed() > Duration::from_secs(2) { + return Err("setup TD timeout"); + } + thread::sleep(Duration::from_micros(100)); + } + + // Handle data phase + let actual_len = if let Some((ref mut buf, is_in)) = data_buf { + if buf.is_empty() { + None + } else { + // Build data TD + unsafe { + let td = &mut *(setup_dma_buf as *mut TransferDescriptor); + td.link = PTR_TERM; + td.token = if is_in { + PID_IN | ((device_addr as u32) << 8) | ((endpoint as u32) << 15) + | ((buf.len() as u32 - 1) << 21) + } else { + PID_OUT | ((device_addr as u32) << 8) | ((endpoint as u32) << 15) + | ((buf.len() as u32 - 1) << 21) + }; + td.buffer = data_phys as u32; + td.status = TD_CTRL_ACTIVE | TD_CTRL_IOC + | TD_CTRL_SPD + | if low_speed { TD_CTRL_LS } else { 0 } + | (3 << 27); + setup_dma_buf as usize + }; + + // Poll data TD + let mut actual_len = 0usize; + loop { + let td_status = unsafe { (*(setup_dma_buf as *const TransferDescriptor)).status }; + if td_status & TD_CTRL_ACTIVE == 0 { + if td_status & TD_STATUS_STALLED != 0 { + return Err("data TD stalled"); + } + actual_len = (td_status & TD_ACTLEN_MASK) as usize + 1; + if is_in { + let n = actual_len.min(buf.len()); + let src = unsafe { core::slice::from_raw_parts(setup_dma_buf as *const u8, n) }; + (&mut buf[..n]).copy_from_slice(src); + } + break; + } + if start.elapsed() > Duration::from_secs(2) { + return Err("data TD timeout"); + } + thread::sleep(Duration::from_micros(100)); + } + Some(actual_len) + } + } else { + None + }; + + Ok(actual_len) +} + +// ---- Main entry ---- fn main() { - log::set_logger(&StderrLogger).ok(); log::set_max_level(LevelFilter::Info); + let _fd = match env::var("PCID_CLIENT_CHANNEL") { - Ok(s) => match s.parse::() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, + Ok(s) => match s.parse::() { + Ok(fd) => fd, + Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } + }, Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); } }; let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default(); info!("UHCI USB 1.1 at {}", device_path); + + // Read BAR4 (I/O base) from PCI config space let config_path = format!("{}/config", device_path); - match fs::read(&config_path) { - Ok(data) if data.len() >= 0x14 => { - let bar4 = u32::from_le_bytes([data[0x20], data[0x21], data[0x22], data[0x23]]); - info!("UHCI I/O base: 0x{:04X} (BAR4)", bar4 & 0xFFE0); - info!("uhcid: I/O port detected, ready for port enumeration"); + let bar4 = match fs::read(&config_path) { + Ok(data) if data.len() >= 0x24 => { + u32::from_le_bytes([data[0x20], data[0x21], data[0x22], data[0x23]]) } - _ => warn!("cannot read PCI config"), + _ => { error!("cannot read PCI config"); process::exit(1); } + }; + + let io_base = (bar4 & 0xFFE0) as u16; // bits 4:0 reserved for I/O BAR + info!("UHCI I/O base: 0x{:04X}", io_base); + + // Allocate DMA memory + // Frame list: 1024 entries × 4 bytes, 4KB aligned + let (frame_list, frame_list_phys) = alloc_dma(FRAME_COUNT * 4, FRAME_LIST_ALIGN); + // Dummy QH: terminates the frame list entries + let (dummy_qh, dummy_qh_phys) = alloc_dma(core::mem::size_of::(), 16); + + unsafe { + let qh = &mut *(dummy_qh as *mut QueueHead); + qh.link = PTR_TERM; + qh.element = PTR_TERM; + } + + // Initialize frame list: all entries point to the dummy QH + unsafe { + let frames = core::slice::from_raw_parts_mut(frame_list as *mut u32, FRAME_COUNT); + for entry in frames.iter_mut() { + *entry = (dummy_qh_phys as u32) | PTR_QH; + } + } + + // Reset and initialize controller + let ctrl = UhciController { + io_base, + port_count: 2, + devices: vec![None, None], + _frame_list_dma: Vec::new(), + _qh_dma: Vec::new(), + _td_dma: Vec::new(), + }; + + ctrl.reset_controller(); + ctrl.init_frame_list(frame_list_phys, dummy_qh_phys); + + info!("uhcid: controller initialized, polling ports"); + + // Main polling loop + loop { + for port in 0..ctrl.port_count { + let portsc = ctrl.port_status(port); + + // Detect newly connected device + if (portsc & PORT_CONNECT) != 0 && (portsc & PORT_CSC) != 0 { + // Clear CSC + ctrl.port_write(port, 0, 0, 0); + + info!("uhcid: port {} connect detected (status 0x{:04X})", port + 1, portsc); + + // Reset and enumerate + if ctrl.port_reset(port) { + match enumerate_device(&ctrl, port) { + Ok(dev) => { + info!( + "uhcid: port {} device {:04x}:{:04x} class {:02x}", + port + 1, dev.vendor_id, dev.product_id, dev.device_class, + ); + // P0-B1: auto-spawn class drivers + usb_core::spawn::spawn_class_driver_for_port( + dev.device_class, + dev.device_subclass, + dev.device_protocol, + "usb", + &format!("{}", port + 1), + 0, + ); + } + Err(e) => { + warn!("uhcid: port {} enumeration failed: {}", port + 1, e); + } + } + } + } + + // Detect disconnect + if (portsc & PORT_CONNECT) == 0 && (portsc & PORT_CSC) != 0 { + ctrl.port_write(port, 0, 0, 0); + info!("uhcid: port {} disconnected", port + 1); + } + } + thread::sleep(Duration::from_millis(100)); } - loop { std::thread::sleep(std::time::Duration::from_secs(10)); } +} + +fn enumerate_device(ctrl: &UhciController, port: usize) -> Result { + let portsc = ctrl.port_status(port); + let low_speed = (portsc & PORT_LOW_SPEED) != 0; + + // Step 1: Get 8-byte device descriptor header + let get_desc: [u8; 8] = [ + 0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, // GET_DESCRIPTOR(DEVICE, 8) + ]; + let mut header = [0u8; 8]; + control_transfer( + ctrl.io_base, 0, 0, low_speed, &get_desc, + Some((&mut header, true)), + )?; + let max_packet0_device = header[7]; + info!("uhcid: port {} max packet size: {}", port + 1, max_packet0_device); + + // Step 2: Set address + let addr: u8 = (port + 1) as u8; + let set_addr: [u8; 8] = [ + 0x00, 0x05, addr, 0x00, 0x00, 0x00, 0x00, 0x00, // SET_ADDRESS(addr) + ]; + control_transfer(ctrl.io_base, 0, 0, low_speed, &set_addr, None)?; + thread::sleep(Duration::from_millis(10)); // spec: 2ms settle after SET_ADDRESS + + // Step 3: Get full device descriptor + let mut dev_desc = [0u8; 18]; + let get_full_desc: [u8; 8] = [ + 0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, // GET_DESCRIPTOR(DEVICE, 18) + ]; + control_transfer( + ctrl.io_base, addr, 0, low_speed, &get_full_desc, + Some((&mut dev_desc, true)), + )?; + + let vendor_id = u16::from_le_bytes([dev_desc[8], dev_desc[9]]); + let product_id = u16::from_le_bytes([dev_desc[10], dev_desc[11]]); + let device_class = dev_desc[4]; + let device_subclass = dev_desc[5]; + let device_protocol = dev_desc[6]; + + // Step 4: Get config descriptor header + let mut cfg_header = [0u8; 9]; + let get_cfg_hdr: [u8; 8] = [ + 0x80, 0x06, 0x00, 0x02, 0x00, 0x00, 0x09, 0x00, // GET_DESCRIPTOR(CONFIG, 9) + ]; + control_transfer( + ctrl.io_base, addr, 0, low_speed, &get_cfg_hdr, + Some((&mut cfg_header, true)), + )?; + let total_len = u16::from_le_bytes([cfg_header[2], cfg_header[3]]); + + // Step 5: Get full config descriptor + let mut config = vec![0u8; total_len as usize]; + let get_cfg: [u8; 8] = [ + 0x80, 0x06, 0x00, 0x02, 0x00, 0x00, + (total_len & 0xFF) as u8, + ((total_len >> 8) & 0xFF) as u8, + ]; + control_transfer( + ctrl.io_base, addr, 0, low_speed, &get_cfg, + Some((&mut config, true)), + )?; + + Ok(PortDevice { + address: addr, + vendor_id, + product_id, + device_class, + device_subclass, + device_protocol, + device_descriptor: dev_desc, + config_descriptor: config, + low_speed, + }) } diff --git a/local/recipes/drivers/uhcid/source/src/registers.rs b/local/recipes/drivers/uhcid/source/src/registers.rs index aaeebaf858..3743b03918 100644 --- a/local/recipes/drivers/uhcid/source/src/registers.rs +++ b/local/recipes/drivers/uhcid/source/src/registers.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] + +// UHCI I/O register offsets (16-bit aligned, accessed via inw/outw) pub const USBCMD: u16 = 0x00; pub const USBSTS: u16 = 0x02; pub const USBINTR: u16 = 0x04; @@ -8,24 +10,102 @@ pub const SOFMOD: u16 = 0x0C; pub const PORTSC1: u16 = 0x10; pub const PORTSC2: u16 = 0x12; +// USBCMD bits pub const CMD_RUN_STOP: u16 = 1 << 0; pub const CMD_HOST_RESET: u16 = 1 << 1; pub const CMD_GLOBAL_RESET: u16 = 1 << 2; pub const CMD_CONFIGURE: u16 = 1 << 6; pub const CMD_MAX_PACKET_64: u16 = 1 << 7; +// USBSTS bits pub const STS_INTERRUPT: u16 = 1 << 0; pub const STS_ERROR: u16 = 1 << 1; pub const STS_RESUME: u16 = 1 << 2; pub const STS_HOST_ERROR: u16 = 1 << 3; pub const STS_HALTED: u16 = 1 << 5; +// PORTSC bits pub const PORT_CONNECT: u16 = 1 << 0; -pub const PORT_ENABLE: u16 = 1 << 1; -pub const PORT_SUSPEND: u16 = 1 << 2; -pub const PORT_OVER_CURRENT: u16 = 1 << 3; -pub const PORT_RESET: u16 = 1 << 4; +pub const PORT_CSC: u16 = 1 << 1; // Connect Status Change +pub const PORT_ENABLE: u16 = 1 << 2; +pub const PORT_PEC: u16 = 1 << 3; // Port Enable Change +pub const PORT_RESUME: u16 = 1 << 6; pub const PORT_LOW_SPEED: u16 = 1 << 8; +pub const PORT_RESET: u16 = 1 << 9; +pub const PORT_SUSPEND: u16 = 1 << 12; + +// PORTSC change bits (write 1 to clear) +pub const PORT_CHANGE_BITS: u16 = PORT_CSC | PORT_PEC; pub const FRAME_COUNT: usize = 1024; pub const FRAME_LIST_ALIGN: usize = 4096; + +// ---- Transfer Descriptor (TD) and Queue Head (QH) structures ---- +// +// These are the hardware-level data structures the UHCI controller +// walks in DMA-accessible memory. Each field is a 32-bit little-endian +// value. We represent them as packed C structs and access them via +// raw pointers. + +/// Link pointer — points to the next QH, TD, or is a terminator. +/// Bits 1-0 encode the type: 00=TD, 10=QH, 01=Depth-first, 01+Depth=Breadth-first +/// Bit 0 = Terminate (1 = end of list) +pub const PTR_TERM: u32 = 1; // Terminate this list +pub const PTR_QH: u32 = 2; // Points to a Queue Head +pub const PTR_DEPTH: u32 = 4; // Depth-first traversal + +/// A Queue Head. Links into the frame list. The element pointer +/// is updated by the HC as it processes TDs. +#[repr(C, align(16))] +pub struct QueueHead { + pub link: u32, // next QH pointer (or TERM) + pub element: u32, // next TD pointer (updated by HC) +} + +/// A Transfer Descriptor. Linked list of these forms a USB transfer. +/// The status field is updated by the HC on completion. +/// +/// Fields: +/// link — next TD pointer (PTR_TERM or another TD's phys addr) +/// status — status+control word (written by HC on completion) +/// bits: 29:SPD 27-28:C_ERR 26:LS 25:IOS 24:IOC 23:ACTIVE +/// 22:STALLED 21:DBUFERR 20:BABBLE 19:NAK 18:CRC/TIMEO +/// 17:BITSTUFF 0-10:ACTLEN(actual length - 1) +/// token — token word +/// bits: 21-31:MAXLEN 19:TOGGLE 15-18:ENDP 8-14:DEVADDR 0-7:PID +/// buffer — physical address of data buffer +#[repr(C, align(16))] +pub struct TransferDescriptor { + pub link: u32, + pub status: u32, + pub token: u32, + pub buffer: u32, +} + +// TD status/control bits +pub const TD_CTRL_ACTIVE: u32 = 1 << 23; +pub const TD_CTRL_IOC: u32 = 1 << 24; // Interrupt on Complete +pub const TD_CTRL_LS: u32 = 1 << 26; // Low Speed +pub const TD_CTRL_C_ERR_MASK: u32 = 3 << 27; // Error counter +pub const TD_CTRL_SPD: u32 = 1 << 29; // Short Packet Detect + +pub const TD_STATUS_STALLED: u32 = 1 << 22; +pub const TD_STATUS_BABBLE: u32 = 1 << 20; +pub const TD_STATUS_NAK: u32 = 1 << 19; +pub const TD_STATUS_CRC: u32 = 1 << 18; +pub const TD_ACTLEN_MASK: u32 = 0x7FF; // bits 0-10 + +// USB Packet IDs for the token field +pub const PID_SETUP: u32 = 0x2D; +pub const PID_IN: u32 = 0x69; +pub const PID_OUT: u32 = 0xE1; + +// Maximum NAK/error retry count for a single transfer +pub const MAX_RETRIES: u32 = 3; + +// Port reset hold time (50ms in microseconds) +pub const PORT_RESET_HOLD_US: u64 = 50_000; +// Port reset settle time (10ms in microseconds) +pub const PORT_RESET_SETTLE_US: u64 = 10_000; +// Max bulk packet size for USB 1.1 +pub const MAX_PACKET_SIZE: usize = 64;