use tock_registers::interfaces::{Readable, Writeable};
use tock_registers::LocalRegisterCopy;
use crate::usb::error::{UsbError, UsbResult};
use crate::utils::cache;
use crate::usb;
use crate::usb::setup;
use super::regs::{Dwc2HostChannel, Dwc2Regs, HCCHAR, HCINT, HCTSIZ, HFNUM};
#[allow(dead_code)]
pub type HcintSnapshot = LocalRegisterCopy<u32, HCINT::Register>;
#[inline]
fn regs() -> &'static Dwc2Regs {
usb::dwc2_regs().expect("DWC2 base not set (call set_dwc2_base_virt)")
}
#[inline]
fn channel(ch: u32) -> &'static Dwc2HostChannel {
usb::dwc2_channel(ch).expect("invalid DWC2 host channel index")
}
const CH_CTL: u32 = 0;
const CH_BULK: u32 = 1;
const HCCHAR_CHENA: u32 = 1 << 31;
const HCCHAR_CHDIS: u32 = 1 << 30;
const HCCHAR_ODDFRM: u32 = 1 << 29;
const HCCHAR_EPDIR: u32 = 1 << 15;
const HCCHAR_EPTYPE_CONTROL: u32 = 0 << 18;
const HCCHAR_EPTYPE_ISOCH: u32 = 1 << 18;
const HCCHAR_EPTYPE_BULK: u32 = 2 << 18;
const HCCHAR_MC_SHIFT: u32 = 20;
const HCINT_ALL_W1C: u32 = 0x7FF;
pub const PID_DATA0: u32 = 0;
pub const PID_DATA2: u32 = 1;
pub const PID_DATA1: u32 = 2;
pub const PID_SETUP: u32 = 3;
#[repr(C, align(256))]
struct DmaBuf {
bytes: [u8; 1024],
uvc_bulk: [u8; 384 * 1024],
}
static mut DMA_BUF: DmaBuf = DmaBuf {
bytes: [0; 1024],
uvc_bulk: [0; 384 * 1024],
};
pub const DMA_OFF_UVC_BULK: usize = 1024;
pub const UVC_BULK_DMA_CAP: usize = 384 * 1024;
const DMA_BUF_TOTAL: usize = 1024 + UVC_BULK_DMA_CAP;
const _: () = assert!(DMA_BUF_TOTAL <= 1024 + 384 * 1024);
#[inline]
pub fn dma_rx_slice(off: usize, len: usize) -> Option<&'static [u8]> {
if len == 0 || off.checked_add(len)? > DMA_BUF_TOTAL {
return None;
}
Some(unsafe { core::slice::from_raw_parts(dma_ptr().add(off), len) })
}
pub fn dma_write_at(off: usize, src: &[u8]) -> UsbResult<()> {
let end = off.checked_add(src.len()).ok_or(UsbError::Protocol("dma write overflow"))?;
if end > DMA_BUF_TOTAL {
return Err(UsbError::Protocol("dma write out of buf"));
}
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), dma_ptr().add(off), src.len());
}
Ok(())
}
const OFF_EP0: usize = 0;
const DMA_OFF_SMALL_IO: usize = 256;
pub const DMA_OFF_CBW: usize = 320;
pub const DMA_OFF_CSW: usize = 384;
pub const DMA_OFF_SECTOR: usize = DMA_OFF_UVC_BULK;
pub const MSC_SECTOR_DMA_CAP: usize = UVC_BULK_DMA_CAP;
#[inline]
fn spin_delay(n: u32) {
for _ in 0..n {
core::hint::spin_loop();
}
}
#[inline]
fn dma_ptr() -> *mut u8 {
core::ptr::addr_of_mut!(DMA_BUF).cast::<u8>()
}
fn dma_phys(off: usize) -> u32 {
unsafe { usb::usb_dma_phys_for(dma_ptr().add(off)) }
}
#[inline]
fn usb_bus_fence_before_dma() {
#[cfg(target_arch = "riscv64")]
unsafe {
core::arch::asm!("fence rw, rw", options(nostack));
}
}
fn ch_wait_disabled(ch: u32) -> UsbResult<()> {
let c = channel(ch);
for _ in 0..2_000_000u32 {
if !c.hcchar.is_set(HCCHAR::CHENA) {
return Ok(());
}
spin_delay(8);
}
Err(UsbError::Timeout)
}
fn ch_halt(ch: u32) {
let c = channel(ch);
let v = c.hcchar.get();
if v & HCCHAR_CHENA == 0 {
return;
}
c.hcchar.set(v | HCCHAR_CHENA | HCCHAR_CHDIS);
for _ in 0..500_000u32 {
if !c.hcchar.is_set(HCCHAR::CHENA) {
return;
}
spin_delay(8);
}
}
fn ch_wait_halted(ch: u32) -> UsbResult<HcintSnapshot> {
let c = channel(ch);
for _ in 0..8_000_000u32 {
let hi = c.hcint.extract();
if hi.is_set(HCINT::CHHLTD) {
c.hcint.set(hi.get());
return Ok(hi);
}
spin_delay(8);
}
Err(UsbError::Timeout)
}
unsafe fn ch_xfer(ch: u32, hcchar: u32, hctsiz: u32, dma_off: u32) -> UsbResult<HcintSnapshot> {
let c = channel(ch);
let dmap = dma_phys(dma_off as usize);
const NAK_RETRIES: u32 = 64;
const XACT_RETRIES: u32 = 8;
let mut xact_left = XACT_RETRIES;
for attempt in 0..=NAK_RETRIES {
ch_wait_disabled(ch)?;
ch_halt(ch);
c.hcsplt.set(0);
c.hcint.set(HCINT_ALL_W1C);
c.hctsiz.set(hctsiz);
usb_bus_fence_before_dma();
c.hcdma.set(dmap);
usb_bus_fence_before_dma();
c.hcchar.set(hcchar | HCCHAR_CHENA);
let st = ch_wait_halted(ch)?;
if st.is_set(HCINT::STALL) {
return Err(UsbError::Stall);
}
if st.is_set(HCINT::XACTERR) {
if xact_left == 0 {
log::info!("USB-XACT EXHAUSTED ch={} hcchar={:#010x} hctsiz={:#010x} dma={:#010x} hcint={:#010x}",
ch, hcchar, hctsiz, dmap, st.get());
return Err(UsbError::Protocol("ch xfer error (XACT)"));
}
xact_left -= 1;
spin_delay(2_000_000);
continue;
}
if st.is_set(HCINT::NAK) {
if attempt == NAK_RETRIES {
log::info!("USB-NAK EXHAUSTED ch={} hcchar={:#010x} hctsiz={:#010x} dma={:#010x} hcint={:#010x}",
ch, hcchar, hctsiz, dmap, st.get());
return Err(UsbError::Protocol("ch xfer NAK exhausted"));
}
spin_delay(200_000);
continue;
}
if !st.is_set(HCINT::XFERCOMPL) {
log::info!("USB-CHHLTD-NO-XFER ch={} hcchar={:#010x} hctsiz={:#010x} dma={:#010x} hcint={:#010x}",
ch, hcchar, hctsiz, dmap, st.get());
return Err(UsbError::Protocol("CHHLTD without XFERCOMPL"));
}
return Ok(st);
}
unreachable!()
}
unsafe fn ch_xfer_video_retryable(
ch: u32,
hcchar: u32,
hctsiz: u32,
dma_off: u32,
) -> UsbResult<HcintSnapshot> {
let c = channel(ch);
ch_wait_disabled(ch)?;
ch_halt(ch);
c.hcsplt.set(0);
c.hcint.set(HCINT_ALL_W1C);
c.hctsiz.set(hctsiz);
let dmap = dma_phys(dma_off as usize);
usb_bus_fence_before_dma();
c.hcdma.set(dmap);
usb_bus_fence_before_dma();
c.hcchar.set(hcchar | HCCHAR_CHENA);
let st = ch_wait_halted(ch)?;
if st.is_set(HCINT::STALL) {
return Err(UsbError::Stall);
}
if st.is_set(HCINT::XACTERR) {
return Err(UsbError::Protocol("ch xfer XACTERR"));
}
if st.is_set(HCINT::NAK) {
return Err(UsbError::Nak);
}
if !st.is_set(HCINT::XFERCOMPL) {
return Err(UsbError::Protocol("CHHLTD without XFERCOMPL"));
}
Ok(st)
}
unsafe fn hcchar_control(dev: u32, ep: u32, mps: u32, dir_in: bool) -> u32 {
let mut v = mps & 0x7ff;
v |= (ep & 0xf) << 11;
if dir_in {
v |= HCCHAR_EPDIR;
}
v |= HCCHAR_EPTYPE_CONTROL;
v |= (dev & 0x7f) << 22;
v
}
unsafe fn hcchar_bulk(dev: u32, ep: u32, mps: u32, dir_in: bool) -> u32 {
let mut v = mps & 0x7ff;
v |= (ep & 0xf) << 11;
if dir_in {
v |= HCCHAR_EPDIR;
}
v |= HCCHAR_EPTYPE_BULK;
v |= (dev & 0x7f) << 22;
v
}
unsafe fn hcchar_isoch(dev: u32, ep: u32, mps: u32, mult: u32, dir_in: bool) -> u32 {
let mut v = mps & 0x7ff;
v |= (ep & 0xf) << 11;
if dir_in {
v |= HCCHAR_EPDIR;
}
v |= HCCHAR_EPTYPE_ISOCH;
let mc = mult.clamp(1, 3) & 0x3;
v |= mc << HCCHAR_MC_SHIFT;
v |= (dev & 0x7f) << 22;
v
}
#[inline]
fn next_uframe_oddfrm() -> u32 {
let fr = regs().hfnum.read(HFNUM::FRNUM);
if (fr & 1) == 0 { HCCHAR_ODDFRM } else { 0 }
}
#[inline]
pub fn current_uframe() -> u32 {
regs().hfnum.read(HFNUM::FRNUM)
}
unsafe fn hctsiz(pid: u32, pktcnt: u32, xfersize: u32) -> u32 {
(HCTSIZ::PID.val(pid) + HCTSIZ::PKTCNT.val(pktcnt) + HCTSIZ::XFERSIZE.val(xfersize)).value
}
pub fn usb_post_set_address_delay() {
spin_delay(20_000_000);
}
pub fn usb_post_hub_port_reset_delay() {
spin_delay(30_000_000);
}
#[inline]
fn normalize_ep0_mps(b: u8) -> u32 {
match b {
8 | 16 | 32 | 64 => b as u32,
_ => 8,
}
}
pub fn ep0_control_write_no_data(dev: u32, setup: [u8; 8], ep0_mps: u32) -> UsbResult<()> {
unsafe {
core::ptr::copy_nonoverlapping(setup.as_ptr(), dma_ptr().add(OFF_EP0), 8);
cache::dcache_clean_for_dma(dma_ptr().add(OFF_EP0), 8);
let hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(CH_CTL, hc, hctsiz(PID_SETUP, 1, 8), OFF_EP0 as u32)?;
let hc = hcchar_control(dev, 0, ep0_mps, true);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_DATA1, 1, 0),
OFF_EP0 as u32,
)?;
Ok(())
}
}
pub fn set_usb_address(addr: u8, ep0_mps: u32) -> UsbResult<()> {
ep0_control_write_no_data(0, setup::set_address(addr), ep0_mps)
}
pub fn set_configuration(dev: u32, cfg: u8, ep0_mps: u32) -> UsbResult<()> {
ep0_control_write_no_data(dev, setup::set_configuration(cfg), ep0_mps)
}
#[allow(dead_code)]
pub fn get_configuration(dev: u32, ep0_mps: u32) -> UsbResult<u8> {
unsafe {
let setup_pkt = setup::get_configuration();
core::ptr::copy_nonoverlapping(setup_pkt.as_ptr(), dma_ptr().add(OFF_EP0), 8);
cache::dcache_clean_for_dma(dma_ptr().add(OFF_EP0), 8);
let mut hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_SETUP, 1, 8),
OFF_EP0 as u32,
)?;
hc = hcchar_control(dev, 0, ep0_mps, true);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_DATA1, 1, 1),
OFF_EP0 as u32,
)?;
cache::dcache_invalidate_after_dma(dma_ptr().add(OFF_EP0), 1);
let v = dma_ptr().add(OFF_EP0).read();
hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_DATA1, 1, 0),
OFF_EP0 as u32,
)?;
Ok(v)
}
}
pub fn get_device_vid_pid_default_addr() -> UsbResult<(u16, u16, u32, u8)> {
unsafe {
let wlen: u16 = 18;
let setup_pkt = setup::get_descriptor_device(wlen);
core::ptr::copy_nonoverlapping(setup_pkt.as_ptr(), dma_ptr().add(OFF_EP0), 8);
cache::dcache_clean_for_dma(dma_ptr().add(OFF_EP0), 8);
let mut hc = hcchar_control(0, 0, 64, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_SETUP, 1, 8),
OFF_EP0 as u32,
)?;
hc = hcchar_control(0, 0, 64, true);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_DATA1, 1, wlen as u32),
OFF_EP0 as u32,
)?;
cache::dcache_invalidate_after_dma(dma_ptr().add(OFF_EP0), wlen as usize);
let sl = core::slice::from_raw_parts(dma_ptr().add(OFF_EP0), wlen as usize);
if sl.len() < 12 {
return Err(UsbError::Protocol("short descriptor"));
}
let vid = u16::from_le_bytes([sl[8], sl[9]]);
let pid = u16::from_le_bytes([sl[10], sl[11]]);
let ep0_mps = normalize_ep0_mps(sl[7]);
let b_device_class = sl[4];
hc = hcchar_control(0, 0, 64, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_DATA1, 1, 0),
OFF_EP0 as u32,
)?;
Ok((vid, pid, ep0_mps, b_device_class))
}
}
pub fn hub_set_port_feature(dev: u32, port: u16, feature: u16, ep0_mps: u32) -> UsbResult<()> {
ep0_control_write_no_data(dev, setup::hub_set_port_feature(port, feature), ep0_mps)
}
pub fn hub_clear_port_feature(dev: u32, port: u16, feature: u16, ep0_mps: u32) -> UsbResult<()> {
ep0_control_write_no_data(dev, setup::hub_clear_port_feature(port, feature), ep0_mps)
}
pub fn ep0_control_read(dev: u32, setup_pkt: [u8; 8], ep0_mps: u32, out: &mut [u8]) -> UsbResult<()> {
if out.is_empty() || out.len() > 4096 {
return Err(UsbError::Protocol("bad ep0 read len"));
}
let total = out.len() as u32;
unsafe {
core::ptr::copy_nonoverlapping(setup_pkt.as_ptr(), dma_ptr().add(OFF_EP0), 8);
cache::dcache_clean_for_dma(dma_ptr().add(OFF_EP0), 8);
let mut hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_SETUP, 1, 8),
OFF_EP0 as u32,
)?;
let mut left = total;
let mut out_off: usize = 0;
let mut toggle = PID_DATA1;
while left > 0 {
let chunk = left.min(ep0_mps);
let pkts = pktcnt_for(ep0_mps, chunk);
hc = hcchar_control(dev, 0, ep0_mps, true);
ch_xfer(
CH_CTL,
hc,
hctsiz(toggle, pkts, chunk),
DMA_OFF_SMALL_IO as u32,
)?;
cache::dcache_invalidate_after_dma(dma_ptr().add(DMA_OFF_SMALL_IO), chunk as usize);
core::ptr::copy_nonoverlapping(
dma_ptr().add(DMA_OFF_SMALL_IO),
out.as_mut_ptr().add(out_off),
chunk as usize,
);
out_off += chunk as usize;
left -= chunk;
toggle = if toggle == PID_DATA1 {
PID_DATA0
} else {
PID_DATA1
};
}
hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_DATA1, 1, 0),
OFF_EP0 as u32,
)?;
Ok(())
}
}
pub fn ep0_control_write(dev: u32, setup_pkt: [u8; 8], ep0_mps: u32, data: &[u8]) -> UsbResult<()> {
if data.len() > 4096 {
return Err(UsbError::Protocol("bad ep0 write data len"));
}
unsafe {
core::ptr::copy_nonoverlapping(setup_pkt.as_ptr(), dma_ptr().add(OFF_EP0), 8);
cache::dcache_clean_for_dma(dma_ptr().add(OFF_EP0), 8);
let mut hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_SETUP, 1, 8),
OFF_EP0 as u32,
)?;
let mut left = data.len() as u32;
let mut src: usize = 0;
let mut toggle = PID_DATA1;
while left > 0 {
let chunk = left.min(ep0_mps);
let pkts = pktcnt_for(ep0_mps, chunk);
core::ptr::copy_nonoverlapping(
data.as_ptr().add(src),
dma_ptr().add(DMA_OFF_SMALL_IO),
chunk as usize,
);
cache::dcache_clean_for_dma(dma_ptr().add(DMA_OFF_SMALL_IO), chunk as usize);
hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(
CH_CTL,
hc,
hctsiz(toggle, pkts, chunk),
DMA_OFF_SMALL_IO as u32,
)?;
src += chunk as usize;
left -= chunk;
toggle = if toggle == PID_DATA1 {
PID_DATA0
} else {
PID_DATA1
};
}
hc = hcchar_control(dev, 0, ep0_mps, true);
ch_xfer(
CH_CTL,
hc,
hctsiz(PID_DATA1, 1, 0),
OFF_EP0 as u32,
)?;
Ok(())
}
}
pub fn ep0_control_read_one_byte(dev: u32, setup_pkt: [u8; 8], ep0_mps: u32) -> UsbResult<u8> {
unsafe {
core::ptr::copy_nonoverlapping(setup_pkt.as_ptr(), dma_ptr().add(OFF_EP0), 8);
cache::dcache_clean_for_dma(dma_ptr().add(OFF_EP0), 8);
let mut hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(CH_CTL, hc, hctsiz(PID_SETUP, 1, 8), OFF_EP0 as u32)?;
hc = hcchar_control(dev, 0, ep0_mps, true);
ch_xfer(CH_CTL, hc, hctsiz(PID_DATA1, 1, 1), OFF_EP0 as u32)?;
cache::dcache_invalidate_after_dma(dma_ptr().add(OFF_EP0), 1);
let v = dma_ptr().add(OFF_EP0).read();
hc = hcchar_control(dev, 0, ep0_mps, false);
ch_xfer(CH_CTL, hc, hctsiz(PID_DATA1, 1, 0), OFF_EP0 as u32)?;
Ok(v)
}
}
pub fn dma_copy_out(off: usize, dst: &mut [u8]) {
unsafe {
core::ptr::copy_nonoverlapping(dma_ptr().add(off), dst.as_mut_ptr(), dst.len());
}
}
fn pktcnt_for(mps: u32, nbytes: u32) -> u32 {
if mps == 0 {
return 1;
}
nbytes.div_ceil(mps)
}
pub fn bulk_out(dev: u32, ep: u32, mps: u32, pid: u32, data: &[u8], dma_off: usize) -> UsbResult<()> {
if data.is_empty() || data.len() > 0x7ffff {
return Err(UsbError::Protocol("bad bulk out len"));
}
unsafe {
core::ptr::copy_nonoverlapping(data.as_ptr(), dma_ptr().add(dma_off), data.len());
cache::dcache_clean_for_dma(dma_ptr().add(dma_off), data.len());
let hc = hcchar_bulk(dev, ep, mps, false);
let pkts = pktcnt_for(mps, data.len() as u32);
ch_xfer(
CH_BULK,
hc,
hctsiz(pid, pkts, data.len() as u32),
dma_off as u32,
)?;
Ok(())
}
}
#[inline]
fn spin_short() {
spin_delay(64);
}
pub fn bulk_in(dev: u32, ep: u32, mps: u32, pid: u32, len: usize, dma_off: usize) -> UsbResult<usize> {
if len == 0 || len > 0x7ffff {
return Err(UsbError::Protocol("bad bulk in len"));
}
unsafe {
let hc = hcchar_bulk(dev, ep, mps, true);
let pkts = pktcnt_for(mps, len as u32);
let tsiz = hctsiz(pid, pkts, len as u32);
for _ in 0..4_000_000u32 {
match ch_xfer_video_retryable(CH_BULK, hc, tsiz, dma_off as u32) {
Ok(_) => {}
Err(UsbError::Nak) => {
spin_short();
continue;
}
Err(e) => return Err(e),
}
let rem = channel(CH_BULK).hctsiz.read(HCTSIZ::XFERSIZE);
let mut actual = (len as u32).saturating_sub(rem) as usize;
actual = actual.min(len);
if actual == 0 && len > 0 {
actual = len;
}
if actual > 0 {
cache::dcache_invalidate_after_dma(dma_ptr().add(dma_off), actual);
}
return Ok(actual);
}
Err(UsbError::Timeout)
}
}
pub fn isoch_in_uframe(dev: u32, ep: u32, mps_raw: u16, dma_off: usize) -> UsbResult<usize> {
let mps = u32::from(mps_raw & 0x7ff);
let mult = u32::from((mps_raw >> 11) & 0x3) + 1;
if mps == 0 || mult == 0 || mult > 3 {
return Err(UsbError::Protocol("bad isoch mps_raw"));
}
let xfersize = mps.saturating_mul(mult);
if (xfersize as usize) > UVC_BULK_DMA_CAP {
return Err(UsbError::Protocol("isoch xfer > dma cap"));
}
let pid = match mult {
3 => PID_DATA2,
2 => PID_DATA1,
_ => PID_DATA0,
};
let pktcnt = mult;
unsafe {
let hc_base = hcchar_isoch(dev, ep, mps, mult, true);
let tsiz = hctsiz(pid, pktcnt, xfersize);
let c = channel(CH_BULK);
ch_wait_disabled(CH_BULK)?;
ch_halt(CH_BULK);
c.hcsplt.set(0);
c.hcint.set(HCINT_ALL_W1C);
c.hctsiz.set(tsiz);
let dmap = dma_phys(dma_off);
usb_bus_fence_before_dma();
c.hcdma.set(dmap);
usb_bus_fence_before_dma();
let oddfrm = next_uframe_oddfrm();
c.hcchar.set(hc_base | oddfrm | HCCHAR_CHENA);
let st = ch_wait_halted(CH_BULK)?;
if st.is_set(HCINT::STALL) {
return Err(UsbError::Stall);
}
if st.is_set(HCINT::AHBERR) {
return Err(UsbError::Hardware("AHBERR on isoch"));
}
if st.is_set(HCINT::FRMOVRN)
|| st.is_set(HCINT::XACTERR)
|| st.is_set(HCINT::BBLERR)
|| st.is_set(HCINT::DATATGLERR)
|| st.is_set(HCINT::NYET)
|| st.is_set(HCINT::NAK)
{
return Ok(0);
}
if !st.is_set(HCINT::XFERCOMPL) {
return Ok(0);
}
let rem = c.hctsiz.read(HCTSIZ::XFERSIZE);
let actual = xfersize.saturating_sub(rem) as usize;
if actual > 0 {
cache::dcache_invalidate_after_dma(dma_ptr().add(dma_off), actual);
}
Ok(actual)
}
}
#[allow(dead_code)]
pub fn isoch_in(dev: u32, ep: u32, mps: u32, len: usize, dma_off: usize) -> UsbResult<usize> {
if len == 0 || len > 0x7ffff || len > mps as usize {
return Err(UsbError::Protocol("bad isoch in len"));
}
unsafe {
let hc = hcchar_isoch(dev, ep, mps, 1, true);
let pkts = pktcnt_for(mps, len as u32);
let tsiz = hctsiz(PID_DATA0, pkts, len as u32);
let c = channel(CH_BULK);
ch_wait_disabled(CH_BULK)?;
ch_halt(CH_BULK);
c.hcsplt.set(0);
c.hcint.set(HCINT_ALL_W1C);
c.hctsiz.set(tsiz);
let dmap = dma_phys(dma_off);
usb_bus_fence_before_dma();
c.hcdma.set(dmap);
usb_bus_fence_before_dma();
let oddfrm = next_uframe_oddfrm();
c.hcchar.set(hc | oddfrm | HCCHAR_CHENA);
let st = ch_wait_halted(CH_BULK)?;
if st.is_set(HCINT::STALL) {
return Err(UsbError::Stall);
}
if st.is_set(HCINT::FRMOVRN)
|| st.is_set(HCINT::XACTERR)
|| st.is_set(HCINT::BBLERR)
|| st.is_set(HCINT::NYET)
{
return Ok(0);
}
if !st.is_set(HCINT::XFERCOMPL) {
return Ok(0);
}
let rem = c.hctsiz.read(HCTSIZ::XFERSIZE);
let actual = (len as u32).saturating_sub(rem) as usize;
if actual > 0 {
cache::dcache_invalidate_after_dma(dma_ptr().add(dma_off), actual);
}
Ok(actual)
}
}