docs: Linux 7.0 → 7.1 reference across all active docs
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.
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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`):
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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<Option<PortDevice>>,
|
||||
_frame_list_dma: Vec<DmaBuffer>,
|
||||
_qh_dma: Vec<DmaBuffer>,
|
||||
_td_dma: Vec<DmaBuffer>,
|
||||
}
|
||||
|
||||
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<u8>,
|
||||
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<Option<usize>, &'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::<usize>() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } },
|
||||
Ok(s) => match s.parse::<usize>() {
|
||||
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::<QueueHead>(), 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<PortDevice, &'static str> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user