From 0fbc358ee2d0830de79344520c8ad73b303b3b40 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 20 Feb 2020 23:43:05 +0100 Subject: [PATCH 01/38] wip --- Cargo.toml | 7 + examples/usb/.cargo/config | 2 + examples/usb/.gdbinit | 13 + examples/usb/Cargo.toml | 18 + examples/usb/README.md | 14 + examples/usb/src/main.rs | 72 ++++ nrf-hal-common/Cargo.toml | 6 +- nrf-hal-common/src/lib.rs | 2 + nrf-hal-common/src/usbd/errata.rs | 57 +++ nrf-hal-common/src/usbd/mod.rs | 638 ++++++++++++++++++++++++++++++ 10 files changed, 828 insertions(+), 1 deletion(-) create mode 100644 examples/usb/.cargo/config create mode 100644 examples/usb/.gdbinit create mode 100644 examples/usb/Cargo.toml create mode 100644 examples/usb/README.md create mode 100644 examples/usb/src/main.rs create mode 100644 nrf-hal-common/src/usbd/errata.rs create mode 100644 nrf-hal-common/src/usbd/mod.rs diff --git a/Cargo.toml b/Cargo.toml index a6b0b921..a5b8116a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,10 @@ lto = false debug = true lto = true opt-level = "s" + +[patch.crates-io.usb-device] +git = "https://github.com/jonas-schievink/usb-device.git" +branch = "inhibit-setaddr-resp" + +[patch.crates-io.panic-semihosting] +path = "../panic-semihosting" diff --git a/examples/usb/.cargo/config b/examples/usb/.cargo/config new file mode 100644 index 00000000..08b949e1 --- /dev/null +++ b/examples/usb/.cargo/config @@ -0,0 +1,2 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = 'arm-none-eabi-gdb' diff --git a/examples/usb/.gdbinit b/examples/usb/.gdbinit new file mode 100644 index 00000000..c80bd21b --- /dev/null +++ b/examples/usb/.gdbinit @@ -0,0 +1,13 @@ +# disable "are you sure you want to quit?" +define hook-quit + set confirm off +end + +target remote :3333 + +# print demangled symbols by default +set print asm-demangle on + +monitor arm semihosting enable +load +cont diff --git a/examples/usb/Cargo.toml b/examples/usb/Cargo.toml new file mode 100644 index 00000000..6d951471 --- /dev/null +++ b/examples/usb/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "usb" +version = "0.1.0" +authors = ["Jonas Schievink "] +edition = "2018" + +[dependencies] +cortex-m = "0.6.2" +cortex-m-rt = "0.6.12" +cortex-m-semihosting = "0.3.5" +panic-semihosting = "0.5.3" +nrf52840-pac = "0.8.0" +usb-device = "0.2.5" +usbd-serial = "0.1.0" + +[dependencies.nrf52840-hal] +version = "0.8.0" +path = "../../nrf52840-hal" diff --git a/examples/usb/README.md b/examples/usb/README.md new file mode 100644 index 00000000..822d9478 --- /dev/null +++ b/examples/usb/README.md @@ -0,0 +1,14 @@ +# spi-demo +SPIM demonstation code. +Connect a resistor between pin 22 and 23 on the demo board to feed MOSI directly back to MISO +If all tests pass all four Led (Led1 to Led4) will light up, in case of error only at least one of the Led will remain turned off. + + +## HW connections +Pin Connecton +P0.24 SPIclk +P0.23 MOSI +P0.22 MISO + +This is designed for nRF52-DK board: +https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52-DK diff --git a/examples/usb/src/main.rs b/examples/usb/src/main.rs new file mode 100644 index 00000000..5fd36e77 --- /dev/null +++ b/examples/usb/src/main.rs @@ -0,0 +1,72 @@ +#![no_std] +#![no_main] + +use panic_semihosting as _; + +use cortex_m::peripheral::SCB; +use cortex_m_rt::entry; +use cortex_m_semihosting::hprintln; +use nrf52840_hal::gpio::{p0, p1, Level}; +use nrf52840_hal::prelude::*; +use nrf52840_hal::timer::{OneShot, Timer}; +use nrf52840_hal::usbd::Usbd; +use nrf52840_pac::{interrupt, Peripherals, TIMER0}; +use usb_device::device::{UsbDeviceBuilder, UsbDeviceState, UsbVidPid}; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +#[interrupt] +fn TIMER0() { + SCB::sys_reset(); +} + +#[entry] +fn main() -> ! { + static mut EP_BUF: [u8; 256] = [0; 256]; + + let core = cortex_m::Peripherals::take().unwrap(); + let periph = Peripherals::take().unwrap(); + while !periph + .POWER + .usbregstatus + .read() + .vbusdetect() + .is_vbus_present() + {} + + let mut nvic = core.NVIC; + let mut timer = Timer::one_shot(periph.TIMER0); + let usbd = periph.USBD; + let p0 = p0::Parts::new(periph.P0); + let p1 = p1::Parts::new(periph.P1); + + let mut led = p0.p0_23.into_push_pull_output(Level::High); + let btn = p1.p1_00.into_pullup_input(); + while btn.is_high().unwrap() {} + + timer.enable_interrupt(Some(&mut nvic)); + timer.start(Timer::::TICKS_PER_SECOND * 3); + + led.set_low().unwrap(); + + let usb_bus = Usbd::new_alloc(usbd, EP_BUF); + let mut serial = SerialPort::new(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .product("nRF52840 Serial Port Demo") + .device_class(USB_CLASS_CDC) + .build(); + + hprintln!("").ok(); + let mut state = UsbDeviceState::Default; + loop { + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let new_state = usb_dev.state(); + if new_state != state { + hprintln!("{:?} {:#x}", new_state, usb_dev.bus().device_address()).ok(); + state = new_state; + } + } +} diff --git a/nrf-hal-common/Cargo.toml b/nrf-hal-common/Cargo.toml index 79530f5a..d8773c08 100644 --- a/nrf-hal-common/Cargo.toml +++ b/nrf-hal-common/Cargo.toml @@ -38,6 +38,10 @@ version = "0.2.3" optional = true version = "0.9.0" +[dependencies.usb-device] +version = "0.2.5" +optional = true + [dependencies.nrf52810-pac] optional = true version = "0.9.0" @@ -73,5 +77,5 @@ doc = [] 52811 = ["nrf52811-pac"] 52832 = ["nrf52832-pac"] 52833 = ["nrf52833-pac"] -52840 = ["nrf52840-pac"] +52840 = ["nrf52840-pac", "usb-device"] 9160 = ["nrf9160-pac"] diff --git a/nrf-hal-common/src/lib.rs b/nrf-hal-common/src/lib.rs index 391a72fb..0973f1fc 100644 --- a/nrf-hal-common/src/lib.rs +++ b/nrf-hal-common/src/lib.rs @@ -79,6 +79,8 @@ pub mod uarte; #[cfg(not(feature = "9160"))] pub mod uicr; pub mod wdt; +#[cfg(feature = "52840")] +pub mod usbd; pub mod prelude { pub use crate::hal::digital::v2::*; diff --git a/nrf-hal-common/src/usbd/errata.rs b/nrf-hal-common/src/usbd/errata.rs new file mode 100644 index 00000000..4780d81f --- /dev/null +++ b/nrf-hal-common/src/usbd/errata.rs @@ -0,0 +1,57 @@ +/// Writes `val` to `addr`. Used to apply Errata workarounds. +unsafe fn poke(addr: u32, val: u32) { + *(addr as *mut u32) = val; +} + +/// Reads 32 bits from `addr`. +unsafe fn peek(addr: u32) -> u32 { + *(addr as *mut u32) +} + +pub fn pre_enable() { + // Works around Erratum 187 on chip revisions 1 and 2. + unsafe { + poke(0x4006EC00, 0x00009375); + poke(0x4006ED14, 0x00000003); + poke(0x4006EC00, 0x00009375); + } + + pre_wakeup(); +} + +pub fn post_enable() { + post_wakeup(); + + // Works around Erratum 187 on chip revisions 1 and 2. + unsafe { + poke(0x4006EC00, 0x00009375); + poke(0x4006ED14, 0x00000000); + poke(0x4006EC00, 0x00009375); + } +} + +pub fn pre_wakeup() { + // Works around Erratum 171 on chip revisions 1 and 2. + + unsafe { + if peek(0x4006EC00) == 0x00000000 { + poke(0x4006EC00, 0x00009375); + } + + poke(0x4006EC14, 0x000000C0); + poke(0x4006EC00, 0x00009375); + } +} + +pub fn post_wakeup() { + // Works around Erratum 171 on chip revisions 1 and 2. + + unsafe { + if peek(0x4006EC00) == 0x00000000 { + poke(0x4006EC00, 0x00009375); + } + + poke(0x4006EC14, 0x00000000); + poke(0x4006EC00, 0x00009375); + } +} diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs new file mode 100644 index 00000000..aaf5337d --- /dev/null +++ b/nrf-hal-common/src/usbd/mod.rs @@ -0,0 +1,638 @@ +//! A `usb-device` implementation using the USBD peripheral. +//! +//! Difficulties: +//! * Control EP 0 is special: +//! * Setup stage is put in registers, not RAM. +//! * Different events are used to initiate transfers. +//! * No notification when the status stage is ACK'd. + +mod errata; + +use crate::target::USBD; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::{cell::Cell, mem, ptr, slice}; +use cortex_m::interrupt::{self, Mutex}; +use usb_device::{ + bus::{PollResult, UsbBus, UsbBusAllocator}, + endpoint::{EndpointAddress, EndpointType}, + UsbDirection, UsbError, +}; + +fn dma_start() { + compiler_fence(Ordering::Release); +} + +fn dma_end() { + compiler_fence(Ordering::Acquire); +} + +struct Buffers { + // Ptr and size is stored separately to save space + in_bufs: [*mut u8; 9], + out_bufs: [*mut u8; 9], + + // Buffers can be up to 64 Bytes since this is a Full-Speed implementation. + in_lens: [u8; 9], + out_lens: [u8; 9], +} + +impl Buffers { + fn new() -> Self { + Self { + in_bufs: [ptr::null_mut(); 9], + out_bufs: [ptr::null_mut(); 9], + in_lens: [0; 9], + out_lens: [0; 9], + } + } +} + +unsafe impl Sync for Buffers {} + +/// USB device implementation. +pub struct Usbd { + periph: Mutex, + unalloc_buffers: &'static mut [u8], + bufs: Buffers, + used_in: u8, + used_out: u8, + iso_in_used: bool, + iso_out_used: bool, + // FIXME: The used flags should probably be collapsed into u16's. + /// Keeps track of which IN (device -> host) buffers are currently in use. + /// + /// The bits for every IN EP are set in `write` and remain 1 until `poll` detects that the write + /// is finished (or at least DMA from the buffer has finished). While the bit is 1, `write` + /// returns `WouldBlock`. + in_bufs_in_use: Mutex>, +} + +impl Usbd { + /// Creates a new USB bus, taking ownership of the raw peripheral. + /// + /// # Parameters + /// + /// * `periph`: The raw USBD peripheral. + /// * `endpoint_buffers`: Backing storage for the endpoint buffers. This + /// needs to be big enough to accomodate all buffers of all endpoints, or + /// `alloc_ep` will fail. + #[inline] + pub fn new_alloc(periph: USBD, endpoint_buffers: &'static mut [u8]) -> UsbBusAllocator { + UsbBusAllocator::new(Self { + periph: Mutex::new(periph), + unalloc_buffers: endpoint_buffers, + bufs: Buffers::new(), + used_in: 0, + used_out: 0, + iso_in_used: false, + iso_out_used: false, + in_bufs_in_use: Mutex::new(Cell::new(0)), + }) + } + + /// Fetches the address assigned to the device (only valid when device is configured). + pub fn device_address(&self) -> u8 { + unsafe { &*USBD::ptr() }.usbaddr.read().addr().bits() + } + + fn alloc_ep_buf(&mut self, size: u16) -> usb_device::Result<&'static mut [u8]> { + assert!(size <= 64); + if self.unalloc_buffers.len() < usize::from(size) { + Err(UsbError::EndpointMemoryOverflow) + } else { + let (alloc, remaining) = + mem::replace(&mut self.unalloc_buffers, &mut []).split_at_mut(size.into()); + self.unalloc_buffers = remaining; + Ok(alloc) + } + } + + fn is_used(&self, ep: EndpointAddress) -> bool { + if ep.index() == 8 { + // ISO + let flag = if ep.is_in() { + self.iso_in_used + } else { + self.iso_out_used + }; + return flag; + } + + if ep.is_in() { + self.used_in & (1 << ep.index()) != 0 + } else { + self.used_out & (1 << ep.index()) != 0 + } + } + + fn read_control_setup(&self, regs: &USBD, buf: &mut [u8]) -> usb_device::Result { + const SETUP_LEN: usize = 8; + + if buf.len() < SETUP_LEN { + return Err(UsbError::BufferOverflow); + } + + // This is unfortunate: Reassemble the nicely split-up setup packet back into bytes, only + // for the usb-device code to copy the bytes back out into structured data. + // The bytes are split across registers, leaving a 3-Byte gap between them, so we couldn't + // even memcpy all of them at once. Weird peripheral. + buf[0] = regs.bmrequesttype.read().bits() as u8; + buf[1] = regs.brequest.read().brequest().bits(); + buf[2] = regs.wvaluel.read().wvaluel().bits(); + buf[3] = regs.wvalueh.read().wvalueh().bits(); + buf[4] = regs.windexl.read().windexl().bits(); + buf[5] = regs.windexh.read().windexh().bits(); + buf[6] = regs.wlengthl.read().wlengthl().bits(); + buf[7] = regs.wlengthh.read().wlengthh().bits(); + + if regs.bmrequesttype.read().direction().is_host_to_device() { + let ptr = self.bufs.out_bufs[0]; + let len = self.bufs.out_lens[0]; + + unsafe { + regs.epout0.ptr.write(|w| w.bits(ptr as u32)); + regs.epout0.maxcnt.write(|w| w.bits(u32::from(len))); + } + regs.tasks_ep0rcvout + .write(|w| w.tasks_ep0rcvout().set_bit()); + } + + Ok(SETUP_LEN) + } +} + +impl UsbBus for Usbd { + fn alloc_ep( + &mut self, + ep_dir: UsbDirection, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> usb_device::Result { + // Endpoint addresses are fixed in hardware: + // - 0x80 / 0x00 - Control EP0 + // - 0x81 / 0x01 - Bulk/Interrupt EP1 + // - 0x82 / 0x02 - Bulk/Interrupt EP2 + // - 0x83 / 0x03 - Bulk/Interrupt EP3 + // - 0x84 / 0x04 - Bulk/Interrupt EP4 + // - 0x85 / 0x05 - Bulk/Interrupt EP5 + // - 0x86 / 0x06 - Bulk/Interrupt EP6 + // - 0x87 / 0x07 - Bulk/Interrupt EP7 + // - 0x88 / 0x08 - Isochronous + + // Endpoint directions are allocated individually. + + let buf = self.alloc_ep_buf(max_packet_size)?; + + if false { + unimplemented!( + "alloc_ep({:?}, {:?}, {:?}, {}, {})", + ep_dir, + ep_addr, + ep_type, + max_packet_size, + interval, + ); + } + + let (used, bufs, lens) = match ep_dir { + UsbDirection::In => ( + &mut self.used_in, + &mut self.bufs.in_bufs, + &mut self.bufs.in_lens, + ), + UsbDirection::Out => ( + &mut self.used_out, + &mut self.bufs.out_bufs, + &mut self.bufs.out_lens, + ), + }; + + let alloc_index = match ep_type { + EndpointType::Isochronous => { + let flag = match ep_dir { + UsbDirection::In => &mut self.iso_in_used, + UsbDirection::Out => &mut self.iso_out_used, + }; + + if *flag { + return Err(UsbError::EndpointOverflow); + } else { + *flag = true; + bufs[8] = buf.as_mut_ptr(); + lens[8] = buf.len() as u8; + return Ok(EndpointAddress::from_parts(0x08, ep_dir)); + } + } + EndpointType::Control => 0, + EndpointType::Interrupt | EndpointType::Bulk => { + let leading = used.leading_zeros(); + if leading == 0 { + return Err(UsbError::EndpointOverflow); + } + + if leading == 8 { + // Even CONTROL is free, don't allocate that + 1 + } else { + 8 - leading + } + } + }; + + if *used & (1 << alloc_index) != 0 { + return Err(UsbError::EndpointOverflow); + } + + *used |= 1 << alloc_index; + bufs[alloc_index as usize] = buf.as_mut_ptr(); + lens[alloc_index as usize] = buf.len() as u8; + + let addr = EndpointAddress::from_parts(alloc_index as usize, ep_dir); + Ok(addr) + } + + #[inline] + fn enable(&mut self) { + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + errata::pre_enable(); + + regs.enable.write(|w| w.enable().enabled()); + + // Wait until the peripheral is ready. + while !regs.eventcause.read().ready().is_ready() {} + regs.eventcause.write(|w| w.ready().set_bit()); // Write 1 to clear. + + errata::post_enable(); + + // Enable the USB pullup, allowing enumeration. + regs.usbpullup.write(|w| w.connect().enabled()); + }); + } + + #[inline] + fn reset(&self) { + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + // Neat little hack to work around svd2rust not coalescing these on its own. + let epin = [ + ®s.epin0, + ®s.epin1, + ®s.epin2, + ®s.epin3, + ®s.epin4, + ®s.epin5, + ®s.epin6, + ®s.epin7, + ]; + let epout = [ + ®s.epout0, + ®s.epout1, + ®s.epout2, + ®s.epout3, + ®s.epout4, + ®s.epout5, + ®s.epout6, + ®s.epout7, + ]; + + // TODO: Initialize ISO buffers + // Initialize all data pointers for the endpoint buffers, since they never change. + for i in 0..8 { + let in_enabled = self.used_in & (1 << i) != 0; + let out_enabled = self.used_out & (1 << i) != 0; + + if in_enabled { + unsafe { + epin[i].ptr.write(|w| w.bits(self.bufs.in_bufs[i] as u32)); + epin[i] + .maxcnt + .write(|w| w.bits(u32::from(self.bufs.in_lens[i]))); + } + } + + if out_enabled { + unsafe { + epout[i].ptr.write(|w| w.bits(self.bufs.out_bufs[i] as u32)); + epout[i] + .maxcnt + .write(|w| w.bits(u32::from(self.bufs.out_lens[i]))); + } + } + } + + // TODO: Merge `used_{in,out}` with `iso_{in,out}_used` so ISO is enabled here as well. + // Make the enabled endpoints respond to traffic. + unsafe { + regs.epinen.write(|w| w.bits(self.used_in.into())); + regs.epouten.write(|w| w.bits(self.used_out.into())); + } + }); + } + + #[inline] + fn set_device_address(&self, _addr: u8) { + // Nothing to do, the peripheral handles this. + } + + fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> usb_device::Result { + if !self.is_used(ep_addr) { + return Err(UsbError::InvalidEndpoint); + } + + if ep_addr.is_out() { + return Err(UsbError::InvalidEndpoint); + } + + // A 0-length write to Control EP 0 is a status stage acknowledging a control write xfer + if ep_addr.index() == 0 && buf.is_empty() { + // There is no need to mark the buffer as used here. + + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + regs.tasks_ep0status + .write(|w| w.tasks_ep0status().set_bit()); + }); + // XXX anything else to do? + return Ok(0); + } + + let i = ep_addr.index(); + + if usize::from(self.bufs.in_lens[i]) < buf.len() { + return Err(UsbError::BufferOverflow); + } + + interrupt::free(|cs| { + let in_bufs_in_use = self.in_bufs_in_use.borrow(cs); + + // We cannot use the ENDEPIN event to detect whether it's fine to write to the buffer, + // since that is not active when first initializing the peripheral. It also gets cleared + // when `poll`ed, and thus would be unavailable here. + // We store an additional bitflags in the peripheral wrapper to store which IN buffers are + // in use by DMA, and which may be written to. + if in_bufs_in_use.get() & (1 << i) != 0 { + return Err(UsbError::WouldBlock); + } + + // Mark buffer as "in use". + in_bufs_in_use.set(in_bufs_in_use.get() | (1 << i)); + + Ok(()) + })?; + + // Now the target buffer is locked and we can copy the data outside of the critical + // sections. If we get interrupted, the buffer can't be acquired (and the code that would + // *unlock* the buffer should never be invoked here anyways). + let ptr = self.bufs.in_bufs[i]; + let len = self.bufs.in_lens[i]; + let slice = unsafe { slice::from_raw_parts_mut(ptr, usize::from(len)) }; + slice[..buf.len()].copy_from_slice(buf); + + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + let epin = [ + ®s.epin0, + ®s.epin1, + ®s.epin2, + ®s.epin3, + ®s.epin4, + ®s.epin5, + ®s.epin6, + ®s.epin7, + ]; + + // Set the buffer length so the right number of bytes are transmitted. + // Safety: `buf.len()` has been checked to be <= the max buffer length. + unsafe { + epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8)); + epin[i].ptr.write(|w| w.bits(self.bufs.in_bufs[i] as u32)); + } + + // Kick off device -> host transmission. This starts DMA, so a compiler fence is needed. + dma_start(); + regs.tasks_startepin[i].write(|w| w.tasks_startepin().set_bit()); + + Ok(buf.len()) + }) + } + + fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> usb_device::Result { + if !self.is_used(ep_addr) { + return Err(UsbError::InvalidEndpoint); + } + + if ep_addr.is_in() { + return Err(UsbError::InvalidEndpoint); + } + + let i = ep_addr.index(); + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + // Control EP 0 is special + if i == 0 { + // Control setup packet is special, since it is put in registers, not a buffer. + if regs.events_ep0setup.read().events_ep0setup().bit_is_set() { + regs.events_ep0setup.reset(); + return self.read_control_setup(regs, buf); + } else { + // XXX hack! + regs.tasks_ep0status + .write(|w| w.tasks_ep0status().set_bit()); + return Ok(0); + } + } + + // Is the endpoint ready? (ie. has DMA finished?) + // TODO: ISO + if !regs.events_endepout[i] + .read() + .events_endepout() + .bit_is_set() + { + // Not yet ready. + return Err(UsbError::WouldBlock); + } + + regs.events_endepout[i].reset(); + + // How much was transferred? + let len = regs.size.epout[i].read().size().bits(); + + if usize::from(len) > buf.len() { + return Err(UsbError::BufferOverflow); + } + + // Make sure it's smaller than the buffer size. + let bufsz = self.bufs.out_lens[i]; + assert!(len <= bufsz); + + let ptr = self.bufs.out_bufs[i]; + + // Safety: `len` is in bounds and DMA is not writing to the buffer. + let slice = unsafe { slice::from_raw_parts(ptr, usize::from(len)) }; + buf[..usize::from(len)].copy_from_slice(slice); + + // Done copying. Now we need to allow the EP to receive the next packet (ie. clear NAK). + // This is done by writing anything to `SIZE.EPOUT[i]`. + // Safety note: This effectively starts DMA, so we need a corresponding barrier. + dma_start(); + regs.size.epout[i].reset(); + + Ok(usize::from(len)) + }) + } + + fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + unsafe { + if ep_addr.index() == 0 { + regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().set_bit()); + } else { + regs.epstall.write(|w| { + w.ep() + .bits(ep_addr.index() as u8 & 0b111) + .io() + .bit(ep_addr.is_in()) + .stall() + .bit(stalled) + }); + } + } + }); + } + + fn is_stalled(&self, ep_addr: EndpointAddress) -> bool { + unimplemented!("is_stalled(ep={:?})", ep_addr); + } + + #[inline] + fn suspend(&self) { + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + regs.lowpower.write(|w| w.lowpower().low_power()); + }); + } + + #[inline] + fn resume(&self) { + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + errata::pre_wakeup(); + + regs.lowpower.write(|w| w.lowpower().force_normal()); + + }); + } + + fn poll(&self) -> PollResult { + interrupt::free(|cs| { + let in_bufs_in_use = self.in_bufs_in_use.borrow(cs); + let regs = self.periph.borrow(cs); + + if regs.events_usbreset.read().events_usbreset().bit_is_set() { + regs.events_usbreset.reset(); + return PollResult::Reset; + } else if regs.events_usbevent.read().events_usbevent().bit_is_set() { + // "Write 1 to clear" + if regs.eventcause.read().suspend().bit() { + regs.eventcause.write(|w| w.suspend().bit(true)); + return PollResult::Suspend; + } else if regs.eventcause.read().resume().bit() { + regs.eventcause.write(|w| w.resume().bit(true)); + return PollResult::Resume; + } else { + regs.events_usbevent.reset(); + } + } + + // Check for any finished transmissions. + let mut in_complete = 0; + let mut out_complete = 0; + for i in 0..=7 { + if i == 0 { + if regs + .events_ep0datadone + .read() + .events_ep0datadone() + .bit_is_set() + { + dma_end(); + + // Clear event, since we must only report this once. + regs.events_ep0datadone.reset(); + in_complete |= 1 << i; + + // The associated buffer is free again. + in_bufs_in_use.set(in_bufs_in_use.get() & !(1 << i)); + } + } else { + if regs.events_endepin[i].read().events_endepin().bit_is_set() { + dma_end(); + + // Clear event, since we must only report this once. + regs.events_endepin[i].reset(); + in_complete |= 1 << i; + + // The associated buffer is free again. + in_bufs_in_use.set(in_bufs_in_use.get() & !(1 << i)); + } + } + + if regs.events_endepout[i] + .read() + .events_endepout() + .bit_is_set() + { + // Do not clear OUT events, since we have to continue reporting them until the + // buffer is read. + out_complete |= 1 << i; + } + } + + // Setup packets are only relevant on the control EP 0. + let mut ep_setup = 0; + if regs.events_ep0setup.read().events_ep0setup().bit_is_set() { + ep_setup = 1; + } + + // TODO: Check ISO EP + + PollResult::Data { + ep_out: out_complete, + ep_in_complete: in_complete, + ep_setup, + } + }) + } + + fn force_reset(&self) -> usb_device::Result<()> { + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + regs.usbpullup.write(|w| w.connect().disabled()); + // TODO delay needed? + regs.usbpullup.write(|w| w.connect().enabled()); + }); + + Ok(()) + } + + /// The peripheral handles this for us. + /// + /// The Reference Manual says: + /// + /// > Note: The USBD peripheral handles the SetAddress transfer by itself. As a consequence, the + /// > software shall not process this command other than updating its state machine (see Device + /// > state diagram), nor initiate a status stage. If necessary, the address assigned by the + /// > host can be read out from the USBADDR register after the command has been processed. + const INHIBIT_SET_ADDRESS_RESPONSE: bool = true; +} From 86e84a87e24f95a03ef2ac2879ea5318e8718c22 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 4 May 2020 15:54:24 +0200 Subject: [PATCH 02/38] errata: use volatile operations the addresses look like MMIO registers --- nrf-hal-common/src/usbd/errata.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nrf-hal-common/src/usbd/errata.rs b/nrf-hal-common/src/usbd/errata.rs index 4780d81f..8cc2dad9 100644 --- a/nrf-hal-common/src/usbd/errata.rs +++ b/nrf-hal-common/src/usbd/errata.rs @@ -1,11 +1,11 @@ /// Writes `val` to `addr`. Used to apply Errata workarounds. unsafe fn poke(addr: u32, val: u32) { - *(addr as *mut u32) = val; + (addr as *mut u32).write_volatile(val); } /// Reads 32 bits from `addr`. unsafe fn peek(addr: u32) -> u32 { - *(addr as *mut u32) + (addr as *mut u32).read_volatile() } pub fn pre_enable() { From 261eeaeb947ee659ac629d527d3fefabe7711636 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 4 May 2020 16:32:20 +0200 Subject: [PATCH 03/38] 4 byte align bulk/interrupt endpoint buffers --- nrf-hal-common/src/usbd/mod.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index aaf5337d..686ecb99 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -95,11 +95,29 @@ impl Usbd { unsafe { &*USBD::ptr() }.usbaddr.read().addr().bits() } - fn alloc_ep_buf(&mut self, size: u16) -> usb_device::Result<&'static mut [u8]> { - assert!(size <= 64); + fn alloc_ep_buf( + &mut self, + ep_type: EndpointType, + mut size: u16, + ) -> usb_device::Result<&'static mut [u8]> { if self.unalloc_buffers.len() < usize::from(size) { Err(UsbError::EndpointMemoryOverflow) } else { + if ep_type == EndpointType::Bulk || ep_type == EndpointType::Interrupt { + // datasheet: buffer must be 4-byte aligned and its size must be a multiple of 4 + let rem = self.unalloc_buffers.as_mut_ptr() as usize % 4; + if rem != 0 { + let (_padding, remaining) = + mem::replace(&mut self.unalloc_buffers, &mut []).split_at_mut(4 - rem); + self.unalloc_buffers = remaining; + } + + let rem = size % 4; + if rem != 0 { + size = size + 4 - rem; + } + } + assert!(size <= 64); let (alloc, remaining) = mem::replace(&mut self.unalloc_buffers, &mut []).split_at_mut(size.into()); self.unalloc_buffers = remaining; @@ -183,7 +201,7 @@ impl UsbBus for Usbd { // Endpoint directions are allocated individually. - let buf = self.alloc_ep_buf(max_packet_size)?; + let buf = self.alloc_ep_buf(ep_type, max_packet_size)?; if false { unimplemented!( From faf0cb3478ff1c97545b509c6d22a900b5c70136 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 7 May 2020 16:06:10 +0200 Subject: [PATCH 04/38] don't return PollResult::Data when there's no data --- nrf-hal-common/src/usbd/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 686ecb99..2c4252bb 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -625,10 +625,14 @@ impl UsbBus for Usbd { // TODO: Check ISO EP - PollResult::Data { - ep_out: out_complete, - ep_in_complete: in_complete, - ep_setup, + if out_complete != 0 || in_complete != 0 || ep_setup != 0 { + PollResult::Data { + ep_out: out_complete, + ep_in_complete: in_complete, + ep_setup, + } + } else { + PollResult::None } }) } From 2c3693b178c37c2d5d709c009ee6557e43838abd Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 7 May 2020 16:12:16 +0200 Subject: [PATCH 05/38] EP0: shoehorn status stage --- nrf-hal-common/src/usbd/mod.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 2c4252bb..191fcb9b 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -52,6 +52,8 @@ unsafe impl Sync for Buffers {} /// USB device implementation. pub struct Usbd { periph: Mutex, + // argument passed to `UsbDeviceBuilder.max_packet_size_0` + max_packet_size_0: u16, unalloc_buffers: &'static mut [u8], bufs: Buffers, used_in: u8, @@ -80,6 +82,7 @@ impl Usbd { pub fn new_alloc(periph: USBD, endpoint_buffers: &'static mut [u8]) -> UsbBusAllocator { UsbBusAllocator::new(Self { periph: Mutex::new(periph), + max_packet_size_0: 0, unalloc_buffers: endpoint_buffers, bufs: Buffers::new(), used_in: 0, @@ -201,6 +204,11 @@ impl UsbBus for Usbd { // Endpoint directions are allocated individually. + // store user-supplied value + if ep_addr.map(|addr| addr.index()) == Some(0) { + self.max_packet_size_0 = max_packet_size; + } + let buf = self.alloc_ep_buf(ep_type, max_packet_size)?; if false { @@ -432,6 +440,21 @@ impl UsbBus for Usbd { epin[i].ptr.write(|w| w.bits(self.bufs.in_bufs[i] as u32)); } + if i == 0 { + // EPIN0: a short packet (len < max_packet_size0) indicates the end of the data + // stage and must be followed by us responding with an ACK token to an OUT token + // sent from the host (AKA the status stage) -- `usb-device` provides no call back + // for that so we'll trigger the status stage using a shortcut + let is_short_packet = buf.len() < self.max_packet_size_0.into(); + regs.shorts.modify(|_, w| { + if is_short_packet { + w.ep0datadone_ep0status().set_bit() + } else { + w.ep0datadone_ep0status().clear_bit() + } + }) + } + // Kick off device -> host transmission. This starts DMA, so a compiler fence is needed. dma_start(); regs.tasks_startepin[i].write(|w| w.tasks_startepin().set_bit()); From b9e0eb46b5a2cd2944a7364f7d530077d60e173a Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 7 May 2020 18:25:30 +0200 Subject: [PATCH 06/38] make bulk reads works or at least they seem to work --- nrf-hal-common/src/usbd/mod.rs | 72 ++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 191fcb9b..d5cfddc9 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -351,12 +351,26 @@ impl UsbBus for Usbd { } } + // XXX this is not spec compliant; the endpoints should only be enabled after the device + // has been put in the Configured state. However, usb-device provides no hook to do that // TODO: Merge `used_{in,out}` with `iso_{in,out}_used` so ISO is enabled here as well. // Make the enabled endpoints respond to traffic. unsafe { regs.epinen.write(|w| w.bits(self.used_in.into())); regs.epouten.write(|w| w.bits(self.used_out.into())); } + + for i in 1..8 { + let out_enabled = self.used_out & (1 << i) != 0; + + // when first enabled, bulk/interrupt OUT endpoints will *not* receive data (the + // peripheral will NAK all incoming packets) until we write a zero to the SIZE + // register (see figure 203 of the 52840 manual). To avoid that we write a 0 to the + // SIZE register + if out_enabled { + regs.size.epout[i].reset(); + } + } }); } @@ -501,10 +515,27 @@ impl UsbBus for Usbd { return Err(UsbError::WouldBlock); } + // the above check indicates the DMA transfer is complete + dma_end(); + regs.events_endepout[i].reset(); + let epout = [ + ®s.epout0, + ®s.epout1, + ®s.epout2, + ®s.epout3, + ®s.epout4, + ®s.epout5, + ®s.epout6, + ®s.epout7, + ]; + // How much was transferred? - let len = regs.size.epout[i].read().size().bits(); + // as soon as the DMA transfer is over the peripheral will start receiving new packets + // and may overwrite the SIZE register so read the MAXCNT register, which contains the + // SIZE of the last OUT packet, instead + let len = epout[i].maxcnt.read().maxcnt().bits(); if usize::from(len) > buf.len() { return Err(UsbError::BufferOverflow); @@ -520,17 +551,17 @@ impl UsbBus for Usbd { let slice = unsafe { slice::from_raw_parts(ptr, usize::from(len)) }; buf[..usize::from(len)].copy_from_slice(slice); - // Done copying. Now we need to allow the EP to receive the next packet (ie. clear NAK). - // This is done by writing anything to `SIZE.EPOUT[i]`. - // Safety note: This effectively starts DMA, so we need a corresponding barrier. - dma_start(); - regs.size.epout[i].reset(); - Ok(usize::from(len)) }) } fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { + semidap::trace!( + "Usbd::set_stalled(index={}, stalled={})", + ep_addr.index() as u8, + stalled as u8 + ); + interrupt::free(|cs| { let regs = self.periph.borrow(cs); @@ -627,6 +658,33 @@ impl UsbBus for Usbd { // The associated buffer is free again. in_bufs_in_use.set(in_bufs_in_use.get() & !(1 << i)); } + + // (see figure 203 of 52840-PS ) + // OUT endpoints are buffered; incoming packets will first be copied + // into the peripheral's internal memory (not visible to us). That event is + // reported as an EPDATA event that updates the EPDATASTATUS register + // what we must do at that stage is start a DMA transfer from that hidden + // memory to RAM. we start that transfer right here + let offset = 16 + i; + if regs.epdatastatus.read().bits() & (1 << offset) != 0 { + // MAXCNT must match SIZE + let size = regs.size.epout[i].read().bits(); + let epout = [ + ®s.epout0, + ®s.epout1, + ®s.epout2, + ®s.epout3, + ®s.epout4, + ®s.epout5, + ®s.epout6, + ®s.epout7, + ]; + epout[i].maxcnt.write(|w| unsafe { w.bits(size) }); + dma_start(); + regs.tasks_startepout[i].write(|w| w.tasks_startepout().set_bit()); + // clear flag so we don't start the DMA transfer more than once + regs.epdatastatus.write(|w| unsafe { w.bits(1 << offset) }); + } } if regs.events_endepout[i] From 714b4f8204b7852d6b383c7cfc5abbfe034fc5ec Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 7 May 2020 18:30:26 +0200 Subject: [PATCH 07/38] demand a Clocks token to ensure the external oscillator has been enabled --- nrf-hal-common/src/usbd/mod.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index d5cfddc9..cf60c977 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -8,7 +8,10 @@ mod errata; -use crate::target::USBD; +use crate::{ + clocks::{Clocks, ExternalOscillator}, + target::USBD, +}; use core::sync::atomic::{compiler_fence, Ordering}; use core::{cell::Cell, mem, ptr, slice}; use cortex_m::interrupt::{self, Mutex}; @@ -50,7 +53,7 @@ impl Buffers { unsafe impl Sync for Buffers {} /// USB device implementation. -pub struct Usbd { +pub struct Usbd<'c> { periph: Mutex, // argument passed to `UsbDeviceBuilder.max_packet_size_0` max_packet_size_0: u16, @@ -67,9 +70,12 @@ pub struct Usbd { /// is finished (or at least DMA from the buffer has finished). While the bit is 1, `write` /// returns `WouldBlock`. in_bufs_in_use: Mutex>, + + // used to freeze `Clocks` and ensure they remain in the `ExternalOscillator` state + _clocks: &'c (), } -impl Usbd { +impl<'c> Usbd<'c> { /// Creates a new USB bus, taking ownership of the raw peripheral. /// /// # Parameters @@ -79,7 +85,11 @@ impl Usbd { /// needs to be big enough to accomodate all buffers of all endpoints, or /// `alloc_ep` will fail. #[inline] - pub fn new_alloc(periph: USBD, endpoint_buffers: &'static mut [u8]) -> UsbBusAllocator { + pub fn new_alloc( + periph: USBD, + endpoint_buffers: &'static mut [u8], + _clocks: &'c Clocks, + ) -> UsbBusAllocator { UsbBusAllocator::new(Self { periph: Mutex::new(periph), max_packet_size_0: 0, @@ -90,6 +100,7 @@ impl Usbd { iso_in_used: false, iso_out_used: false, in_bufs_in_use: Mutex::new(Cell::new(0)), + _clocks: &(), }) } @@ -182,7 +193,7 @@ impl Usbd { } } -impl UsbBus for Usbd { +impl UsbBus for Usbd<'_> { fn alloc_ep( &mut self, ep_dir: UsbDirection, From 788ff0fd59aac02f8da2319153f432a4fe98bd7e Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 7 May 2020 18:34:56 +0200 Subject: [PATCH 08/38] remove stray print statement --- nrf-hal-common/src/usbd/mod.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index cf60c977..043b15ac 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -567,12 +567,6 @@ impl UsbBus for Usbd<'_> { } fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { - semidap::trace!( - "Usbd::set_stalled(index={}, stalled={})", - ep_addr.index() as u8, - stalled as u8 - ); - interrupt::free(|cs| { let regs = self.periph.borrow(cs); From b84300b05055466b883173ef716ac2a324a1192d Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 7 May 2020 18:35:35 +0200 Subject: [PATCH 09/38] make the example compile again --- examples/usb/src/main.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/usb/src/main.rs b/examples/usb/src/main.rs index 5fd36e77..8d245c80 100644 --- a/examples/usb/src/main.rs +++ b/examples/usb/src/main.rs @@ -10,6 +10,7 @@ use nrf52840_hal::gpio::{p0, p1, Level}; use nrf52840_hal::prelude::*; use nrf52840_hal::timer::{OneShot, Timer}; use nrf52840_hal::usbd::Usbd; +use nrf52840_hal::clocks::Clocks; use nrf52840_pac::{interrupt, Peripherals, TIMER0}; use usb_device::device::{UsbDeviceBuilder, UsbDeviceState, UsbVidPid}; use usbd_serial::{SerialPort, USB_CLASS_CDC}; @@ -21,7 +22,7 @@ fn TIMER0() { #[entry] fn main() -> ! { - static mut EP_BUF: [u8; 256] = [0; 256]; + static mut EP_BUF: [u8; 512] = [0; 512]; let core = cortex_m::Peripherals::take().unwrap(); let periph = Peripherals::take().unwrap(); @@ -33,6 +34,18 @@ fn main() -> ! { .is_vbus_present() {} + // wait until USB 3.3V supply is stable + while !periph + .POWER + .events_usbpwrrdy + .read() + .events_usbpwrrdy() + .bit_is_clear() + {} + + let clocks = Clocks::new(periph.CLOCK); + let clocks = clocks.enable_ext_hfosc(); + let mut nvic = core.NVIC; let mut timer = Timer::one_shot(periph.TIMER0); let usbd = periph.USBD; @@ -48,12 +61,13 @@ fn main() -> ! { led.set_low().unwrap(); - let usb_bus = Usbd::new_alloc(usbd, EP_BUF); + let usb_bus = Usbd::new_alloc(usbd, EP_BUF, &clocks); let mut serial = SerialPort::new(&usb_bus); let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) .product("nRF52840 Serial Port Demo") .device_class(USB_CLASS_CDC) + .max_packet_size_0(64) // (makes control transfers 8x faster) .build(); hprintln!("").ok(); From d1d70b4f7ed9e37fffbcfa665d2dc2c8912fdd90 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 11 May 2020 19:13:21 +0200 Subject: [PATCH 10/38] remove patch --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5b8116a..1745dc44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,3 @@ opt-level = "s" [patch.crates-io.usb-device] git = "https://github.com/jonas-schievink/usb-device.git" branch = "inhibit-setaddr-resp" - -[patch.crates-io.panic-semihosting] -path = "../panic-semihosting" From 7fb2998812cb2723b3a139b8d079de6dc9746b4f Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Mon, 11 May 2020 19:14:57 +0200 Subject: [PATCH 11/38] rebase fixes --- examples/usb/Cargo.toml | 4 ++-- examples/usb/src/main.rs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/usb/Cargo.toml b/examples/usb/Cargo.toml index 6d951471..7573444b 100644 --- a/examples/usb/Cargo.toml +++ b/examples/usb/Cargo.toml @@ -9,10 +9,10 @@ cortex-m = "0.6.2" cortex-m-rt = "0.6.12" cortex-m-semihosting = "0.3.5" panic-semihosting = "0.5.3" -nrf52840-pac = "0.8.0" +nrf52840-pac = "0.9.0" usb-device = "0.2.5" usbd-serial = "0.1.0" [dependencies.nrf52840-hal] -version = "0.8.0" +version = "0.10.0" path = "../../nrf52840-hal" diff --git a/examples/usb/src/main.rs b/examples/usb/src/main.rs index 8d245c80..d0b71532 100644 --- a/examples/usb/src/main.rs +++ b/examples/usb/src/main.rs @@ -24,7 +24,6 @@ fn TIMER0() { fn main() -> ! { static mut EP_BUF: [u8; 512] = [0; 512]; - let core = cortex_m::Peripherals::take().unwrap(); let periph = Peripherals::take().unwrap(); while !periph .POWER @@ -46,7 +45,6 @@ fn main() -> ! { let clocks = Clocks::new(periph.CLOCK); let clocks = clocks.enable_ext_hfosc(); - let mut nvic = core.NVIC; let mut timer = Timer::one_shot(periph.TIMER0); let usbd = periph.USBD; let p0 = p0::Parts::new(periph.P0); @@ -56,7 +54,7 @@ fn main() -> ! { let btn = p1.p1_00.into_pullup_input(); while btn.is_high().unwrap() {} - timer.enable_interrupt(Some(&mut nvic)); + timer.enable_interrupt(); timer.start(Timer::::TICKS_PER_SECOND * 3); led.set_low().unwrap(); From 5b216e27eb056752a38e2f30bcd57f709c1a5ed5 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 14 Feb 2021 12:47:23 -0800 Subject: [PATCH 12/38] Rebase #144, formerly #133 to pull in USB for nrf52480 Fixes compilation by pointing to current versions and takes in @unrelentingtech's fork of usb-device and suggestion in https://github.com/nrf-rs/nrf-hal/pull/144#pullrequestreview-574865733 I folded in the hello world portion from japaric's PR summary text. refs: https://github.com/mvirkkunen/usb-device/pull/51 closes: https://github.com/nrf-rs/nrf-hal/pull/144 --- Cargo.toml | 2 +- examples/usb/Cargo.toml | 5 +++-- examples/usb/src/main.rs | 6 ++++++ nrf-hal-common/Cargo.toml | 2 +- nrf-hal-common/src/usbd/mod.rs | 7 +++---- nrf52840-hal/Cargo.toml | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1745dc44..d1102756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,5 @@ lto = true opt-level = "s" [patch.crates-io.usb-device] -git = "https://github.com/jonas-schievink/usb-device.git" +git = "https://github.com/unrelentingtech/usb-device.git" branch = "inhibit-setaddr-resp" diff --git a/examples/usb/Cargo.toml b/examples/usb/Cargo.toml index 7573444b..14aa144c 100644 --- a/examples/usb/Cargo.toml +++ b/examples/usb/Cargo.toml @@ -10,9 +10,10 @@ cortex-m-rt = "0.6.12" cortex-m-semihosting = "0.3.5" panic-semihosting = "0.5.3" nrf52840-pac = "0.9.0" -usb-device = "0.2.5" +usb-device = "0.2.7" usbd-serial = "0.1.0" [dependencies.nrf52840-hal] -version = "0.10.0" +version = "0.12.0" path = "../../nrf52840-hal" + diff --git a/examples/usb/src/main.rs b/examples/usb/src/main.rs index d0b71532..2b847639 100644 --- a/examples/usb/src/main.rs +++ b/examples/usb/src/main.rs @@ -51,6 +51,7 @@ fn main() -> ! { let p1 = p1::Parts::new(periph.P1); let mut led = p0.p0_23.into_push_pull_output(Level::High); + let btn = p1.p1_00.into_pullup_input(); while btn.is_high().unwrap() {} @@ -79,6 +80,11 @@ fn main() -> ! { if new_state != state { hprintln!("{:?} {:#x}", new_state, usb_dev.bus().device_address()).ok(); state = new_state; + + if state == UsbDeviceState::Configured { + serial.write(b"Hello, world!\n").unwrap(); + serial.flush().unwrap(); + } } } } diff --git a/nrf-hal-common/Cargo.toml b/nrf-hal-common/Cargo.toml index d8773c08..966e5e0a 100644 --- a/nrf-hal-common/Cargo.toml +++ b/nrf-hal-common/Cargo.toml @@ -39,7 +39,7 @@ optional = true version = "0.9.0" [dependencies.usb-device] -version = "0.2.5" +version = "0.2.7" optional = true [dependencies.nrf52810-pac] diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 043b15ac..ec5ac8a0 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -10,7 +10,7 @@ mod errata; use crate::{ clocks::{Clocks, ExternalOscillator}, - target::USBD, + pac::USBD, }; use core::sync::atomic::{compiler_fence, Ordering}; use core::{cell::Cell, mem, ptr, slice}; @@ -470,7 +470,7 @@ impl UsbBus for Usbd<'_> { // stage and must be followed by us responding with an ACK token to an OUT token // sent from the host (AKA the status stage) -- `usb-device` provides no call back // for that so we'll trigger the status stage using a shortcut - let is_short_packet = buf.len() < self.max_packet_size_0.into(); + let is_short_packet = buf.len() < self.max_packet_size_0 as usize; regs.shorts.modify(|_, w| { if is_short_packet { w.ep0datadone_ep0status().set_bit() @@ -572,7 +572,7 @@ impl UsbBus for Usbd<'_> { unsafe { if ep_addr.index() == 0 { - regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().set_bit()); + regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(stalled)); } else { regs.epstall.write(|w| { w.ep() @@ -607,7 +607,6 @@ impl UsbBus for Usbd<'_> { errata::pre_wakeup(); regs.lowpower.write(|w| w.lowpower().force_normal()); - }); } diff --git a/nrf52840-hal/Cargo.toml b/nrf52840-hal/Cargo.toml index 8f4f21fd..07e76678 100644 --- a/nrf52840-hal/Cargo.toml +++ b/nrf52840-hal/Cargo.toml @@ -22,7 +22,7 @@ nrf52840-pac = "0.9.0" [dependencies.nrf-hal-common] path = "../nrf-hal-common" default-features = false -features = ["52840"] +features = ["52840", "usb-device"] version = "=0.12.0" [dependencies.embedded-hal] From 1c87602396d9c03c5f5ddce53c9db4c5c37b0629 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sun, 14 Feb 2021 13:39:39 -0800 Subject: [PATCH 13/38] fixup CI for newly add usb test refs: #144 --- xtask/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index ee4b9b59..77422556 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -36,6 +36,7 @@ pub static EXAMPLES: &[(&str, &[&str])] = &[ ("twim-demo", &[]), ("twis-demo", &[]), ("twis-dma-demo", &[]), + ("usb", &[]), ("wdt-demo", &[]), ]; From f36a7e0807ab7a907e53190137741046ae71e410 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Wed, 17 Feb 2021 07:12:40 +0300 Subject: [PATCH 14/38] Reset shorts on SETUP to prevent spurious status stages --- nrf-hal-common/src/usbd/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index ec5ac8a0..9e4e8eb5 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -706,6 +706,11 @@ impl UsbBus for Usbd<'_> { let mut ep_setup = 0; if regs.events_ep0setup.read().events_ep0setup().bit_is_set() { ep_setup = 1; + + // Reset shorts + regs.shorts.modify(|_, w| { + w.ep0datadone_ep0status().clear_bit() + }); } // TODO: Check ISO EP From fa4011e7cda2cd221aabcb459e99b7e527c94236 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Wed, 17 Feb 2021 07:24:00 +0300 Subject: [PATCH 15/38] Introduce EP0State --- nrf-hal-common/src/usbd/mod.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 9e4e8eb5..d316d759 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -52,6 +52,11 @@ impl Buffers { unsafe impl Sync for Buffers {} +#[derive(Copy, Clone)] +struct EP0State { + direction: UsbDirection, +} + /// USB device implementation. pub struct Usbd<'c> { periph: Mutex, @@ -70,6 +75,7 @@ pub struct Usbd<'c> { /// is finished (or at least DMA from the buffer has finished). While the bit is 1, `write` /// returns `WouldBlock`. in_bufs_in_use: Mutex>, + ep0_state: Mutex>, // used to freeze `Clocks` and ensure they remain in the `ExternalOscillator` state _clocks: &'c (), @@ -100,6 +106,9 @@ impl<'c> Usbd<'c> { iso_in_used: false, iso_out_used: false, in_bufs_in_use: Mutex::new(Cell::new(0)), + ep0_state: Mutex::new(Cell::new(EP0State { + direction: UsbDirection::Out, + })), _clocks: &(), }) } @@ -157,7 +166,7 @@ impl<'c> Usbd<'c> { } } - fn read_control_setup(&self, regs: &USBD, buf: &mut [u8]) -> usb_device::Result { + fn read_control_setup(&self, regs: &USBD, buf: &mut [u8], ep0_state: &mut EP0State) -> usb_device::Result { const SETUP_LEN: usize = 8; if buf.len() < SETUP_LEN { @@ -177,6 +186,11 @@ impl<'c> Usbd<'c> { buf[6] = regs.wlengthl.read().wlengthl().bits(); buf[7] = regs.wlengthh.read().wlengthh().bits(); + ep0_state.direction = match regs.bmrequesttype.read().direction().is_host_to_device() { + false => UsbDirection::In, + true => UsbDirection::Out, + }; + if regs.bmrequesttype.read().direction().is_host_to_device() { let ptr = self.bufs.out_bufs[0]; let len = self.bufs.out_lens[0]; @@ -506,7 +520,10 @@ impl UsbBus for Usbd<'_> { // Control setup packet is special, since it is put in registers, not a buffer. if regs.events_ep0setup.read().events_ep0setup().bit_is_set() { regs.events_ep0setup.reset(); - return self.read_control_setup(regs, buf); + + let ep0_state = unsafe { &mut *self.ep0_state.borrow(cs).as_ptr() }; + + return self.read_control_setup(regs, buf, ep0_state); } else { // XXX hack! regs.tasks_ep0status From c6d6aeb583c1c2da7db0bb21b1d882c04cbb6721 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Wed, 17 Feb 2021 07:43:22 +0300 Subject: [PATCH 16/38] Rewrite read/write logic --- nrf-hal-common/src/usbd/mod.rs | 233 ++++++++++++++------------------- 1 file changed, 98 insertions(+), 135 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index d316d759..3cfb39b5 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -13,7 +13,8 @@ use crate::{ pac::USBD, }; use core::sync::atomic::{compiler_fence, Ordering}; -use core::{cell::Cell, mem, ptr, slice}; +use core::{cell::Cell, mem, ptr}; +use core::mem::MaybeUninit; use cortex_m::interrupt::{self, Mutex}; use usb_device::{ bus::{PollResult, UsbBus, UsbBusAllocator}, @@ -68,13 +69,6 @@ pub struct Usbd<'c> { used_out: u8, iso_in_used: bool, iso_out_used: bool, - // FIXME: The used flags should probably be collapsed into u16's. - /// Keeps track of which IN (device -> host) buffers are currently in use. - /// - /// The bits for every IN EP are set in `write` and remain 1 until `poll` detects that the write - /// is finished (or at least DMA from the buffer has finished). While the bit is 1, `write` - /// returns `WouldBlock`. - in_bufs_in_use: Mutex>, ep0_state: Mutex>, // used to freeze `Clocks` and ensure they remain in the `ExternalOscillator` state @@ -105,7 +99,6 @@ impl<'c> Usbd<'c> { used_out: 0, iso_in_used: false, iso_out_used: false, - in_bufs_in_use: Mutex::new(Cell::new(0)), ep0_state: Mutex::new(Cell::new(EP0State { direction: UsbDirection::Out, })), @@ -415,15 +408,19 @@ impl UsbBus for Usbd<'_> { // A 0-length write to Control EP 0 is a status stage acknowledging a control write xfer if ep_addr.index() == 0 && buf.is_empty() { - // There is no need to mark the buffer as used here. - - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - regs.tasks_ep0status - .write(|w| w.tasks_ep0status().set_bit()); + let is_out = interrupt::free(|cs| { + let ep0_state = self.ep0_state.borrow(cs).get(); + ep0_state.direction == UsbDirection::Out }); - // XXX anything else to do? - return Ok(0); + + if is_out { + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + regs.tasks_ep0status.write(|w| w.tasks_ep0status().set_bit()); + }); + return Ok(0); + } } let i = ep_addr.index(); @@ -433,33 +430,18 @@ impl UsbBus for Usbd<'_> { } interrupt::free(|cs| { - let in_bufs_in_use = self.in_bufs_in_use.borrow(cs); - - // We cannot use the ENDEPIN event to detect whether it's fine to write to the buffer, - // since that is not active when first initializing the peripheral. It also gets cleared - // when `poll`ed, and thus would be unavailable here. - // We store an additional bitflags in the peripheral wrapper to store which IN buffers are - // in use by DMA, and which may be written to. - if in_bufs_in_use.get() & (1 << i) != 0 { + let regs = self.periph.borrow(cs); + + if regs.epstatus.read().bits() & (1 << i) != 0 { return Err(UsbError::WouldBlock); } - // Mark buffer as "in use". - in_bufs_in_use.set(in_bufs_in_use.get() | (1 << i)); - - Ok(()) - })?; - - // Now the target buffer is locked and we can copy the data outside of the critical - // sections. If we get interrupted, the buffer can't be acquired (and the code that would - // *unlock* the buffer should never be invoked here anyways). - let ptr = self.bufs.in_bufs[i]; - let len = self.bufs.in_lens[i]; - let slice = unsafe { slice::from_raw_parts_mut(ptr, usize::from(len)) }; - slice[..buf.len()].copy_from_slice(buf); - - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); + let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); + unsafe { + let slice = &mut *ram_buf.as_mut_ptr(); + slice[..buf.len()].copy_from_slice(buf); + } + let ram_buf = unsafe { ram_buf.assume_init() }; let epin = [ ®s.epin0, @@ -475,8 +457,12 @@ impl UsbBus for Usbd<'_> { // Set the buffer length so the right number of bytes are transmitted. // Safety: `buf.len()` has been checked to be <= the max buffer length. unsafe { + if buf.is_empty() { + epin[i].ptr.write(|w| w.bits(0)); + } else { + epin[i].ptr.write(|w| w.bits(ram_buf.as_ptr() as u32)); + } epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8)); - epin[i].ptr.write(|w| w.bits(self.bufs.in_bufs[i] as u32)); } if i == 0 { @@ -491,12 +477,21 @@ impl UsbBus for Usbd<'_> { } else { w.ep0datadone_ep0status().clear_bit() } - }) + }); } + // Clear ENDEPIN[i] flag + regs.events_endepin[i].reset(); + // Kick off device -> host transmission. This starts DMA, so a compiler fence is needed. dma_start(); regs.tasks_startepin[i].write(|w| w.tasks_startepin().set_bit()); + while regs.events_endepin[i].read().events_endepin().bit_is_clear() {} + regs.events_endepin[i].reset(); + dma_end(); + + // Clear EPSTATUS.EPIN[i] flag + regs.epstatus.write(|w| unsafe { w.bits(1 << i) }); Ok(buf.len()) }) @@ -525,28 +520,35 @@ impl UsbBus for Usbd<'_> { return self.read_control_setup(regs, buf, ep0_state); } else { - // XXX hack! - regs.tasks_ep0status - .write(|w| w.tasks_ep0status().set_bit()); - return Ok(0); + // Is the endpoint ready? + if regs.events_ep0datadone.read().events_ep0datadone().bit_is_clear() { + // Not yet ready. + return Err(UsbError::WouldBlock); + } + } + } else { + // Is the endpoint ready? + let epdatastatus = regs.epdatastatus.read().bits(); + if epdatastatus & (1 << (i + 16)) == 0 { + // Not yet ready. + return Err(UsbError::WouldBlock); } } - // Is the endpoint ready? (ie. has DMA finished?) - // TODO: ISO - if !regs.events_endepout[i] - .read() - .events_endepout() - .bit_is_set() - { - // Not yet ready. - return Err(UsbError::WouldBlock); + // Check that the packet fits into the buffer + let size = regs.size.epout[i].read().bits(); + if size as usize > buf.len() { + return Err(UsbError::BufferOverflow); } - // the above check indicates the DMA transfer is complete - dma_end(); + // Clear status + if i == 0 { + regs.events_ep0datadone.reset(); + } else { + regs.epdatastatus.write(|w| unsafe { w.bits(1 << (i + 16)) }); + } - regs.events_endepout[i].reset(); + // We checked that the endpoint has data, time to read it let epout = [ ®s.epout0, @@ -558,28 +560,26 @@ impl UsbBus for Usbd<'_> { ®s.epout6, ®s.epout7, ]; + epout[i].ptr.write(|w| unsafe { w.bits(buf.as_ptr() as u32) }); + // MAXCNT must match SIZE + epout[i].maxcnt.write(|w| unsafe { w.bits(size) }); - // How much was transferred? - // as soon as the DMA transfer is over the peripheral will start receiving new packets - // and may overwrite the SIZE register so read the MAXCNT register, which contains the - // SIZE of the last OUT packet, instead - let len = epout[i].maxcnt.read().maxcnt().bits(); - - if usize::from(len) > buf.len() { - return Err(UsbError::BufferOverflow); - } + dma_start(); + regs.events_endepout[i].reset(); + regs.tasks_startepout[i].write(|w| w.tasks_startepout().set_bit()); + while regs.events_endepout[i].read().events_endepout().bit_is_clear() {} + regs.events_endepout[i].reset(); + dma_end(); - // Make sure it's smaller than the buffer size. - let bufsz = self.bufs.out_lens[i]; - assert!(len <= bufsz); + // TODO: ISO - let ptr = self.bufs.out_bufs[i]; + // Read is complete, clear status flag + regs.epdatastatus.write(|w| unsafe { w.bits(1 << (i + 16)) }); - // Safety: `len` is in bounds and DMA is not writing to the buffer. - let slice = unsafe { slice::from_raw_parts(ptr, usize::from(len)) }; - buf[..usize::from(len)].copy_from_slice(slice); + // Enable the endpoint + regs.size.epout[i].reset(); - Ok(usize::from(len)) + Ok(size as usize) }) } @@ -629,7 +629,6 @@ impl UsbBus for Usbd<'_> { fn poll(&self) -> PollResult { interrupt::free(|cs| { - let in_bufs_in_use = self.in_bufs_in_use.borrow(cs); let regs = self.periph.borrow(cs); if regs.events_usbreset.read().events_usbreset().bit_is_set() { @@ -651,70 +650,34 @@ impl UsbBus for Usbd<'_> { // Check for any finished transmissions. let mut in_complete = 0; let mut out_complete = 0; - for i in 0..=7 { - if i == 0 { - if regs - .events_ep0datadone - .read() - .events_ep0datadone() - .bit_is_set() - { - dma_end(); - - // Clear event, since we must only report this once. - regs.events_ep0datadone.reset(); - in_complete |= 1 << i; - - // The associated buffer is free again. - in_bufs_in_use.set(in_bufs_in_use.get() & !(1 << i)); - } + if regs.events_ep0datadone.read().events_ep0datadone().bit_is_set() { + let ep0_state = self.ep0_state.borrow(cs).get(); + if ep0_state.direction == UsbDirection::In { + // Clear event, since we must only report this once. + regs.events_ep0datadone.reset(); + + in_complete |= 1; } else { - if regs.events_endepin[i].read().events_endepin().bit_is_set() { - dma_end(); + // Do not clear OUT events, since we have to continue reporting them until the + // buffer is read. - // Clear event, since we must only report this once. - regs.events_endepin[i].reset(); - in_complete |= 1 << i; + out_complete |= 1; + } + } + let epdatastatus = regs.epdatastatus.read().bits(); + for i in 1..=7 { + if epdatastatus & (1 << i) != 0 { + // EPDATASTATUS.EPIN[i] is set - // The associated buffer is free again. - in_bufs_in_use.set(in_bufs_in_use.get() & !(1 << i)); - } + // Clear event, since we must only report this once. + regs.epdatastatus.write(|w| unsafe { w.bits(1 << i) }); - // (see figure 203 of 52840-PS ) - // OUT endpoints are buffered; incoming packets will first be copied - // into the peripheral's internal memory (not visible to us). That event is - // reported as an EPDATA event that updates the EPDATASTATUS register - // what we must do at that stage is start a DMA transfer from that hidden - // memory to RAM. we start that transfer right here - let offset = 16 + i; - if regs.epdatastatus.read().bits() & (1 << offset) != 0 { - // MAXCNT must match SIZE - let size = regs.size.epout[i].read().bits(); - let epout = [ - ®s.epout0, - ®s.epout1, - ®s.epout2, - ®s.epout3, - ®s.epout4, - ®s.epout5, - ®s.epout6, - ®s.epout7, - ]; - epout[i].maxcnt.write(|w| unsafe { w.bits(size) }); - dma_start(); - regs.tasks_startepout[i].write(|w| w.tasks_startepout().set_bit()); - // clear flag so we don't start the DMA transfer more than once - regs.epdatastatus.write(|w| unsafe { w.bits(1 << offset) }); - } + in_complete |= 1 << i; } + if epdatastatus & (1 << (i + 16)) != 0 { + // EPDATASTATUS.EPOUT[i] is set + // This flag will be cleared in `read()` - if regs.events_endepout[i] - .read() - .events_endepout() - .bit_is_set() - { - // Do not clear OUT events, since we have to continue reporting them until the - // buffer is read. out_complete |= 1 << i; } } From b938651e88106cc2ffc4d3443463a2e491aaf00d Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Wed, 17 Feb 2021 07:56:43 +0300 Subject: [PATCH 17/38] Add hack to detect interrupted IN transfers --- nrf-hal-common/src/usbd/mod.rs | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 3cfb39b5..97f8bedf 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -53,9 +53,16 @@ impl Buffers { unsafe impl Sync for Buffers {} +#[derive(Copy, Clone)] +enum TransferState { + NoTransfer, + Started(u16), +} + #[derive(Copy, Clone)] struct EP0State { direction: UsbDirection, + in_transfer_state: TransferState, } /// USB device implementation. @@ -101,6 +108,7 @@ impl<'c> Usbd<'c> { iso_out_used: false, ep0_state: Mutex::new(Cell::new(EP0State { direction: UsbDirection::Out, + in_transfer_state: TransferState::NoTransfer, })), _clocks: &(), }) @@ -478,6 +486,13 @@ impl UsbBus for Usbd<'_> { w.ep0datadone_ep0status().clear_bit() } }); + + // Hack: send status stage if the IN transfer is not acknowledged after a few frames + let frame_counter = regs.framecntr.read().framecntr().bits(); + let ep0_state = self.ep0_state.borrow(cs); + let mut state = ep0_state.get(); + state.in_transfer_state = TransferState::Started(frame_counter); + ep0_state.set(state); } // Clear ENDEPIN[i] flag @@ -647,6 +662,25 @@ impl UsbBus for Usbd<'_> { } } + if regs.events_sof.read().events_sof().bit_is_set() { + regs.events_sof.reset(); + + // Check if we have a timeout for EP0 IN transfer + let ep0_state = self.ep0_state.borrow(cs); + let mut state = ep0_state.get(); + if let TransferState::Started(counter) = state.in_transfer_state { + let frame_counter = regs.framecntr.read().framecntr().bits(); + if frame_counter.wrapping_sub(counter) >= 5 { + // Send a status stage to ACK a pending OUT transfer + regs.tasks_ep0status.write(|w| w.tasks_ep0status().set_bit()); + + // reset the state + state.in_transfer_state = TransferState::NoTransfer; + ep0_state.set(state); + } + } + } + // Check for any finished transmissions. let mut in_complete = 0; let mut out_complete = 0; @@ -657,6 +691,12 @@ impl UsbBus for Usbd<'_> { regs.events_ep0datadone.reset(); in_complete |= 1; + + // Reset a timeout for the IN transfer + let ep0_state = self.ep0_state.borrow(cs); + let mut state = ep0_state.get(); + state.in_transfer_state = TransferState::NoTransfer; + ep0_state.set(state); } else { // Do not clear OUT events, since we have to continue reporting them until the // buffer is read. From 1b3e38b19e035eedd05ed668ad9cb7b2833656c8 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Wed, 17 Feb 2021 08:19:18 +0300 Subject: [PATCH 18/38] Inhibit SetAddress response --- Cargo.toml | 4 ---- nrf-hal-common/src/usbd/mod.rs | 36 ++++++++++++++++++---------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d1102756..a6b0b921 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,3 @@ lto = false debug = true lto = true opt-level = "s" - -[patch.crates-io.usb-device] -git = "https://github.com/unrelentingtech/usb-device.git" -branch = "inhibit-setaddr-resp" diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 97f8bedf..77881901 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -63,6 +63,7 @@ enum TransferState { struct EP0State { direction: UsbDirection, in_transfer_state: TransferState, + is_set_address: bool, } /// USB device implementation. @@ -109,6 +110,7 @@ impl<'c> Usbd<'c> { ep0_state: Mutex::new(Cell::new(EP0State { direction: UsbDirection::Out, in_transfer_state: TransferState::NoTransfer, + is_set_address: false, })), _clocks: &(), }) @@ -192,6 +194,8 @@ impl<'c> Usbd<'c> { true => UsbDirection::Out, }; + ep0_state.is_set_address = (buf[0] == 0x00) && (buf[1] == 0x05); + if regs.bmrequesttype.read().direction().is_host_to_device() { let ptr = self.bufs.out_bufs[0]; let len = self.bufs.out_lens[0]; @@ -416,17 +420,25 @@ impl UsbBus for Usbd<'_> { // A 0-length write to Control EP 0 is a status stage acknowledging a control write xfer if ep_addr.index() == 0 && buf.is_empty() { - let is_out = interrupt::free(|cs| { + let exit = interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + let ep0_state = self.ep0_state.borrow(cs).get(); - ep0_state.direction == UsbDirection::Out - }); - if is_out { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); + if ep0_state.is_set_address { + // Inhibit + return true; + } + if ep0_state.direction == UsbDirection::Out { regs.tasks_ep0status.write(|w| w.tasks_ep0status().set_bit()); - }); + return true; + } + + false + }); + + if exit { return Ok(0); } } @@ -757,14 +769,4 @@ impl UsbBus for Usbd<'_> { Ok(()) } - - /// The peripheral handles this for us. - /// - /// The Reference Manual says: - /// - /// > Note: The USBD peripheral handles the SetAddress transfer by itself. As a consequence, the - /// > software shall not process this command other than updating its state machine (see Device - /// > state diagram), nor initiate a status stage. If necessary, the address assigned by the - /// > host can be read out from the USBADDR register after the command has been processed. - const INHIBIT_SET_ADDRESS_RESPONSE: bool = true; } From e78f3d0b5e29b06be0c821ac258237615b48c874 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Wed, 17 Feb 2021 20:04:23 +0200 Subject: [PATCH 19/38] Apply suggestions from code review Co-authored-by: Wez Furlong --- nrf-hal-common/src/usbd/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 77881901..7032be9e 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -196,7 +196,7 @@ impl<'c> Usbd<'c> { ep0_state.is_set_address = (buf[0] == 0x00) && (buf[1] == 0x05); - if regs.bmrequesttype.read().direction().is_host_to_device() { + if ep0_state.direction == UsbDirection::Out { let ptr = self.bufs.out_bufs[0]; let len = self.bufs.out_lens[0]; @@ -499,7 +499,9 @@ impl UsbBus for Usbd<'_> { } }); - // Hack: send status stage if the IN transfer is not acknowledged after a few frames + // Hack: trigger status stage if the IN transfer is not acknowledged after a few frames, + // so record the current frame here; the actual test and status stage activation happens + // in the poll method. let frame_counter = regs.framecntr.read().framecntr().bits(); let ep0_state = self.ep0_state.borrow(cs); let mut state = ep0_state.get(); From a7a7e84734ea62b3faa90c07ba433e8bc3e7ecd7 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Thu, 18 Feb 2021 04:57:55 +0300 Subject: [PATCH 20/38] Replace unsafe borrow with get() and set() --- nrf-hal-common/src/usbd/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 7032be9e..63ddfe98 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -545,9 +545,12 @@ impl UsbBus for Usbd<'_> { if regs.events_ep0setup.read().events_ep0setup().bit_is_set() { regs.events_ep0setup.reset(); - let ep0_state = unsafe { &mut *self.ep0_state.borrow(cs).as_ptr() }; + let ep0_state = self.ep0_state.borrow(cs); + let mut state = ep0_state.get(); + let n = self.read_control_setup(regs, buf, &mut state)?; + ep0_state.set(state); - return self.read_control_setup(regs, buf, ep0_state); + return Ok(n) } else { // Is the endpoint ready? if regs.events_ep0datadone.read().events_ep0datadone().bit_is_clear() { From 84ccc05f9ad77f71d9d6217eeb5bc4b58ec24ab7 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Mon, 22 Feb 2021 01:17:02 +0300 Subject: [PATCH 21/38] Issue a status stage when the transfer data is fully written --- nrf-hal-common/src/usbd/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 63ddfe98..8526ab13 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -62,6 +62,7 @@ enum TransferState { #[derive(Copy, Clone)] struct EP0State { direction: UsbDirection, + remaining_size: u16, in_transfer_state: TransferState, is_set_address: bool, } @@ -109,6 +110,7 @@ impl<'c> Usbd<'c> { iso_out_used: false, ep0_state: Mutex::new(Cell::new(EP0State { direction: UsbDirection::Out, + remaining_size: 0, in_transfer_state: TransferState::NoTransfer, is_set_address: false, })), @@ -193,7 +195,7 @@ impl<'c> Usbd<'c> { false => UsbDirection::In, true => UsbDirection::Out, }; - + ep0_state.remaining_size = (buf[6] as u16) | ((buf[7] as u16) << 8); ep0_state.is_set_address = (buf[0] == 0x00) && (buf[1] == 0x05); if ep0_state.direction == UsbDirection::Out { @@ -435,6 +437,14 @@ impl UsbBus for Usbd<'_> { return true; } + if ep0_state.direction == UsbDirection::In && ep0_state.remaining_size == 0 { + // Device sent all the requested data, no need to send ZLP. + // Host will issue an OUT transfer in this case, device should + // respond with a status stage. + regs.tasks_ep0status.write(|w| w.tasks_ep0status().set_bit()); + return true; + } + false }); @@ -499,6 +509,10 @@ impl UsbBus for Usbd<'_> { } }); + let mut ep0_state = self.ep0_state.borrow(cs).get(); + ep0_state.remaining_size = ep0_state.remaining_size.saturating_sub(buf.len() as u16); + self.ep0_state.borrow(cs).set(ep0_state); + // Hack: trigger status stage if the IN transfer is not acknowledged after a few frames, // so record the current frame here; the actual test and status stage activation happens // in the poll method. From 5d0c0eb694cec86c83e47a399af8d15d4aa32269 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Mon, 22 Feb 2021 02:48:19 +0300 Subject: [PATCH 22/38] Don't prepare DMA transfer in read_control_setup --- nrf-hal-common/src/usbd/mod.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 8526ab13..f942b588 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -199,13 +199,6 @@ impl<'c> Usbd<'c> { ep0_state.is_set_address = (buf[0] == 0x00) && (buf[1] == 0x05); if ep0_state.direction == UsbDirection::Out { - let ptr = self.bufs.out_bufs[0]; - let len = self.bufs.out_lens[0]; - - unsafe { - regs.epout0.ptr.write(|w| w.bits(ptr as u32)); - regs.epout0.maxcnt.write(|w| w.bits(u32::from(len))); - } regs.tasks_ep0rcvout .write(|w| w.tasks_ep0rcvout().set_bit()); } From 048e51d3356b652877b29451fddf065e6b212272 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Mon, 22 Feb 2021 02:50:47 +0300 Subject: [PATCH 23/38] Don't prepare DMA transfers in reset --- nrf-hal-common/src/usbd/mod.rs | 45 ---------------------------------- 1 file changed, 45 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index f942b588..b4f407f6 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -329,52 +329,7 @@ impl UsbBus for Usbd<'_> { interrupt::free(|cs| { let regs = self.periph.borrow(cs); - // Neat little hack to work around svd2rust not coalescing these on its own. - let epin = [ - ®s.epin0, - ®s.epin1, - ®s.epin2, - ®s.epin3, - ®s.epin4, - ®s.epin5, - ®s.epin6, - ®s.epin7, - ]; - let epout = [ - ®s.epout0, - ®s.epout1, - ®s.epout2, - ®s.epout3, - ®s.epout4, - ®s.epout5, - ®s.epout6, - ®s.epout7, - ]; - // TODO: Initialize ISO buffers - // Initialize all data pointers for the endpoint buffers, since they never change. - for i in 0..8 { - let in_enabled = self.used_in & (1 << i) != 0; - let out_enabled = self.used_out & (1 << i) != 0; - - if in_enabled { - unsafe { - epin[i].ptr.write(|w| w.bits(self.bufs.in_bufs[i] as u32)); - epin[i] - .maxcnt - .write(|w| w.bits(u32::from(self.bufs.in_lens[i]))); - } - } - - if out_enabled { - unsafe { - epout[i].ptr.write(|w| w.bits(self.bufs.out_bufs[i] as u32)); - epout[i] - .maxcnt - .write(|w| w.bits(u32::from(self.bufs.out_lens[i]))); - } - } - } // XXX this is not spec compliant; the endpoints should only be enabled after the device // has been put in the Configured state. However, usb-device provides no hook to do that From 7ad844a8e4c0cd55d0ff4ddc8437200e367abffb Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Mon, 22 Feb 2021 02:59:28 +0300 Subject: [PATCH 24/38] Remove backing buffers --- examples/usb/src/main.rs | 4 +-- nrf-hal-common/src/usbd/mod.rs | 58 +++------------------------------- 2 files changed, 6 insertions(+), 56 deletions(-) diff --git a/examples/usb/src/main.rs b/examples/usb/src/main.rs index 2b847639..dfa417e9 100644 --- a/examples/usb/src/main.rs +++ b/examples/usb/src/main.rs @@ -22,8 +22,6 @@ fn TIMER0() { #[entry] fn main() -> ! { - static mut EP_BUF: [u8; 512] = [0; 512]; - let periph = Peripherals::take().unwrap(); while !periph .POWER @@ -60,7 +58,7 @@ fn main() -> ! { led.set_low().unwrap(); - let usb_bus = Usbd::new_alloc(usbd, EP_BUF, &clocks); + let usb_bus = Usbd::new(usbd, &clocks); let mut serial = SerialPort::new(&usb_bus); let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index b4f407f6..20c04c72 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -13,7 +13,7 @@ use crate::{ pac::USBD, }; use core::sync::atomic::{compiler_fence, Ordering}; -use core::{cell::Cell, mem, ptr}; +use core::cell::Cell; use core::mem::MaybeUninit; use cortex_m::interrupt::{self, Mutex}; use usb_device::{ @@ -31,10 +31,6 @@ fn dma_end() { } struct Buffers { - // Ptr and size is stored separately to save space - in_bufs: [*mut u8; 9], - out_bufs: [*mut u8; 9], - // Buffers can be up to 64 Bytes since this is a Full-Speed implementation. in_lens: [u8; 9], out_lens: [u8; 9], @@ -43,8 +39,6 @@ struct Buffers { impl Buffers { fn new() -> Self { Self { - in_bufs: [ptr::null_mut(); 9], - out_bufs: [ptr::null_mut(); 9], in_lens: [0; 9], out_lens: [0; 9], } @@ -72,7 +66,6 @@ pub struct Usbd<'c> { periph: Mutex, // argument passed to `UsbDeviceBuilder.max_packet_size_0` max_packet_size_0: u16, - unalloc_buffers: &'static mut [u8], bufs: Buffers, used_in: u8, used_out: u8, @@ -90,19 +83,14 @@ impl<'c> Usbd<'c> { /// # Parameters /// /// * `periph`: The raw USBD peripheral. - /// * `endpoint_buffers`: Backing storage for the endpoint buffers. This - /// needs to be big enough to accomodate all buffers of all endpoints, or - /// `alloc_ep` will fail. #[inline] - pub fn new_alloc( + pub fn new( periph: USBD, - endpoint_buffers: &'static mut [u8], _clocks: &'c Clocks, ) -> UsbBusAllocator { UsbBusAllocator::new(Self { periph: Mutex::new(periph), max_packet_size_0: 0, - unalloc_buffers: endpoint_buffers, bufs: Buffers::new(), used_in: 0, used_out: 0, @@ -123,36 +111,6 @@ impl<'c> Usbd<'c> { unsafe { &*USBD::ptr() }.usbaddr.read().addr().bits() } - fn alloc_ep_buf( - &mut self, - ep_type: EndpointType, - mut size: u16, - ) -> usb_device::Result<&'static mut [u8]> { - if self.unalloc_buffers.len() < usize::from(size) { - Err(UsbError::EndpointMemoryOverflow) - } else { - if ep_type == EndpointType::Bulk || ep_type == EndpointType::Interrupt { - // datasheet: buffer must be 4-byte aligned and its size must be a multiple of 4 - let rem = self.unalloc_buffers.as_mut_ptr() as usize % 4; - if rem != 0 { - let (_padding, remaining) = - mem::replace(&mut self.unalloc_buffers, &mut []).split_at_mut(4 - rem); - self.unalloc_buffers = remaining; - } - - let rem = size % 4; - if rem != 0 { - size = size + 4 - rem; - } - } - assert!(size <= 64); - let (alloc, remaining) = - mem::replace(&mut self.unalloc_buffers, &mut []).split_at_mut(size.into()); - self.unalloc_buffers = remaining; - Ok(alloc) - } - } - fn is_used(&self, ep: EndpointAddress) -> bool { if ep.index() == 8 { // ISO @@ -234,8 +192,6 @@ impl UsbBus for Usbd<'_> { self.max_packet_size_0 = max_packet_size; } - let buf = self.alloc_ep_buf(ep_type, max_packet_size)?; - if false { unimplemented!( "alloc_ep({:?}, {:?}, {:?}, {}, {})", @@ -247,15 +203,13 @@ impl UsbBus for Usbd<'_> { ); } - let (used, bufs, lens) = match ep_dir { + let (used, lens) = match ep_dir { UsbDirection::In => ( &mut self.used_in, - &mut self.bufs.in_bufs, &mut self.bufs.in_lens, ), UsbDirection::Out => ( &mut self.used_out, - &mut self.bufs.out_bufs, &mut self.bufs.out_lens, ), }; @@ -271,8 +225,7 @@ impl UsbBus for Usbd<'_> { return Err(UsbError::EndpointOverflow); } else { *flag = true; - bufs[8] = buf.as_mut_ptr(); - lens[8] = buf.len() as u8; + lens[8] = max_packet_size as u8; return Ok(EndpointAddress::from_parts(0x08, ep_dir)); } } @@ -297,8 +250,7 @@ impl UsbBus for Usbd<'_> { } *used |= 1 << alloc_index; - bufs[alloc_index as usize] = buf.as_mut_ptr(); - lens[alloc_index as usize] = buf.len() as u8; + lens[alloc_index as usize] = max_packet_size as u8; let addr = EndpointAddress::from_parts(alloc_index as usize, ep_dir); Ok(addr) From 48b50e636ad9ba0617ebd259d724640795892999 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Fri, 26 Feb 2021 19:24:31 +0300 Subject: [PATCH 25/38] Track IN endpoint state --- nrf-hal-common/src/usbd/mod.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 20c04c72..68fcc302 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -72,6 +72,7 @@ pub struct Usbd<'c> { iso_in_used: bool, iso_out_used: bool, ep0_state: Mutex>, + busy_in_endpoints: Mutex>, // used to freeze `Clocks` and ensure they remain in the `ExternalOscillator` state _clocks: &'c (), @@ -102,6 +103,7 @@ impl<'c> Usbd<'c> { in_transfer_state: TransferState::NoTransfer, is_set_address: false, })), + busy_in_endpoints: Mutex::new(Cell::new(0)), _clocks: &(), }) } @@ -303,6 +305,8 @@ impl UsbBus for Usbd<'_> { regs.size.epout[i].reset(); } } + + self.busy_in_endpoints.borrow(cs).set(0); }); } @@ -361,7 +365,11 @@ impl UsbBus for Usbd<'_> { interrupt::free(|cs| { let regs = self.periph.borrow(cs); + let busy_in_endpoints = self.busy_in_endpoints.borrow(cs); + if busy_in_endpoints.get() & (1 << i) != 0 { + return Err(UsbError::WouldBlock); + } if regs.epstatus.read().bits() & (1 << i) != 0 { return Err(UsbError::WouldBlock); } @@ -436,6 +444,9 @@ impl UsbBus for Usbd<'_> { // Clear EPSTATUS.EPIN[i] flag regs.epstatus.write(|w| unsafe { w.bits(1 << i) }); + // Mark the endpoint as busy + busy_in_endpoints.set(busy_in_endpoints.get() | (1 << i)); + Ok(buf.len()) }) } @@ -547,6 +558,11 @@ impl UsbBus for Usbd<'_> { }); } } + + if stalled { + let busy_in_endpoints = self.busy_in_endpoints.borrow(cs); + busy_in_endpoints.set(busy_in_endpoints.get() & !(1 << ep_addr.index())); + } }); } @@ -576,6 +592,7 @@ impl UsbBus for Usbd<'_> { fn poll(&self) -> PollResult { interrupt::free(|cs| { let regs = self.periph.borrow(cs); + let busy_in_endpoints = self.busy_in_endpoints.borrow(cs); if regs.events_usbreset.read().events_usbreset().bit_is_set() { regs.events_usbreset.reset(); @@ -628,6 +645,9 @@ impl UsbBus for Usbd<'_> { let mut state = ep0_state.get(); state.in_transfer_state = TransferState::NoTransfer; ep0_state.set(state); + + // Mark the endpoint as not busy + busy_in_endpoints.set(busy_in_endpoints.get() & !1); } else { // Do not clear OUT events, since we have to continue reporting them until the // buffer is read. @@ -644,6 +664,9 @@ impl UsbBus for Usbd<'_> { regs.epdatastatus.write(|w| unsafe { w.bits(1 << i) }); in_complete |= 1 << i; + + // Mark the endpoint as not busy + busy_in_endpoints.set(busy_in_endpoints.get() & !(1 << i)); } if epdatastatus & (1 << (i + 16)) != 0 { // EPDATASTATUS.EPOUT[i] is set From 6ffd9e1bd52d438fb713465e148eb0d28e2fa470 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Sat, 5 Jun 2021 00:16:04 +0300 Subject: [PATCH 26/38] Remove redundant clear operation --- nrf-hal-common/src/usbd/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 68fcc302..ce34beef 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -530,9 +530,6 @@ impl UsbBus for Usbd<'_> { // TODO: ISO - // Read is complete, clear status flag - regs.epdatastatus.write(|w| unsafe { w.bits(1 << (i + 16)) }); - // Enable the endpoint regs.size.epout[i].reset(); From 922ced5fa1e3111e06b3899e4fc6bfecdee4e391 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Sat, 5 Jun 2021 00:16:30 +0300 Subject: [PATCH 27/38] Check EPDATASTATUS inside write() This helps in a situation when `poll()` is not called periodically, for example, when main loop is busy writing data to an endpoint. This situation happens in the usb_serial example. --- nrf-hal-common/src/usbd/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index ce34beef..c4333780 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -368,7 +368,17 @@ impl UsbBus for Usbd<'_> { let busy_in_endpoints = self.busy_in_endpoints.borrow(cs); if busy_in_endpoints.get() & (1 << i) != 0 { - return Err(UsbError::WouldBlock); + // Maybe this endpoint is not busy? + let epdatastatus = regs.epdatastatus.read().bits(); + if epdatastatus & (1 << i) != 0 { + // Clear the event flag + regs.epdatastatus.write(|w| unsafe { w.bits(1 << i) }); + + // Clear the busy status and continue + busy_in_endpoints.set(busy_in_endpoints.get() & !(1 << i)); + } else { + return Err(UsbError::WouldBlock); + } } if regs.epstatus.read().bits() & (1 << i) != 0 { return Err(UsbError::WouldBlock); From 4f33ab89ccb72173716d256f8ab81c40997c374b Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Sat, 5 Jun 2021 14:50:05 +0300 Subject: [PATCH 28/38] Cleanup --- nrf-hal-common/src/usbd/mod.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index c4333780..eab5bcc0 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -194,17 +194,6 @@ impl UsbBus for Usbd<'_> { self.max_packet_size_0 = max_packet_size; } - if false { - unimplemented!( - "alloc_ep({:?}, {:?}, {:?}, {}, {})", - ep_dir, - ep_addr, - ep_type, - max_packet_size, - interval, - ); - } - let (used, lens) = match ep_dir { UsbDirection::In => ( &mut self.used_in, From 2e469df0653c1ada2ea5398e1b8b98dcce17234d Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Sat, 5 Jun 2021 15:21:54 +0300 Subject: [PATCH 29/38] Implement is_stalled() --- nrf-hal-common/src/usbd/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index eab5bcc0..9bd385c8 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -563,7 +563,15 @@ impl UsbBus for Usbd<'_> { } fn is_stalled(&self, ep_addr: EndpointAddress) -> bool { - unimplemented!("is_stalled(ep={:?})", ep_addr); + interrupt::free(|cs| { + let regs = self.periph.borrow(cs); + + let i = ep_addr.index(); + match ep_addr.direction() { + UsbDirection::Out => regs.halted.epout[i].read().getstatus().is_halted(), + UsbDirection::In => regs.halted.epin[i].read().getstatus().is_halted(), + } + }) } #[inline] From f55b5418bad6aee69451c096c9ff74832abc6f8a Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Sat, 5 Jun 2021 15:22:31 +0300 Subject: [PATCH 30/38] Add serial and test_class examples --- examples/usb/examples/serial.rs | 79 +++++++++++++++++++++++++++++ examples/usb/examples/test_class.rs | 47 +++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 examples/usb/examples/serial.rs create mode 100644 examples/usb/examples/test_class.rs diff --git a/examples/usb/examples/serial.rs b/examples/usb/examples/serial.rs new file mode 100644 index 00000000..905875cf --- /dev/null +++ b/examples/usb/examples/serial.rs @@ -0,0 +1,79 @@ +#![no_std] +#![no_main] + +use panic_semihosting as _; + +use cortex_m_rt::entry; +use nrf52840_hal::usbd::Usbd; +use nrf52840_hal::clocks::Clocks; +use nrf52840_pac::Peripherals; +use usb_device::device::{UsbDeviceBuilder, UsbVidPid}; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + + +#[entry] +fn main() -> ! { + let periph = Peripherals::take().unwrap(); + while !periph + .POWER + .usbregstatus + .read() + .vbusdetect() + .is_vbus_present() + {} + + // wait until USB 3.3V supply is stable + while !periph + .POWER + .events_usbpwrrdy + .read() + .events_usbpwrrdy() + .bit_is_clear() + {} + + let clocks = Clocks::new(periph.CLOCK); + let clocks = clocks.enable_ext_hfosc(); + + let usbd = periph.USBD; + + let usb_bus = Usbd::new(usbd, &clocks); + let mut serial = SerialPort::new(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .device_class(USB_CLASS_CDC) + .max_packet_size_0(64) // (makes control transfers 8x faster) + .build(); + + loop { + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let mut buf = [0u8; 64]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + let mut write_offset = 0; + while write_offset < count { + match serial.write(&buf[write_offset..count]) { + Ok(len) if len > 0 => { + write_offset += len; + }, + _ => {}, + } + } + } + _ => {} + } + } +} diff --git a/examples/usb/examples/test_class.rs b/examples/usb/examples/test_class.rs new file mode 100644 index 00000000..53209353 --- /dev/null +++ b/examples/usb/examples/test_class.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use panic_semihosting as _; + +use cortex_m_rt::entry; +use nrf52840_hal::usbd::Usbd; +use nrf52840_hal::clocks::Clocks; +use nrf52840_pac::Peripherals; +use usb_device::test_class::TestClass; + +#[entry] +fn main() -> ! { + let periph = Peripherals::take().unwrap(); + while !periph + .POWER + .usbregstatus + .read() + .vbusdetect() + .is_vbus_present() + {} + + // wait until USB 3.3V supply is stable + while !periph + .POWER + .events_usbpwrrdy + .read() + .events_usbpwrrdy() + .bit_is_clear() + {} + + let clocks = Clocks::new(periph.CLOCK); + let clocks = clocks.enable_ext_hfosc(); + + let usbd = periph.USBD; + let usb_bus = Usbd::new(usbd, &clocks); + + let mut test = TestClass::new(&usb_bus); + + let mut usb_dev = { test.make_device(&usb_bus) }; + + loop { + if usb_dev.poll(&mut [&mut test]) { + test.poll(); + } + } +} From d93fc5e25ba0d88b52c829a26f13ce54567a4adb Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Sat, 5 Jun 2021 15:31:13 +0300 Subject: [PATCH 31/38] Fix warning --- nrf-hal-common/src/usbd/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 9bd385c8..c90774f3 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -174,7 +174,7 @@ impl UsbBus for Usbd<'_> { ep_addr: Option, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + _interval: u8, ) -> usb_device::Result { // Endpoint addresses are fixed in hardware: // - 0x80 / 0x00 - Control EP0 From 74b797ebecfaa09d7b9986c0bdd2b67abfcc97f9 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Sat, 12 Jun 2021 16:20:15 +0300 Subject: [PATCH 32/38] Cleanup examples --- examples/usb/Cargo.toml | 2 - examples/usb/examples/serial.rs | 22 +--------- examples/usb/examples/test_class.rs | 20 +--------- examples/usb/src/main.rs | 62 ++++------------------------- 4 files changed, 10 insertions(+), 96 deletions(-) diff --git a/examples/usb/Cargo.toml b/examples/usb/Cargo.toml index 14aa144c..90fbd95e 100644 --- a/examples/usb/Cargo.toml +++ b/examples/usb/Cargo.toml @@ -5,9 +5,7 @@ authors = ["Jonas Schievink "] edition = "2018" [dependencies] -cortex-m = "0.6.2" cortex-m-rt = "0.6.12" -cortex-m-semihosting = "0.3.5" panic-semihosting = "0.5.3" nrf52840-pac = "0.9.0" usb-device = "0.2.7" diff --git a/examples/usb/examples/serial.rs b/examples/usb/examples/serial.rs index 905875cf..c5af03fa 100644 --- a/examples/usb/examples/serial.rs +++ b/examples/usb/examples/serial.rs @@ -10,33 +10,13 @@ use nrf52840_pac::Peripherals; use usb_device::device::{UsbDeviceBuilder, UsbVidPid}; use usbd_serial::{SerialPort, USB_CLASS_CDC}; - #[entry] fn main() -> ! { let periph = Peripherals::take().unwrap(); - while !periph - .POWER - .usbregstatus - .read() - .vbusdetect() - .is_vbus_present() - {} - - // wait until USB 3.3V supply is stable - while !periph - .POWER - .events_usbpwrrdy - .read() - .events_usbpwrrdy() - .bit_is_clear() - {} - let clocks = Clocks::new(periph.CLOCK); let clocks = clocks.enable_ext_hfosc(); - let usbd = periph.USBD; - - let usb_bus = Usbd::new(usbd, &clocks); + let usb_bus = Usbd::new(periph.USBD, &clocks); let mut serial = SerialPort::new(&usb_bus); let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) diff --git a/examples/usb/examples/test_class.rs b/examples/usb/examples/test_class.rs index 53209353..7b8b93c5 100644 --- a/examples/usb/examples/test_class.rs +++ b/examples/usb/examples/test_class.rs @@ -12,28 +12,10 @@ use usb_device::test_class::TestClass; #[entry] fn main() -> ! { let periph = Peripherals::take().unwrap(); - while !periph - .POWER - .usbregstatus - .read() - .vbusdetect() - .is_vbus_present() - {} - - // wait until USB 3.3V supply is stable - while !periph - .POWER - .events_usbpwrrdy - .read() - .events_usbpwrrdy() - .bit_is_clear() - {} - let clocks = Clocks::new(periph.CLOCK); let clocks = clocks.enable_ext_hfosc(); - let usbd = periph.USBD; - let usb_bus = Usbd::new(usbd, &clocks); + let usb_bus = Usbd::new(periph.USBD, &clocks); let mut test = TestClass::new(&usb_bus); diff --git a/examples/usb/src/main.rs b/examples/usb/src/main.rs index dfa417e9..63aa83bd 100644 --- a/examples/usb/src/main.rs +++ b/examples/usb/src/main.rs @@ -3,62 +3,25 @@ use panic_semihosting as _; -use cortex_m::peripheral::SCB; use cortex_m_rt::entry; -use cortex_m_semihosting::hprintln; -use nrf52840_hal::gpio::{p0, p1, Level}; use nrf52840_hal::prelude::*; use nrf52840_hal::timer::{OneShot, Timer}; use nrf52840_hal::usbd::Usbd; use nrf52840_hal::clocks::Clocks; -use nrf52840_pac::{interrupt, Peripherals, TIMER0}; +use nrf52840_pac::{Peripherals, TIMER0}; use usb_device::device::{UsbDeviceBuilder, UsbDeviceState, UsbVidPid}; use usbd_serial::{SerialPort, USB_CLASS_CDC}; -#[interrupt] -fn TIMER0() { - SCB::sys_reset(); -} - #[entry] fn main() -> ! { let periph = Peripherals::take().unwrap(); - while !periph - .POWER - .usbregstatus - .read() - .vbusdetect() - .is_vbus_present() - {} - - // wait until USB 3.3V supply is stable - while !periph - .POWER - .events_usbpwrrdy - .read() - .events_usbpwrrdy() - .bit_is_clear() - {} - let clocks = Clocks::new(periph.CLOCK); let clocks = clocks.enable_ext_hfosc(); - let mut timer = Timer::one_shot(periph.TIMER0); - let usbd = periph.USBD; - let p0 = p0::Parts::new(periph.P0); - let p1 = p1::Parts::new(periph.P1); - - let mut led = p0.p0_23.into_push_pull_output(Level::High); + let mut timer = Timer::periodic(periph.TIMER0); + timer.start(Timer::::TICKS_PER_SECOND); - let btn = p1.p1_00.into_pullup_input(); - while btn.is_high().unwrap() {} - - timer.enable_interrupt(); - timer.start(Timer::::TICKS_PER_SECOND * 3); - - led.set_low().unwrap(); - - let usb_bus = Usbd::new(usbd, &clocks); + let usb_bus = Usbd::new(periph.USBD, &clocks); let mut serial = SerialPort::new(&usb_bus); let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) @@ -67,21 +30,12 @@ fn main() -> ! { .max_packet_size_0(64) // (makes control transfers 8x faster) .build(); - hprintln!("").ok(); - let mut state = UsbDeviceState::Default; loop { - if !usb_dev.poll(&mut [&mut serial]) { - continue; - } - - let new_state = usb_dev.state(); - if new_state != state { - hprintln!("{:?} {:#x}", new_state, usb_dev.bus().device_address()).ok(); - state = new_state; + usb_dev.poll(&mut [&mut serial]); - if state == UsbDeviceState::Configured { - serial.write(b"Hello, world!\n").unwrap(); - serial.flush().unwrap(); + if usb_dev.state() == UsbDeviceState::Configured && serial.dtr() { + if timer.wait().is_ok() { + serial.write(b"Hello, world!\n").ok(); } } } From 9ce50b5b9089bf6345b0624829e13c7402402505 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Tue, 6 Jul 2021 16:59:26 +0200 Subject: [PATCH 33/38] Fix USB demo --- examples/usb/.cargo/config | 2 - examples/usb/Embed.toml | 63 +++++++++++++++++++ examples/usb/README.md | 16 ++--- examples/usb/{examples => src/bin}/serial.rs | 0 .../usb/{examples => src/bin}/test_class.rs | 0 examples/usb/src/main.rs | 42 ------------- 6 files changed, 67 insertions(+), 56 deletions(-) delete mode 100644 examples/usb/.cargo/config create mode 100755 examples/usb/Embed.toml rename examples/usb/{examples => src/bin}/serial.rs (100%) rename examples/usb/{examples => src/bin}/test_class.rs (100%) delete mode 100644 examples/usb/src/main.rs diff --git a/examples/usb/.cargo/config b/examples/usb/.cargo/config deleted file mode 100644 index 08b949e1..00000000 --- a/examples/usb/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = 'arm-none-eabi-gdb' diff --git a/examples/usb/Embed.toml b/examples/usb/Embed.toml new file mode 100755 index 00000000..fda29b6b --- /dev/null +++ b/examples/usb/Embed.toml @@ -0,0 +1,63 @@ +[default.probe] +# USB vendor ID +# usb_vid = "1337" +# USB product ID +# usb_pid = "1337" +# Serial number +# serial = "12345678" +# The protocol to be used for communicating with the target. +protocol = "Swd" +# The speed in kHz of the data link to the target. +# speed = 1337 + +[default.flashing] +# Whether or not the target should be flashed. +enabled = true +# Whether or not the target should be halted after reset. +# DEPRECATED, moved to reset section +halt_afterwards = false +# Whether or not bytes erased but not rewritten with data from the ELF +# should be restored with their contents before erasing. +restore_unwritten_bytes = false +# The path where an SVG of the assembled flash layout should be written to. +# flash_layout_output_path = "out.svg" + +[default.reset] +# Whether or not the target should be reset. +# When flashing is enabled as well, the target will be reset after flashing. +enabled = true +# Whether or not the target should be halted after reset. +halt_afterwards = false + +[default.general] +# The chip name of the chip to be debugged. +chip = "nRF52840" +# A list of chip descriptions to be loaded during runtime. +chip_descriptions = [] +# The default log level to be used. Possible values are one of: +# "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" +log_level = "WARN" + +[default.rtt] +# Whether or not an RTTUI should be opened after flashing. +# This is exclusive and cannot be used with GDB at the moment. +enabled = true +# A list of channel associations to be displayed. If left empty, all channels are displayed. +channels = [ + # { up = 0, down = 0, name = "name", format = "String" } +] +# The duration in ms for which the logger should retry to attach to RTT. +timeout = 3000 +# Whether timestamps in the RTTUI are enabled +show_timestamps = true +# Whether to save rtt history buffer on exit. +log_enabled = false +# Where to save rtt history buffer relative to manifest path. +log_path = "./logs" + +[default.gdb] +# Whether or not a GDB server should be opened after flashing. +# This is exclusive and cannot be used with RTT at the moment. +enabled = false +# The connection string in host:port format wher the GDB server will open a socket. +# gdb_connection_string diff --git a/examples/usb/README.md b/examples/usb/README.md index 822d9478..66d725fe 100644 --- a/examples/usb/README.md +++ b/examples/usb/README.md @@ -1,14 +1,6 @@ -# spi-demo -SPIM demonstation code. -Connect a resistor between pin 22 and 23 on the demo board to feed MOSI directly back to MISO -If all tests pass all four Led (Led1 to Led4) will light up, in case of error only at least one of the Led will remain turned off. +# USB examples +This demo provides two binaries: -## HW connections -Pin Connecton -P0.24 SPIclk -P0.23 MOSI -P0.22 MISO - -This is designed for nRF52-DK board: -https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52-DK +- `serial`: a USB serial "echo" example. +- `test_class`: exposes the test device from the `usb-device` crate. diff --git a/examples/usb/examples/serial.rs b/examples/usb/src/bin/serial.rs similarity index 100% rename from examples/usb/examples/serial.rs rename to examples/usb/src/bin/serial.rs diff --git a/examples/usb/examples/test_class.rs b/examples/usb/src/bin/test_class.rs similarity index 100% rename from examples/usb/examples/test_class.rs rename to examples/usb/src/bin/test_class.rs diff --git a/examples/usb/src/main.rs b/examples/usb/src/main.rs deleted file mode 100644 index 63aa83bd..00000000 --- a/examples/usb/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![no_std] -#![no_main] - -use panic_semihosting as _; - -use cortex_m_rt::entry; -use nrf52840_hal::prelude::*; -use nrf52840_hal::timer::{OneShot, Timer}; -use nrf52840_hal::usbd::Usbd; -use nrf52840_hal::clocks::Clocks; -use nrf52840_pac::{Peripherals, TIMER0}; -use usb_device::device::{UsbDeviceBuilder, UsbDeviceState, UsbVidPid}; -use usbd_serial::{SerialPort, USB_CLASS_CDC}; - -#[entry] -fn main() -> ! { - let periph = Peripherals::take().unwrap(); - let clocks = Clocks::new(periph.CLOCK); - let clocks = clocks.enable_ext_hfosc(); - - let mut timer = Timer::periodic(periph.TIMER0); - timer.start(Timer::::TICKS_PER_SECOND); - - let usb_bus = Usbd::new(periph.USBD, &clocks); - let mut serial = SerialPort::new(&usb_bus); - - let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) - .product("nRF52840 Serial Port Demo") - .device_class(USB_CLASS_CDC) - .max_packet_size_0(64) // (makes control transfers 8x faster) - .build(); - - loop { - usb_dev.poll(&mut [&mut serial]); - - if usb_dev.state() == UsbDeviceState::Configured && serial.dtr() { - if timer.wait().is_ok() { - serial.write(b"Hello, world!\n").ok(); - } - } - } -} From 6ede424ad84936cdc31f7bfe00d0faa05b81e49f Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Tue, 6 Jul 2021 17:31:10 +0200 Subject: [PATCH 34/38] Clean up Cargo features, support nRF52833 --- nrf-hal-common/Cargo.toml | 2 +- nrf-hal-common/src/lib.rs | 2 +- nrf-hal-common/src/usbd/mod.rs | 2 -- nrf52840-hal/Cargo.toml | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nrf-hal-common/Cargo.toml b/nrf-hal-common/Cargo.toml index 897c1b6c..741721a7 100644 --- a/nrf-hal-common/Cargo.toml +++ b/nrf-hal-common/Cargo.toml @@ -77,6 +77,6 @@ doc = [] 52810 = ["nrf52810-pac"] 52811 = ["nrf52811-pac"] 52832 = ["nrf52832-pac"] -52833 = ["nrf52833-pac"] +52833 = ["nrf52833-pac", "usb-device"] 52840 = ["nrf52840-pac", "usb-device"] 9160 = ["nrf9160-pac"] diff --git a/nrf-hal-common/src/lib.rs b/nrf-hal-common/src/lib.rs index e5a3e70a..5e5113fb 100644 --- a/nrf-hal-common/src/lib.rs +++ b/nrf-hal-common/src/lib.rs @@ -81,7 +81,7 @@ pub mod uarte; #[cfg(not(feature = "9160"))] pub mod uicr; pub mod wdt; -#[cfg(feature = "52840")] +#[cfg(feature = "usb-device")] pub mod usbd; pub mod prelude { diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index c90774f3..627de8c6 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -45,8 +45,6 @@ impl Buffers { } } -unsafe impl Sync for Buffers {} - #[derive(Copy, Clone)] enum TransferState { NoTransfer, diff --git a/nrf52840-hal/Cargo.toml b/nrf52840-hal/Cargo.toml index f8cf75ec..2464f813 100644 --- a/nrf52840-hal/Cargo.toml +++ b/nrf52840-hal/Cargo.toml @@ -23,7 +23,7 @@ nrf52840-pac = "0.9.0" [dependencies.nrf-hal-common] path = "../nrf-hal-common" default-features = false -features = ["52840", "usb-device"] +features = ["52840"] version = "=0.12.2" [dependencies.embedded-hal] From dbe0b5a2d4eb2cfdbe153c8e24892ee933408462 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Tue, 6 Jul 2021 17:33:26 +0200 Subject: [PATCH 35/38] Add a 1ms delay to `force_reset` --- nrf-hal-common/src/usbd/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs index 627de8c6..78c828ef 100644 --- a/nrf-hal-common/src/usbd/mod.rs +++ b/nrf-hal-common/src/usbd/mod.rs @@ -707,7 +707,13 @@ impl UsbBus for Usbd<'_> { interrupt::free(|cs| { let regs = self.periph.borrow(cs); regs.usbpullup.write(|w| w.connect().disabled()); - // TODO delay needed? + + // FIXME: it is unclear how much of a delay is needed here, so we use a conservative 1ms + // Note that this is very bad for latency-sensitive apps since it happens in a critical + // section. `force_reset` should be avoided if that is an issue. + // We run at 64 MHz, so 64k cycles are 1ms. + cortex_m::asm::delay(64_000); + regs.usbpullup.write(|w| w.connect().enabled()); }); From 844b7823acefe46618c4521cfcaf1037014b585a Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Tue, 6 Jul 2021 20:44:43 +0200 Subject: [PATCH 36/38] Use nrf-usbd --- examples/usb/src/bin/serial.rs | 8 +- examples/usb/src/bin/test_class.rs | 4 +- nrf-hal-common/Cargo.toml | 12 +- nrf-hal-common/src/lib.rs | 4 +- nrf-hal-common/src/usbd.rs | 21 + nrf-hal-common/src/usbd/errata.rs | 57 --- nrf-hal-common/src/usbd/mod.rs | 722 ----------------------------- 7 files changed, 35 insertions(+), 793 deletions(-) create mode 100644 nrf-hal-common/src/usbd.rs delete mode 100644 nrf-hal-common/src/usbd/errata.rs delete mode 100644 nrf-hal-common/src/usbd/mod.rs diff --git a/examples/usb/src/bin/serial.rs b/examples/usb/src/bin/serial.rs index c5af03fa..e64cdd6a 100644 --- a/examples/usb/src/bin/serial.rs +++ b/examples/usb/src/bin/serial.rs @@ -4,8 +4,8 @@ use panic_semihosting as _; use cortex_m_rt::entry; -use nrf52840_hal::usbd::Usbd; use nrf52840_hal::clocks::Clocks; +use nrf52840_hal::usbd::{UsbPeripheral, Usbd}; use nrf52840_pac::Peripherals; use usb_device::device::{UsbDeviceBuilder, UsbVidPid}; use usbd_serial::{SerialPort, USB_CLASS_CDC}; @@ -16,7 +16,7 @@ fn main() -> ! { let clocks = Clocks::new(periph.CLOCK); let clocks = clocks.enable_ext_hfosc(); - let usb_bus = Usbd::new(periph.USBD, &clocks); + let usb_bus = Usbd::new(UsbPeripheral::new(periph.USBD, &clocks)); let mut serial = SerialPort::new(&usb_bus); let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) @@ -48,8 +48,8 @@ fn main() -> ! { match serial.write(&buf[write_offset..count]) { Ok(len) if len > 0 => { write_offset += len; - }, - _ => {}, + } + _ => {} } } } diff --git a/examples/usb/src/bin/test_class.rs b/examples/usb/src/bin/test_class.rs index 7b8b93c5..0980d06c 100644 --- a/examples/usb/src/bin/test_class.rs +++ b/examples/usb/src/bin/test_class.rs @@ -4,8 +4,8 @@ use panic_semihosting as _; use cortex_m_rt::entry; -use nrf52840_hal::usbd::Usbd; use nrf52840_hal::clocks::Clocks; +use nrf52840_hal::usbd::{UsbPeripheral, Usbd}; use nrf52840_pac::Peripherals; use usb_device::test_class::TestClass; @@ -15,7 +15,7 @@ fn main() -> ! { let clocks = Clocks::new(periph.CLOCK); let clocks = clocks.enable_ext_hfosc(); - let usb_bus = Usbd::new(periph.USBD, &clocks); + let usb_bus = Usbd::new(UsbPeripheral::new(periph.USBD, &clocks)); let mut test = TestClass::new(&usb_bus); diff --git a/nrf-hal-common/Cargo.toml b/nrf-hal-common/Cargo.toml index 741721a7..1addc18d 100644 --- a/nrf-hal-common/Cargo.toml +++ b/nrf-hal-common/Cargo.toml @@ -39,10 +39,6 @@ version = "0.2.3" optional = true version = "0.9.0" -[dependencies.usb-device] -version = "0.2.7" -optional = true - [dependencies.nrf52810-pac] optional = true version = "0.9.0" @@ -67,6 +63,10 @@ version = "0.9.0" optional = true version = "0.2.1" +[dependencies.nrf-usbd] +git = "https://github.com/nrf-rs/nrf-usbd.git" +optional = true + [dependencies.embedded-hal] features = ["unproven"] version = "0.2.4" @@ -77,6 +77,6 @@ doc = [] 52810 = ["nrf52810-pac"] 52811 = ["nrf52811-pac"] 52832 = ["nrf52832-pac"] -52833 = ["nrf52833-pac", "usb-device"] -52840 = ["nrf52840-pac", "usb-device"] +52833 = ["nrf52833-pac", "nrf-usbd"] +52840 = ["nrf52840-pac", "nrf-usbd"] 9160 = ["nrf9160-pac"] diff --git a/nrf-hal-common/src/lib.rs b/nrf-hal-common/src/lib.rs index 5e5113fb..6d240329 100644 --- a/nrf-hal-common/src/lib.rs +++ b/nrf-hal-common/src/lib.rs @@ -80,9 +80,9 @@ pub mod uart; pub mod uarte; #[cfg(not(feature = "9160"))] pub mod uicr; -pub mod wdt; -#[cfg(feature = "usb-device")] +#[cfg(feature = "nrf-usbd")] pub mod usbd; +pub mod wdt; pub mod prelude { pub use crate::hal::digital::v2::*; diff --git a/nrf-hal-common/src/usbd.rs b/nrf-hal-common/src/usbd.rs new file mode 100644 index 00000000..9557d045 --- /dev/null +++ b/nrf-hal-common/src/usbd.rs @@ -0,0 +1,21 @@ +use crate::clocks::ExternalOscillator; +use crate::pac::USBD; +use crate::Clocks; + +pub use nrf_usbd::Usbd; + +#[allow(dead_code)] // fields are unused and only hold ownership +pub struct UsbPeripheral<'a> { + usbd: USBD, + clocks: &'a (), +} + +impl<'a> UsbPeripheral<'a> { + pub fn new(usbd: USBD, _clocks: &'a Clocks) -> Self { + Self { usbd, clocks: &() } + } +} + +unsafe impl<'a> nrf_usbd::UsbPeripheral for UsbPeripheral<'a> { + const REGISTERS: *const () = USBD::ptr() as *const _; +} diff --git a/nrf-hal-common/src/usbd/errata.rs b/nrf-hal-common/src/usbd/errata.rs deleted file mode 100644 index 8cc2dad9..00000000 --- a/nrf-hal-common/src/usbd/errata.rs +++ /dev/null @@ -1,57 +0,0 @@ -/// Writes `val` to `addr`. Used to apply Errata workarounds. -unsafe fn poke(addr: u32, val: u32) { - (addr as *mut u32).write_volatile(val); -} - -/// Reads 32 bits from `addr`. -unsafe fn peek(addr: u32) -> u32 { - (addr as *mut u32).read_volatile() -} - -pub fn pre_enable() { - // Works around Erratum 187 on chip revisions 1 and 2. - unsafe { - poke(0x4006EC00, 0x00009375); - poke(0x4006ED14, 0x00000003); - poke(0x4006EC00, 0x00009375); - } - - pre_wakeup(); -} - -pub fn post_enable() { - post_wakeup(); - - // Works around Erratum 187 on chip revisions 1 and 2. - unsafe { - poke(0x4006EC00, 0x00009375); - poke(0x4006ED14, 0x00000000); - poke(0x4006EC00, 0x00009375); - } -} - -pub fn pre_wakeup() { - // Works around Erratum 171 on chip revisions 1 and 2. - - unsafe { - if peek(0x4006EC00) == 0x00000000 { - poke(0x4006EC00, 0x00009375); - } - - poke(0x4006EC14, 0x000000C0); - poke(0x4006EC00, 0x00009375); - } -} - -pub fn post_wakeup() { - // Works around Erratum 171 on chip revisions 1 and 2. - - unsafe { - if peek(0x4006EC00) == 0x00000000 { - poke(0x4006EC00, 0x00009375); - } - - poke(0x4006EC14, 0x00000000); - poke(0x4006EC00, 0x00009375); - } -} diff --git a/nrf-hal-common/src/usbd/mod.rs b/nrf-hal-common/src/usbd/mod.rs deleted file mode 100644 index 78c828ef..00000000 --- a/nrf-hal-common/src/usbd/mod.rs +++ /dev/null @@ -1,722 +0,0 @@ -//! A `usb-device` implementation using the USBD peripheral. -//! -//! Difficulties: -//! * Control EP 0 is special: -//! * Setup stage is put in registers, not RAM. -//! * Different events are used to initiate transfers. -//! * No notification when the status stage is ACK'd. - -mod errata; - -use crate::{ - clocks::{Clocks, ExternalOscillator}, - pac::USBD, -}; -use core::sync::atomic::{compiler_fence, Ordering}; -use core::cell::Cell; -use core::mem::MaybeUninit; -use cortex_m::interrupt::{self, Mutex}; -use usb_device::{ - bus::{PollResult, UsbBus, UsbBusAllocator}, - endpoint::{EndpointAddress, EndpointType}, - UsbDirection, UsbError, -}; - -fn dma_start() { - compiler_fence(Ordering::Release); -} - -fn dma_end() { - compiler_fence(Ordering::Acquire); -} - -struct Buffers { - // Buffers can be up to 64 Bytes since this is a Full-Speed implementation. - in_lens: [u8; 9], - out_lens: [u8; 9], -} - -impl Buffers { - fn new() -> Self { - Self { - in_lens: [0; 9], - out_lens: [0; 9], - } - } -} - -#[derive(Copy, Clone)] -enum TransferState { - NoTransfer, - Started(u16), -} - -#[derive(Copy, Clone)] -struct EP0State { - direction: UsbDirection, - remaining_size: u16, - in_transfer_state: TransferState, - is_set_address: bool, -} - -/// USB device implementation. -pub struct Usbd<'c> { - periph: Mutex, - // argument passed to `UsbDeviceBuilder.max_packet_size_0` - max_packet_size_0: u16, - bufs: Buffers, - used_in: u8, - used_out: u8, - iso_in_used: bool, - iso_out_used: bool, - ep0_state: Mutex>, - busy_in_endpoints: Mutex>, - - // used to freeze `Clocks` and ensure they remain in the `ExternalOscillator` state - _clocks: &'c (), -} - -impl<'c> Usbd<'c> { - /// Creates a new USB bus, taking ownership of the raw peripheral. - /// - /// # Parameters - /// - /// * `periph`: The raw USBD peripheral. - #[inline] - pub fn new( - periph: USBD, - _clocks: &'c Clocks, - ) -> UsbBusAllocator { - UsbBusAllocator::new(Self { - periph: Mutex::new(periph), - max_packet_size_0: 0, - bufs: Buffers::new(), - used_in: 0, - used_out: 0, - iso_in_used: false, - iso_out_used: false, - ep0_state: Mutex::new(Cell::new(EP0State { - direction: UsbDirection::Out, - remaining_size: 0, - in_transfer_state: TransferState::NoTransfer, - is_set_address: false, - })), - busy_in_endpoints: Mutex::new(Cell::new(0)), - _clocks: &(), - }) - } - - /// Fetches the address assigned to the device (only valid when device is configured). - pub fn device_address(&self) -> u8 { - unsafe { &*USBD::ptr() }.usbaddr.read().addr().bits() - } - - fn is_used(&self, ep: EndpointAddress) -> bool { - if ep.index() == 8 { - // ISO - let flag = if ep.is_in() { - self.iso_in_used - } else { - self.iso_out_used - }; - return flag; - } - - if ep.is_in() { - self.used_in & (1 << ep.index()) != 0 - } else { - self.used_out & (1 << ep.index()) != 0 - } - } - - fn read_control_setup(&self, regs: &USBD, buf: &mut [u8], ep0_state: &mut EP0State) -> usb_device::Result { - const SETUP_LEN: usize = 8; - - if buf.len() < SETUP_LEN { - return Err(UsbError::BufferOverflow); - } - - // This is unfortunate: Reassemble the nicely split-up setup packet back into bytes, only - // for the usb-device code to copy the bytes back out into structured data. - // The bytes are split across registers, leaving a 3-Byte gap between them, so we couldn't - // even memcpy all of them at once. Weird peripheral. - buf[0] = regs.bmrequesttype.read().bits() as u8; - buf[1] = regs.brequest.read().brequest().bits(); - buf[2] = regs.wvaluel.read().wvaluel().bits(); - buf[3] = regs.wvalueh.read().wvalueh().bits(); - buf[4] = regs.windexl.read().windexl().bits(); - buf[5] = regs.windexh.read().windexh().bits(); - buf[6] = regs.wlengthl.read().wlengthl().bits(); - buf[7] = regs.wlengthh.read().wlengthh().bits(); - - ep0_state.direction = match regs.bmrequesttype.read().direction().is_host_to_device() { - false => UsbDirection::In, - true => UsbDirection::Out, - }; - ep0_state.remaining_size = (buf[6] as u16) | ((buf[7] as u16) << 8); - ep0_state.is_set_address = (buf[0] == 0x00) && (buf[1] == 0x05); - - if ep0_state.direction == UsbDirection::Out { - regs.tasks_ep0rcvout - .write(|w| w.tasks_ep0rcvout().set_bit()); - } - - Ok(SETUP_LEN) - } -} - -impl UsbBus for Usbd<'_> { - fn alloc_ep( - &mut self, - ep_dir: UsbDirection, - ep_addr: Option, - ep_type: EndpointType, - max_packet_size: u16, - _interval: u8, - ) -> usb_device::Result { - // Endpoint addresses are fixed in hardware: - // - 0x80 / 0x00 - Control EP0 - // - 0x81 / 0x01 - Bulk/Interrupt EP1 - // - 0x82 / 0x02 - Bulk/Interrupt EP2 - // - 0x83 / 0x03 - Bulk/Interrupt EP3 - // - 0x84 / 0x04 - Bulk/Interrupt EP4 - // - 0x85 / 0x05 - Bulk/Interrupt EP5 - // - 0x86 / 0x06 - Bulk/Interrupt EP6 - // - 0x87 / 0x07 - Bulk/Interrupt EP7 - // - 0x88 / 0x08 - Isochronous - - // Endpoint directions are allocated individually. - - // store user-supplied value - if ep_addr.map(|addr| addr.index()) == Some(0) { - self.max_packet_size_0 = max_packet_size; - } - - let (used, lens) = match ep_dir { - UsbDirection::In => ( - &mut self.used_in, - &mut self.bufs.in_lens, - ), - UsbDirection::Out => ( - &mut self.used_out, - &mut self.bufs.out_lens, - ), - }; - - let alloc_index = match ep_type { - EndpointType::Isochronous => { - let flag = match ep_dir { - UsbDirection::In => &mut self.iso_in_used, - UsbDirection::Out => &mut self.iso_out_used, - }; - - if *flag { - return Err(UsbError::EndpointOverflow); - } else { - *flag = true; - lens[8] = max_packet_size as u8; - return Ok(EndpointAddress::from_parts(0x08, ep_dir)); - } - } - EndpointType::Control => 0, - EndpointType::Interrupt | EndpointType::Bulk => { - let leading = used.leading_zeros(); - if leading == 0 { - return Err(UsbError::EndpointOverflow); - } - - if leading == 8 { - // Even CONTROL is free, don't allocate that - 1 - } else { - 8 - leading - } - } - }; - - if *used & (1 << alloc_index) != 0 { - return Err(UsbError::EndpointOverflow); - } - - *used |= 1 << alloc_index; - lens[alloc_index as usize] = max_packet_size as u8; - - let addr = EndpointAddress::from_parts(alloc_index as usize, ep_dir); - Ok(addr) - } - - #[inline] - fn enable(&mut self) { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - - errata::pre_enable(); - - regs.enable.write(|w| w.enable().enabled()); - - // Wait until the peripheral is ready. - while !regs.eventcause.read().ready().is_ready() {} - regs.eventcause.write(|w| w.ready().set_bit()); // Write 1 to clear. - - errata::post_enable(); - - // Enable the USB pullup, allowing enumeration. - regs.usbpullup.write(|w| w.connect().enabled()); - }); - } - - #[inline] - fn reset(&self) { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - - // TODO: Initialize ISO buffers - - // XXX this is not spec compliant; the endpoints should only be enabled after the device - // has been put in the Configured state. However, usb-device provides no hook to do that - // TODO: Merge `used_{in,out}` with `iso_{in,out}_used` so ISO is enabled here as well. - // Make the enabled endpoints respond to traffic. - unsafe { - regs.epinen.write(|w| w.bits(self.used_in.into())); - regs.epouten.write(|w| w.bits(self.used_out.into())); - } - - for i in 1..8 { - let out_enabled = self.used_out & (1 << i) != 0; - - // when first enabled, bulk/interrupt OUT endpoints will *not* receive data (the - // peripheral will NAK all incoming packets) until we write a zero to the SIZE - // register (see figure 203 of the 52840 manual). To avoid that we write a 0 to the - // SIZE register - if out_enabled { - regs.size.epout[i].reset(); - } - } - - self.busy_in_endpoints.borrow(cs).set(0); - }); - } - - #[inline] - fn set_device_address(&self, _addr: u8) { - // Nothing to do, the peripheral handles this. - } - - fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> usb_device::Result { - if !self.is_used(ep_addr) { - return Err(UsbError::InvalidEndpoint); - } - - if ep_addr.is_out() { - return Err(UsbError::InvalidEndpoint); - } - - // A 0-length write to Control EP 0 is a status stage acknowledging a control write xfer - if ep_addr.index() == 0 && buf.is_empty() { - let exit = interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - - let ep0_state = self.ep0_state.borrow(cs).get(); - - if ep0_state.is_set_address { - // Inhibit - return true; - } - - if ep0_state.direction == UsbDirection::Out { - regs.tasks_ep0status.write(|w| w.tasks_ep0status().set_bit()); - return true; - } - - if ep0_state.direction == UsbDirection::In && ep0_state.remaining_size == 0 { - // Device sent all the requested data, no need to send ZLP. - // Host will issue an OUT transfer in this case, device should - // respond with a status stage. - regs.tasks_ep0status.write(|w| w.tasks_ep0status().set_bit()); - return true; - } - - false - }); - - if exit { - return Ok(0); - } - } - - let i = ep_addr.index(); - - if usize::from(self.bufs.in_lens[i]) < buf.len() { - return Err(UsbError::BufferOverflow); - } - - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - let busy_in_endpoints = self.busy_in_endpoints.borrow(cs); - - if busy_in_endpoints.get() & (1 << i) != 0 { - // Maybe this endpoint is not busy? - let epdatastatus = regs.epdatastatus.read().bits(); - if epdatastatus & (1 << i) != 0 { - // Clear the event flag - regs.epdatastatus.write(|w| unsafe { w.bits(1 << i) }); - - // Clear the busy status and continue - busy_in_endpoints.set(busy_in_endpoints.get() & !(1 << i)); - } else { - return Err(UsbError::WouldBlock); - } - } - if regs.epstatus.read().bits() & (1 << i) != 0 { - return Err(UsbError::WouldBlock); - } - - let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); - unsafe { - let slice = &mut *ram_buf.as_mut_ptr(); - slice[..buf.len()].copy_from_slice(buf); - } - let ram_buf = unsafe { ram_buf.assume_init() }; - - let epin = [ - ®s.epin0, - ®s.epin1, - ®s.epin2, - ®s.epin3, - ®s.epin4, - ®s.epin5, - ®s.epin6, - ®s.epin7, - ]; - - // Set the buffer length so the right number of bytes are transmitted. - // Safety: `buf.len()` has been checked to be <= the max buffer length. - unsafe { - if buf.is_empty() { - epin[i].ptr.write(|w| w.bits(0)); - } else { - epin[i].ptr.write(|w| w.bits(ram_buf.as_ptr() as u32)); - } - epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8)); - } - - if i == 0 { - // EPIN0: a short packet (len < max_packet_size0) indicates the end of the data - // stage and must be followed by us responding with an ACK token to an OUT token - // sent from the host (AKA the status stage) -- `usb-device` provides no call back - // for that so we'll trigger the status stage using a shortcut - let is_short_packet = buf.len() < self.max_packet_size_0 as usize; - regs.shorts.modify(|_, w| { - if is_short_packet { - w.ep0datadone_ep0status().set_bit() - } else { - w.ep0datadone_ep0status().clear_bit() - } - }); - - let mut ep0_state = self.ep0_state.borrow(cs).get(); - ep0_state.remaining_size = ep0_state.remaining_size.saturating_sub(buf.len() as u16); - self.ep0_state.borrow(cs).set(ep0_state); - - // Hack: trigger status stage if the IN transfer is not acknowledged after a few frames, - // so record the current frame here; the actual test and status stage activation happens - // in the poll method. - let frame_counter = regs.framecntr.read().framecntr().bits(); - let ep0_state = self.ep0_state.borrow(cs); - let mut state = ep0_state.get(); - state.in_transfer_state = TransferState::Started(frame_counter); - ep0_state.set(state); - } - - // Clear ENDEPIN[i] flag - regs.events_endepin[i].reset(); - - // Kick off device -> host transmission. This starts DMA, so a compiler fence is needed. - dma_start(); - regs.tasks_startepin[i].write(|w| w.tasks_startepin().set_bit()); - while regs.events_endepin[i].read().events_endepin().bit_is_clear() {} - regs.events_endepin[i].reset(); - dma_end(); - - // Clear EPSTATUS.EPIN[i] flag - regs.epstatus.write(|w| unsafe { w.bits(1 << i) }); - - // Mark the endpoint as busy - busy_in_endpoints.set(busy_in_endpoints.get() | (1 << i)); - - Ok(buf.len()) - }) - } - - fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> usb_device::Result { - if !self.is_used(ep_addr) { - return Err(UsbError::InvalidEndpoint); - } - - if ep_addr.is_in() { - return Err(UsbError::InvalidEndpoint); - } - - let i = ep_addr.index(); - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - - // Control EP 0 is special - if i == 0 { - // Control setup packet is special, since it is put in registers, not a buffer. - if regs.events_ep0setup.read().events_ep0setup().bit_is_set() { - regs.events_ep0setup.reset(); - - let ep0_state = self.ep0_state.borrow(cs); - let mut state = ep0_state.get(); - let n = self.read_control_setup(regs, buf, &mut state)?; - ep0_state.set(state); - - return Ok(n) - } else { - // Is the endpoint ready? - if regs.events_ep0datadone.read().events_ep0datadone().bit_is_clear() { - // Not yet ready. - return Err(UsbError::WouldBlock); - } - } - } else { - // Is the endpoint ready? - let epdatastatus = regs.epdatastatus.read().bits(); - if epdatastatus & (1 << (i + 16)) == 0 { - // Not yet ready. - return Err(UsbError::WouldBlock); - } - } - - // Check that the packet fits into the buffer - let size = regs.size.epout[i].read().bits(); - if size as usize > buf.len() { - return Err(UsbError::BufferOverflow); - } - - // Clear status - if i == 0 { - regs.events_ep0datadone.reset(); - } else { - regs.epdatastatus.write(|w| unsafe { w.bits(1 << (i + 16)) }); - } - - // We checked that the endpoint has data, time to read it - - let epout = [ - ®s.epout0, - ®s.epout1, - ®s.epout2, - ®s.epout3, - ®s.epout4, - ®s.epout5, - ®s.epout6, - ®s.epout7, - ]; - epout[i].ptr.write(|w| unsafe { w.bits(buf.as_ptr() as u32) }); - // MAXCNT must match SIZE - epout[i].maxcnt.write(|w| unsafe { w.bits(size) }); - - dma_start(); - regs.events_endepout[i].reset(); - regs.tasks_startepout[i].write(|w| w.tasks_startepout().set_bit()); - while regs.events_endepout[i].read().events_endepout().bit_is_clear() {} - regs.events_endepout[i].reset(); - dma_end(); - - // TODO: ISO - - // Enable the endpoint - regs.size.epout[i].reset(); - - Ok(size as usize) - }) - } - - fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - - unsafe { - if ep_addr.index() == 0 { - regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(stalled)); - } else { - regs.epstall.write(|w| { - w.ep() - .bits(ep_addr.index() as u8 & 0b111) - .io() - .bit(ep_addr.is_in()) - .stall() - .bit(stalled) - }); - } - } - - if stalled { - let busy_in_endpoints = self.busy_in_endpoints.borrow(cs); - busy_in_endpoints.set(busy_in_endpoints.get() & !(1 << ep_addr.index())); - } - }); - } - - fn is_stalled(&self, ep_addr: EndpointAddress) -> bool { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - - let i = ep_addr.index(); - match ep_addr.direction() { - UsbDirection::Out => regs.halted.epout[i].read().getstatus().is_halted(), - UsbDirection::In => regs.halted.epin[i].read().getstatus().is_halted(), - } - }) - } - - #[inline] - fn suspend(&self) { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - regs.lowpower.write(|w| w.lowpower().low_power()); - }); - } - - #[inline] - fn resume(&self) { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - - errata::pre_wakeup(); - - regs.lowpower.write(|w| w.lowpower().force_normal()); - }); - } - - fn poll(&self) -> PollResult { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - let busy_in_endpoints = self.busy_in_endpoints.borrow(cs); - - if regs.events_usbreset.read().events_usbreset().bit_is_set() { - regs.events_usbreset.reset(); - return PollResult::Reset; - } else if regs.events_usbevent.read().events_usbevent().bit_is_set() { - // "Write 1 to clear" - if regs.eventcause.read().suspend().bit() { - regs.eventcause.write(|w| w.suspend().bit(true)); - return PollResult::Suspend; - } else if regs.eventcause.read().resume().bit() { - regs.eventcause.write(|w| w.resume().bit(true)); - return PollResult::Resume; - } else { - regs.events_usbevent.reset(); - } - } - - if regs.events_sof.read().events_sof().bit_is_set() { - regs.events_sof.reset(); - - // Check if we have a timeout for EP0 IN transfer - let ep0_state = self.ep0_state.borrow(cs); - let mut state = ep0_state.get(); - if let TransferState::Started(counter) = state.in_transfer_state { - let frame_counter = regs.framecntr.read().framecntr().bits(); - if frame_counter.wrapping_sub(counter) >= 5 { - // Send a status stage to ACK a pending OUT transfer - regs.tasks_ep0status.write(|w| w.tasks_ep0status().set_bit()); - - // reset the state - state.in_transfer_state = TransferState::NoTransfer; - ep0_state.set(state); - } - } - } - - // Check for any finished transmissions. - let mut in_complete = 0; - let mut out_complete = 0; - if regs.events_ep0datadone.read().events_ep0datadone().bit_is_set() { - let ep0_state = self.ep0_state.borrow(cs).get(); - if ep0_state.direction == UsbDirection::In { - // Clear event, since we must only report this once. - regs.events_ep0datadone.reset(); - - in_complete |= 1; - - // Reset a timeout for the IN transfer - let ep0_state = self.ep0_state.borrow(cs); - let mut state = ep0_state.get(); - state.in_transfer_state = TransferState::NoTransfer; - ep0_state.set(state); - - // Mark the endpoint as not busy - busy_in_endpoints.set(busy_in_endpoints.get() & !1); - } else { - // Do not clear OUT events, since we have to continue reporting them until the - // buffer is read. - - out_complete |= 1; - } - } - let epdatastatus = regs.epdatastatus.read().bits(); - for i in 1..=7 { - if epdatastatus & (1 << i) != 0 { - // EPDATASTATUS.EPIN[i] is set - - // Clear event, since we must only report this once. - regs.epdatastatus.write(|w| unsafe { w.bits(1 << i) }); - - in_complete |= 1 << i; - - // Mark the endpoint as not busy - busy_in_endpoints.set(busy_in_endpoints.get() & !(1 << i)); - } - if epdatastatus & (1 << (i + 16)) != 0 { - // EPDATASTATUS.EPOUT[i] is set - // This flag will be cleared in `read()` - - out_complete |= 1 << i; - } - } - - // Setup packets are only relevant on the control EP 0. - let mut ep_setup = 0; - if regs.events_ep0setup.read().events_ep0setup().bit_is_set() { - ep_setup = 1; - - // Reset shorts - regs.shorts.modify(|_, w| { - w.ep0datadone_ep0status().clear_bit() - }); - } - - // TODO: Check ISO EP - - if out_complete != 0 || in_complete != 0 || ep_setup != 0 { - PollResult::Data { - ep_out: out_complete, - ep_in_complete: in_complete, - ep_setup, - } - } else { - PollResult::None - } - }) - } - - fn force_reset(&self) -> usb_device::Result<()> { - interrupt::free(|cs| { - let regs = self.periph.borrow(cs); - regs.usbpullup.write(|w| w.connect().disabled()); - - // FIXME: it is unclear how much of a delay is needed here, so we use a conservative 1ms - // Note that this is very bad for latency-sensitive apps since it happens in a critical - // section. `force_reset` should be avoided if that is an issue. - // We run at 64 MHz, so 64k cycles are 1ms. - cortex_m::asm::delay(64_000); - - regs.usbpullup.write(|w| w.connect().enabled()); - }); - - Ok(()) - } -} From 3ea49b7e5bfd1abbd67fa7c662a44ccf4db6713c Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Wed, 7 Jul 2021 13:22:03 +0200 Subject: [PATCH 37/38] Use crates.io version of nrf-usbd --- nrf-hal-common/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrf-hal-common/Cargo.toml b/nrf-hal-common/Cargo.toml index 1addc18d..387ba26b 100644 --- a/nrf-hal-common/Cargo.toml +++ b/nrf-hal-common/Cargo.toml @@ -64,7 +64,7 @@ optional = true version = "0.2.1" [dependencies.nrf-usbd] -git = "https://github.com/nrf-rs/nrf-usbd.git" +version = "0.1.0" optional = true [dependencies.embedded-hal] From 91851964ff5da36d94f5e6d942c7fc7f3cf89602 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Wed, 7 Jul 2021 14:36:46 +0200 Subject: [PATCH 38/38] Address review comments --- nrf-hal-common/src/usbd.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nrf-hal-common/src/usbd.rs b/nrf-hal-common/src/usbd.rs index 9557d045..42b666cf 100644 --- a/nrf-hal-common/src/usbd.rs +++ b/nrf-hal-common/src/usbd.rs @@ -1,18 +1,22 @@ +use core::marker::PhantomData; + use crate::clocks::ExternalOscillator; use crate::pac::USBD; use crate::Clocks; pub use nrf_usbd::Usbd; -#[allow(dead_code)] // fields are unused and only hold ownership pub struct UsbPeripheral<'a> { - usbd: USBD, - clocks: &'a (), + _usbd: USBD, + _clocks: PhantomData<&'a ()>, } impl<'a> UsbPeripheral<'a> { pub fn new(usbd: USBD, _clocks: &'a Clocks) -> Self { - Self { usbd, clocks: &() } + Self { + _usbd: usbd, + _clocks: PhantomData, + } } }