#[cfg(test)]
mod tests;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use core::fmt;
use crate::bus::usb::{
Completion, DeviceAddress, HCD_RANK, MAX_PORTS, SetupPacket, Speed, Status, UsbBus, buses,
};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{AccessKind, LazyHandle};
use crate::core::space::{
AccessConstraints, AddressSpace, MemAttrs, MemOps, MemResult, Region, RegionRef, RequesterId,
};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicU32, AtomicU64, LockRank, Mutex, Ordering};
use crate::core::value::Width;
use crate::core::wire::{Level, WireSource};
use crate::machine::realize::{BindCtx, Instance};
const CLASS_NAME: &str = "usb.ehci";
pub(crate) const STATE_VERSION: u32 = 1;
pub const DEFAULT_CAPLENGTH: u8 = 0x20;
pub const HCIVERSION: u16 = 0x0100;
pub const REGISTER_BYTES: u64 = 0x100;
const CMD_RS: u32 = 1 << 0;
const CMD_HCRESET: u32 = 1 << 1;
const CMD_FLS_SHIFT: u32 = 2;
const CMD_PSE: u32 = 1 << 4;
const CMD_ASE: u32 = 1 << 5;
const CMD_IAAD: u32 = 1 << 6;
const CMD_LHCR: u32 = 1 << 7;
const CMD_MASK: u32 = CMD_RS
| CMD_HCRESET
| (0x3 << CMD_FLS_SHIFT)
| CMD_PSE
| CMD_ASE
| CMD_IAAD
| CMD_LHCR
| (0x3 << 8)
| (1 << 11)
| (0xff << 16);
const CMD_RESET_VALUE: u32 = 0x0008_0000;
pub const STS_USBINT: u32 = 1 << 0;
pub const STS_USBERRINT: u32 = 1 << 1;
pub const STS_PORT_CHANGE: u32 = 1 << 2;
pub const STS_FLR: u32 = 1 << 3;
pub const STS_HSE: u32 = 1 << 4;
pub const STS_IAA: u32 = 1 << 5;
pub const STS_HCHALTED: u32 = 1 << 12;
pub const STS_RECLAMATION: u32 = 1 << 13;
pub const STS_PSS: u32 = 1 << 14;
pub const STS_ASS: u32 = 1 << 15;
pub const STS_W1C: u32 = STS_USBINT | STS_USBERRINT | STS_PORT_CHANGE | STS_FLR | STS_HSE | STS_IAA;
pub const PORT_CCS: u32 = 1 << 0;
pub const PORT_CSC: u32 = 1 << 1;
pub const PORT_PE: u32 = 1 << 2;
pub const PORT_PEC: u32 = 1 << 3;
const PORT_OCA: u32 = 1 << 4;
const PORT_OCC: u32 = 1 << 5;
const PORT_FPR: u32 = 1 << 6;
const PORT_SUSPEND: u32 = 1 << 7;
pub const PORT_RESET: u32 = 1 << 8;
const PORT_LS_SHIFT: u32 = 10;
pub const PORT_PP: u32 = 1 << 12;
pub const PORT_OWNER: u32 = 1 << 13;
const PORT_W1C: u32 = PORT_CSC | PORT_PEC | PORT_OCC;
const PORT_WRITABLE: u32 =
PORT_PE | PORT_FPR | PORT_SUSPEND | PORT_RESET | PORT_OWNER | (0x3 << 14);
const LINK_T: u32 = 1 << 0;
const LINK_TYP_SHIFT: u32 = 1;
const TYP_QH: u32 = 1;
const LINK_ADDR: u32 = !0x1f;
const EPCHAR_ADDR: u32 = 0x7f;
const EPCHAR_I: u32 = 1 << 7;
const EPCHAR_EP_SHIFT: u32 = 8;
const EPCHAR_EPS_SHIFT: u32 = 12;
const EPCHAR_DTC: u32 = 1 << 14;
const EPCHAR_H: u32 = 1 << 15;
const EPCHAR_MPS_SHIFT: u32 = 16;
const EPCHAR_MPS_MASK: u32 = 0x7ff;
const TOKEN_ACTIVE: u32 = 1 << 7;
const TOKEN_HALTED: u32 = 1 << 6;
const TOKEN_DBE: u32 = 1 << 5;
const TOKEN_BABBLE: u32 = 1 << 4;
const TOKEN_XACTERR: u32 = 1 << 3;
const TOKEN_STATUS: u32 = 0xff;
const TOKEN_PID_SHIFT: u32 = 8;
const TOKEN_IOC: u32 = 1 << 15;
const TOKEN_CPAGE_SHIFT: u32 = 12;
const TOKEN_BYTES_SHIFT: u32 = 16;
const TOKEN_BYTES_MASK: u32 = 0x7fff;
const TOKEN_TOGGLE: u32 = 1 << 31;
const PID_OUT: u32 = 0;
const PID_IN: u32 = 1;
const PID_SETUP: u32 = 2;
const PAGE_SIZE: u32 = 4096;
const BUFFER_PAGES: usize = 5;
const QH_DWORDS: usize = 12;
const QH_OVERLAY: usize = 4;
const QTD_DWORDS: usize = 8;
pub const MAX_ASYNC_QH: usize = 64;
pub const MAX_PERIODIC_NODES: usize = 64;
pub const MAX_QTD_ADVANCE: usize = 32;
pub const MAX_PACKETS: usize = 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Params {
pub ports: u8,
pub microframe_ticks: u64,
pub caplength: u8,
pub dual_role: bool,
}
impl Default for Params {
fn default() -> Params {
Params {
ports: 1,
microframe_ticks: 7500,
caplength: DEFAULT_CAPLENGTH,
dual_role: false,
}
}
}
pub const MODE_IDLE: u32 = 0;
pub const MODE_DEVICE: u32 = 2;
pub const MODE_HOST: u32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Regs {
ticks: u64,
usbcmd: u32,
usbsts: u32,
usbintr: u32,
frindex: u32,
ctrldssegment: u32,
periodic_base: u32,
async_addr: u32,
configflag: u32,
portsc: [u32; MAX_PORTS],
usbmode: u32,
otgsc: u32,
burstsize: u32,
txfilltuning: u32,
}
impl Regs {
fn reset(ports: u8, dual_role: bool) -> Regs {
let mut regs = Regs {
ticks: 0,
usbcmd: CMD_RESET_VALUE,
usbsts: STS_HCHALTED,
usbintr: 0,
frindex: 0,
ctrldssegment: 0,
periodic_base: 0,
async_addr: 0,
configflag: 0,
portsc: [0; MAX_PORTS],
usbmode: if dual_role { MODE_IDLE } else { MODE_HOST },
otgsc: 0,
burstsize: 0x0000_1010,
txfilltuning: 0,
};
for port in regs.portsc.iter_mut().take(usize::from(ports)) {
*port = PORT_PP | PORT_OWNER;
}
regs
}
fn running(&self, dual_role: bool) -> bool {
self.usbcmd & CMD_RS != 0
&& self.usbsts & STS_HCHALTED == 0
&& (!dual_role || self.usbmode & 0x3 == MODE_HOST)
}
fn frame_list_size(&self) -> u32 {
match (self.usbcmd >> CMD_FLS_SHIFT) & 0x3 {
0 => 1024,
1 => 512,
2 => 256,
_ => 1024,
}
}
}
pub struct Hcd {
bus: Arc<UsbBus>,
params: Params,
regs: Mutex<Regs>,
ticks: AtomicU64,
next_event: AtomicU64,
space: Mutex<Option<Weak<AddressSpace>>>,
requester: AtomicU32,
irq: Mutex<Option<WireSource>>,
irq_level: AtomicU32,
lazy: Mutex<Option<LazyHandle>>,
}
const NO_EVENT: u64 = u64::MAX;
impl fmt::Debug for Hcd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Hcd");
s.field("ports", &self.params.ports);
match self.regs.try_lock() {
Some(regs) => s.field("regs", &*regs).finish_non_exhaustive(),
None => s.field("regs", &"<in use>").finish_non_exhaustive(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum After {
Nothing,
Reset,
Port(u8),
AllPorts,
}
impl Hcd {
#[must_use]
pub fn new(bus: Arc<UsbBus>, params: Params) -> Hcd {
let params = Params {
ports: params.ports.clamp(1, MAX_PORTS as u8),
microframe_ticks: params.microframe_ticks.max(1),
..params
};
Hcd {
bus,
params,
regs: Mutex::with_rank(HCD_RANK, Regs::reset(params.ports, params.dual_role)),
ticks: AtomicU64::new(0),
next_event: AtomicU64::new(NO_EVENT),
space: Mutex::with_rank(LockRank::WIRE, None),
requester: AtomicU32::new(RequesterId::ANONYMOUS.0),
irq: Mutex::with_rank(LockRank::WIRE, None),
irq_level: AtomicU32::new(0),
lazy: Mutex::with_rank(LockRank::WIRE, None),
}
}
#[must_use]
pub fn params(&self) -> Params {
self.params
}
#[must_use]
pub fn bus(&self) -> &Arc<UsbBus> {
&self.bus
}
#[must_use]
pub fn ticks(&self) -> u64 {
self.ticks.load(Ordering::Relaxed)
}
#[must_use]
pub fn irq_level(&self) -> Level {
Level::from_bool(self.irq_level.load(Ordering::Relaxed) != 0)
}
#[must_use]
pub fn status(&self) -> u32 {
self.regs.lock().usbsts
}
#[must_use]
pub fn portsc(&self, port: u8) -> u32 {
self.regs
.lock()
.portsc
.get(usize::from(port))
.copied()
.unwrap_or(0)
}
pub fn attach_space(&self, space: &Arc<AddressSpace>, requester: RequesterId) {
*self.space.lock() = Some(Arc::downgrade(space));
self.requester.store(requester.0, Ordering::Relaxed);
}
pub fn connect_irq(&self, source: WireSource) {
*self.irq.lock() = Some(source);
self.refresh_irq();
}
pub fn attach_lazy(&self, handle: LazyHandle) {
*self.lazy.lock() = Some(handle);
}
fn space(&self) -> Option<Arc<AddressSpace>> {
self.space.lock().as_ref().and_then(Weak::upgrade)
}
fn attrs(&self) -> MemAttrs {
MemAttrs::DEFAULT.with_requester(RequesterId(self.requester.load(Ordering::Relaxed)))
}
pub fn sync_for(&self, attrs: MemAttrs) {
let handle = self.lazy.lock().clone();
let Some(handle) = handle else {
return;
};
let kind = if attrs.debug {
AccessKind::Debug
} else {
AccessKind::Guest
};
let _ = handle.sync(kind);
}
fn publish(&self, regs: &Regs) {
self.ticks.store(regs.ticks, Ordering::Relaxed);
let next = if regs.running(self.params.dual_role) {
let mf = self.params.microframe_ticks;
(regs.ticks / mf + 1).saturating_mul(mf)
} else {
NO_EVENT
};
self.next_event.store(next, Ordering::Relaxed);
}
#[must_use]
pub fn next_event_tick(&self) -> Option<u64> {
match self.next_event.load(Ordering::Relaxed) {
NO_EVENT => None,
tick => Some(tick),
}
}
pub fn refresh_irq(&self) {
let asserted = {
let regs = self.regs.lock();
regs.usbsts & regs.usbintr & STS_W1C != 0
};
self.irq_level.store(u32::from(asserted), Ordering::Relaxed);
let port = self.irq.lock().clone();
if let Some(port) = port {
port.set(Level::from_bool(asserted));
}
}
#[must_use]
pub fn read_cap(&self, offset: u64) -> u32 {
match offset {
0x00 => u32::from(self.params.caplength) | (u32::from(HCIVERSION) << 16),
0x04 => u32::from(self.params.ports),
0x08 => 0x0000_0002,
0x0c => 0,
_ => 0,
}
}
#[must_use]
pub fn read_op(&self, offset: u64) -> u32 {
let regs = self.regs.lock();
match offset {
0x00 => regs.usbcmd,
0x04 => regs.usbsts,
0x08 => regs.usbintr,
0x0c => regs.frindex,
0x10 => regs.ctrldssegment,
0x14 => regs.periodic_base,
0x18 => regs.async_addr,
0x40 => regs.configflag,
_ => {
if let Some(port) = Hcd::port_at(offset) {
return regs.portsc.get(usize::from(port)).copied().unwrap_or(0);
}
0
}
}
}
#[must_use]
pub fn port_at(offset: u64) -> Option<u8> {
if !(0x44..0x44 + 4 * MAX_PORTS as u64).contains(&offset) || !offset.is_multiple_of(4) {
return None;
}
Some(((offset - 0x44) / 4) as u8)
}
pub fn write_op(&self, offset: u64, value: u32) -> After {
let mut regs = self.regs.lock();
match offset {
0x00 => {
if value & CMD_HCRESET != 0 {
return After::Reset;
}
let was_running = regs.usbcmd & CMD_RS != 0;
regs.usbcmd = value & CMD_MASK & !CMD_HCRESET;
let running = regs.usbcmd & CMD_RS != 0;
if running != was_running {
if running {
regs.usbsts &= !STS_HCHALTED;
} else {
regs.usbsts |= STS_HCHALTED;
}
}
Hcd::sync_schedule_status(&mut regs);
self.publish(®s);
After::Nothing
}
0x04 => {
regs.usbsts &= !(value & STS_W1C);
After::Nothing
}
0x08 => {
regs.usbintr = value & STS_W1C;
After::Nothing
}
0x0c => {
if regs.usbsts & STS_HCHALTED != 0 {
regs.frindex = value & 0x3fff;
self.publish(®s);
}
After::Nothing
}
0x10 => {
regs.ctrldssegment = value;
After::Nothing
}
0x14 => {
regs.periodic_base = value & !0xfff;
After::Nothing
}
0x18 => {
regs.async_addr = value & LINK_ADDR;
After::Nothing
}
0x40 => {
let was = regs.configflag & 1;
regs.configflag = value & 1;
if regs.configflag != was {
let configured = regs.configflag != 0;
for port in regs.portsc.iter_mut().take(usize::from(self.params.ports)) {
if configured {
*port &= !PORT_OWNER;
} else {
*port = (*port | PORT_OWNER) & !PORT_PE;
}
}
}
After::AllPorts
}
_ => {
let Some(port) = Hcd::port_at(offset) else {
return After::Nothing;
};
if usize::from(port) >= usize::from(self.params.ports) {
return After::Nothing;
}
let index = usize::from(port);
let old = regs.portsc[index];
let mut new = old & !(value & PORT_W1C);
new = (new & !PORT_WRITABLE) | (value & PORT_WRITABLE);
new &= !PORT_OCA;
regs.portsc[index] = new;
After::Port(port)
}
}
}
fn sync_schedule_status(regs: &mut Regs) {
let halted = regs.usbsts & STS_HCHALTED != 0;
regs.usbsts &= !(STS_PSS | STS_ASS);
if !halted {
if regs.usbcmd & CMD_PSE != 0 {
regs.usbsts |= STS_PSS;
}
if regs.usbcmd & CMD_ASE != 0 {
regs.usbsts |= STS_ASS;
}
}
}
#[must_use]
pub fn read_extra(&self, which: Extra) -> u32 {
let regs = self.regs.lock();
match which {
Extra::UsbMode => regs.usbmode,
Extra::Otgsc => regs.otgsc,
Extra::BurstSize => regs.burstsize,
Extra::TxFillTuning => regs.txfilltuning,
}
}
pub fn write_extra(&self, which: Extra, value: u32) -> After {
let mut regs = self.regs.lock();
match which {
Extra::UsbMode => {
if regs.usbmode & 0x3 == MODE_IDLE {
regs.usbmode = value & 0x1f;
} else {
regs.usbmode = (regs.usbmode & 0x3) | (value & 0x1c);
}
Hcd::sync_schedule_status(&mut regs);
self.publish(®s);
}
Extra::Otgsc => {
regs.otgsc = value & 0x7f7f_7f7f;
}
Extra::BurstSize => regs.burstsize = value & 0x0000_ffff,
Extra::TxFillTuning => regs.txfilltuning = value & 0x003f_3fff,
}
After::Nothing
}
pub fn act(&self, after: After) {
match after {
After::Nothing => {}
After::Reset => self.controller_reset(),
After::Port(port) => self.settle_port(port),
After::AllPorts => {
for port in 0..self.params.ports {
self.settle_port(port);
}
}
}
self.refresh_irq();
}
fn controller_reset(&self) {
{
let mut regs = self.regs.lock();
let ticks = regs.ticks;
*regs = Regs {
ticks,
..Regs::reset(self.params.ports, self.params.dual_role)
};
self.publish(®s);
}
for port in 0..self.params.ports {
self.bus.set_enabled(port, false);
}
for port in 0..self.params.ports {
self.settle_port(port);
}
}
fn settle_port(&self, port: u8) {
let index = usize::from(port);
if index >= usize::from(self.params.ports) {
return;
}
let connected = self.bus.connected(port);
let speed = self.bus.speed(port);
let plugged_changed = self.bus.take_change(port);
enum Act {
Nothing,
Disable,
}
let act = {
let mut regs = self.regs.lock();
let mut sc = regs.portsc[index];
let configured = regs.configflag & 1 != 0;
if plugged_changed {
sc |= PORT_CSC;
regs.usbsts |= STS_PORT_CHANGE;
}
sc = (sc & !PORT_CCS) | if connected { PORT_CCS } else { 0 };
sc |= PORT_PP;
if !configured {
sc |= PORT_OWNER;
}
sc &= !(0x3 << PORT_LS_SHIFT);
if connected && speed == Some(Speed::Low) {
sc |= 0x1 << PORT_LS_SHIFT;
if configured {
sc |= PORT_OWNER;
}
}
let mut act = Act::Nothing;
if !connected {
if sc & PORT_PE != 0 {
sc |= PORT_PEC;
}
sc &= !(PORT_PE | PORT_RESET | PORT_SUSPEND);
act = Act::Disable;
} else if sc & PORT_RESET != 0 {
sc &= !PORT_PE;
act = Act::Disable;
} else if sc & PORT_OWNER != 0 {
sc &= !PORT_PE;
act = Act::Disable;
}
regs.portsc[index] = sc;
act
};
match act {
Act::Nothing => {}
Act::Disable => self.bus.set_enabled(port, false),
}
}
pub fn finish_reset(&self, port: u8) {
let index = usize::from(port);
self.bus.reset_port(port);
let speed = self.bus.speed(port);
let keep = {
let mut regs = self.regs.lock();
let mut sc = regs.portsc[index];
sc &= !PORT_RESET;
let keep = match speed {
Some(Speed::High) => {
if sc & PORT_PE == 0 {
sc |= PORT_PEC;
}
sc |= PORT_PE;
true
}
Some(_) => {
sc &= !(PORT_PE | PORT_CCS);
sc |= PORT_OWNER | PORT_CSC;
regs.usbsts |= STS_PORT_CHANGE;
false
}
None => {
sc &= !PORT_PE;
false
}
};
regs.portsc[index] = sc;
keep
};
self.bus.set_enabled(port, keep);
}
pub fn advance_to(&self, target: u64) {
loop {
{
let mut regs = self.regs.lock();
if !regs.running(self.params.dual_role) {
regs.ticks = regs.ticks.max(target);
self.publish(®s);
return;
}
let mf = self.params.microframe_ticks;
let next = (regs.ticks / mf + 1).saturating_mul(mf);
if next > target {
regs.ticks = regs.ticks.max(target);
self.publish(®s);
return;
}
regs.ticks = next;
self.publish(®s);
}
self.microframe();
}
}
fn microframe(&self) {
if self.bus.any_change() {
for port in 0..self.params.ports {
self.settle_port(port);
}
}
let (frindex, periodic, asynchronous, periodic_base, async_addr, doorbell) = {
let mut regs = self.regs.lock();
let size = regs.frame_list_size();
let next = (regs.frindex + 1) & 0x3fff;
if (next / 8).is_multiple_of(size) && (regs.frindex / 8) % size == size - 1 {
regs.usbsts |= STS_FLR;
}
regs.frindex = next;
Hcd::sync_schedule_status(&mut regs);
(
regs.frindex,
regs.usbcmd & CMD_PSE != 0,
regs.usbcmd & CMD_ASE != 0,
regs.periodic_base,
regs.async_addr,
regs.usbcmd & CMD_IAAD != 0,
)
};
let Some(space) = self.space() else {
self.refresh_irq();
return;
};
if periodic && periodic_base != 0 {
self.walk_periodic(&space, frindex, periodic_base);
}
if asynchronous && async_addr != 0 {
self.walk_async(&space, async_addr);
}
if doorbell && asynchronous {
let mut regs = self.regs.lock();
regs.usbcmd &= !CMD_IAAD;
regs.usbsts |= STS_IAA;
}
self.refresh_irq();
}
fn walk_periodic(&self, space: &AddressSpace, frindex: u32, base: u32) {
let size = {
let regs = self.regs.lock();
regs.frame_list_size()
};
let index = (frindex / 8) % size;
let microframe = frindex % 8;
let Some(mut link) = self.read32(space, base.wrapping_add(index * 4)) else {
self.host_system_error();
return;
};
for _ in 0..MAX_PERIODIC_NODES {
if link & LINK_T != 0 {
return;
}
let node = link & LINK_ADDR;
if node == 0 {
return;
}
let typ = (link >> LINK_TYP_SHIFT) & 0x3;
let next = match self.read32(space, node) {
Some(next) => next,
None => {
self.host_system_error();
return;
}
};
if typ == TYP_QH {
let smask = match self.read32(space, node.wrapping_add(8)) {
Some(caps) => caps & 0xff,
None => {
self.host_system_error();
return;
}
};
if smask & (1 << microframe) != 0 {
self.execute_qh(space, node);
}
}
link = next;
}
}
fn walk_async(&self, space: &AddressSpace, head: u32) {
let mut node = head & LINK_ADDR;
for step in 0..MAX_ASYNC_QH {
if node == 0 {
return;
}
self.execute_qh(space, node);
let Some(link) = self.read32(space, node) else {
self.host_system_error();
return;
};
if link & LINK_T != 0 {
return;
}
let next = link & LINK_ADDR;
if next == head & LINK_ADDR {
return;
}
node = next;
let _ = step;
}
}
fn execute_qh(&self, space: &AddressSpace, addr: u32) {
let mut qh = [0u32; QH_DWORDS];
for (i, slot) in qh.iter_mut().enumerate() {
let Some(value) = self.read32(space, addr.wrapping_add((i * 4) as u32)) else {
self.host_system_error();
return;
};
*slot = value;
}
let epchar = qh[1];
let device = DeviceAddress((epchar & EPCHAR_ADDR) as u8);
let endpoint = ((epchar >> EPCHAR_EP_SHIFT) & 0xf) as u8;
let mps = ((epchar >> EPCHAR_MPS_SHIFT) & EPCHAR_MPS_MASK).max(1);
let dtc = epchar & EPCHAR_DTC != 0;
let _ = (epchar & EPCHAR_H, (epchar >> EPCHAR_EPS_SHIFT) & 0x3);
let mut dirty = false;
let mut advanced = 0usize;
loop {
if qh[QH_OVERLAY + 2] & TOKEN_HALTED != 0 {
break;
}
if qh[QH_OVERLAY + 2] & TOKEN_ACTIVE == 0 {
if advanced >= MAX_QTD_ADVANCE {
break;
}
if epchar & EPCHAR_I != 0 {
break;
}
let candidate = if qh[QH_OVERLAY] & LINK_T == 0 {
qh[QH_OVERLAY] & LINK_ADDR
} else if advanced == 0 {
qh[3] & LINK_ADDR
} else {
0
};
if candidate == 0 {
break;
}
let mut qtd = [0u32; QTD_DWORDS];
let mut faulted = false;
for (i, slot) in qtd.iter_mut().enumerate() {
match self.read32(space, candidate.wrapping_add((i * 4) as u32)) {
Some(value) => *slot = value,
None => {
faulted = true;
break;
}
}
}
if faulted {
self.host_system_error();
break;
}
if qtd[2] & TOKEN_ACTIVE == 0 {
break;
}
let toggle = if dtc {
qtd[2] & TOKEN_TOGGLE
} else {
qh[QH_OVERLAY + 2] & TOKEN_TOGGLE
};
qh[3] = candidate;
qh[QH_OVERLAY] = qtd[0];
qh[QH_OVERLAY + 1] = qtd[1];
qh[QH_OVERLAY + 2] = (qtd[2] & !TOKEN_TOGGLE) | toggle;
qh[QH_OVERLAY + 3..QH_DWORDS].copy_from_slice(&qtd[3..QTD_DWORDS]);
advanced += 1;
}
let outcome = self.run_transfer(space, &mut qh, device, endpoint, mps);
dirty = true;
if qh[QH_OVERLAY + 2] & TOKEN_ACTIVE != 0 {
break;
}
let current = qh[3] & LINK_ADDR;
if current != 0 {
let _ = self.write32(space, current.wrapping_add(8), qh[QH_OVERLAY + 2]);
let _ = self.write32(space, current.wrapping_add(12), qh[QH_OVERLAY + 3]);
}
if outcome.halted {
break;
}
if outcome.short_packet && qh[QH_OVERLAY + 1] & LINK_T == 0 {
qh[QH_OVERLAY] = qh[QH_OVERLAY + 1];
}
}
if dirty {
for (i, value) in qh.iter().enumerate().skip(3) {
if self
.write32(space, addr.wrapping_add((i * 4) as u32), *value)
.is_none()
{
self.host_system_error();
return;
}
}
}
}
fn run_transfer(
&self,
space: &AddressSpace,
qh: &mut [u32; QH_DWORDS],
device: DeviceAddress,
endpoint: u8,
mps: u32,
) -> Outcome {
let mut token = qh[QH_OVERLAY + 2];
let pid = (token >> TOKEN_PID_SHIFT) & 0x3;
let mut total = (token >> TOKEN_BYTES_SHIFT) & TOKEN_BYTES_MASK;
let mut page = ((token >> TOKEN_CPAGE_SHIFT) & 0x7) as usize;
let mut offset = qh[QH_OVERLAY + 3] & (PAGE_SIZE - 1);
let mut toggle = token & TOKEN_TOGGLE != 0;
let ioc = token & TOKEN_IOC != 0;
let mut outcome = Outcome::default();
let mut status = 0u32;
let mut retire = false;
for packet in 0..MAX_PACKETS {
if total == 0 && packet > 0 {
retire = true;
break;
}
let want = mps.min(total);
let completion = match pid {
PID_SETUP => {
if total < SetupPacket::SIZE as u32 {
status |= TOKEN_XACTERR | TOKEN_HALTED;
retire = true;
break;
}
let mut raw = [0u8; 8];
let n = 8usize;
let Some(_) = self.buffer_read(space, qh, page, offset, &mut raw[..n]) else {
status |= TOKEN_DBE | TOKEN_HALTED;
retire = true;
break;
};
let status_code = self.bus.setup(device, endpoint, SetupPacket::decode(&raw));
if status_code == Status::Ack {
toggle = true;
}
Completion {
status: status_code,
len: n as u64,
}
}
PID_IN => {
let mut buf = alloc::vec![0u8; want as usize];
let completion = self.bus.read(device, endpoint, &mut buf);
if completion.status == Status::Ack {
let n = (completion.len as u32).min(want) as usize;
if self
.buffer_write(space, qh, page, offset, &buf[..n])
.is_none()
{
status |= TOKEN_DBE | TOKEN_HALTED;
retire = true;
break;
}
if (n as u32) < want {
outcome.short_packet = true;
}
Completion::ack(n as u64)
} else {
completion
}
}
PID_OUT => {
let mut buf = alloc::vec![0u8; want as usize];
if self
.buffer_read(space, qh, page, offset, &mut buf)
.is_none()
{
status |= TOKEN_DBE | TOKEN_HALTED;
retire = true;
break;
}
self.bus.write(device, endpoint, &buf)
}
_ => {
status |= TOKEN_XACTERR | TOKEN_HALTED;
retire = true;
break;
}
};
match completion.status {
Status::Ack => {
let moved = (completion.len as u32).min(total);
let Some((next_page, next_offset)) = Hcd::advance(page, offset, moved) else {
status |= TOKEN_DBE | TOKEN_HALTED;
retire = true;
break;
};
page = next_page;
offset = next_offset;
total -= moved;
if pid != PID_SETUP {
toggle = !toggle;
}
if outcome.short_packet || total == 0 {
retire = true;
break;
}
}
Status::Nak => {
break;
}
Status::Stall => {
status |= TOKEN_HALTED;
retire = true;
break;
}
Status::Babble => {
status |= TOKEN_BABBLE | TOKEN_HALTED;
retire = true;
break;
}
Status::NoDevice | Status::Error => {
status |= TOKEN_XACTERR | TOKEN_HALTED;
retire = true;
break;
}
}
}
outcome.halted = status & TOKEN_HALTED != 0;
token &= !(TOKEN_STATUS
| (TOKEN_BYTES_MASK << TOKEN_BYTES_SHIFT)
| (0x7 << TOKEN_CPAGE_SHIFT)
| TOKEN_TOGGLE);
token |= status;
token |= (total & TOKEN_BYTES_MASK) << TOKEN_BYTES_SHIFT;
token |= ((page as u32) & 0x7) << TOKEN_CPAGE_SHIFT;
if toggle {
token |= TOKEN_TOGGLE;
}
if !retire {
token |= TOKEN_ACTIVE;
}
qh[QH_OVERLAY + 2] = token;
qh[QH_OVERLAY + 3] = (qh[QH_OVERLAY + 3] & !(PAGE_SIZE - 1)) | (offset & (PAGE_SIZE - 1));
if retire {
let mut regs = self.regs.lock();
if ioc {
regs.usbsts |= STS_USBINT;
}
if outcome.halted {
regs.usbsts |= STS_USBERRINT;
}
}
outcome
}
fn advance(page: usize, offset: u32, moved: u32) -> Option<(usize, u32)> {
let mut page = page;
let mut offset = offset.checked_add(moved)?;
while offset >= PAGE_SIZE {
offset -= PAGE_SIZE;
page += 1;
if page >= BUFFER_PAGES {
return (offset == 0 && page == BUFFER_PAGES)
.then_some((BUFFER_PAGES - 1, PAGE_SIZE));
}
}
Some((page, offset))
}
fn buffer_address(qh: &[u32; QH_DWORDS], page: usize, offset: u32) -> Option<u32> {
if page >= BUFFER_PAGES || offset >= PAGE_SIZE {
return None;
}
Some((qh[QH_OVERLAY + 3 + page] & !(PAGE_SIZE - 1)).wrapping_add(offset))
}
fn buffer_read(
&self,
space: &AddressSpace,
qh: &[u32; QH_DWORDS],
page: usize,
offset: u32,
dst: &mut [u8],
) -> Option<()> {
let mut page = page;
let mut offset = offset;
let mut done = 0usize;
while done < dst.len() {
let addr = Hcd::buffer_address(qh, page, offset)?;
let room = (PAGE_SIZE - offset) as usize;
let n = room.min(dst.len() - done);
space
.read_bytes(u64::from(addr), &mut dst[done..done + n], self.attrs())
.ok()?;
done += n;
let (next_page, next_offset) = Hcd::advance(page, offset, n as u32)?;
page = next_page;
offset = next_offset;
if offset == PAGE_SIZE {
return (done == dst.len()).then_some(());
}
}
Some(())
}
fn buffer_write(
&self,
space: &AddressSpace,
qh: &[u32; QH_DWORDS],
page: usize,
offset: u32,
src: &[u8],
) -> Option<()> {
let mut page = page;
let mut offset = offset;
let mut done = 0usize;
while done < src.len() {
let addr = Hcd::buffer_address(qh, page, offset)?;
let room = (PAGE_SIZE - offset) as usize;
let n = room.min(src.len() - done);
space
.write_bytes(u64::from(addr), &src[done..done + n], self.attrs())
.ok()?;
done += n;
let (next_page, next_offset) = Hcd::advance(page, offset, n as u32)?;
page = next_page;
offset = next_offset;
if offset == PAGE_SIZE {
return (done == src.len()).then_some(());
}
}
Some(())
}
fn read32(&self, space: &AddressSpace, addr: u32) -> Option<u32> {
space
.read(u64::from(addr), Width::U32, self.attrs())
.ok()
.map(|value| value as u32)
}
fn write32(&self, space: &AddressSpace, addr: u32, value: u32) -> Option<()> {
space
.write(u64::from(addr), Width::U32, u64::from(value), self.attrs())
.ok()
}
fn host_system_error(&self) {
let mut regs = self.regs.lock();
regs.usbsts |= STS_HSE | STS_HCHALTED;
regs.usbcmd &= !CMD_RS;
Hcd::sync_schedule_status(&mut regs);
self.publish(®s);
}
pub fn reset(&self, _kind: ResetKind) {
{
let mut regs = self.regs.lock();
let ticks = regs.ticks;
*regs = Regs {
ticks,
..Regs::reset(self.params.ports, self.params.dual_role)
};
self.publish(®s);
}
for port in 0..self.params.ports {
self.bus.set_enabled(port, false);
}
for port in 0..self.params.ports {
self.settle_port(port);
}
self.refresh_irq();
}
pub fn save<S: Sink + ?Sized>(&self, w: &mut S) -> Result<()> {
let regs = *self.regs.lock();
w.write_u64(regs.ticks)?;
w.write_u32(regs.usbcmd)?;
w.write_u32(regs.usbsts)?;
w.write_u32(regs.usbintr)?;
w.write_u32(regs.frindex)?;
w.write_u32(regs.ctrldssegment)?;
w.write_u32(regs.periodic_base)?;
w.write_u32(regs.async_addr)?;
w.write_u32(regs.configflag)?;
w.write_u32(regs.usbmode)?;
w.write_u32(regs.otgsc)?;
w.write_u32(regs.burstsize)?;
w.write_u32(regs.txfilltuning)?;
w.write_seq_len(MAX_PORTS as u64)?;
for port in regs.portsc {
w.write_u32(port)?;
}
Ok(())
}
pub fn load<'a, S: Source<'a> + ?Sized>(&self, r: &mut S) -> Result<()> {
let mut regs = Regs {
ticks: r.read_u64()?,
usbcmd: r.read_u32()?,
usbsts: r.read_u32()?,
usbintr: r.read_u32()?,
frindex: r.read_u32()?,
ctrldssegment: r.read_u32()?,
periodic_base: r.read_u32()?,
async_addr: r.read_u32()?,
configflag: r.read_u32()?,
usbmode: r.read_u32()?,
otgsc: r.read_u32()?,
burstsize: r.read_u32()?,
txfilltuning: r.read_u32()?,
portsc: [0; MAX_PORTS],
};
let count = r.read_seq_len(4)?;
if count != MAX_PORTS as u64 {
return Err(Error::State(alloc::format!(
"usb.ehci: a snapshot with {count} ports, not {MAX_PORTS}"
)));
}
for port in &mut regs.portsc {
*port = r.read_u32()?;
}
{
let mut slot = self.regs.lock();
*slot = regs;
self.publish(&slot);
}
for port in 0..self.params.ports {
let enabled = self.portsc(port) & (PORT_PE | PORT_OWNER) == PORT_PE;
self.bus.set_enabled(port, enabled);
}
self.refresh_irq();
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Extra {
UsbMode,
Otgsc,
BurstSize,
TxFillTuning,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct Outcome {
short_packet: bool,
halted: bool,
}
#[derive(Debug)]
pub struct EhciController {
hcd: Arc<Hcd>,
region: RegionRef,
}
impl EhciController {
pub fn new(props: &Props) -> Result<EhciController> {
let mut r = props.reader();
let bus_name = r.require_str("bus")?.to_string();
let ports = r.or_range("ports", 1u64, 1..=MAX_PORTS as u64)?;
let microframe = r.or_range("microframe", 7500u64, 1..=u64::from(u32::MAX))?;
r.finish()?;
let bus = buses::attach(props, &bus_name, ports as u8)?;
if bus.port_count() < ports as u8 {
return Err(Error::Config {
at: String::from(CLASS_NAME),
message: alloc::format!(
"the USB bus `{bus_name}` already has {} ports and this controller asked for \
{ports}; the first object to name a bus fixes its size",
bus.port_count()
),
});
}
Ok(EhciController::with_bus(
bus,
Params {
ports: ports as u8,
microframe_ticks: microframe,
caplength: DEFAULT_CAPLENGTH,
dual_role: false,
},
))
}
#[must_use]
pub fn with_bus(bus: Arc<UsbBus>, params: Params) -> EhciController {
let hcd = Arc::new(Hcd::new(bus, params));
let port = Arc::new(EhciPort {
hcd: Arc::clone(&hcd),
});
let region = Arc::new(Region::io("ehci", REGISTER_BYTES, port as Arc<dyn MemOps>));
EhciController { hcd, region }
}
#[must_use]
pub fn hcd(&self) -> &Arc<Hcd> {
&self.hcd
}
}
pub mod pin {
pub const IRQ: &str = "irq";
}
struct EhciPort {
hcd: Arc<Hcd>,
}
impl fmt::Debug for EhciPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EhciPort").finish_non_exhaustive()
}
}
impl MemOps for EhciPort {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
self.hcd.sync_for(attrs);
let caplength = u64::from(self.hcd.params.caplength);
let value = if offset < caplength {
self.hcd.read_cap(offset & !0x3)
} else {
self.hcd.read_op((offset - caplength) & !0x3)
};
narrow_read(offset, value, dst)
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
let caplength = u64::from(self.hcd.params.caplength);
if offset < caplength {
return Ok(());
}
let Some(value) = word_write(src) else {
return Err(BusError::BadAccess);
};
self.hcd.sync_for(attrs);
let op = offset - caplength;
let reset = Hcd::port_at(op)
.map(|port| (port, self.hcd.portsc(port)))
.filter(|(_, sc)| sc & PORT_RESET != 0);
let after = self.hcd.write_op(op, value);
self.hcd.act(after);
if let Some((port, _)) = reset
&& self.hcd.portsc(port) & PORT_RESET == 0
{
self.hcd.finish_reset(port);
self.hcd.refresh_irq();
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO
.with_widths(Width::U8, Width::U32)
.with_natural_alignment(true)
}
}
pub(crate) fn narrow_read(offset: u64, value: u32, dst: &mut [u8]) -> MemResult {
let bytes = value.to_le_bytes();
let lane = (offset & 0x3) as usize;
match dst.len() {
1 | 2 | 4 => {
if lane + dst.len() > 4 {
return Err(BusError::BadAccess);
}
dst.copy_from_slice(&bytes[lane..lane + dst.len()]);
Ok(())
}
_ => Err(BusError::BadAccess),
}
}
pub(crate) fn word_write(src: &[u8]) -> Option<u32> {
(src.len() == 4).then(|| u32::from_le_bytes([src[0], src[1], src[2], src[3]]))
}
impl Device for EhciController {
fn class(&self) -> &'static DeviceClass {
&EHCI_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, kind: ResetKind) {
self.hcd.reset(kind);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
self.hcd.save(w)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
self.hcd.load(r)
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "regs").then(|| Arc::clone(&self.region))
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
if port != pin::IRQ {
return Err(Error::Config {
at: String::from(port),
message: alloc::format!(
"an EHCI controller drives `{}` and nothing else",
pin::IRQ
),
});
}
self.hcd.connect_irq(source);
Ok(())
}
fn announce(&self, _port: &str) {
self.hcd.refresh_irq();
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.hcd.ticks()
}
fn advance_to(&self, tick: u64) {
self.hcd.advance_to(tick);
}
fn next_event_tick(&self) -> Option<u64> {
self.hcd.next_event_tick()
}
fn attach_lazy(&self, handle: LazyHandle) {
self.hcd.attach_lazy(handle);
}
}
impl Instance for EhciController {
fn bind(&self, ctx: &BindCtx<'_>) -> Result<()> {
let space = ctx.space().ok_or_else(|| Error::Config {
at: ctx.path().to_string(),
message: String::from(
"an EHCI controller masters the bus its queue heads live on: add `space = mem` \
to the object",
),
})?;
self.hcd.attach_space(space, ctx.requester());
Ok(())
}
}
pub static EHCI_CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "a generic EHCI USB 2.0 host controller: the EHCI 1.0 register file and the \
QH/qTD schedule walker, DMA-reading its work out of guest RAM",
properties: EHCI_PROPERTIES,
construct: |props| Ok(Box::new(EhciController::new(props)?)),
};
pub(crate) static EHCI_PROPERTIES: &[PropertySpec] = &[
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: true,
summary: "the named USB bus this controller is the root of",
},
PropertySpec {
name: "ports",
kind: ValueKind::Uint,
required: false,
summary: "how many root ports, 1 to 15 (default 1)",
},
PropertySpec {
name: "microframe",
kind: ValueKind::Uint,
required: false,
summary: "clock-domain ticks in one 125 us microframe (default 7500, exact at 60 MHz)",
},
];
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&EHCI_CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| {
Ok(Arc::new(EhciController::new(props)?))
})
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("bus", ValueKind::Str).required())
.prop(PropSchema::new("ports", ValueKind::Uint).range(1, MAX_PORTS as u64))
.prop(PropSchema::new("microframe", ValueKind::Uint).range(1, u64::from(u32::MAX)))
.port(pin::IRQ, PortDir::Out)
.region("")
.region("regs")
}