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:
2026-07-07 01:32:36 +03:00
parent f9ace4a956
commit ef3dd8b1fd
9 changed files with 533 additions and 26 deletions
+1 -1
View File
@@ -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.1T2.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 -2
View File
@@ -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 |
|---|---|---|
+1 -1
View File
@@ -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 |
+1 -1
View File
@@ -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" }
+438 -14
View File
@@ -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;