Files
RedBear-OS/local/recipes/drivers/ehcid/source/src/main.rs
T
vasilito 1e71b37bdb chore: close session — commit all remaining pre-existing state
Finalize all non-artifact changes accumulated from other sessions:
- config updates, recipe changes, source edits, patches
- pkgar/cache artifacts intentionally excluded (build outputs)

This is the maximum achievable scope for this session.
Hardware-accelerated KDE blocked by: QML gate, KWin/Plasma builds,
hardware GPU validation — all require build system + physical GPU.
2026-05-01 03:15:20 +01:00

1846 lines
59 KiB
Rust

mod registers;
use std::collections::BTreeMap;
use std::env;
use std::fmt::Write as _;
use std::fs;
use std::io::{self, Write};
use std::mem::size_of;
use std::process;
use std::sync::atomic::{Ordering, fence};
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use std::time::Duration;
use log::{LevelFilter, Metadata, Record, error, info, warn};
use redox_driver_sys::dma::DmaBuffer;
use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion};
use redox_driver_sys::pcid_client::PcidClient;
use redox_scheme::scheme::{SchemeState, SchemeSync, register_sync_scheme};
use redox_scheme::{CallerCtx, OpenResult, SignalBehavior, Socket};
use syscall::Stat;
use syscall::error::{
EACCES, EBADF, EINVAL, ENOENT, EROFS, Error as SysError, Result as SysResult,
};
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE};
use syscall::schemev2::NewFdFlags;
use usb_core::{
PortStatus, SetupPacket, TransferDirection, UsbError, UsbHostController,
parse_config_descriptor, parse_device_descriptor,
};
use registers::*;
const SCHEME_NAME: &str = "usb";
const SCHEME_ROOT_ID: usize = 1;
const MMIO_MAP_SIZE: usize = 0x1000;
const FRAME_LIST_LEN: usize = 1024;
const CONTROL_TD_COUNT: usize = 3;
const DEFAULT_CONTROL_MPS: u16 = 64;
const PORT_POLL_INTERVAL: Duration = Duration::from_millis(100);
const PORT_RESET_HOLD: Duration = Duration::from_millis(50);
const PORT_RESET_SETTLE: Duration = Duration::from_millis(10);
const WAIT_STEP: Duration = Duration::from_millis(1);
const CONTROL_TRANSFER_TIMEOUT_POLLS: usize = 1000;
const MAX_CONFIG_DESCRIPTOR_LEN: usize = 512;
const MAX_SCHEME_CONTROL_BYTES: usize = 4096;
const STATUS_CLEAR_BITS: u32 = STS_USB_INTERRUPT
| STS_USB_ERROR_INTERRUPT
| STS_PORT_CHANGE_DETECT
| STS_FRAME_LIST_ROLLOVER
| STS_HOST_SYSTEM_ERROR
| STS_INTERRUPT_ON_ASYNC_ADVANCE;
static LOGGER: StderrLogger = StderrLogger;
struct StderrLogger;
impl log::Log for StderrLogger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
metadata.level() <= LevelFilter::Info
}
fn log(&self, record: &Record<'_>) {
if self.enabled(record.metadata()) {
let _ = writeln!(
io::stderr().lock(),
"[{}] ehcid: {}",
record.level(),
record.args()
);
}
}
fn flush(&self) {}
}
#[derive(Clone, Debug, Default)]
struct PortDevice {
address: u8,
max_packet_size0: u16,
device_descriptor: Vec<u8>,
config_descriptor: Vec<u8>,
vendor_id: u16,
product_id: u16,
device_class: u8,
device_subclass: u8,
device_protocol: u8,
}
#[derive(Clone, Debug, Default)]
struct PortRecord {
last_portsc: u32,
last_status: Option<PortStatus>,
companion_owned: bool,
last_error: Option<String>,
device: Option<PortDevice>,
}
#[derive(Clone, Debug)]
struct ControlRequest {
request_type: u8,
request: u8,
value: u16,
index: u16,
length: u16,
data: Vec<u8>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum HandleKind {
PortDir { port: usize },
Status { port: usize },
Descriptor { port: usize },
Control { port: usize },
Config { port: usize },
}
#[derive(Clone, Debug)]
struct HandleState {
kind: HandleKind,
response: Vec<u8>,
}
struct EhciScheme {
controller: Arc<Mutex<EhciController>>,
handles: BTreeMap<usize, HandleState>,
next_id: usize,
}
struct EhciController {
controller_name: String,
mmio: MmioRegion,
op_base: usize,
n_ports: u8,
frame_list: DmaBuffer,
async_qh: DmaBuffer,
periodic_qh: DmaBuffer,
dma_segment: u32,
has_64bit: bool,
next_address: u8,
ports: Vec<PortRecord>,
}
impl EhciController {
fn find_port_by_address(&self, addr: u8) -> Option<usize> {
self.ports.iter().position(|r| r.device.as_ref().map_or(false, |d| d.address == addr))
}
fn new(device_path: &str, channel_fd: usize) -> Result<Self, String> {
info!("EHCI USB 2.0 at {} (fd={})", device_path, channel_fd);
let mut pcid = PcidClient::connect_default()
.ok_or_else(|| "failed to connect to PCID client channel".to_string())?;
pcid.enable_device()
.map_err(|err| format!("failed to enable PCI device: {err}"))?;
let config_path = format!("{device_path}/config");
let config = match fs::read(&config_path) {
Ok(data) => data,
Err(err) => return Err(format!("cannot read PCI config at {config_path}: {err}")),
};
let mmio_base = parse_mmio_bar(&config)?;
info!("MMIO base: 0x{mmio_base:016X}");
let mmio = MmioRegion::map(
mmio_base,
MMIO_MAP_SIZE,
CacheType::DeviceMemory,
MmioProt::READ_WRITE,
)
.map_err(|err| format!("failed to map EHCI MMIO region: {err}"))?;
let caplength = mmio.read8(CAPLENGTH);
let op_base = registers::op_base(caplength);
let hcsparams = mmio.read32(HCSPARAMS);
let hccparams = mmio.read32(HCCPARAMS);
let caps = HcCapParams::from_hcsparams(hcsparams);
let n_ports = caps.n_ports;
let has_64bit = (hccparams & HCCPARAMS_64BIT) != 0;
if n_ports == 0 {
return Err("EHCI controller reports zero ports".to_string());
}
info!("ports: {}, caplength: {}", n_ports, caplength);
let mut frame_list = DmaBuffer::allocate(FRAME_LIST_LEN * size_of::<u32>(), 4096)
.map_err(|err| format!("failed to allocate frame list: {err}"))?;
init_frame_list(&mut frame_list);
let async_qh = DmaBuffer::allocate(size_of::<QueueHead>(), 64)
.map_err(|err| format!("failed to allocate async queue head: {err}"))?;
let periodic_qh = DmaBuffer::allocate(size_of::<QueueHead>(), 64)
.map_err(|err| format!("failed to allocate periodic queue head: {err}"))?;
let dma_segment = ensure_dma_segment(
has_64bit,
&[
frame_list.physical_address() as u64,
async_qh.physical_address() as u64,
periodic_qh.physical_address() as u64,
],
)?;
let mut controller = Self {
controller_name: device_path.to_string(),
mmio,
op_base,
n_ports,
frame_list,
async_qh,
periodic_qh,
dma_segment,
has_64bit,
next_address: 1,
ports: vec![PortRecord::default(); usize::from(n_ports)],
};
controller.reset_and_start()?;
controller.poll_ports_once();
Ok(controller)
}
fn reset_and_start(&mut self) -> Result<(), String> {
self.stop_controller()?;
self.write_op32(USBCMD, CMD_HCRESET);
self.wait_until(
"controller reset completion",
|| self.read_op32(USBCMD) & CMD_HCRESET == 0,
1000,
)?;
self.clear_interrupt_status();
self.write_op32(CTRLDSSEGMENT, self.dma_segment);
self.write_op32(
PERIODICLISTBASE,
low32(self.frame_list.physical_address() as u64),
);
self.initialize_async_qh(0, DEFAULT_CONTROL_MPS);
self.write_op32(
ASYNCLISTADDR,
low32(self.async_qh.physical_address() as u64),
);
self.write_op32(
USBINTR,
INTR_USB_INTERRUPT_ENABLE
| INTR_USB_ERROR_INTERRUPT_ENABLE
| INTR_PORT_CHANGE_ENABLE
| INTR_HOST_SYSTEM_ERROR_ENABLE
| INTR_ASYNC_ADVANCE_ENABLE,
);
self.write_op32(
USBCMD,
CMD_RUN_STOP
| CMD_FRAME_LIST_SIZE_1024
| CMD_PERIODIC_SCHEDULE_ENABLE
| CMD_ASYNC_SCHEDULE_ENABLE
| interrupt_threshold(8),
);
self.wait_until(
"host controller run state",
|| self.read_op32(USBSTS) & STS_HC_HALTED == 0,
1000,
)?;
self.wait_until(
"periodic schedule activation",
|| self.read_op32(USBSTS) & STS_PERIODIC_SCHEDULE_STATUS != 0,
1000,
)?;
self.wait_until(
"async schedule activation",
|| self.read_op32(USBSTS) & STS_ASYNC_SCHEDULE_STATUS != 0,
1000,
)?;
self.write_op32(CONFIGFLAG, CF_FLAG);
for port in 0..self.port_count() {
self.ensure_port_power(port);
let status = self.read_portsc(port);
self.clear_port_changes(port, status);
}
info!(
"ehcid: controller initialized, {} ports, async list at 0x{:08X}",
self.n_ports,
low32(self.async_qh.physical_address() as u64)
);
Ok(())
}
fn stop_controller(&mut self) -> Result<(), String> {
let command = self.read_op32(USBCMD);
if command & CMD_RUN_STOP != 0 {
self.write_op32(USBCMD, command & !CMD_RUN_STOP);
self.wait_until(
"controller halt",
|| self.read_op32(USBSTS) & STS_HC_HALTED != 0,
1000,
)?;
}
Ok(())
}
fn wait_until<F>(&self, label: &str, mut predicate: F, iterations: usize) -> Result<(), String>
where
F: FnMut() -> bool,
{
for _ in 0..iterations {
if predicate() {
return Ok(());
}
thread::sleep(WAIT_STEP);
}
Err(format!("timed out waiting for {label}"))
}
fn clear_interrupt_status(&mut self) {
let status = self.read_op32(USBSTS);
if status & STS_HOST_SYSTEM_ERROR != 0 {
warn!("EHCI host system error reported in USBSTS: 0x{status:08x}");
}
let clear = status & STATUS_CLEAR_BITS;
if clear != 0 {
self.write_op32(USBSTS, clear);
}
}
fn initialize_async_qh(&mut self, device_address: u8, max_packet_size: u16) {
let qh_phys = self.async_qh.physical_address() as u64;
let mut qh = QueueHead::new();
qh.horiz_link = qh_link_pointer(qh_phys);
qh.caps[0] = qh_endpoint_characteristics(device_address, 0, max_packet_size, true);
qh.caps[1] = qh_endpoint_capabilities();
unsafe {
std::ptr::write_volatile(self.async_qh.as_mut_ptr() as *mut QueueHead, qh);
}
}
fn prepare_async_qh(&mut self, device_address: u8, endpoint: u8, max_packet_size: u16, first_td_phys: u32) {
let qh_ptr = self.async_qh.as_mut_ptr() as *mut QueueHead;
unsafe {
let qh = &mut *qh_ptr;
qh.horiz_link = qh_link_pointer(self.async_qh.physical_address() as u64);
qh.caps[0] = qh_endpoint_characteristics(device_address, endpoint, max_packet_size, true);
qh.caps[1] = qh_endpoint_capabilities();
qh.current_qtd = 0;
qh.overlay[0] = first_td_phys & !0x1F;
qh.overlay[1] = TD_TERMINATE;
qh.overlay[2] = 0;
qh.overlay[3] = 0;
qh.overlay[4] = 0;
qh.overlay[5] = 0;
qh.overlay[6] = 0;
qh.overlay[7] = 0;
}
}
fn disarm_async_qh(&mut self) {
let qh_ptr = self.async_qh.as_mut_ptr() as *mut QueueHead;
unsafe {
let qh = &mut *qh_ptr;
qh.current_qtd = 0;
qh.overlay[0] = TD_TERMINATE;
qh.overlay[1] = TD_TERMINATE;
qh.overlay[2] = 0;
qh.overlay[3] = 0;
qh.overlay[4] = 0;
qh.overlay[5] = 0;
qh.overlay[6] = 0;
qh.overlay[7] = 0;
}
}
fn arm_periodic_qh(&mut self, device_address: u8, max_packet: u16, endpoint: u8, td_phys: u64) {
let fl = self.frame_list.as_mut_ptr() as *mut u32;
let qh_val = qh_endpoint_characteristics(device_address, endpoint, max_packet, false);
unsafe {
let qh_ptr = self.periodic_qh.as_mut_ptr() as *mut QueueHead;
let qh = &mut *qh_ptr;
qh.horiz_link = qh_link_pointer(self.periodic_qh.physical_address() as u64);
qh.caps[0] = qh_val;
qh.caps[1] = qh_endpoint_capabilities();
qh.current_qtd = 0;
qh.overlay[0] = (td_phys as u32) & !0x1F;
qh.overlay[1] = TD_TERMINATE;
qh.overlay[2] = 0; qh.overlay[3] = 0;
qh.overlay[4] = 0; qh.overlay[5] = 0;
qh.overlay[6] = 0; qh.overlay[7] = 0;
*fl = (self.periodic_qh.physical_address() as u32) | 2;
}
}
fn disarm_periodic_qh(&mut self) {
let fl = self.frame_list.as_mut_ptr() as *mut u32;
unsafe { *fl = TD_TERMINATE; }
}
fn ensure_controller_running(&mut self) {
let status = self.read_op32(USBSTS);
let command = self.read_op32(USBCMD);
let required = CMD_RUN_STOP | CMD_ASYNC_SCHEDULE_ENABLE | CMD_PERIODIC_SCHEDULE_ENABLE;
if status & STS_HC_HALTED != 0
|| status & STS_ASYNC_SCHEDULE_STATUS == 0
|| status & STS_PERIODIC_SCHEDULE_STATUS == 0
|| command & required != required
{
self.write_op32(
USBCMD,
CMD_RUN_STOP
| CMD_FRAME_LIST_SIZE_1024
| CMD_PERIODIC_SCHEDULE_ENABLE
| CMD_ASYNC_SCHEDULE_ENABLE
| interrupt_threshold(8),
);
}
}
fn read32(&self, offset: usize) -> u32 {
self.mmio.read32(offset)
}
fn write32(&self, offset: usize, value: u32) {
self.mmio.write32(offset, value)
}
fn read_op32(&self, offset: usize) -> u32 {
self.read32(self.op_base + offset)
}
fn write_op32(&self, offset: usize, value: u32) {
self.write32(self.op_base + offset, value)
}
fn read_portsc(&self, port: usize) -> u32 {
self.read_op32(portsc_offset(port))
}
fn write_portsc(&self, port: usize, value: u32) {
self.write_op32(portsc_offset(port), value)
}
fn port_write_value(
&self,
current: u32,
set_bits: u32,
clear_bits: u32,
clear_changes: u32,
) -> u32 {
let mut value = current & PORTSC_WRITE_MASK;
value &= !clear_bits;
value |= set_bits & PORTSC_WRITE_MASK;
value |= clear_changes & PORTSC_CHANGE_BITS;
value
}
fn clear_port_changes(&self, port: usize, current: u32) {
let clear = current & PORTSC_CHANGE_BITS;
if clear != 0 {
self.write_portsc(port, self.port_write_value(current, 0, 0, clear));
}
}
fn ensure_port_power(&self, port: usize) {
let current = self.read_portsc(port);
if current & PORT_POWER == 0 {
self.write_portsc(port, self.port_write_value(current, PORT_POWER, 0, current));
thread::sleep(WAIT_STEP);
}
}
fn handoff_to_companion(&mut self, port: usize) {
let current = self.read_portsc(port);
self.write_portsc(
port,
self.port_write_value(current, PORT_OWNER | PORT_POWER, PORT_RESET, current),
);
self.ports[port].companion_owned = true;
self.ports[port].device = None;
info!("ehcid: handed port {} to companion controller", port + 1);
}
fn poll_ports_once(&mut self) {
self.clear_interrupt_status();
for port in 0..self.port_count() {
let portsc = self.read_portsc(port);
let status = decode_port_status(portsc);
let had_device = self.ports[port].device.is_some();
let had_companion = self.ports[port].companion_owned;
self.ports[port].last_portsc = portsc;
self.ports[port].last_status = Some(status.clone());
if portsc & PORTSC_CHANGE_BITS != 0 {
self.clear_port_changes(port, portsc);
}
if !status.connected {
if had_device || had_companion {
info!("ehcid: device disconnected from port {}", port + 1);
}
self.ports[port].device = None;
self.ports[port].companion_owned = false;
self.ports[port].last_error = None;
continue;
}
if portsc & PORT_OWNER != 0 {
self.ports[port].companion_owned = true;
continue;
}
let should_probe = self.ports[port].device.is_none()
&& ((portsc & PORT_CONNECT_CHANGE != 0) || (portsc & PORT_ENABLE != 0));
if should_probe {
match self.initialize_port(port) {
Ok(()) => {}
Err(err) => {
warn!("ehcid: port {} initialization failed: {}", port + 1, err);
self.ports[port].last_error = Some(err);
}
}
}
}
}
fn initialize_port(&mut self, port: usize) -> Result<(), String> {
self.ports[port].device = None;
self.ports[port].companion_owned = false;
if !self.port_reset(port) {
let portsc = self.read_portsc(port);
if portsc & PORT_OWNER != 0 {
self.ports[port].companion_owned = true;
self.ports[port].last_error = None;
return Ok(());
}
return Err("port reset did not produce an enabled high-speed port".to_string());
}
let device = self.enumerate_port_device(port)?;
info!(
"ehcid: port {} device {:04x}:{:04x} address {}",
port + 1,
device.vendor_id,
device.product_id,
device.address
);
self.ports[port].device = Some(device);
self.ports[port].last_error = None;
Ok(())
}
fn enumerate_port_device(&mut self, port: usize) -> Result<PortDevice, String> {
let mut header = [0_u8; 8];
let get_device_header = SetupPacket {
request_type: 0x80,
request: 0x06,
value: 0x0100,
index: 0,
length: 8,
};
self.submit_control_transfer(
port,
0,
DEFAULT_CONTROL_MPS,
&get_device_header,
&mut header,
)
.map_err(|err| format!("failed to fetch device descriptor header: {err:?}"))?;
let max_packet_size0 = u16::from(header[7].max(8));
let address = self.allocate_device_address()?;
let set_address = SetupPacket {
request_type: 0x00,
request: 0x05,
value: u16::from(address),
index: 0,
length: 0,
};
self.submit_control_transfer(port, 0, max_packet_size0, &set_address, &mut [])
.map_err(|err| format!("failed to set device address {}: {err:?}", address))?;
thread::sleep(PORT_RESET_SETTLE);
let mut device_descriptor = [0_u8; 18];
let get_device_descriptor = SetupPacket {
request_type: 0x80,
request: 0x06,
value: 0x0100,
index: 0,
length: 18,
};
self.submit_control_transfer(
port,
address,
max_packet_size0,
&get_device_descriptor,
&mut device_descriptor,
)
.map_err(|err| format!("failed to fetch full device descriptor: {err:?}"))?;
let descriptor = parse_device_descriptor(&device_descriptor)
.ok_or_else(|| "device descriptor parse failed".to_string())?;
let config_descriptor = self.read_config_descriptor(port, address, max_packet_size0);
Ok(PortDevice {
address,
max_packet_size0,
device_descriptor: device_descriptor.to_vec(),
config_descriptor,
vendor_id: descriptor.vendor_id,
product_id: descriptor.product_id,
device_class: descriptor.device_class,
device_subclass: descriptor.device_subclass,
device_protocol: descriptor.device_protocol,
})
}
fn read_config_descriptor(
&mut self,
port: usize,
address: u8,
max_packet_size0: u16,
) -> Vec<u8> {
let header_request = SetupPacket {
request_type: 0x80,
request: 0x06,
value: 0x0200,
index: 0,
length: 9,
};
let mut header = [0_u8; 9];
if self
.submit_control_transfer(
port,
address,
max_packet_size0,
&header_request,
&mut header,
)
.is_err()
{
return Vec::new();
}
let Some(config) = parse_config_descriptor(&header) else {
return Vec::new();
};
let total_length = usize::from(config.total_length).clamp(
usize::from(header_request.length),
MAX_CONFIG_DESCRIPTOR_LEN,
);
let full_request = SetupPacket {
length: total_length as u16,
..header_request
};
let mut data = vec![0_u8; total_length];
match self.submit_control_transfer(
port,
address,
max_packet_size0,
&full_request,
&mut data,
) {
Ok(actual) => {
data.truncate(actual);
data
}
Err(_) => Vec::new(),
}
}
fn allocate_device_address(&mut self) -> Result<u8, String> {
for _ in 0..127 {
let candidate = self.next_address;
self.next_address = if self.next_address >= 127 {
1
} else {
self.next_address + 1
};
if !self.address_in_use(candidate) {
return Ok(candidate);
}
}
Err("no free USB device addresses remain".to_string())
}
fn address_in_use(&self, address: u8) -> bool {
self.ports.iter().any(|record| {
record
.device
.as_ref()
.map(|device| device.address == address)
.unwrap_or(false)
})
}
fn ensure_dma_segment_matches(&self, phys: u64, label: &str) -> Result<u32, UsbError> {
let segment = dma_segment(phys);
if !self.has_64bit && segment != 0 {
warn!(
"ehcid: DMA buffer {} requires 64-bit addressing but the controller is 32-bit-only",
label
);
return Err(UsbError::IoError);
}
if segment != self.dma_segment {
warn!(
"ehcid: DMA buffer {} is in segment 0x{:08x}, expected 0x{:08x}",
label, segment, self.dma_segment
);
return Err(UsbError::IoError);
}
Ok(low32(phys))
}
fn submit_control_transfer(
&mut self,
port: usize,
device_address: u8,
max_packet_size: u16,
setup: &SetupPacket,
data: &mut [u8],
) -> Result<usize, UsbError> {
if port >= self.port_count() {
return Err(UsbError::NoDevice);
}
if usize::from(setup.length) != data.len() {
return Err(UsbError::IoError);
}
if data.len() > 0x7FFF {
return Err(UsbError::Unsupported);
}
if max_packet_size == 0 {
return Err(UsbError::IoError);
}
self.ensure_controller_running();
let setup_bytes = setup_packet_bytes(setup);
let mut setup_dma =
DmaBuffer::allocate(setup_bytes.len(), 8).map_err(|_| UsbError::IoError)?;
dma_write_bytes(&mut setup_dma, &setup_bytes);
self.ensure_dma_segment_matches(setup_dma.physical_address() as u64, "setup")?;
let mut data_dma = if data.is_empty() {
None
} else {
Some(DmaBuffer::allocate(data.len(), 4096).map_err(|_| UsbError::IoError)?)
};
if let Some(buffer) = data_dma.as_mut() {
self.ensure_dma_segment_matches(buffer.physical_address() as u64, "data")?;
if setup.request_type & 0x80 == 0 {
dma_write_bytes(buffer, data);
}
}
let mut td_dma =
DmaBuffer::allocate(CONTROL_TD_COUNT * size_of::<TransferDescriptor>(), 32)
.map_err(|_| UsbError::IoError)?;
self.ensure_dma_segment_matches(td_dma.physical_address() as u64, "qtd")?;
let td_pool = unsafe {
std::slice::from_raw_parts_mut(
td_dma.as_mut_ptr() as *mut TransferDescriptor,
CONTROL_TD_COUNT,
)
};
let first_td_phys = build_control_transfer(
setup_dma.physical_address() as u64,
&setup_bytes,
data_dma
.as_ref()
.map(|buffer| buffer.physical_address() as u64)
.unwrap_or(0),
data.len(),
setup.request_type & 0x80 != 0,
td_pool,
td_dma.physical_address() as u64,
)
.ok_or(UsbError::IoError)?;
self.prepare_async_qh(device_address, 0, max_packet_size, first_td_phys);
self.clear_interrupt_status();
fence(Ordering::SeqCst);
let td_count = if data.is_empty() { 2 } else { 3 };
for _ in 0..CONTROL_TRANSFER_TIMEOUT_POLLS {
let mut active = false;
let mut error_token = None;
for index in 0..td_count {
let token = read_td_token(&td_dma, index);
if token & TD_ACTIVE != 0 {
active = true;
}
if token & (TD_HALTED | TD_BUFERR | TD_BABBLE | TD_XACTERR | TD_MISSED) != 0 {
error_token = Some(token);
break;
}
}
if let Some(token) = error_token {
self.disarm_async_qh();
self.ports[port].last_error = Some(format!("transfer failure token=0x{token:08x}"));
return Err(map_td_error(token));
}
if !active {
let actual = if data.is_empty() {
0
} else {
let data_token = read_td_token(&td_dma, 1);
let remaining =
((data_token & TD_TOTAL_BYTES_MASK) >> TD_TOTAL_BYTES_SHIFT) as usize;
data.len().saturating_sub(remaining)
};
if let Some(buffer) = data_dma.as_ref() {
if setup.request_type & 0x80 != 0 && actual != 0 {
dma_read_bytes(buffer, &mut data[..actual]);
}
}
self.disarm_async_qh();
self.clear_interrupt_status();
return Ok(actual);
}
thread::sleep(WAIT_STEP);
}
self.disarm_async_qh();
self.ports[port].last_error = Some("transfer timed out".to_string());
Err(UsbError::Timeout)
}
fn port_record(&self, port: usize) -> Option<&PortRecord> {
self.ports.get(port)
}
fn execute_control_request(
&mut self,
port: usize,
request: &ControlRequest,
) -> Result<Vec<u8>, String> {
let Some(device) = self
.ports
.get(port)
.and_then(|record| record.device.clone())
else {
return Err(format!("port {} is not enumerated", port + 1));
};
let mut data = if request.request_type & 0x80 != 0 {
vec![0_u8; usize::from(request.length)]
} else {
request.data.clone()
};
let setup = SetupPacket {
request_type: request.request_type,
request: request.request,
value: request.value,
index: request.index,
length: request.length,
};
let actual = self
.submit_control_transfer(
port,
device.address,
device.max_packet_size0,
&setup,
&mut data,
)
.map_err(|err| format!("control transfer failed: {err:?}"))?;
if request.request_type & 0x80 != 0 {
data.truncate(actual);
Ok(data)
} else {
Ok(format!("ok transferred={actual}\n").into_bytes())
}
}
fn port_count(&self) -> usize {
usize::from(self.n_ports)
}
}
impl UsbHostController for EhciController {
fn port_count(&self) -> usize {
usize::from(self.n_ports)
}
fn port_status(&self, port: usize) -> Option<PortStatus> {
if port >= usize::from(self.n_ports) {
return None;
}
Some(decode_port_status(self.read_portsc(port)))
}
fn port_reset(&mut self, port: usize) -> bool {
if port >= self.port_count() {
return false;
}
self.ensure_port_power(port);
let current = self.read_portsc(port);
if current & PORT_CONNECT == 0 {
return false;
}
self.clear_port_changes(port, current);
let reset_value = self.port_write_value(current, PORT_POWER | PORT_RESET, 0, current);
self.write_portsc(port, reset_value);
thread::sleep(PORT_RESET_HOLD);
let after_hold = self.read_portsc(port);
let clear_reset = self.port_write_value(after_hold, PORT_POWER, PORT_RESET, after_hold);
self.write_portsc(port, clear_reset);
thread::sleep(PORT_RESET_SETTLE);
for _ in 0..100 {
let status = self.read_portsc(port);
if status & PORT_OWNER != 0 {
return false;
}
if status & PORT_CONNECT == 0 {
return false;
}
if status & PORT_ENABLE != 0 {
return true;
}
thread::sleep(WAIT_STEP);
}
self.handoff_to_companion(port);
false
}
fn control_transfer(
&mut self,
device_address: u8,
setup: &SetupPacket,
data: &mut [u8],
) -> Result<usize, UsbError> {
let Some(port) = address_port(self, device_address) else {
return Err(UsbError::NoDevice);
};
let Some(max_packet_size0) = self
.ports
.get(port)
.and_then(|record| record.device.as_ref().map(|device| device.max_packet_size0))
else {
return Err(UsbError::NoDevice);
};
self.submit_control_transfer(port, device_address, max_packet_size0, setup, data)
}
fn bulk_transfer(
&mut self,
device_address: u8,
endpoint: u8,
data: &mut [u8],
direction: TransferDirection,
) -> Result<usize, UsbError> {
let port = self.find_port_by_address(device_address).ok_or(UsbError::NoDevice)?;
let max_packet = self.ports[port].device.as_ref().map(|d| d.max_packet_size0).unwrap_or(512);
self.ensure_controller_running();
if data.len() > 0x7FFF { return Err(UsbError::Unsupported); }
let mut data_dma = if data.is_empty() { None }
else { Some(DmaBuffer::allocate(data.len(), 4096).map_err(|_| UsbError::IoError)?) };
if let Some(buf) = data_dma.as_mut() {
self.ensure_dma_segment_matches(buf.physical_address() as u64, "bulk_data")?;
if direction != TransferDirection::In { dma_write_bytes(buf, data); }
}
let mut td_dma = DmaBuffer::allocate(2 * size_of::<TransferDescriptor>(), 32).map_err(|_| UsbError::IoError)?;
self.ensure_dma_segment_matches(td_dma.physical_address() as u64, "bulk_td")?;
let tds = unsafe { std::slice::from_raw_parts_mut(td_dma.as_mut_ptr() as *mut TransferDescriptor, 2) };
let dma_phys = data_dma.as_ref().map(|b| b.physical_address() as u64).unwrap_or(0);
let first = build_bulk_transfer(dma_phys, data.len(), direction == TransferDirection::In, tds, td_dma.physical_address() as u64);
self.prepare_async_qh(device_address, endpoint, max_packet, first);
fence(Ordering::SeqCst);
for _ in 0..CONTROL_TRANSFER_TIMEOUT_POLLS {
let t0 = read_td_token(&td_dma, 0); let t1 = read_td_token(&td_dma, 1);
if (t0 | t1) & (TD_HALTED | TD_BUFERR | TD_BABBLE | TD_XACTERR | TD_MISSED) != 0 { self.disarm_async_qh(); return Err(map_td_error(t0 | t1)); }
if (t0 | t1) & TD_ACTIVE == 0 {
let rem = ((t0 & TD_TOTAL_BYTES_MASK) >> TD_TOTAL_BYTES_SHIFT) as usize;
let actual = data.len().saturating_sub(rem);
if direction == TransferDirection::In && actual > 0 { if let Some(buf) = data_dma.as_ref() { dma_read_bytes(buf, &mut data[..actual]); } }
self.disarm_async_qh(); return Ok(actual);
}
thread::sleep(WAIT_STEP);
}
self.disarm_async_qh(); Err(UsbError::Timeout)
}
fn interrupt_transfer(
&mut self,
device_address: u8,
endpoint: u8,
data: &mut [u8],
) -> Result<usize, UsbError> {
let port = self.find_port_by_address(device_address).ok_or(UsbError::NoDevice)?;
let max_packet = self.ports[port].device.as_ref().map(|d| d.max_packet_size0).unwrap_or(64);
self.ensure_controller_running();
if data.len() > 64 { return Err(UsbError::Unsupported); }
let data_dma = DmaBuffer::allocate(data.len(), 64).map_err(|_| UsbError::IoError)?;
self.ensure_dma_segment_matches(data_dma.physical_address() as u64, "intr_data")?;
let mut td_dma = DmaBuffer::allocate(size_of::<TransferDescriptor>(), 32).map_err(|_| UsbError::IoError)?;
self.ensure_dma_segment_matches(td_dma.physical_address() as u64, "intr_td")?;
let td = unsafe { &mut *(td_dma.as_mut_ptr() as *mut TransferDescriptor) };
*td = build_intr_td(data_dma.physical_address() as u64, data.len(), td_dma.physical_address() as u64);
self.arm_periodic_qh(device_address, max_packet, endpoint, td_dma.physical_address() as u64);
fence(Ordering::SeqCst);
for _ in 0..(CONTROL_TRANSFER_TIMEOUT_POLLS / 2) {
let token = read_td_token(&td_dma, 0);
if token & (TD_HALTED | TD_BUFERR | TD_BABBLE | TD_XACTERR | TD_MISSED) != 0 { self.disarm_periodic_qh(); return Err(map_td_error(token)); }
if token & TD_ACTIVE == 0 {
let rem = ((token & TD_TOTAL_BYTES_MASK) >> TD_TOTAL_BYTES_SHIFT) as usize;
let actual = data.len().saturating_sub(rem);
if actual > 0 { dma_read_bytes(&data_dma, &mut data[..actual]); }
self.disarm_periodic_qh(); return Ok(actual);
}
thread::sleep(WAIT_STEP);
}
self.disarm_periodic_qh(); Err(UsbError::Timeout)
}
fn set_address(&mut self, device_address: u8) -> bool {
device_address > 0 && device_address <= 127
}
fn name(&self) -> &str {
&self.controller_name
}
}
impl EhciScheme {
fn new(controller: Arc<Mutex<EhciController>>) -> Self {
Self {
controller,
handles: BTreeMap::new(),
next_id: SCHEME_ROOT_ID + 1,
}
}
fn alloc_handle(&mut self, kind: HandleKind) -> usize {
let id = self.next_id;
self.next_id += 1;
self.handles.insert(
id,
HandleState {
kind,
response: Vec::new(),
},
);
id
}
fn handle(&self, id: usize) -> SysResult<&HandleState> {
self.handles.get(&id).ok_or(SysError::new(EBADF))
}
fn parse_port_component(&self, component: &str) -> SysResult<usize> {
let Some(raw_port) = component.strip_prefix("port") else {
return Err(SysError::new(ENOENT));
};
let port_number = raw_port
.parse::<usize>()
.map_err(|_| SysError::new(ENOENT))?;
if port_number == 0 {
return Err(SysError::new(ENOENT));
}
let port_index = port_number - 1;
let controller = lock_controller(&self.controller);
if port_index >= controller.port_count() {
return Err(SysError::new(ENOENT));
}
Ok(port_index)
}
fn resolve_root_path(&self, path: &str) -> SysResult<HandleKind> {
let mut parts = path.split('/');
let Some(port_component) = parts.next() else {
return Err(SysError::new(ENOENT));
};
let port = self.parse_port_component(port_component)?;
match (parts.next(), parts.next()) {
(None, None) => Ok(HandleKind::PortDir { port }),
(Some("status"), None) => Ok(HandleKind::Status { port }),
(Some("descriptor"), None) => Ok(HandleKind::Descriptor { port }),
(Some("control"), None) => Ok(HandleKind::Control { port }),
(Some("config"), None) => Ok(HandleKind::Config { port }),
_ => Err(SysError::new(ENOENT)),
}
}
fn resolve_port_child(&self, port: usize, path: &str) -> SysResult<HandleKind> {
match path {
"status" => Ok(HandleKind::Status { port }),
"descriptor" => Ok(HandleKind::Descriptor { port }),
"control" => Ok(HandleKind::Control { port }),
"config" => Ok(HandleKind::Config { port }),
_ => Err(SysError::new(ENOENT)),
}
}
fn root_listing(&self) -> Vec<u8> {
let controller = lock_controller(&self.controller);
let mut listing = String::new();
for port in 0..controller.port_count() {
let _ = writeln!(&mut listing, "port{}", port + 1);
}
listing.into_bytes()
}
fn config_bytes(&self, port: usize) -> SysResult<Vec<u8>> {
let ctrl = self.controller.lock().map_err(|_| SysError::new(syscall::EIO))?;
let record = ctrl.ports.get(port).ok_or(SysError::new(syscall::ENOENT))?;
if let Some(ref dev) = record.device {
Ok(format!("class={:02x} subclass={:02x} vendor={:04x} device={:04x}\n",
dev.device_class, dev.device_subclass, dev.vendor_id, dev.product_id,
).into_bytes())
} else {
Ok(b"no device\n".to_vec())
}
}
fn status_bytes(&self, port: usize) -> SysResult<Vec<u8>> {
let controller = lock_controller(&self.controller);
let Some(record) = controller.port_record(port) else {
return Err(SysError::new(ENOENT));
};
let status = record
.last_status
.clone()
.unwrap_or_else(|| decode_port_status(record.last_portsc));
let mut out = String::new();
let _ = writeln!(&mut out, "port={}", port + 1);
let _ = writeln!(&mut out, "portsc=0x{:08x}", record.last_portsc);
let _ = writeln!(&mut out, "connected={}", bool_word(status.connected));
let _ = writeln!(&mut out, "enabled={}", bool_word(status.enabled));
let _ = writeln!(&mut out, "suspended={}", bool_word(status.suspended));
let _ = writeln!(&mut out, "over_current={}", bool_word(status.over_current));
let _ = writeln!(&mut out, "reset={}", bool_word(status.reset));
let _ = writeln!(&mut out, "power={}", bool_word(status.power));
let _ = writeln!(&mut out, "low_speed={}", bool_word(status.low_speed));
let _ = writeln!(&mut out, "high_speed={}", bool_word(status.high_speed));
let _ = writeln!(&mut out, "test_mode={}", bool_word(status.test_mode));
let _ = writeln!(&mut out, "indicator={}", bool_word(status.indicator));
let _ = writeln!(
&mut out,
"companion_owned={}",
bool_word(record.companion_owned)
);
if let Some(device) = record.device.as_ref() {
let _ = writeln!(&mut out, "address={}", device.address);
let _ = writeln!(&mut out, "vendor_id=0x{:04x}", device.vendor_id);
let _ = writeln!(&mut out, "product_id=0x{:04x}", device.product_id);
}
if let Some(last_error) = record.last_error.as_ref() {
let _ = writeln!(&mut out, "last_error={}", last_error);
}
Ok(out.into_bytes())
}
fn descriptor_bytes(&self, port: usize) -> SysResult<Vec<u8>> {
let controller = lock_controller(&self.controller);
let Some(record) = controller.port_record(port) else {
return Err(SysError::new(ENOENT));
};
let Some(device) = record.device.as_ref() else {
return Ok(b"state=unenumerated\n".to_vec());
};
let mut out = String::new();
let _ = writeln!(&mut out, "address={}", device.address);
let _ = writeln!(&mut out, "vendor_id=0x{:04x}", device.vendor_id);
let _ = writeln!(&mut out, "product_id=0x{:04x}", device.product_id);
let _ = writeln!(&mut out, "device_class=0x{:02x}", device.device_class);
let _ = writeln!(&mut out, "device_subclass=0x{:02x}", device.device_subclass);
let _ = writeln!(&mut out, "device_protocol=0x{:02x}", device.device_protocol);
let _ = writeln!(&mut out, "max_packet_size0={}", device.max_packet_size0);
let _ = writeln!(
&mut out,
"device_descriptor={}",
hex_encode(&device.device_descriptor)
);
if !device.config_descriptor.is_empty() {
let _ = writeln!(
&mut out,
"config_descriptor={}",
hex_encode(&device.config_descriptor)
);
}
Ok(out.into_bytes())
}
fn handle_control_write(&mut self, port: usize, buf: &[u8]) -> SysResult<Vec<u8>> {
let request = parse_control_request(buf).map_err(|_| SysError::new(EINVAL))?;
let mut controller = lock_controller(&self.controller);
controller
.execute_control_request(port, &request)
.map_err(|_| SysError::new(EINVAL))
}
fn handle_bytes(&self, id: usize) -> SysResult<Vec<u8>> {
if id == SCHEME_ROOT_ID {
return Ok(self.root_listing());
}
let handle = self.handle(id)?;
match &handle.kind {
HandleKind::PortDir { .. } => Ok(b"status\ndescriptor\ncontrol\nconfig\n".to_vec()),
HandleKind::Status { port } => self.status_bytes(*port),
HandleKind::Descriptor { port } => self.descriptor_bytes(*port),
HandleKind::Control { .. } => Ok(handle.response.clone()),
HandleKind::Config { port } => self.config_bytes(*port),
}
}
fn handle_path(&self, id: usize) -> SysResult<String> {
if id == SCHEME_ROOT_ID {
return Ok(format!("{SCHEME_NAME}:/"));
}
let handle = self.handle(id)?;
let path = match handle.kind {
HandleKind::PortDir { port } => format!("{SCHEME_NAME}:/port{}", port + 1),
HandleKind::Status { port } => format!("{SCHEME_NAME}:/port{}/status", port + 1),
HandleKind::Descriptor { port } => {
format!("{SCHEME_NAME}:/port{}/descriptor", port + 1)
}
HandleKind::Control { port } => format!("{SCHEME_NAME}:/port{}/control", port + 1),
HandleKind::Config { port } => format!("{SCHEME_NAME}:/port{}/config", port + 1),
};
Ok(path)
}
}
impl SchemeSync for EhciScheme {
fn scheme_root(&mut self) -> SysResult<usize> {
Ok(SCHEME_ROOT_ID)
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> SysResult<OpenResult> {
let cleaned = path.trim_matches('/');
if cleaned.is_empty() {
if dirfd == SCHEME_ROOT_ID {
return Ok(OpenResult::ThisScheme {
number: SCHEME_ROOT_ID,
flags: NewFdFlags::POSITIONED,
});
}
let kind = self.handle(dirfd)?.kind.clone();
return Ok(OpenResult::ThisScheme {
number: self.alloc_handle(kind),
flags: NewFdFlags::POSITIONED,
});
}
let kind = if dirfd == SCHEME_ROOT_ID {
self.resolve_root_path(cleaned)?
} else {
match self.handle(dirfd)?.kind.clone() {
HandleKind::PortDir { port } => self.resolve_port_child(port, cleaned)?,
_ => return Err(SysError::new(EACCES)),
}
};
Ok(OpenResult::ThisScheme {
number: self.alloc_handle(kind),
flags: NewFdFlags::POSITIONED,
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> SysResult<usize> {
let data = self.handle_bytes(id)?;
copy_with_offset(buf, offset, &data)
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> SysResult<usize> {
let kind = if id == SCHEME_ROOT_ID {
return Err(SysError::new(EROFS));
} else {
self.handle(id)?.kind.clone()
};
match kind {
HandleKind::Control { port } => {
let response = self.handle_control_write(port, buf)?;
if let Some(handle) = self.handles.get_mut(&id) {
handle.response = response;
}
Ok(buf.len())
}
_ => Err(SysError::new(EROFS)),
}
}
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> SysResult<()> {
let data_len = match id {
SCHEME_ROOT_ID => self.root_listing().len(),
_ => self.handle_bytes(id)?.len(),
};
stat.st_mode = if id == SCHEME_ROOT_ID {
MODE_DIR | 0o755
} else {
match self.handle(id)?.kind {
HandleKind::PortDir { .. } => MODE_DIR | 0o755,
HandleKind::Status { .. } | HandleKind::Descriptor { .. } => MODE_FILE | 0o444,
HandleKind::Control { .. } | HandleKind::Config { .. } => MODE_FILE | 0o644,
}
};
stat.st_size = match u64::try_from(data_len) {
Ok(size) => size,
Err(_) => u64::MAX,
};
Ok(())
}
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> SysResult<usize> {
let path = self.handle_path(id)?;
copy_with_offset(buf, 0, path.as_bytes())
}
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> SysResult<()> {
if id != SCHEME_ROOT_ID {
let _ = self.handle(id)?;
}
Ok(())
}
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> SysResult<usize> {
if id != SCHEME_ROOT_ID {
let _ = self.handle(id)?;
}
Ok(0)
}
fn fevent(&mut self, id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> SysResult<EventFlags> {
if id != SCHEME_ROOT_ID {
let _ = self.handle(id)?;
}
Ok(EventFlags::empty())
}
fn on_close(&mut self, id: usize) {
if id != SCHEME_ROOT_ID {
self.handles.remove(&id);
}
}
}
fn init_logging() {
let _ = log::set_logger(&LOGGER);
log::set_max_level(LevelFilter::Info);
}
fn bool_word(value: bool) -> &'static str {
if value { "yes" } else { "no" }
}
fn lock_controller(shared: &Arc<Mutex<EhciController>>) -> MutexGuard<'_, EhciController> {
match shared.lock() {
Ok(guard) => guard,
Err(poisoned) => {
warn!("ehcid: controller mutex was poisoned; continuing with recovered state");
poisoned.into_inner()
}
}
}
fn parse_mmio_bar(config: &[u8]) -> Result<u64, String> {
let Some(bar0) = read_config_dword(config, 0x10) else {
return Err("PCI config space is too short to contain BAR0".to_string());
};
if bar0 == 0 {
return Err("BAR0 is zero".to_string());
}
if bar0 & 0x1 != 0 {
return Err("BAR0 is I/O space; EHCI requires MMIO".to_string());
}
let mut base = u64::from(bar0 & 0xFFFF_FFF0);
if bar0 & 0x6 == 0x4 {
let Some(bar1) = read_config_dword(config, 0x14) else {
return Err(
"PCI config space is too short to contain BAR1 for a 64-bit BAR".to_string(),
);
};
base |= u64::from(bar1) << 32;
}
Ok(base)
}
fn read_config_dword(config: &[u8], offset: usize) -> Option<u32> {
if config.len() < offset.saturating_add(4) {
return None;
}
Some(u32::from_le_bytes([
config[offset],
config[offset + 1],
config[offset + 2],
config[offset + 3],
]))
}
fn ensure_dma_segment(has_64bit: bool, phys_addrs: &[u64]) -> Result<u32, String> {
let mut segment = None;
for &phys_addr in phys_addrs {
let current = dma_segment(phys_addr);
if !has_64bit && current != 0 {
return Err(format!(
"controller is 32-bit-only but DMA buffer landed above 4GB: 0x{phys_addr:016x}"
));
}
match segment {
Some(existing) if existing != current => {
return Err(format!(
"EHCI data structures must share one DMA segment, found 0x{existing:08x} and 0x{current:08x}"
));
}
None => segment = Some(current),
_ => {}
}
}
Ok(segment.unwrap_or(0))
}
fn low32(value: u64) -> u32 {
(value & u64::from(u32::MAX)) as u32
}
fn dma_segment(value: u64) -> u32 {
(value >> 32) as u32
}
fn interrupt_threshold(microframes: u8) -> u32 {
(u32::from(microframes) & 0xFF) << 16
}
fn init_frame_list(frame_list: &mut DmaBuffer) {
let ptr = frame_list.as_mut_ptr() as *mut u32;
for index in 0..FRAME_LIST_LEN {
unsafe {
std::ptr::write_volatile(ptr.add(index), QH_TERMINATE);
}
}
}
fn decode_port_status(portsc: u32) -> PortStatus {
let line_status = portsc & PORT_LINE_STATUS;
PortStatus {
connected: portsc & PORT_CONNECT != 0,
enabled: portsc & PORT_ENABLE != 0,
suspended: portsc & PORT_SUSPEND != 0,
over_current: portsc & PORT_OVER_CURRENT_ACTIVE != 0,
reset: portsc & PORT_RESET != 0,
power: portsc & PORT_POWER != 0,
low_speed: line_status == PORT_LINE_STATUS_K,
high_speed: portsc & PORT_ENABLE != 0,
test_mode: portsc & PORT_TEST_CONTROL != 0,
indicator: portsc & PORT_INDICATOR != 0,
}
}
fn read_td_token(buffer: &DmaBuffer, index: usize) -> u32 {
let td_ptr = buffer.as_ptr() as *const TransferDescriptor;
unsafe { std::ptr::read_volatile(std::ptr::addr_of!((*td_ptr.add(index)).token)) }
}
fn dma_write_bytes(buffer: &mut DmaBuffer, data: &[u8]) {
if data.is_empty() {
return;
}
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), buffer.as_mut_ptr(), data.len());
}
}
fn dma_read_bytes(buffer: &DmaBuffer, output: &mut [u8]) {
if output.is_empty() {
return;
}
unsafe {
std::ptr::copy_nonoverlapping(buffer.as_ptr(), output.as_mut_ptr(), output.len());
}
}
fn map_td_error(token: u32) -> UsbError {
if token & TD_BABBLE != 0 {
UsbError::Babble
} else if token & TD_HALTED != 0 {
UsbError::Stall
} else if token & (TD_BUFERR | TD_XACTERR | TD_MISSED) != 0 {
UsbError::DataError
} else {
UsbError::IoError
}
}
fn setup_packet_bytes(setup: &SetupPacket) -> [u8; 8] {
let value = setup.value.to_le_bytes();
let index = setup.index.to_le_bytes();
let length = setup.length.to_le_bytes();
[
setup.request_type,
setup.request,
value[0],
value[1],
index[0],
index[1],
length[0],
length[1],
]
}
fn parse_control_request(buf: &[u8]) -> Result<ControlRequest, String> {
let text =
std::str::from_utf8(buf).map_err(|err| format!("control request is not UTF-8: {err}"))?;
let mut request_type = None;
let mut request = None;
let mut value = None;
let mut index = None;
let mut length = None;
let mut data = None;
for token in text.split_whitespace() {
let Some((key, raw_value)) = token.split_once('=') else {
return Err(format!("invalid token '{token}', expected key=value"));
};
match key {
"request_type" | "bmRequestType" => {
request_type = Some(parse_numeric::<u8>(raw_value)?)
}
"request" | "bRequest" => request = Some(parse_numeric::<u8>(raw_value)?),
"value" | "wValue" => value = Some(parse_numeric::<u16>(raw_value)?),
"index" | "wIndex" => index = Some(parse_numeric::<u16>(raw_value)?),
"length" | "wLength" => length = Some(parse_numeric::<u16>(raw_value)?),
"data" => data = Some(parse_hex_bytes(raw_value)?),
_ => return Err(format!("unsupported control field '{key}'")),
}
}
let request_type = request_type.ok_or_else(|| "missing request_type".to_string())?;
let request = request.ok_or_else(|| "missing request".to_string())?;
let value = value.ok_or_else(|| "missing value".to_string())?;
let index = index.ok_or_else(|| "missing index".to_string())?;
let length = length.ok_or_else(|| "missing length".to_string())?;
if usize::from(length) > MAX_SCHEME_CONTROL_BYTES || usize::from(length) > 0x7FFF {
return Err(format!(
"requested control payload {} is outside the supported single-qTD range",
length
));
}
let payload = if request_type & 0x80 != 0 {
if data
.as_ref()
.map(|bytes| !bytes.is_empty())
.unwrap_or(false)
{
return Err(
"IN control requests must not provide an outgoing data payload".to_string(),
);
}
Vec::new()
} else {
let bytes = data.unwrap_or_default();
if bytes.len() != usize::from(length) {
return Err(format!(
"OUT control payload length mismatch: expected {}, got {}",
length,
bytes.len()
));
}
bytes
};
Ok(ControlRequest {
request_type,
request,
value,
index,
length,
data: payload,
})
}
fn parse_numeric<T>(value: &str) -> Result<T, String>
where
T: TryFrom<u64>,
{
let parsed = if let Some(hex) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
u64::from_str_radix(hex, 16).map_err(|err| format!("invalid hex value '{value}': {err}"))?
} else {
value
.parse::<u64>()
.map_err(|err| format!("invalid integer value '{value}': {err}"))?
};
T::try_from(parsed).map_err(|_| format!("value '{value}' is out of range"))
}
fn parse_hex_bytes(value: &str) -> Result<Vec<u8>, String> {
let cleaned: String = value
.chars()
.filter(|ch| !ch.is_ascii_whitespace() && *ch != ':' && *ch != '-')
.collect();
if cleaned.is_empty() {
return Ok(Vec::new());
}
if cleaned.len() % 2 != 0 {
return Err(format!("hex payload '{value}' has an odd number of digits"));
}
let mut bytes = Vec::with_capacity(cleaned.len() / 2);
for chunk in cleaned.as_bytes().chunks(2) {
let chunk = std::str::from_utf8(chunk)
.map_err(|err| format!("invalid hex payload '{value}': {err}"))?;
let byte = u8::from_str_radix(chunk, 16)
.map_err(|err| format!("invalid hex byte '{chunk}' in payload '{value}': {err}"))?;
bytes.push(byte);
}
Ok(bytes)
}
fn hex_encode(bytes: &[u8]) -> String {
let mut out = String::new();
for (index, byte) in bytes.iter().enumerate() {
if index != 0 {
out.push(' ');
}
let _ = write!(&mut out, "{byte:02x}");
}
out
}
fn copy_with_offset(buf: &mut [u8], offset: u64, data: &[u8]) -> SysResult<usize> {
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
if offset >= data.len() {
return Ok(0);
}
let count = (data.len() - offset).min(buf.len());
buf[..count].copy_from_slice(&data[offset..offset + count]);
Ok(count)
}
fn address_port(controller: &EhciController, address: u8) -> Option<usize> {
controller.ports.iter().position(|record| {
record
.device
.as_ref()
.map(|device| device.address == address)
.unwrap_or(false)
})
}
fn poll_ports_loop(controller: Arc<Mutex<EhciController>>) {
loop {
{
let mut controller = lock_controller(&controller);
controller.poll_ports_once();
}
thread::sleep(PORT_POLL_INTERVAL);
}
}
fn run_scheme(controller: Arc<Mutex<EhciController>>) -> Result<(), String> {
let socket =
Socket::create().map_err(|err| format!("failed to create scheme socket: {err}"))?;
let mut scheme = EhciScheme::new(controller);
let mut state = SchemeState::new();
register_sync_scheme(&socket, SCHEME_NAME, &mut scheme)
.map_err(|err| format!("failed to register scheme:{SCHEME_NAME}: {err}"))?;
libredox::call::setrens(0, 0)
.map_err(|err| format!("failed to enter null namespace: {err}"))?;
info!("ehcid: registered /scheme/{SCHEME_NAME}");
info!("ehcid: ready — polling for device connections");
loop {
let request = match socket.next_request(SignalBehavior::Restart) {
Ok(Some(request)) => request,
Ok(None) => {
info!("ehcid: scheme socket closed, shutting down");
break;
}
Err(err) => return Err(format!("failed to read scheme request: {err}")),
};
if let redox_scheme::RequestKind::Call(request) = request.kind() {
let response = request.handle_sync(&mut scheme, &mut state);
socket
.write_response(response, SignalBehavior::Restart)
.map_err(|err| format!("failed to write scheme response: {err}"))?;
}
}
Ok(())
}
fn main() {
init_logging();
let channel_fd = match env::var("PCID_CLIENT_CHANNEL") {
Ok(raw) => match raw.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 = match env::var("PCID_DEVICE_PATH") {
Ok(path) if !path.is_empty() => path,
Ok(_) => {
error!("PCID_DEVICE_PATH is empty");
process::exit(1);
}
Err(_) => {
error!("PCID_DEVICE_PATH not set");
process::exit(1);
}
};
let controller = match EhciController::new(&device_path, channel_fd) {
Ok(controller) => Arc::new(Mutex::new(controller)),
Err(err) => {
error!("{err}");
process::exit(1);
}
};
if let Err(err) = thread::Builder::new()
.name("ehci-port-poll".to_string())
.spawn({
let controller = Arc::clone(&controller);
move || poll_ports_loop(controller)
})
{
error!("failed to spawn EHCI port polling thread: {err}");
process::exit(1);
}
if let Err(err) = run_scheme(controller) {
error!("{err}");
process::exit(1);
}
}