#[cfg(test)]
mod tests;
use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::bus::usb::{
Completion, DeviceAddress, HCD_RANK, SetupPacket, Speed, Status, TransferType, 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, MemAttrs, MemOps, MemResult, Region, RegionRef};
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::Instance;
const CLASS_NAME: &str = "usb.dwc2";
pub(crate) const STATE_VERSION: u32 = 1;
pub const ROOT_PORT: u8 = 0;
pub const MAX_CHANNELS: usize = 16;
pub const REGISTER_BYTES: u64 = 0x4_0000;
pub const FIFO_BASE: u64 = 0x1000;
pub const FIFO_WINDOW: u64 = 0x1000;
pub const MAX_TRANSACTIONS_PER_FRAME: usize = 1024;
pub const MIN_FRAME_PHY_CLOCKS: u64 = 1000;
pub const PROTOCOL_OVERHEAD_BYTES: u32 = 13;
const GOTGCTL: u64 = 0x000;
const GOTGINT: u64 = 0x004;
const GAHBCFG: u64 = 0x008;
const GUSBCFG: u64 = 0x00c;
const GRSTCTL: u64 = 0x010;
const GINTSTS: u64 = 0x014;
const GINTMSK: u64 = 0x018;
const GRXSTSR: u64 = 0x01c;
const GRXSTSP: u64 = 0x020;
const GRXFSIZ: u64 = 0x024;
const GNPTXFSIZ: u64 = 0x028;
const GNPTXSTS: u64 = 0x02c;
const GCCFG: u64 = 0x038;
const CID: u64 = 0x03c;
const HPTXFSIZ: u64 = 0x100;
const HCFG: u64 = 0x400;
const HFIR: u64 = 0x404;
const HFNUM: u64 = 0x408;
const HPTXSTS: u64 = 0x410;
const HAINT: u64 = 0x414;
const HAINTMSK: u64 = 0x418;
const HPRT: u64 = 0x440;
const HCCHAR_BASE: u64 = 0x500;
const CHANNEL_STRIDE: u64 = 0x20;
const DEVICE_BASE: u64 = 0x800;
const PCGCCTL: u64 = 0xe00;
const DEVICE_END: u64 = PCGCCTL;
const OTGCTL_SRQSCS: u32 = 1 << 0;
const OTGCTL_CIDSTS: u32 = 1 << 16;
const OTGCTL_ASVLD: u32 = 1 << 18;
const OTGCTL_BSVLD: u32 = 1 << 19;
const OTGCTL_WRITABLE: u32 = (1 << 1) | (1 << 9) | (1 << 10) | (1 << 11);
const AHBCFG_GINTMSK: u32 = 1 << 0;
const AHBCFG_WRITABLE: u32 = AHBCFG_GINTMSK | (0xf << 1) | (1 << 7) | (1 << 8);
const USBCFG_PHYSEL: u32 = 1 << 6;
const USBCFG_FHMOD: u32 = 1 << 29;
const USBCFG_FDMOD: u32 = 1 << 30;
const USBCFG_WRITABLE: u32 = 0x7
| USBCFG_PHYSEL
| (1 << 8)
| (1 << 9)
| (0xf << 10)
| USBCFG_FHMOD
| USBCFG_FDMOD
| (1 << 31);
const USBCFG_RESET_VALUE: u32 = 0x0000_0a00 | USBCFG_PHYSEL;
const RSTCTL_CSRST: u32 = 1 << 0;
const RSTCTL_FCRST: u32 = 1 << 2;
const RSTCTL_RXFFLSH: u32 = 1 << 4;
const RSTCTL_TXFFLSH: u32 = 1 << 5;
const RSTCTL_TXFNUM_SHIFT: u32 = 6;
const RSTCTL_AHBIDL: u32 = 1 << 31;
pub const GINT_CMOD: u32 = 1 << 0;
pub const GINT_MMIS: u32 = 1 << 1;
const GINT_OTGINT: u32 = 1 << 2;
pub const GINT_SOF: u32 = 1 << 3;
pub const GINT_RXFLVL: u32 = 1 << 4;
const GINT_NPTXFE: u32 = 1 << 5;
const GINT_IPXFR: u32 = 1 << 21;
pub const GINT_HPRTINT: u32 = 1 << 24;
pub const GINT_HCINT: u32 = 1 << 25;
const GINT_PTXFE: u32 = 1 << 26;
const GINT_CIDSCHG: u32 = 1 << 28;
pub const GINT_DISCINT: u32 = 1 << 29;
const GINT_SRQINT: u32 = 1 << 30;
const GINT_WKUINT: u32 = 1 << 31;
const GINT_W1C: u32 = GINT_MMIS
| GINT_SOF
| (1 << 10)
| (1 << 11)
| (1 << 12)
| (1 << 13)
| (1 << 14)
| (1 << 15)
| (1 << 20)
| GINT_IPXFR
| GINT_CIDSCHG
| GINT_DISCINT
| GINT_SRQINT
| GINT_WKUINT;
const HCFG_FSLSPCS_MASK: u32 = 0x3;
const HCFG_FSLSS: u32 = 1 << 2;
pub const HPRT_PCSTS: u32 = 1 << 0;
pub const HPRT_PCDET: u32 = 1 << 1;
pub const HPRT_PENA: u32 = 1 << 2;
pub const HPRT_PENCHNG: u32 = 1 << 3;
const HPRT_POCA: u32 = 1 << 4;
const HPRT_POCCHNG: u32 = 1 << 5;
const HPRT_PRES: u32 = 1 << 6;
const HPRT_PSUSP: u32 = 1 << 7;
pub const HPRT_PRST: u32 = 1 << 8;
const HPRT_PLSTS_SHIFT: u32 = 10;
pub const HPRT_PPWR: u32 = 1 << 12;
const HPRT_PSPD_SHIFT: u32 = 17;
const PSPD_HIGH: u32 = 0;
const PSPD_FULL: u32 = 1;
const PSPD_LOW: u32 = 2;
const HPRT_W1C: u32 = HPRT_PCDET | HPRT_PENA | HPRT_PENCHNG | HPRT_POCCHNG;
const HPRT_WRITABLE: u32 = HPRT_PRES | HPRT_PSUSP | HPRT_PRST | HPRT_PPWR | (0xf << 13);
const HCCHAR_MPSIZ_MASK: u32 = 0x7ff;
const HCCHAR_EPNUM_SHIFT: u32 = 11;
const HCCHAR_EPDIR: u32 = 1 << 15;
const HCCHAR_EPTYP_SHIFT: u32 = 18;
const HCCHAR_DAD_SHIFT: u32 = 22;
const HCCHAR_CHDIS: u32 = 1 << 30;
const HCCHAR_CHENA: u32 = 1 << 31;
pub const HCINT_XFRC: u32 = 1 << 0;
pub const HCINT_CHH: u32 = 1 << 1;
pub const HCINT_STALL: u32 = 1 << 3;
pub const HCINT_NAK: u32 = 1 << 4;
pub const HCINT_ACK: u32 = 1 << 5;
pub const HCINT_TXERR: u32 = 1 << 7;
pub const HCINT_BBERR: u32 = 1 << 8;
const HCINT_MASK: u32 = 0x7ff;
const TSIZ_XFRSIZ_MASK: u32 = 0x7_ffff;
const TSIZ_PKTCNT_SHIFT: u32 = 19;
const TSIZ_PKTCNT_MASK: u32 = 0x3ff;
const TSIZ_DPID_SHIFT: u32 = 29;
const DPID_DATA0: u32 = 0;
const DPID_DATA1: u32 = 2;
const DPID_SETUP: u32 = 3;
const RXSTS_BCNT_SHIFT: u32 = 4;
const RXSTS_DPID_SHIFT: u32 = 15;
const RXSTS_PKTSTS_SHIFT: u32 = 17;
const PKTSTS_IN_DATA: u32 = 0b0010;
const PKTSTS_IN_COMPLETE: u32 = 0b0011;
const PKTSTS_CHANNEL_HALTED: u32 = 0b0111;
const FIFO_DEPTH_MASK: u32 = 0xffff;
const FIFO_DEPTH_SHIFT: u32 = 16;
const TX_QUEUE_DEPTH: u32 = 8;
const NO_EVENT: u64 = u64::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Params {
pub channels: u8,
pub fifo_words: u32,
pub phy_ticks: u64,
pub max_speed: Speed,
pub cid: u32,
}
impl Default for Params {
fn default() -> Params {
Params {
channels: 8,
fifo_words: 320,
phy_ticks: 1,
max_speed: Speed::Full,
cid: 0,
}
}
}
impl Params {
fn clamped(self) -> Params {
Params {
channels: self.channels.clamp(1, MAX_CHANNELS as u8),
fifo_words: self.fifo_words.clamp(1, FIFO_DEPTH_MASK),
phy_ticks: self.phy_ticks.max(1),
..self
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct Channel {
hcchar: u32,
hcsplt: u32,
hcint: u32,
hcintmsk: u32,
hctsiz: u32,
tx: VecDeque<u8>,
}
impl Channel {
fn mps(&self) -> u32 {
self.hcchar & HCCHAR_MPSIZ_MASK
}
fn endpoint(&self) -> u8 {
((self.hcchar >> HCCHAR_EPNUM_SHIFT) & 0xf) as u8
}
fn address(&self) -> DeviceAddress {
DeviceAddress(((self.hcchar >> HCCHAR_DAD_SHIFT) & 0x7f) as u8)
}
fn dir_in(&self) -> bool {
self.hcchar & HCCHAR_EPDIR != 0
}
fn transfer_type(&self) -> TransferType {
TransferType::from_attribute_bits(((self.hcchar >> HCCHAR_EPTYP_SHIFT) & 0x3) as u8)
}
fn periodic(&self) -> bool {
matches!(
self.transfer_type(),
TransferType::Isochronous | TransferType::Interrupt
)
}
fn armed(&self) -> bool {
self.hcchar & HCCHAR_CHENA != 0 && self.hcchar & HCCHAR_CHDIS == 0
}
fn xfrsiz(&self) -> u32 {
self.hctsiz & TSIZ_XFRSIZ_MASK
}
fn pktcnt(&self) -> u32 {
(self.hctsiz >> TSIZ_PKTCNT_SHIFT) & TSIZ_PKTCNT_MASK
}
fn dpid(&self) -> u32 {
(self.hctsiz >> TSIZ_DPID_SHIFT) & 0x3
}
fn set_xfrsiz(&mut self, value: u32) {
self.hctsiz = (self.hctsiz & !TSIZ_XFRSIZ_MASK) | (value & TSIZ_XFRSIZ_MASK);
}
fn set_pktcnt(&mut self, value: u32) {
self.hctsiz = (self.hctsiz & !(TSIZ_PKTCNT_MASK << TSIZ_PKTCNT_SHIFT))
| ((value & TSIZ_PKTCNT_MASK) << TSIZ_PKTCNT_SHIFT);
}
fn set_dpid(&mut self, value: u32) {
self.hctsiz =
(self.hctsiz & !(0x3 << TSIZ_DPID_SHIFT)) | ((value & 0x3) << TSIZ_DPID_SHIFT);
}
fn halt(&mut self) {
self.hcchar &= !HCCHAR_CHENA;
self.hcint |= HCINT_CHH;
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct RxPacket {
status: u32,
data: Vec<u8>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct RxFifo {
queue: VecDeque<RxPacket>,
current: Option<RxPacket>,
read: usize,
}
fn words_of(bytes: usize) -> u32 {
(bytes as u32).div_ceil(4)
}
impl RxFifo {
fn words(&self) -> u32 {
let queued: u32 = self
.queue
.iter()
.map(|p| 1 + words_of(p.data.len()))
.fold(0, u32::saturating_add);
let current = self
.current
.as_ref()
.map_or(0, |p| words_of(p.data.len().saturating_sub(self.read)));
queued.saturating_add(current)
}
fn level(&self) -> bool {
!self.queue.is_empty()
|| self
.current
.as_ref()
.is_some_and(|p| self.read < p.data.len())
}
fn peek_status(&self) -> u32 {
self.queue.front().map_or(0, |p| p.status)
}
fn pop_status(&mut self) -> u32 {
match self.queue.pop_front() {
Some(packet) => {
let status = packet.status;
self.current = Some(packet);
self.read = 0;
status
}
None => 0,
}
}
fn peek_word(&self) -> u32 {
let Some(packet) = self.current.as_ref() else {
return 0;
};
let mut word = [0u8; 4];
for (i, slot) in word.iter_mut().enumerate() {
if let Some(byte) = packet.data.get(self.read + i) {
*slot = *byte;
}
}
u32::from_le_bytes(word)
}
fn pop_word(&mut self) -> u32 {
let word = self.peek_word();
if let Some(packet) = self.current.as_ref() {
self.read = (self.read + 4).min(packet.data.len());
}
word
}
fn clear(&mut self) {
self.queue.clear();
self.current = None;
self.read = 0;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct State {
ticks: u64,
gotgctl: u32,
gotgint: u32,
gahbcfg: u32,
gusbcfg: u32,
gintsts: u32,
gintmsk: u32,
grxfsiz: u32,
gnptxfsiz: u32,
hptxfsiz: u32,
gccfg: u32,
cid: u32,
hcfg: u32,
hfir: u32,
frnum: u32,
hprt: u32,
haintmsk: u32,
pcgcctl: u32,
channels: [Channel; MAX_CHANNELS],
rx: RxFifo,
}
impl State {
fn reset(params: Params) -> State {
State {
ticks: 0,
gotgctl: 0,
gotgint: 0,
gahbcfg: 0,
gusbcfg: USBCFG_RESET_VALUE,
gintsts: 0,
gintmsk: 0,
grxfsiz: 0x0000_0200,
gnptxfsiz: 0x0200_0200,
hptxfsiz: 0x0200_0600,
gccfg: 0,
cid: params.cid,
hcfg: 0,
hfir: 0xea60,
frnum: 0x3fff,
hprt: 0,
haintmsk: 0,
pcgcctl: 0,
channels: core::array::from_fn(|_| Channel::default()),
rx: RxFifo::default(),
}
}
}
fn pspd_code(speed: Speed) -> u32 {
match speed {
Speed::High => PSPD_HIGH,
Speed::Full => PSPD_FULL,
Speed::Low => PSPD_LOW,
}
}
fn pspd_speed(hprt: u32) -> Speed {
match (hprt >> HPRT_PSPD_SHIFT) & 0x3 {
PSPD_HIGH => Speed::High,
PSPD_LOW => Speed::Low,
_ => Speed::Full,
}
}
fn bytes_per_frame(speed: Speed) -> u32 {
match speed {
Speed::Low => 187,
Speed::Full => 1500,
Speed::High => 60_000,
}
}
fn halted_packet(channel: usize) -> RxPacket {
RxPacket {
status: (channel as u32) | (PKTSTS_CHANNEL_HALTED << RXSTS_PKTSTS_SHIFT),
data: Vec::new(),
}
}
fn next_dpid(dpid: u32) -> u32 {
match dpid {
DPID_DATA0 | DPID_SETUP => DPID_DATA1,
_ => DPID_DATA0,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum After {
Nothing,
CoreReset,
Port,
FinishReset,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Job {
device: DeviceAddress,
endpoint: u8,
kind: JobKind,
periodic: bool,
want: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum JobKind {
Setup(SetupPacket),
In,
Out(Vec<u8>),
}
impl Job {
fn cost(&self) -> u32 {
self.want.saturating_add(PROTOCOL_OVERHEAD_BYTES)
}
}
pub struct Dwc2 {
bus: Arc<UsbBus>,
params: Params,
state: Mutex<State>,
ticks: AtomicU64,
next_event: AtomicU64,
irq: Mutex<Option<WireSource>>,
irq_level: AtomicU32,
lazy: Mutex<Option<LazyHandle>>,
}
impl fmt::Debug for Dwc2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Dwc2");
s.field("channels", &self.params.channels);
s.field("max_speed", &self.params.max_speed);
match self.state.try_lock() {
Some(state) => s.field("hprt", &state.hprt).finish_non_exhaustive(),
None => s.field("state", &"<in use>").finish_non_exhaustive(),
}
}
}
impl Dwc2 {
#[must_use]
pub fn new(bus: Arc<UsbBus>, params: Params) -> Dwc2 {
let params = params.clamped();
Dwc2 {
bus,
params,
state: Mutex::with_rank(HCD_RANK, State::reset(params)),
ticks: AtomicU64::new(0),
next_event: AtomicU64::new(NO_EVENT),
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 interrupt_status(&self) -> u32 {
let state = self.state.lock();
self.gintsts(&state)
}
#[must_use]
pub fn port_status(&self) -> u32 {
self.state.lock().hprt
}
#[must_use]
pub fn channel_status(&self, channel: u8) -> u32 {
self.state
.lock()
.channels
.get(usize::from(channel))
.map_or(0, |c| c.hcint)
}
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 host_mode(state: &State) -> bool {
state.gusbcfg & USBCFG_FDMOD == 0
}
fn running(state: &State) -> bool {
Dwc2::host_mode(state) && state.pcgcctl & 1 == 0
}
fn frame_ticks(&self, state: &State) -> u64 {
u64::from(state.hfir & 0xffff)
.max(MIN_FRAME_PHY_CLOCKS)
.saturating_mul(self.params.phy_ticks)
.max(1)
}
fn publish(&self, state: &State) {
self.ticks.store(state.ticks, Ordering::Relaxed);
let next = if Dwc2::running(state) {
let ft = self.frame_ticks(state);
(state.ticks / ft + 1).saturating_mul(ft)
} 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 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);
}
pub fn advance_to(&self, target: u64) {
loop {
{
let mut state = self.state.lock();
if !Dwc2::running(&state) {
state.ticks = state.ticks.max(target);
self.publish(&state);
return;
}
let ft = self.frame_ticks(&state);
let next = (state.ticks / ft + 1).saturating_mul(ft);
if next > target {
state.ticks = state.ticks.max(target);
self.publish(&state);
return;
}
state.ticks = next;
self.publish(&state);
}
self.frame();
}
}
fn frame(&self) {
if self.bus.any_change() {
self.settle_port();
}
{
let mut state = self.state.lock();
state.frnum = (state.frnum + 1) & 0x3fff;
state.gintsts |= GINT_SOF;
}
self.service();
self.refresh_irq();
}
fn settle_port(&self) {
let connected = self.bus.connected(ROOT_PORT);
let speed = self.bus.speed(ROOT_PORT);
let plugged_changed = self.bus.take_change(ROOT_PORT);
let disable = {
let mut state = self.state.lock();
let host = Dwc2::host_mode(&state);
if plugged_changed {
state.hprt |= HPRT_PCDET;
if !connected {
state.gintsts |= GINT_DISCINT;
}
}
let mut port = state.hprt;
port = (port & !HPRT_PCSTS) | if connected { HPRT_PCSTS } else { 0 };
port &= !(HPRT_POCA | (0x3 << HPRT_PLSTS_SHIFT));
if connected && speed == Some(Speed::Low) {
port |= 0x1 << HPRT_PLSTS_SHIFT;
}
let mut disable = false;
if !connected || !host {
if port & HPRT_PENA != 0 {
port |= HPRT_PENCHNG;
}
port &= !(HPRT_PENA | HPRT_PRST | HPRT_PSUSP);
disable = true;
}
state.hprt = port;
disable
};
if disable {
self.bus.set_enabled(ROOT_PORT, false);
}
}
pub fn finish_reset(&self) {
self.bus.reset_port(ROOT_PORT);
let speed = self.bus.speed(ROOT_PORT);
let keep = {
let mut state = self.state.lock();
let mut port = state.hprt & !HPRT_PRST;
port &= !(0x3 << HPRT_PSPD_SHIFT);
let keep = match speed {
Some(speed) if speed <= self.params.max_speed => {
port |= pspd_code(speed) << HPRT_PSPD_SHIFT;
if port & HPRT_PENA == 0 {
port |= HPRT_PENCHNG;
}
port |= HPRT_PENA;
true
}
_ => {
if port & HPRT_PENA != 0 {
port |= HPRT_PENCHNG;
}
port &= !HPRT_PENA;
false
}
};
state.hprt = port;
keep
};
self.bus.set_enabled(ROOT_PORT, keep);
}
fn service(&self) {
let (enabled, speed) = {
let state = self.state.lock();
(
state.hprt & HPRT_PENA != 0 && Dwc2::host_mode(&state),
pspd_speed(state.hprt),
)
};
if !enabled {
return;
}
let mut budget = bytes_per_frame(speed);
let mut served = [false; MAX_CHANNELS];
let mut executed = 0usize;
'frame: loop {
let mut moved = false;
for (channel, served) in served
.iter_mut()
.enumerate()
.take(usize::from(self.params.channels))
{
let Some(job) = self.prepare(channel, *served) else {
continue;
};
let cost = job.cost();
if cost > budget {
break 'frame;
}
budget -= cost;
if job.periodic {
*served = true;
}
let (completion, data) = self.execute(&job);
self.retire(channel, &job, completion, &data);
moved = true;
executed += 1;
if executed >= MAX_TRANSACTIONS_PER_FRAME {
break 'frame;
}
}
if !moved {
break;
}
}
}
fn prepare(&self, channel: usize, served: bool) -> Option<Job> {
let mut state = self.state.lock();
let rx_words = state.rx.words();
let rx_depth = self.rx_depth(&state);
let channels = &mut state.channels;
let c = channels.get_mut(channel)?;
if !c.armed() || c.pktcnt() == 0 {
return None;
}
let periodic = c.periodic();
if periodic && served {
return None;
}
let mps = c.mps();
let device = c.address();
let endpoint = c.endpoint();
if c.dir_in() {
let need = 1 + words_of(mps as usize);
if rx_words.saturating_add(need) > rx_depth {
return None;
}
return Some(Job {
device,
endpoint,
kind: JobKind::In,
periodic,
want: mps,
});
}
let setup = c.dpid() == DPID_SETUP && matches!(c.transfer_type(), TransferType::Control);
if setup {
if c.xfrsiz() < SetupPacket::SIZE as u32 {
c.hcint |= HCINT_TXERR;
c.halt();
return None;
}
if c.tx.len() < SetupPacket::SIZE as usize {
return None;
}
let mut raw = [0u8; 8];
for (i, slot) in raw.iter_mut().enumerate() {
*slot = c.tx[i];
}
return Some(Job {
device,
endpoint,
kind: JobKind::Setup(SetupPacket::decode(&raw)),
periodic,
want: SetupPacket::SIZE as u32,
});
}
let want = mps.min(c.xfrsiz());
if (c.tx.len() as u32) < want {
return None;
}
let data: Vec<u8> = c.tx.iter().copied().take(want as usize).collect();
Some(Job {
device,
endpoint,
kind: JobKind::Out(data),
periodic,
want,
})
}
fn execute(&self, job: &Job) -> (Completion, Vec<u8>) {
match &job.kind {
JobKind::Setup(packet) => {
let status = self.bus.setup(job.device, job.endpoint, *packet);
(
Completion {
status,
len: SetupPacket::SIZE,
},
Vec::new(),
)
}
JobKind::In => {
let mut buf = alloc::vec![0u8; job.want as usize];
let completion = self.bus.read(job.device, job.endpoint, &mut buf);
let n = (completion.len as usize).min(buf.len());
buf.truncate(n);
(completion, buf)
}
JobKind::Out(data) => (self.bus.write(job.device, job.endpoint, data), Vec::new()),
}
}
fn retire(&self, channel: usize, job: &Job, completion: Completion, data: &[u8]) {
let mut state = self.state.lock();
let depth = self.rx_depth(&state);
let mut announce: Vec<RxPacket> = Vec::new();
{
let Some(c) = state.channels.get_mut(channel) else {
return;
};
match completion.status {
Status::Ack => {
c.hcint |= HCINT_ACK;
match &job.kind {
JobKind::Setup(_) => {
for _ in 0..SetupPacket::SIZE {
c.tx.pop_front();
}
let moved = (SetupPacket::SIZE as u32).min(c.xfrsiz());
c.set_xfrsiz(c.xfrsiz().saturating_sub(moved));
c.set_pktcnt(c.pktcnt().saturating_sub(1));
c.set_dpid(DPID_DATA1);
if c.pktcnt() == 0 {
c.hcint |= HCINT_XFRC;
}
}
JobKind::Out(sent) => {
for _ in 0..sent.len() {
c.tx.pop_front();
}
let moved = (sent.len() as u32).min(c.xfrsiz());
c.set_xfrsiz(c.xfrsiz().saturating_sub(moved));
c.set_pktcnt(c.pktcnt().saturating_sub(1));
c.set_dpid(next_dpid(c.dpid()));
if c.pktcnt() == 0 {
c.hcint |= HCINT_XFRC;
}
}
JobKind::In => {
let n = data.len() as u32;
if n > c.xfrsiz() {
c.hcint |= HCINT_BBERR;
c.halt();
announce.push(halted_packet(channel));
} else {
let dpid = c.dpid();
let short = n < c.mps();
c.set_xfrsiz(c.xfrsiz().saturating_sub(n));
c.set_pktcnt(c.pktcnt().saturating_sub(1));
c.set_dpid(next_dpid(dpid));
let complete = short || c.pktcnt() == 0;
if complete {
c.hcint |= HCINT_XFRC;
}
announce.push(RxPacket {
status: (channel as u32)
| (n << RXSTS_BCNT_SHIFT)
| (dpid << RXSTS_DPID_SHIFT)
| (PKTSTS_IN_DATA << RXSTS_PKTSTS_SHIFT),
data: data.to_vec(),
});
if complete {
announce.push(RxPacket {
status: (channel as u32)
| (dpid << RXSTS_DPID_SHIFT)
| (PKTSTS_IN_COMPLETE << RXSTS_PKTSTS_SHIFT),
data: Vec::new(),
});
}
}
}
}
}
Status::Nak => c.hcint |= HCINT_NAK,
Status::Stall => {
c.hcint |= HCINT_STALL;
c.halt();
announce.push(halted_packet(channel));
}
Status::Babble => {
c.hcint |= HCINT_BBERR;
c.halt();
announce.push(halted_packet(channel));
}
Status::NoDevice | Status::Error => {
c.hcint |= HCINT_TXERR;
c.halt();
announce.push(halted_packet(channel));
}
}
}
for packet in announce {
if state.rx.words() >= depth {
break;
}
state.rx.queue.push_back(packet);
}
}
fn rx_depth(&self, state: &State) -> u32 {
(state.grxfsiz & FIFO_DEPTH_MASK).min(self.params.fifo_words)
}
fn tx_depth(&self, state: &State, periodic: bool) -> u32 {
let raw = if periodic {
state.hptxfsiz >> FIFO_DEPTH_SHIFT
} else {
state.gnptxfsiz >> FIFO_DEPTH_SHIFT
};
(raw & FIFO_DEPTH_MASK).min(self.params.fifo_words)
}
fn tx_used(state: &State, periodic: bool, channels: u8) -> u32 {
state
.channels
.iter()
.take(usize::from(channels))
.filter(|c| c.periodic() == periodic)
.map(|c| words_of(c.tx.len()))
.fold(0, u32::saturating_add)
}
fn haint(&self, state: &State) -> u32 {
let mut bits = 0;
for (index, c) in state
.channels
.iter()
.take(usize::from(self.params.channels))
.enumerate()
{
if c.hcint & c.hcintmsk != 0 {
bits |= 1u32 << index;
}
}
bits
}
fn gintsts(&self, state: &State) -> u32 {
let mut value = state.gintsts;
if Dwc2::host_mode(state) {
value |= GINT_CMOD;
}
if state.gotgint != 0 {
value |= GINT_OTGINT;
}
if state.rx.level() {
value |= GINT_RXFLVL;
}
if Dwc2::tx_used(state, false, self.params.channels) == 0 {
value |= GINT_NPTXFE;
}
if Dwc2::tx_used(state, true, self.params.channels) == 0 {
value |= GINT_PTXFE;
}
if state.hprt & (HPRT_PCDET | HPRT_PENCHNG | HPRT_POCCHNG) != 0 {
value |= GINT_HPRTINT;
}
if self.haint(state) & state.haintmsk != 0 {
value |= GINT_HCINT;
}
value
}
pub fn refresh_irq(&self) {
let asserted = {
let state = self.state.lock();
state.gahbcfg & AHBCFG_GINTMSK != 0 && self.gintsts(&state) & state.gintmsk != 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));
}
}
}
impl Dwc2 {
fn wrong_role(state: &State, offset: u64) -> bool {
if Dwc2::host_mode(state) {
(DEVICE_BASE..DEVICE_END).contains(&offset)
} else {
(HCFG..DEVICE_BASE).contains(&offset)
}
}
#[must_use]
pub fn read(&self, offset: u64, debug: bool) -> u32 {
let offset = offset & !0x3;
let mut state = self.state.lock();
if offset >= FIFO_BASE {
let window = (offset - FIFO_BASE) / FIFO_WINDOW;
if window >= MAX_CHANNELS as u64 {
return 0;
}
return if debug {
state.rx.peek_word()
} else {
state.rx.pop_word()
};
}
if !debug && Dwc2::wrong_role(&state, offset) {
state.gintsts |= GINT_MMIS;
}
if (HCCHAR_BASE..DEVICE_BASE).contains(&offset) {
let index = ((offset - HCCHAR_BASE) / CHANNEL_STRIDE) as usize;
let register = (offset - HCCHAR_BASE) % CHANNEL_STRIDE;
if index >= usize::from(self.params.channels) {
return 0;
}
let channel = &state.channels[index];
return match register {
0x00 => channel.hcchar,
0x04 => channel.hcsplt,
0x08 => channel.hcint,
0x0c => channel.hcintmsk,
0x10 => channel.hctsiz,
_ => 0,
};
}
match offset {
GOTGCTL => {
let mut value = state.gotgctl & OTGCTL_WRITABLE;
if Dwc2::host_mode(&state) {
value |= OTGCTL_SRQSCS | OTGCTL_ASVLD;
} else {
value |= OTGCTL_CIDSTS | OTGCTL_BSVLD;
}
value
}
GOTGINT => state.gotgint,
GAHBCFG => state.gahbcfg & AHBCFG_WRITABLE,
GUSBCFG => {
let mut value = state.gusbcfg & USBCFG_WRITABLE;
if self.params.max_speed != Speed::High {
value |= USBCFG_PHYSEL;
}
value
}
GRSTCTL => RSTCTL_AHBIDL,
GINTSTS => self.gintsts(&state),
GINTMSK => state.gintmsk,
GRXSTSR => state.rx.peek_status(),
GRXSTSP => {
if debug {
state.rx.peek_status()
} else {
state.rx.pop_status()
}
}
GRXFSIZ => state.grxfsiz,
GNPTXFSIZ => state.gnptxfsiz,
GNPTXSTS => {
let free = self.tx_depth(&state, false).saturating_sub(Dwc2::tx_used(
&state,
false,
self.params.channels,
));
free | (TX_QUEUE_DEPTH << 16)
}
GCCFG => state.gccfg,
CID => state.cid,
HPTXFSIZ => state.hptxfsiz,
HCFG => {
let mut value = state.hcfg & HCFG_FSLSPCS_MASK;
if self.params.max_speed != Speed::High {
value |= HCFG_FSLSS;
}
value
}
HFIR => state.hfir & 0xffff,
HFNUM => {
let ft = self.frame_ticks(&state);
let remaining = ft - (state.ticks % ft);
state.frnum | ((remaining.min(0xffff) as u32) << 16)
}
HPTXSTS => {
let free = self.tx_depth(&state, true).saturating_sub(Dwc2::tx_used(
&state,
true,
self.params.channels,
));
free | (TX_QUEUE_DEPTH << 16)
}
HAINT => self.haint(&state),
HAINTMSK => state.haintmsk,
HPRT => state.hprt,
PCGCCTL => state.pcgcctl,
_ => 0,
}
}
pub fn write(&self, offset: u64, value: u32) -> After {
let offset = offset & !0x3;
let mut state = self.state.lock();
if offset >= FIFO_BASE {
let window = ((offset - FIFO_BASE) / FIFO_WINDOW) as usize;
if window >= usize::from(self.params.channels) {
return After::Nothing;
}
let periodic = state.channels[window].periodic();
let depth = self.tx_depth(&state, periodic);
let used = Dwc2::tx_used(&state, periodic, self.params.channels);
if used < depth {
state.channels[window].tx.extend(value.to_le_bytes());
}
return After::Nothing;
}
if Dwc2::wrong_role(&state, offset) {
state.gintsts |= GINT_MMIS;
}
let mut after = After::Nothing;
if (HCCHAR_BASE..DEVICE_BASE).contains(&offset) {
let index = ((offset - HCCHAR_BASE) / CHANNEL_STRIDE) as usize;
let register = (offset - HCCHAR_BASE) % CHANNEL_STRIDE;
if index < usize::from(self.params.channels) {
let channel = &mut state.channels[index];
match register {
0x00 => {
channel.hcchar = value;
if value & HCCHAR_CHDIS != 0 {
channel.halt();
}
}
0x04 => channel.hcsplt = value,
0x08 => channel.hcint &= !(value & HCINT_MASK),
0x0c => channel.hcintmsk = value & HCINT_MASK,
0x10 => channel.hctsiz = value,
_ => {}
}
}
self.publish(&state);
return after;
}
match offset {
GOTGCTL => state.gotgctl = value & OTGCTL_WRITABLE,
GOTGINT => state.gotgint &= !value,
GAHBCFG => state.gahbcfg = value & AHBCFG_WRITABLE,
GUSBCFG => {
let was_host = Dwc2::host_mode(&state);
state.gusbcfg = value & USBCFG_WRITABLE;
if Dwc2::host_mode(&state) != was_host {
state.gintsts |= GINT_CIDSCHG;
after = After::Port;
}
}
GRSTCTL => {
if value & RSTCTL_CSRST != 0 {
after = After::CoreReset;
} else {
if value & RSTCTL_RXFFLSH != 0 {
state.rx.clear();
}
if value & RSTCTL_TXFFLSH != 0 {
let which = (value >> RSTCTL_TXFNUM_SHIFT) & 0x1f;
for channel in state.channels.iter_mut() {
let mine = match which {
0 => !channel.periodic(),
1 => channel.periodic(),
0x10 => true,
_ => false,
};
if mine {
channel.tx.clear();
}
}
}
if value & RSTCTL_FCRST != 0 {
state.frnum = 0x3fff;
}
}
}
GINTSTS => state.gintsts &= !(value & GINT_W1C),
GINTMSK => state.gintmsk = value,
GRXFSIZ => state.grxfsiz = value & FIFO_DEPTH_MASK,
GNPTXFSIZ => state.gnptxfsiz = value,
GCCFG => state.gccfg = value,
CID => state.cid = value,
HPTXFSIZ => state.hptxfsiz = value,
HCFG => state.hcfg = value & HCFG_FSLSPCS_MASK,
HFIR => state.hfir = value & 0xffff,
HAINTMSK => state.haintmsk = value & 0xffff,
HPRT => {
let old = state.hprt;
let mut port = old;
port &= !(value & HPRT_W1C);
port = (port & !HPRT_WRITABLE) | (value & HPRT_WRITABLE);
if port & HPRT_PRST != 0 {
port &= !HPRT_PENA;
}
state.hprt = port;
after = if old & HPRT_PRST != 0 && port & HPRT_PRST == 0 {
After::FinishReset
} else if (old ^ port) & (HPRT_PENA | HPRT_PRST | HPRT_PPWR) != 0 {
After::Port
} else {
After::Nothing
};
}
PCGCCTL => state.pcgcctl = value,
_ => {}
}
self.publish(&state);
after
}
pub fn act(&self, after: After) {
match after {
After::Nothing => {}
After::CoreReset => self.core_reset(),
After::Port => {
let enabled = {
let state = self.state.lock();
state.hprt & HPRT_PENA != 0 && Dwc2::host_mode(&state)
};
self.bus.set_enabled(ROOT_PORT, enabled);
self.settle_port();
}
After::FinishReset => self.finish_reset(),
}
}
fn core_reset(&self) {
{
let mut state = self.state.lock();
let ticks = state.ticks;
*state = State {
ticks,
..State::reset(self.params)
};
self.publish(&state);
}
self.bus.set_enabled(ROOT_PORT, false);
self.settle_port();
self.refresh_irq();
}
pub fn reset(&self, _kind: ResetKind) {
{
let mut state = self.state.lock();
let ticks = state.ticks;
*state = State {
ticks,
..State::reset(self.params)
};
self.publish(&state);
}
self.bus.set_enabled(ROOT_PORT, false);
self.settle_port();
self.refresh_irq();
}
pub fn save<S: Sink + ?Sized>(&self, w: &mut S) -> Result<()> {
let state = self.state.lock();
w.write_u64(state.ticks)?;
w.write_u32(state.gotgctl)?;
w.write_u32(state.gotgint)?;
w.write_u32(state.gahbcfg)?;
w.write_u32(state.gusbcfg)?;
w.write_u32(state.gintsts)?;
w.write_u32(state.gintmsk)?;
w.write_u32(state.grxfsiz)?;
w.write_u32(state.gnptxfsiz)?;
w.write_u32(state.hptxfsiz)?;
w.write_u32(state.gccfg)?;
w.write_u32(state.cid)?;
w.write_u32(state.hcfg)?;
w.write_u32(state.hfir)?;
w.write_u32(state.frnum)?;
w.write_u32(state.hprt)?;
w.write_u32(state.haintmsk)?;
w.write_u32(state.pcgcctl)?;
w.write_seq_len(MAX_CHANNELS as u64)?;
for channel in &state.channels {
w.write_u32(channel.hcchar)?;
w.write_u32(channel.hcsplt)?;
w.write_u32(channel.hcint)?;
w.write_u32(channel.hcintmsk)?;
w.write_u32(channel.hctsiz)?;
let staged: Vec<u8> = channel.tx.iter().copied().collect();
w.write_bytes(&staged)?;
}
w.write_seq_len(state.rx.queue.len() as u64)?;
for packet in &state.rx.queue {
w.write_u32(packet.status)?;
w.write_bytes(&packet.data)?;
}
match &state.rx.current {
Some(packet) => {
w.write_bool(true)?;
w.write_u32(packet.status)?;
w.write_bytes(&packet.data)?;
w.write_u64(state.rx.read as u64)?;
}
None => w.write_bool(false)?,
}
Ok(())
}
pub fn load<'a, S: Source<'a> + ?Sized>(&self, r: &mut S) -> Result<()> {
let mut restored = State {
ticks: r.read_u64()?,
gotgctl: r.read_u32()?,
gotgint: r.read_u32()?,
gahbcfg: r.read_u32()?,
gusbcfg: r.read_u32()?,
gintsts: r.read_u32()?,
gintmsk: r.read_u32()?,
grxfsiz: r.read_u32()?,
gnptxfsiz: r.read_u32()?,
hptxfsiz: r.read_u32()?,
gccfg: r.read_u32()?,
cid: r.read_u32()?,
hcfg: r.read_u32()?,
hfir: r.read_u32()?,
frnum: r.read_u32()?,
hprt: r.read_u32()?,
haintmsk: r.read_u32()?,
pcgcctl: r.read_u32()?,
channels: core::array::from_fn(|_| Channel::default()),
rx: RxFifo::default(),
};
let count = r.read_seq_len(24)?;
if count != MAX_CHANNELS as u64 {
return Err(Error::State(alloc::format!(
"usb.dwc2: a snapshot with {count} channels, not {MAX_CHANNELS}"
)));
}
for channel in &mut restored.channels {
channel.hcchar = r.read_u32()?;
channel.hcsplt = r.read_u32()?;
channel.hcint = r.read_u32()?;
channel.hcintmsk = r.read_u32()?;
channel.hctsiz = r.read_u32()?;
channel.tx = r.read_bytes()?.iter().copied().collect();
}
let packets = r.read_seq_len(12)?;
for _ in 0..packets {
let status = r.read_u32()?;
let data = r.read_bytes()?.to_vec();
restored.rx.queue.push_back(RxPacket { status, data });
}
if r.read_bool()? {
let status = r.read_u32()?;
let data = r.read_bytes()?.to_vec();
let read = r.read_u64()? as usize;
restored.rx.read = read.min(data.len());
restored.rx.current = Some(RxPacket { status, data });
}
{
let mut state = self.state.lock();
*state = restored;
self.publish(&state);
}
let enabled = {
let state = self.state.lock();
state.hprt & HPRT_PENA != 0 && Dwc2::host_mode(&state)
};
self.bus.set_enabled(ROOT_PORT, enabled);
self.refresh_irq();
Ok(())
}
}
#[derive(Debug)]
pub struct Dwc2Controller {
core: Arc<Dwc2>,
region: RegionRef,
}
impl Dwc2Controller {
pub fn new(props: &Props) -> Result<Dwc2Controller> {
let mut r = props.reader();
let bus_name = r.require_str("bus")?.to_string();
let channels = r.or_range("channels", 8u64, 1..=MAX_CHANNELS as u64)?;
let fifo = r.or_range("fifo", 320u64, 1..=u64::from(FIFO_DEPTH_MASK))?;
let phyclock = r.or_range("phyclock", 1u64, 1..=u64::from(u32::MAX))?;
let speed = r.or_str("speed", "full")?.to_string();
let cid = r.or_range("cid", 0u64, 0..=u64::from(u32::MAX))?;
r.finish()?;
let max_speed = match speed.as_str() {
"full" => Speed::Full,
"high" => Speed::High,
other => {
return Err(Error::Config {
at: String::from(CLASS_NAME),
message: alloc::format!(
"`speed` is the fastest this core's transceiver can signal, so it is \
`full` or `high`, not `{other}`"
),
});
}
};
let bus = buses::attach(props, &bus_name, 1)?;
Ok(Dwc2Controller::with_bus(
bus,
Params {
channels: channels as u8,
fifo_words: fifo as u32,
phy_ticks: phyclock,
max_speed,
cid: cid as u32,
},
))
}
#[must_use]
pub fn with_bus(bus: Arc<UsbBus>, params: Params) -> Dwc2Controller {
let core = Arc::new(Dwc2::new(bus, params));
let port = Arc::new(Dwc2Port {
core: Arc::clone(&core),
});
let region = Arc::new(Region::io("dwc2", REGISTER_BYTES, port as Arc<dyn MemOps>));
Dwc2Controller { core, region }
}
#[must_use]
pub fn core(&self) -> &Arc<Dwc2> {
&self.core
}
}
pub mod pin {
pub const IRQ: &str = "irq";
}
struct Dwc2Port {
core: Arc<Dwc2>,
}
impl fmt::Debug for Dwc2Port {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Dwc2Port").finish_non_exhaustive()
}
}
impl MemOps for Dwc2Port {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
self.core.sync_for(attrs);
let value = self.core.read(offset, attrs.debug);
if !attrs.debug {
self.core.refresh_irq();
}
narrow_read(offset, value, dst)
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
if attrs.debug {
return Err(BusError::BadAccess);
}
let Some(value) = word_write(src) else {
return Err(BusError::BadAccess);
};
self.core.sync_for(attrs);
let after = self.core.write(offset, value);
self.core.act(after);
self.core.refresh_irq();
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO
.with_widths(Width::U8, Width::U32)
.with_natural_alignment(true)
}
}
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),
}
}
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 Dwc2Controller {
fn class(&self) -> &'static DeviceClass {
&DWC2_CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, kind: ResetKind) {
self.core.reset(kind);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
self.core.save(w)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
self.core.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!("a dwc2 controller drives `{}` and nothing else", pin::IRQ),
});
}
self.core.connect_irq(source);
Ok(())
}
fn announce(&self, _port: &str) {
self.core.refresh_irq();
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.core.ticks()
}
fn advance_to(&self, tick: u64) {
self.core.advance_to(tick);
}
fn next_event_tick(&self) -> Option<u64> {
self.core.next_event_tick()
}
fn attach_lazy(&self, handle: LazyHandle) {
self.core.attach_lazy(handle);
}
}
impl Instance for Dwc2Controller {}
pub static DWC2_CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "a Synopsys DesignWare USB 2.0 OTG host controller (dwc2), as STM32's OTG_FS \
instantiates it: host channels and a shared FIFO, with no DMA and no schedule \
in guest memory",
properties: DWC2_PROPERTIES,
construct: |props| Ok(Box::new(Dwc2Controller::new(props)?)),
};
static DWC2_PROPERTIES: &[PropertySpec] = &[
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: true,
summary: "the named USB bus this controller is the root of",
},
PropertySpec {
name: "channels",
kind: ValueKind::Uint,
required: false,
summary: "how many host channels, 1 to 16 (default 8, an STM32 OTG_FS)",
},
PropertySpec {
name: "fifo",
kind: ValueKind::Uint,
required: false,
summary: "32-bit words of dedicated FIFO RAM (default 320, the 1.25 KiB of an OTG_FS)",
},
PropertySpec {
name: "phyclock",
kind: ValueKind::Uint,
required: false,
summary: "clock-domain ticks in one PHY clock (default 1, the block on its 48 MHz domain)",
},
PropertySpec {
name: "speed",
kind: ValueKind::Str,
required: false,
summary: "the fastest the transceiver can signal: `full` (default) or `high`",
},
PropertySpec {
name: "cid",
kind: ValueKind::Uint,
required: false,
summary: "what the vendor's user-ID register reads (default 0)",
},
];
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&DWC2_CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| {
Ok(Arc::new(Dwc2Controller::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("channels", ValueKind::Uint).range(1, MAX_CHANNELS as u64))
.prop(PropSchema::new("fifo", ValueKind::Uint).range(1, u64::from(FIFO_DEPTH_MASK)))
.prop(PropSchema::new("phyclock", ValueKind::Uint).range(1, u64::from(u32::MAX)))
.prop(PropSchema::new("speed", ValueKind::Str).values(&["full", "high"]))
.prop(PropSchema::new("cid", ValueKind::Uint).range(0, u64::from(u32::MAX)))
.port(pin::IRQ, PortDir::Out)
.region("")
.region("regs")
}