From 37598a5b3792ec1b763b5c16fe422c9e1347d7d6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 9 Mar 2022 01:34:35 +0100 Subject: [PATCH 01/50] wip: experimental async usb stack --- embassy-nrf/Cargo.toml | 3 +- embassy-nrf/src/usb.rs | 493 ++++++++++++++++++++++++++-- embassy-usb/Cargo.toml | 12 + embassy-usb/src/builder.rs | 347 ++++++++++++++++++++ embassy-usb/src/control.rs | 134 ++++++++ embassy-usb/src/descriptor.rs | 387 ++++++++++++++++++++++ embassy-usb/src/driver.rs | 160 +++++++++ embassy-usb/src/fmt.rs | 225 +++++++++++++ embassy-usb/src/lib.rs | 342 +++++++++++++++++++ embassy-usb/src/types.rs | 138 ++++++++ examples/nrf/Cargo.toml | 5 +- examples/nrf/src/bin/usb/cdc_acm.rs | 356 ++++++++++++++++++++ examples/nrf/src/bin/usb/main.rs | 53 +++ examples/nrf/src/bin/usb_uart.rs | 89 ----- examples/nrf/src/bin/usb_uart_io.rs | 66 ---- 15 files changed, 2624 insertions(+), 186 deletions(-) create mode 100644 embassy-usb/Cargo.toml create mode 100644 embassy-usb/src/builder.rs create mode 100644 embassy-usb/src/control.rs create mode 100644 embassy-usb/src/descriptor.rs create mode 100644 embassy-usb/src/driver.rs create mode 100644 embassy-usb/src/fmt.rs create mode 100644 embassy-usb/src/lib.rs create mode 100644 embassy-usb/src/types.rs create mode 100644 examples/nrf/src/bin/usb/cdc_acm.rs create mode 100644 examples/nrf/src/bin/usb/main.rs delete mode 100644 examples/nrf/src/bin/usb_uart.rs delete mode 100644 examples/nrf/src/bin/usb_uart_io.rs diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 5e69a887..36c61c65 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -64,6 +64,7 @@ _gpio-p1 = [] embassy = { version = "0.1.0", path = "../embassy" } embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["nrf"]} embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } +embassy-usb = {version = "0.1.0", path = "../embassy-usb" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.7", git = "https://github.com/embassy-rs/embedded-hal", branch = "embassy2", optional = true} @@ -80,8 +81,6 @@ rand_core = "0.6.3" fixed = "1.10.0" embedded-storage = "0.3.0" cfg-if = "1.0.0" -nrf-usbd = {version = "0.1.1"} -usb-device = "0.2.8" nrf52805-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } nrf52810-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index deab9454..0f7d68d8 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -1,43 +1,423 @@ #![macro_use] +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; +use embassy::interrupt::InterruptExt; +use embassy::time::{with_timeout, Duration}; +use embassy::util::Unborrow; +use embassy::waitqueue::AtomicWaker; +use embassy_hal_common::unborrow; +use embassy_usb::driver::{self, ReadError, WriteError}; +use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; +use futures::future::poll_fn; +use futures::Future; +use pac::NVIC; + +pub use embassy_usb; + use crate::interrupt::Interrupt; use crate::pac; -use core::marker::PhantomData; -use embassy::util::Unborrow; -use nrf_usbd::{UsbPeripheral, Usbd}; -use usb_device::bus::UsbBusAllocator; +static EP0_WAKER: AtomicWaker = AtomicWaker::new(); -pub use embassy_hal_common::usb::*; - -pub struct UsbBus<'d, T: Instance> { +pub struct Driver<'d, T: Instance> { phantom: PhantomData<&'d mut T>, + alloc_in: Allocator, + alloc_out: Allocator, } -unsafe impl<'d, T: Instance> UsbPeripheral for UsbBus<'d, T> { - // todo how to use T::regs - const REGISTERS: *const () = pac::USBD::ptr() as *const (); -} +impl<'d, T: Instance> Driver<'d, T> { + pub fn new( + _usb: impl Unborrow + 'd, + irq: impl Unborrow + 'd, + ) -> Self { + unborrow!(irq); + irq.set_handler(Self::on_interrupt); + irq.unpend(); + irq.enable(); -impl<'d, T: Instance> UsbBus<'d, T> { - pub fn new(_usb: impl Unborrow + 'd) -> UsbBusAllocator>> { - let r = T::regs(); - - r.intenset.write(|w| { - w.sof().set_bit(); - w.usbevent().set_bit(); - w.ep0datadone().set_bit(); - w.ep0setup().set_bit(); - w.usbreset().set_bit() - }); - - Usbd::new(UsbBus { + Self { phantom: PhantomData, - }) + alloc_in: Allocator::new(), + alloc_out: Allocator::new(), + } + } + + fn on_interrupt(_: *mut ()) { + let regs = T::regs(); + + if regs.events_ep0setup.read().bits() != 0 { + regs.intenclr.write(|w| w.ep0setup().clear()); + EP0_WAKER.wake(); + } + if regs.events_ep0datadone.read().bits() != 0 { + regs.intenclr.write(|w| w.ep0datadone().clear()); + EP0_WAKER.wake(); + } + } + + fn set_stalled(ep_addr: EndpointAddress, stalled: bool) { + let regs = T::regs(); + + 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); + w.io().bit(ep_addr.is_in()); + w.stall().bit(stalled) + }); + } + } + + //if stalled { + // self.busy_in_endpoints &= !(1 << ep_addr.index()); + //} + } + + fn is_stalled(ep_addr: EndpointAddress) -> bool { + let regs = T::regs(); + + 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(), + } } } -unsafe impl embassy_hal_common::usb::USBInterrupt for crate::interrupt::USBD {} +impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result { + let index = self + .alloc_in + .allocate(ep_addr, ep_type, max_packet_size, interval)?; + let ep_addr = EndpointAddress::from_parts(index, UsbDirection::In); + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: ep_addr, + ep_type, + max_packet_size, + interval, + }, + }) + } + + fn alloc_endpoint_out( + &mut self, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result { + let index = self + .alloc_out + .allocate(ep_addr, ep_type, max_packet_size, interval)?; + let ep_addr = EndpointAddress::from_parts(index, UsbDirection::Out); + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: ep_addr, + ep_type, + max_packet_size, + interval, + }, + }) + } + + fn enable(self) -> Self::Bus { + let regs = T::regs(); + + 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(); + + unsafe { NVIC::unmask(pac::Interrupt::USBD) }; + + // Enable the USB pullup, allowing enumeration. + regs.usbpullup.write(|w| w.connect().enabled()); + info!("enabled"); + + Bus { + phantom: PhantomData, + alloc_in: self.alloc_in, + alloc_out: self.alloc_out, + } + } +} + +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + alloc_in: Allocator, + alloc_out: Allocator, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + #[inline] + fn reset(&mut self) { + let regs = T::regs(); + + // 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 + unsafe { + regs.epinen.write(|w| w.bits(self.alloc_in.used.into())); + regs.epouten.write(|w| w.bits(self.alloc_out.used.into())); + } + + for i in 1..8 { + let out_enabled = self.alloc_out.used & (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 = 0; + } + + #[inline] + fn set_device_address(&mut self, _addr: u8) { + // Nothing to do, the peripheral handles this. + } + + fn set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + Driver::::set_stalled(ep_addr, stalled) + } + + fn is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + Driver::::is_stalled(ep_addr) + } + + #[inline] + fn suspend(&mut self) { + let regs = T::regs(); + regs.lowpower.write(|w| w.lowpower().low_power()); + } + + #[inline] + fn resume(&mut self) { + let regs = T::regs(); + + errata::pre_wakeup(); + + regs.lowpower.write(|w| w.lowpower().force_normal()); + } +} + +pub enum Out {} +pub enum In {} + +pub struct Endpoint<'d, T: Instance, Dir> { + _phantom: PhantomData<(&'d mut T, Dir)>, + info: EndpointInfo, +} + +impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + fn set_stalled(&self, stalled: bool) { + Driver::::set_stalled(self.info.addr, stalled) + } + + fn is_stalled(&self) -> bool { + Driver::::is_stalled(self.info.addr) + } +} + +impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { + type ReadFuture<'a> + where + Self: 'a, + = impl Future> + 'a; + + fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { + let regs = T::regs(); + + if buf.len() == 0 { + regs.tasks_ep0status.write(|w| unsafe { w.bits(1) }); + return Ok(0); + } + + // Wait for SETUP packet + regs.events_ep0setup.reset(); + regs.intenset.write(|w| w.ep0setup().set()); + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); + if regs.events_ep0setup.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + info!("got SETUP"); + + if buf.len() < 8 { + return Err(ReadError::BufferOverflow); + } + + 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(); + + Ok(8) + } + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + type WriteFuture<'a> + where + Self: 'a, + = impl Future> + 'a; + + fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + info!("write: {:x}", buf); + + let regs = T::regs(); + + let ptr = buf.as_ptr() as u32; + let len = buf.len() as u32; + regs.epin0.ptr.write(|w| unsafe { w.bits(ptr) }); + regs.epin0.maxcnt.write(|w| unsafe { w.bits(len) }); + + regs.events_ep0datadone.reset(); + regs.events_endepin[0].reset(); + + dma_start(); + + regs.tasks_startepin[0].write(|w| unsafe { w.bits(1) }); + info!("write: waiting for endepin..."); + while regs.events_endepin[0].read().bits() == 0 {} + + dma_end(); + + info!("write: waiting for ep0datadone..."); + regs.intenset.write(|w| w.ep0datadone().set()); + let res = with_timeout( + Duration::from_millis(10), + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }), + ) + .await; + + if res.is_err() { + // todo wrong error + return Err(driver::WriteError::BufferOverflow); + } + + info!("write done"); + + Ok(()) + } + } +} + +fn dma_start() { + compiler_fence(Ordering::Release); +} + +fn dma_end() { + compiler_fence(Ordering::Acquire); +} + +struct Allocator { + used: u16, + // Buffers can be up to 64 Bytes since this is a Full-Speed implementation. + lens: [u8; 9], +} + +impl Allocator { + fn new() -> Self { + Self { + used: 0, + lens: [0; 9], + } + } + + fn allocate( + &mut self, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + _interval: u8, + ) -> 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 alloc_index = match ep_type { + EndpointType::Isochronous => 8, + EndpointType::Control => 0, + EndpointType::Interrupt | EndpointType::Bulk => { + // Find rightmost zero bit in 1..=7 + let ones = (self.used >> 1).trailing_ones() as usize; + if ones >= 7 { + return Err(driver::EndpointAllocError); + } + ones + 1 + } + }; + + if self.used & (1 << alloc_index) != 0 { + return Err(driver::EndpointAllocError); + } + + self.used |= 1 << alloc_index; + self.lens[alloc_index] = max_packet_size as u8; + + Ok(alloc_index) + } +} pub(crate) mod sealed { use super::*; @@ -63,3 +443,64 @@ macro_rules! impl_usb { } }; } + +mod errata { + + /// 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/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml new file mode 100644 index 00000000..dfdc8fba --- /dev/null +++ b/embassy-usb/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "embassy-usb" +version = "0.1.0" +edition = "2018" + +[dependencies] +embassy = { version = "0.1.0", path = "../embassy" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +cortex-m = "0.7.3" +num-traits = { version = "0.2.14", default-features = false } diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs new file mode 100644 index 00000000..e92cc8ef --- /dev/null +++ b/embassy-usb/src/builder.rs @@ -0,0 +1,347 @@ +use super::descriptor::{BosWriter, DescriptorWriter}; +use super::driver::{Driver, EndpointAllocError}; +use super::types::*; +use super::UsbDevice; + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config<'a> { + pub(crate) vendor_id: u16, + pub(crate) product_id: u16, + + /// Device class code assigned by USB.org. Set to `0xff` for vendor-specific + /// devices that do not conform to any class. + /// + /// Default: `0x00` (class code specified by interfaces) + pub device_class: u8, + + /// Device sub-class code. Depends on class. + /// + /// Default: `0x00` + pub device_sub_class: u8, + + /// Device protocol code. Depends on class and sub-class. + /// + /// Default: `0x00` + pub device_protocol: u8, + + /// Device release version in BCD. + /// + /// Default: `0x0010` ("0.1") + pub device_release: u16, + + /// Maximum packet size in bytes for the control endpoint 0. + /// + /// Valid values are 8, 16, 32 and 64. There's generally no need to change this from the default + /// value of 8 bytes unless a class uses control transfers for sending large amounts of data, in + /// which case using a larger packet size may be more efficient. + /// + /// Default: 8 bytes + pub max_packet_size_0: u8, + + /// Manufacturer name string descriptor. + /// + /// Default: (none) + pub manufacturer: Option<&'a str>, + + /// Product name string descriptor. + /// + /// Default: (none) + pub product: Option<&'a str>, + + /// Serial number string descriptor. + /// + /// Default: (none) + pub serial_number: Option<&'a str>, + + /// Whether the device supports remotely waking up the host is requested. + /// + /// Default: `false` + pub supports_remote_wakeup: bool, + + /// Configures the device as a composite device with interface association descriptors. + /// + /// If set to `true`, the following fields should have the given values: + /// + /// - `device_class` = `0xEF` + /// - `device_sub_class` = `0x02` + /// - `device_protocol` = `0x01` + pub composite_with_iads: bool, + + /// Whether the device has its own power source. + /// + /// This should be set to `true` even if the device is sometimes self-powered and may not + /// always draw power from the USB bus. + /// + /// Default: `false` + /// + /// See also: `max_power` + pub self_powered: bool, + + /// Maximum current drawn from the USB bus by the device, in milliamps. + /// + /// The default is 100 mA. If your device always uses an external power source and never draws + /// power from the USB bus, this can be set to 0. + /// + /// See also: `self_powered` + /// + /// Default: 100mA + /// Max: 500mA + pub max_power: u16, +} + +impl<'a> Config<'a> { + pub fn new(vid: u16, pid: u16) -> Self { + Self { + device_class: 0x00, + device_sub_class: 0x00, + device_protocol: 0x00, + max_packet_size_0: 8, + vendor_id: vid, + product_id: pid, + device_release: 0x0010, + manufacturer: None, + product: None, + serial_number: None, + self_powered: false, + supports_remote_wakeup: false, + composite_with_iads: false, + max_power: 100, + } + } +} + +/// Used to build new [`UsbDevice`]s. +pub struct UsbDeviceBuilder<'d, D: Driver<'d>> { + config: Config<'d>, + + bus: D, + next_interface_number: u8, + next_string_index: u8, + + // TODO make not pub? + pub device_descriptor: DescriptorWriter<'d>, + pub config_descriptor: DescriptorWriter<'d>, + pub bos_descriptor: BosWriter<'d>, +} + +impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { + /// Creates a builder for constructing a new [`UsbDevice`]. + pub fn new( + bus: D, + config: Config<'d>, + device_descriptor_buf: &'d mut [u8], + config_descriptor_buf: &'d mut [u8], + bos_descriptor_buf: &'d mut [u8], + ) -> Self { + // Magic values specified in USB-IF ECN on IADs. + if config.composite_with_iads + && (config.device_class != 0xEF + || config.device_sub_class != 0x02 + || config.device_protocol != 0x01) + { + panic!("if composite_with_iads is set, you must set device_class = 0xEF, device_sub_class = 0x02, device_protocol = 0x01"); + } + + if config.max_power > 500 { + panic!("The maximum allowed value for `max_power` is 500mA"); + } + + match config.max_packet_size_0 { + 8 | 16 | 32 | 64 => {} + _ => panic!("invalid max_packet_size_0, the allowed values are 8, 16, 32 or 64"), + } + + let mut device_descriptor = DescriptorWriter::new(device_descriptor_buf); + let mut config_descriptor = DescriptorWriter::new(config_descriptor_buf); + let mut bos_descriptor = BosWriter::new(DescriptorWriter::new(bos_descriptor_buf)); + + device_descriptor.device(&config).unwrap(); + config_descriptor.configuration(&config).unwrap(); + bos_descriptor.bos().unwrap(); + + UsbDeviceBuilder { + bus, + config, + next_interface_number: 0, + next_string_index: 4, + + device_descriptor, + config_descriptor, + bos_descriptor, + } + } + + /// Creates the [`UsbDevice`] instance with the configuration in this builder. + pub fn build(mut self) -> UsbDevice<'d, D> { + self.config_descriptor.end_configuration(); + self.bos_descriptor.end_bos(); + + UsbDevice::build( + self.bus, + self.config, + self.device_descriptor.into_buf(), + self.config_descriptor.into_buf(), + self.bos_descriptor.writer.into_buf(), + ) + } + + /// Allocates a new interface number. + pub fn alloc_interface(&mut self) -> InterfaceNumber { + let number = self.next_interface_number; + self.next_interface_number += 1; + + InterfaceNumber::new(number) + } + + /// Allocates a new string index. + pub fn alloc_string(&mut self) -> StringIndex { + let index = self.next_string_index; + self.next_string_index += 1; + + StringIndex::new(index) + } + + /// Allocates an in endpoint. + /// + /// This directly delegates to [`Driver::alloc_endpoint_in`], so see that method for details. In most + /// cases classes should call the endpoint type specific methods instead. + pub fn alloc_endpoint_in( + &mut self, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result { + self.bus + .alloc_endpoint_in(ep_addr, ep_type, max_packet_size, interval) + } + + /// Allocates an out endpoint. + /// + /// This directly delegates to [`Driver::alloc_endpoint_out`], so see that method for details. In most + /// cases classes should call the endpoint type specific methods instead. + pub fn alloc_endpoint_out( + &mut self, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result { + self.bus + .alloc_endpoint_out(ep_addr, ep_type, max_packet_size, interval) + } + + /// Allocates a control in endpoint. + /// + /// This crate implements the control state machine only for endpoint 0. If classes want to + /// support control requests in other endpoints, the state machine must be implemented manually. + /// This should rarely be needed by classes. + /// + /// # Arguments + /// + /// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn alloc_control_endpoint_in(&mut self, max_packet_size: u16) -> D::EndpointIn { + self.alloc_endpoint_in(None, EndpointType::Control, max_packet_size, 0) + .expect("alloc_ep failed") + } + + /// Allocates a control out endpoint. + /// + /// This crate implements the control state machine only for endpoint 0. If classes want to + /// support control requests in other endpoints, the state machine must be implemented manually. + /// This should rarely be needed by classes. + /// + /// # Arguments + /// + /// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn alloc_control_endpoint_out(&mut self, max_packet_size: u16) -> D::EndpointOut { + self.alloc_endpoint_out(None, EndpointType::Control, max_packet_size, 0) + .expect("alloc_ep failed") + } + + /// Allocates a bulk in endpoint. + /// + /// # Arguments + /// + /// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn alloc_bulk_endpoint_in(&mut self, max_packet_size: u16) -> D::EndpointIn { + self.alloc_endpoint_in(None, EndpointType::Bulk, max_packet_size, 0) + .expect("alloc_ep failed") + } + + /// Allocates a bulk out endpoint. + /// + /// # Arguments + /// + /// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn alloc_bulk_endpoint_out(&mut self, max_packet_size: u16) -> D::EndpointOut { + self.alloc_endpoint_out(None, EndpointType::Bulk, max_packet_size, 0) + .expect("alloc_ep failed") + } + + /// Allocates a bulk in endpoint. + /// + /// # Arguments + /// + /// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn alloc_interrupt_endpoint_in( + &mut self, + max_packet_size: u16, + interval: u8, + ) -> D::EndpointIn { + self.alloc_endpoint_in(None, EndpointType::Interrupt, max_packet_size, interval) + .expect("alloc_ep failed") + } + + /// Allocates a bulk in endpoint. + /// + /// # Arguments + /// + /// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn alloc_interrupt_endpoint_out( + &mut self, + max_packet_size: u16, + interval: u8, + ) -> D::EndpointOut { + self.alloc_endpoint_out(None, EndpointType::Interrupt, max_packet_size, interval) + .expect("alloc_ep failed") + } +} diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs new file mode 100644 index 00000000..f1148ac7 --- /dev/null +++ b/embassy-usb/src/control.rs @@ -0,0 +1,134 @@ +use core::mem; + +use super::types::*; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ParseError { + InvalidLength, +} + +/// Control request type. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RequestType { + /// Request is a USB standard request. Usually handled by + /// [`UsbDevice`](crate::device::UsbDevice). + Standard = 0, + /// Request is intended for a USB class. + Class = 1, + /// Request is vendor-specific. + Vendor = 2, + /// Reserved. + Reserved = 3, +} + +/// Control request recipient. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Recipient { + /// Request is intended for the entire device. + Device = 0, + /// Request is intended for an interface. Generally, the `index` field of the request specifies + /// the interface number. + Interface = 1, + /// Request is intended for an endpoint. Generally, the `index` field of the request specifies + /// the endpoint address. + Endpoint = 2, + /// None of the above. + Other = 3, + /// Reserved. + Reserved = 4, +} + +/// A control request read from a SETUP packet. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Request { + /// Direction of the request. + pub direction: UsbDirection, + /// Type of the request. + pub request_type: RequestType, + /// Recipient of the request. + pub recipient: Recipient, + /// Request code. The meaning of the value depends on the previous fields. + pub request: u8, + /// Request value. The meaning of the value depends on the previous fields. + pub value: u16, + /// Request index. The meaning of the value depends on the previous fields. + pub index: u16, + /// Length of the DATA stage. For control OUT transfers this is the exact length of the data the + /// host sent. For control IN transfers this is the maximum length of data the device should + /// return. + pub length: u16, +} + +impl Request { + /// Standard USB control request Get Status + pub const GET_STATUS: u8 = 0; + + /// Standard USB control request Clear Feature + pub const CLEAR_FEATURE: u8 = 1; + + /// Standard USB control request Set Feature + pub const SET_FEATURE: u8 = 3; + + /// Standard USB control request Set Address + pub const SET_ADDRESS: u8 = 5; + + /// Standard USB control request Get Descriptor + pub const GET_DESCRIPTOR: u8 = 6; + + /// Standard USB control request Set Descriptor + pub const SET_DESCRIPTOR: u8 = 7; + + /// Standard USB control request Get Configuration + pub const GET_CONFIGURATION: u8 = 8; + + /// Standard USB control request Set Configuration + pub const SET_CONFIGURATION: u8 = 9; + + /// Standard USB control request Get Interface + pub const GET_INTERFACE: u8 = 10; + + /// Standard USB control request Set Interface + pub const SET_INTERFACE: u8 = 11; + + /// Standard USB control request Synch Frame + pub const SYNCH_FRAME: u8 = 12; + + /// Standard USB feature Endpoint Halt for Set/Clear Feature + pub const FEATURE_ENDPOINT_HALT: u16 = 0; + + /// Standard USB feature Device Remote Wakeup for Set/Clear Feature + pub const FEATURE_DEVICE_REMOTE_WAKEUP: u16 = 1; + + pub(crate) fn parse(buf: &[u8]) -> Result { + if buf.len() != 8 { + return Err(ParseError::InvalidLength); + } + + let rt = buf[0]; + let recipient = rt & 0b11111; + + Ok(Request { + direction: rt.into(), + request_type: unsafe { mem::transmute((rt >> 5) & 0b11) }, + recipient: if recipient <= 3 { + unsafe { mem::transmute(recipient) } + } else { + Recipient::Reserved + }, + request: buf[1], + value: (buf[2] as u16) | ((buf[3] as u16) << 8), + index: (buf[4] as u16) | ((buf[5] as u16) << 8), + length: (buf[6] as u16) | ((buf[7] as u16) << 8), + }) + } + + /// Gets the descriptor type and index from the value field of a GET_DESCRIPTOR request. + pub fn descriptor_type_index(&self) -> (u8, u8) { + ((self.value >> 8) as u8, self.value as u8) + } +} diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs new file mode 100644 index 00000000..746c6b82 --- /dev/null +++ b/embassy-usb/src/descriptor.rs @@ -0,0 +1,387 @@ +use super::builder::Config; +use super::{types::*, CONFIGURATION_VALUE, DEFAULT_ALTERNATE_SETTING}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + BufferFull, + InvalidState, +} + +/// Standard descriptor types +#[allow(missing_docs)] +pub mod descriptor_type { + pub const DEVICE: u8 = 1; + pub const CONFIGURATION: u8 = 2; + pub const STRING: u8 = 3; + pub const INTERFACE: u8 = 4; + pub const ENDPOINT: u8 = 5; + pub const IAD: u8 = 11; + pub const BOS: u8 = 15; + pub const CAPABILITY: u8 = 16; +} + +/// String descriptor language IDs. +pub mod lang_id { + /// English (US) + /// + /// Recommended for use as the first language ID for compatibility. + pub const ENGLISH_US: u16 = 0x0409; +} + +/// Standard capability descriptor types +#[allow(missing_docs)] +pub mod capability_type { + pub const WIRELESS_USB: u8 = 1; + pub const USB_2_0_EXTENSION: u8 = 2; + pub const SS_USB_DEVICE: u8 = 3; + pub const CONTAINER_ID: u8 = 4; + pub const PLATFORM: u8 = 5; +} + +/// A writer for USB descriptors. +pub struct DescriptorWriter<'a> { + buf: &'a mut [u8], + position: usize, + num_interfaces_mark: Option, + num_endpoints_mark: Option, + write_iads: bool, +} + +impl<'a> DescriptorWriter<'a> { + pub(crate) fn new(buf: &'a mut [u8]) -> Self { + DescriptorWriter { + buf, + position: 0, + num_interfaces_mark: None, + num_endpoints_mark: None, + write_iads: false, + } + } + + pub fn into_buf(self) -> &'a mut [u8] { + &mut self.buf[..self.position] + } + + /// Gets the current position in the buffer, i.e. the number of bytes written so far. + pub fn position(&self) -> usize { + self.position + } + + /// Writes an arbitrary (usually class-specific) descriptor. + pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) -> Result<(), Error> { + let length = descriptor.len(); + + if (self.position + 2 + length) > self.buf.len() || (length + 2) > 255 { + return Err(Error::BufferFull); + } + + self.buf[self.position] = (length + 2) as u8; + self.buf[self.position + 1] = descriptor_type; + + let start = self.position + 2; + + self.buf[start..start + length].copy_from_slice(descriptor); + + self.position = start + length; + + Ok(()) + } + + pub(crate) fn device(&mut self, config: &Config) -> Result<(), Error> { + self.write( + descriptor_type::DEVICE, + &[ + 0x10, + 0x02, // bcdUSB 2.1 + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 + config.vendor_id as u8, + (config.vendor_id >> 8) as u8, // idVendor + config.product_id as u8, + (config.product_id >> 8) as u8, // idProduct + config.device_release as u8, + (config.device_release >> 8) as u8, // bcdDevice + config.manufacturer.map_or(0, |_| 1), // iManufacturer + config.product.map_or(0, |_| 2), // iProduct + config.serial_number.map_or(0, |_| 3), // iSerialNumber + 1, // bNumConfigurations + ], + ) + } + + pub(crate) fn configuration(&mut self, config: &Config) -> Result<(), Error> { + self.num_interfaces_mark = Some(self.position + 4); + + self.write_iads = config.composite_with_iads; + + self.write( + descriptor_type::CONFIGURATION, + &[ + 0, + 0, // wTotalLength + 0, // bNumInterfaces + CONFIGURATION_VALUE, // bConfigurationValue + 0, // iConfiguration + 0x80 | if config.self_powered { 0x40 } else { 0x00 } + | if config.supports_remote_wakeup { + 0x20 + } else { + 0x00 + }, // bmAttributes + (config.max_power / 2) as u8, // bMaxPower + ], + ) + } + + pub(crate) fn end_class(&mut self) { + self.num_endpoints_mark = None; + } + + pub(crate) fn end_configuration(&mut self) { + let position = self.position as u16; + self.buf[2..4].copy_from_slice(&position.to_le_bytes()); + } + + /// Writes a interface association descriptor. Call from `UsbClass::get_configuration_descriptors` + /// before writing the USB class or function's interface descriptors if your class has more than + /// one interface and wants to play nicely with composite devices on Windows. If the USB device + /// hosting the class was not configured as composite with IADs enabled, calling this function + /// does nothing, so it is safe to call from libraries. + /// + /// # Arguments + /// + /// * `first_interface` - Number of the function's first interface, previously allocated with + /// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface). + /// * `interface_count` - Number of interfaces in the function. + /// * `function_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices + /// that do not conform to any class. + /// * `function_sub_class` - Sub-class code. Depends on class. + /// * `function_protocol` - Protocol code. Depends on class and sub-class. + pub fn iad( + &mut self, + first_interface: InterfaceNumber, + interface_count: u8, + function_class: u8, + function_sub_class: u8, + function_protocol: u8, + ) -> Result<(), Error> { + if !self.write_iads { + return Ok(()); + } + + self.write( + descriptor_type::IAD, + &[ + first_interface.into(), // bFirstInterface + interface_count, // bInterfaceCount + function_class, + function_sub_class, + function_protocol, + 0, + ], + )?; + + Ok(()) + } + + /// Writes a interface descriptor. + /// + /// # Arguments + /// + /// * `number` - Interface number previously allocated with + /// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface). + /// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices + /// that do not conform to any class. + /// * `interface_sub_class` - Sub-class code. Depends on class. + /// * `interface_protocol` - Protocol code. Depends on class and sub-class. + pub fn interface( + &mut self, + number: InterfaceNumber, + interface_class: u8, + interface_sub_class: u8, + interface_protocol: u8, + ) -> Result<(), Error> { + self.interface_alt( + number, + DEFAULT_ALTERNATE_SETTING, + interface_class, + interface_sub_class, + interface_protocol, + None, + ) + } + + /// Writes a interface descriptor with a specific alternate setting and + /// interface string identifier. + /// + /// # Arguments + /// + /// * `number` - Interface number previously allocated with + /// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface). + /// * `alternate_setting` - Number of the alternate setting + /// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices + /// that do not conform to any class. + /// * `interface_sub_class` - Sub-class code. Depends on class. + /// * `interface_protocol` - Protocol code. Depends on class and sub-class. + /// * `interface_string` - Index of string descriptor describing this interface + + pub fn interface_alt( + &mut self, + number: InterfaceNumber, + alternate_setting: u8, + interface_class: u8, + interface_sub_class: u8, + interface_protocol: u8, + interface_string: Option, + ) -> Result<(), Error> { + if alternate_setting == DEFAULT_ALTERNATE_SETTING { + match self.num_interfaces_mark { + Some(mark) => self.buf[mark] += 1, + None => return Err(Error::InvalidState), + }; + } + + let str_index = interface_string.map_or(0, Into::into); + + self.num_endpoints_mark = Some(self.position + 4); + + self.write( + descriptor_type::INTERFACE, + &[ + number.into(), // bInterfaceNumber + alternate_setting, // bAlternateSetting + 0, // bNumEndpoints + interface_class, // bInterfaceClass + interface_sub_class, // bInterfaceSubClass + interface_protocol, // bInterfaceProtocol + str_index, // iInterface + ], + )?; + + Ok(()) + } + + /// Writes an endpoint descriptor. + /// + /// # Arguments + /// + /// * `endpoint` - Endpoint previously allocated with + /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). + pub fn endpoint(&mut self, endpoint: &EndpointInfo) -> Result<(), Error> { + match self.num_endpoints_mark { + Some(mark) => self.buf[mark] += 1, + None => return Err(Error::InvalidState), + }; + + self.write( + descriptor_type::ENDPOINT, + &[ + endpoint.addr.into(), // bEndpointAddress + endpoint.ep_type as u8, // bmAttributes + endpoint.max_packet_size as u8, + (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize + endpoint.interval, // bInterval + ], + )?; + + Ok(()) + } + + /// Writes a string descriptor. + pub(crate) fn string(&mut self, string: &str) -> Result<(), Error> { + let mut pos = self.position; + + if pos + 2 > self.buf.len() { + return Err(Error::BufferFull); + } + + self.buf[pos] = 0; // length placeholder + self.buf[pos + 1] = descriptor_type::STRING; + + pos += 2; + + for c in string.encode_utf16() { + if pos >= self.buf.len() { + return Err(Error::BufferFull); + } + + self.buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes()); + pos += 2; + } + + self.buf[self.position] = (pos - self.position) as u8; + + self.position = pos; + + Ok(()) + } +} + +/// A writer for Binary Object Store descriptor. +pub struct BosWriter<'a> { + pub(crate) writer: DescriptorWriter<'a>, + num_caps_mark: Option, +} + +impl<'a> BosWriter<'a> { + pub(crate) fn new(writer: DescriptorWriter<'a>) -> Self { + Self { + writer: writer, + num_caps_mark: None, + } + } + + pub(crate) fn bos(&mut self) -> Result<(), Error> { + self.num_caps_mark = Some(self.writer.position + 4); + self.writer.write( + descriptor_type::BOS, + &[ + 0x00, 0x00, // wTotalLength + 0x00, // bNumDeviceCaps + ], + )?; + + self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4])?; + + Ok(()) + } + + /// Writes capability descriptor to a BOS + /// + /// # Arguments + /// + /// * `capability_type` - Type of a capability + /// * `data` - Binary data of the descriptor + pub fn capability(&mut self, capability_type: u8, data: &[u8]) -> Result<(), Error> { + match self.num_caps_mark { + Some(mark) => self.writer.buf[mark] += 1, + None => return Err(Error::InvalidState), + } + + let mut start = self.writer.position; + let blen = data.len(); + + if (start + blen + 3) > self.writer.buf.len() || (blen + 3) > 255 { + return Err(Error::BufferFull); + } + + self.writer.buf[start] = (blen + 3) as u8; + self.writer.buf[start + 1] = descriptor_type::CAPABILITY; + self.writer.buf[start + 2] = capability_type; + + start += 3; + self.writer.buf[start..start + blen].copy_from_slice(data); + self.writer.position = start + blen; + + Ok(()) + } + + pub(crate) fn end_bos(&mut self) { + self.num_caps_mark = None; + let position = self.writer.position as u16; + self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes()); + } +} diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs new file mode 100644 index 00000000..ed4edb57 --- /dev/null +++ b/embassy-usb/src/driver.rs @@ -0,0 +1,160 @@ +use core::future::Future; + +use super::types::*; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAllocError; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] + +/// Operation is unsupported by the driver. +pub struct Unsupported; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] + +/// Errors returned by [`EndpointIn::write`] +pub enum WriteError { + /// The packet is too long to fit in the + /// transmission buffer. This is generally an error in the class implementation, because the + /// class shouldn't provide more data than the `max_packet_size` it specified when allocating + /// the endpoint. + BufferOverflow, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] + +/// Errors returned by [`EndpointOut::read`] +pub enum ReadError { + /// The received packet is too long to + /// fit in `buf`. This is generally an error in the class implementation, because the class + /// should use a buffer that is large enough for the `max_packet_size` it specified when + /// allocating the endpoint. + BufferOverflow, +} + +/// Driver for a specific USB peripheral. Implement this to add support for a new hardware +/// platform. +pub trait Driver<'a> { + type EndpointOut: EndpointOut + 'a; + type EndpointIn: EndpointIn + 'a; + type Bus: Bus + 'a; + + /// Allocates an endpoint and specified endpoint parameters. This method is called by the device + /// and class implementations to allocate endpoints, and can only be called before + /// [`enable`](UsbBus::enable) is called. + /// + /// # Arguments + /// + /// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should + /// attempt to return an endpoint with the specified address. If None, the implementation + /// should return the next available one. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval` - Polling interval parameter for interrupt endpoints. + fn alloc_endpoint_out( + &mut self, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result; + + fn alloc_endpoint_in( + &mut self, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result; + + /// Enables and initializes the USB peripheral. Soon after enabling the device will be reset, so + /// there is no need to perform a USB reset in this method. + fn enable(self) -> Self::Bus; + + /// Indicates that `set_device_address` must be called before accepting the corresponding + /// control transfer, not after. + /// + /// The default value for this constant is `false`, which corresponds to the USB 2.0 spec, 9.4.6 + const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; +} + +pub trait Bus { + /// Called when the host resets the device. This will be soon called after + /// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Reset`]. This method should + /// reset the state of all endpoints and peripheral flags back to a state suitable for + /// enumeration, as well as ensure that all endpoints previously allocated with alloc_ep are + /// initialized as specified. + fn reset(&mut self); + + /// Sets the device USB address to `addr`. + fn set_device_address(&mut self, addr: u8); + + /// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it + /// should be prepared to receive data again. Only used during control transfers. + fn set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); + + /// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. + fn is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; + + /// Causes the USB peripheral to enter USB suspend mode, lowering power consumption and + /// preparing to detect a USB wakeup event. This will be called after + /// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Suspend`]. The device will + /// continue be polled, and it shall return a value other than `Suspend` from `poll` when it no + /// longer detects the suspend condition. + fn suspend(&mut self); + + /// Resumes from suspend mode. This may only be called after the peripheral has been previously + /// suspended. + fn resume(&mut self); + + /// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the + /// device. + /// + /// The default implementation just returns `Unsupported`. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::UsbError::Unsupported) - This UsbBus implementation doesn't support + /// simulating a disconnect or it has not been enabled at creation time. + fn force_reset(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +pub trait Endpoint { + /// Get the endpoint address + fn info(&self) -> &EndpointInfo; + + /// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it + /// should be prepared to receive data again. + fn set_stalled(&self, stalled: bool); + + /// Gets whether the STALL condition is set for an endpoint. + fn is_stalled(&self) -> bool; + + // TODO enable/disable? +} + +pub trait EndpointOut: Endpoint { + type ReadFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Reads a single packet of data from the endpoint, and returns the actual length of + /// the packet. + /// + /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. + fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; +} + +pub trait EndpointIn: Endpoint { + type WriteFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Writes a single packet of data to the endpoint. + fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a>; +} diff --git a/embassy-usb/src/fmt.rs b/embassy-usb/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-usb/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs new file mode 100644 index 00000000..397db96c --- /dev/null +++ b/embassy-usb/src/lib.rs @@ -0,0 +1,342 @@ +#![no_std] +#![feature(generic_associated_types)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +mod builder; +mod control; +pub mod descriptor; +pub mod driver; +pub mod types; + +use self::control::*; +use self::descriptor::*; +use self::driver::*; +use self::types::*; + +pub use self::builder::Config; +pub use self::builder::UsbDeviceBuilder; + +/// The global state of the USB device. +/// +/// In general class traffic is only possible in the `Configured` state. +#[repr(u8)] +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum UsbDeviceState { + /// The USB device has just been created or reset. + Default, + + /// The USB device has received an address from the host. + Addressed, + + /// The USB device has been configured and is fully functional. + Configured, + + /// The USB device has been suspended by the host or it has been unplugged from the USB bus. + Suspend, +} + +/// The bConfiguration value for the not configured state. +pub const CONFIGURATION_NONE: u8 = 0; + +/// The bConfiguration value for the single configuration supported by this device. +pub const CONFIGURATION_VALUE: u8 = 1; + +/// The default value for bAlternateSetting for all interfaces. +pub const DEFAULT_ALTERNATE_SETTING: u8 = 0; + +pub struct UsbDevice<'d, D: Driver<'d>> { + driver: D::Bus, + control_in: D::EndpointIn, + control_out: D::EndpointOut, + + config: Config<'d>, + device_descriptor: &'d [u8], + config_descriptor: &'d [u8], + bos_descriptor: &'d [u8], + + device_state: UsbDeviceState, + remote_wakeup_enabled: bool, + self_powered: bool, + pending_address: u8, +} + +impl<'d, D: Driver<'d>> UsbDevice<'d, D> { + pub(crate) fn build( + mut driver: D, + config: Config<'d>, + device_descriptor: &'d [u8], + config_descriptor: &'d [u8], + bos_descriptor: &'d [u8], + ) -> Self { + let control_out = driver + .alloc_endpoint_out( + Some(0x00.into()), + EndpointType::Control, + config.max_packet_size_0 as u16, + 0, + ) + .expect("failed to alloc control endpoint"); + + let control_in = driver + .alloc_endpoint_in( + Some(0x80.into()), + EndpointType::Control, + config.max_packet_size_0 as u16, + 0, + ) + .expect("failed to alloc control endpoint"); + + // Enable the USB bus. + // This prevent further allocation by consuming the driver. + let driver = driver.enable(); + + Self { + driver, + config, + control_in, + control_out, + device_descriptor, + config_descriptor, + bos_descriptor, + device_state: UsbDeviceState::Default, + remote_wakeup_enabled: false, + self_powered: false, + pending_address: 0, + } + } + + pub async fn run(&mut self) { + loop { + let mut buf = [0; 8]; + let n = self.control_out.read(&mut buf).await.unwrap(); + assert_eq!(n, 8); + let req = Request::parse(&buf).unwrap(); + info!("setup request: {:x}", req); + + // Now that we have properly parsed the setup packet, ensure the end-point is no longer in + // a stalled state. + self.control_out.set_stalled(false); + + match req.direction { + UsbDirection::In => self.handle_control_in(req).await, + UsbDirection::Out => self.handle_control_out(req).await, + } + } + } + + async fn write_chunked(&mut self, data: &[u8]) -> Result<(), driver::WriteError> { + for c in data.chunks(8) { + self.control_in.write(c).await?; + } + if data.len() % 8 == 0 { + self.control_in.write(&[]).await?; + } + Ok(()) + } + + async fn control_out_accept(&mut self, req: Request) { + info!("control out accept"); + // status phase + // todo: cleanup + self.control_out.read(&mut []).await.unwrap(); + } + + async fn control_in_accept(&mut self, req: Request, data: &[u8]) { + info!("control accept {:x}", data); + + let len = data.len().min(req.length as _); + if let Err(e) = self.write_chunked(&data[..len]).await { + info!("write_chunked failed: {:?}", e); + } + + // status phase + // todo: cleanup + self.control_out.read(&mut []).await.unwrap(); + } + + async fn control_in_accept_writer( + &mut self, + req: Request, + f: impl FnOnce(&mut DescriptorWriter), + ) { + let mut buf = [0; 256]; + let mut w = DescriptorWriter::new(&mut buf); + f(&mut w); + let pos = w.position(); + self.control_in_accept(req, &buf[..pos]).await; + } + + fn control_reject(&mut self, req: Request) { + self.control_out.set_stalled(true); + } + + async fn handle_control_out(&mut self, req: Request) { + // TODO actually read the data if there's an OUT data phase. + + const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; + const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; + const DEFAULT_ALTERNATE_SETTING_U16: u16 = DEFAULT_ALTERNATE_SETTING as u16; + + match req.request_type { + RequestType::Standard => match (req.recipient, req.request, req.value) { + ( + Recipient::Device, + Request::CLEAR_FEATURE, + Request::FEATURE_DEVICE_REMOTE_WAKEUP, + ) => { + self.remote_wakeup_enabled = false; + self.control_out_accept(req).await; + } + + (Recipient::Endpoint, Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + //self.bus.set_stalled(((req.index as u8) & 0x8f).into(), false); + self.control_out_accept(req).await; + } + + ( + Recipient::Device, + Request::SET_FEATURE, + Request::FEATURE_DEVICE_REMOTE_WAKEUP, + ) => { + self.remote_wakeup_enabled = true; + self.control_out_accept(req).await; + } + + (Recipient::Endpoint, Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + //self.bus.set_stalled(((req.index as u8) & 0x8f).into(), true); + self.control_out_accept(req).await; + } + + (Recipient::Device, Request::SET_ADDRESS, 1..=127) => { + self.pending_address = req.value as u8; + + // on NRF the hardware auto-handles SET_ADDRESS. + self.control_out_accept(req).await; + } + + (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { + self.device_state = UsbDeviceState::Configured; + self.control_out_accept(req).await; + } + + (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => { + match self.device_state { + UsbDeviceState::Default => { + self.control_out_accept(req).await; + } + _ => { + self.device_state = UsbDeviceState::Addressed; + self.control_out_accept(req).await; + } + } + } + + (Recipient::Interface, Request::SET_INTERFACE, DEFAULT_ALTERNATE_SETTING_U16) => { + // TODO: do something when alternate settings are implemented + self.control_out_accept(req).await; + } + + _ => self.control_reject(req), + }, + _ => self.control_reject(req), + } + } + + async fn handle_control_in(&mut self, req: Request) { + match req.request_type { + RequestType::Standard => match (req.recipient, req.request) { + (Recipient::Device, Request::GET_STATUS) => { + let mut status: u16 = 0x0000; + if self.self_powered { + status |= 0x0001; + } + if self.remote_wakeup_enabled { + status |= 0x0002; + } + self.control_in_accept(req, &status.to_le_bytes()).await; + } + + (Recipient::Interface, Request::GET_STATUS) => { + let status: u16 = 0x0000; + self.control_in_accept(req, &status.to_le_bytes()).await; + } + + (Recipient::Endpoint, Request::GET_STATUS) => { + let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into(); + let mut status: u16 = 0x0000; + if self.driver.is_stalled(ep_addr) { + status |= 0x0001; + } + self.control_in_accept(req, &status.to_le_bytes()).await; + } + + (Recipient::Device, Request::GET_DESCRIPTOR) => { + self.handle_get_descriptor(req).await; + } + + (Recipient::Device, Request::GET_CONFIGURATION) => { + let status = match self.device_state { + UsbDeviceState::Configured => CONFIGURATION_VALUE, + _ => CONFIGURATION_NONE, + }; + self.control_in_accept(req, &status.to_le_bytes()).await; + } + + (Recipient::Interface, Request::GET_INTERFACE) => { + // TODO: change when alternate settings are implemented + let status = DEFAULT_ALTERNATE_SETTING; + self.control_in_accept(req, &status.to_le_bytes()).await; + } + _ => self.control_reject(req), + }, + _ => self.control_reject(req), + } + } + + async fn handle_get_descriptor(&mut self, req: Request) { + let (dtype, index) = req.descriptor_type_index(); + let config = self.config.clone(); + + match dtype { + descriptor_type::BOS => self.control_in_accept(req, self.bos_descriptor).await, + descriptor_type::DEVICE => self.control_in_accept(req, self.device_descriptor).await, + descriptor_type::CONFIGURATION => { + self.control_in_accept(req, self.config_descriptor).await + } + descriptor_type::STRING => { + if index == 0 { + self.control_in_accept_writer(req, |w| { + w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes()) + .unwrap(); + }) + .await + } else { + let s = match index { + 1 => self.config.manufacturer, + 2 => self.config.product, + 3 => self.config.serial_number, + _ => { + let index = StringIndex::new(index); + let lang_id = req.index; + None + //classes + // .iter() + // .filter_map(|cls| cls.get_string(index, lang_id)) + // .nth(0) + } + }; + + if let Some(s) = s { + self.control_in_accept_writer(req, |w| w.string(s).unwrap()) + .await; + } else { + self.control_reject(req) + } + } + } + _ => self.control_reject(req), + } + } +} diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs new file mode 100644 index 00000000..9d00e46c --- /dev/null +++ b/embassy-usb/src/types.rs @@ -0,0 +1,138 @@ +/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from +/// the perspective of the host, which is backward for devices, but the standard directions are used +/// for consistency. +/// +/// The values of the enum also match the direction bit used in endpoint addresses and control +/// request types. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsbDirection { + /// Host to device (OUT) + Out = 0x00, + /// Device to host (IN) + In = 0x80, +} + +impl From for UsbDirection { + fn from(value: u8) -> Self { + unsafe { core::mem::transmute(value & 0x80) } + } +} + +/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the +/// transfer bmAttributes transfer type bits. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointType { + /// Control endpoint. Used for device management. Only the host can initiate requests. Usually + /// used only endpoint 0. + Control = 0b00, + /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet. + Isochronous = 0b01, + /// Bulk endpoint. Used for large amounts of best-effort reliable data. + Bulk = 0b10, + /// Interrupt endpoint. Used for small amounts of time-critical reliable data. + Interrupt = 0b11, +} + +/// Type-safe endpoint address. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAddress(u8); + +impl From for EndpointAddress { + #[inline] + fn from(addr: u8) -> EndpointAddress { + EndpointAddress(addr) + } +} + +impl From for u8 { + #[inline] + fn from(addr: EndpointAddress) -> u8 { + addr.0 + } +} + +impl EndpointAddress { + const INBITS: u8 = UsbDirection::In as u8; + + /// Constructs a new EndpointAddress with the given index and direction. + #[inline] + pub fn from_parts(index: usize, dir: UsbDirection) -> Self { + EndpointAddress(index as u8 | dir as u8) + } + + /// Gets the direction part of the address. + #[inline] + pub fn direction(&self) -> UsbDirection { + if (self.0 & Self::INBITS) != 0 { + UsbDirection::In + } else { + UsbDirection::Out + } + } + + /// Returns true if the direction is IN, otherwise false. + #[inline] + pub fn is_in(&self) -> bool { + (self.0 & Self::INBITS) != 0 + } + + /// Returns true if the direction is OUT, otherwise false. + #[inline] + pub fn is_out(&self) -> bool { + (self.0 & Self::INBITS) == 0 + } + + /// Gets the index part of the endpoint address. + #[inline] + pub fn index(&self) -> usize { + (self.0 & !Self::INBITS) as usize + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointInfo { + pub addr: EndpointAddress, + pub ep_type: EndpointType, + pub max_packet_size: u16, + pub interval: u8, +} + +/// A handle for a USB interface that contains its number. +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InterfaceNumber(u8); + +impl InterfaceNumber { + pub(crate) fn new(index: u8) -> InterfaceNumber { + InterfaceNumber(index) + } +} + +impl From for u8 { + fn from(n: InterfaceNumber) -> u8 { + n.0 + } +} + +/// A handle for a USB string descriptor that contains its index. +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct StringIndex(u8); + +impl StringIndex { + pub(crate) fn new(index: u8) -> StringIndex { + StringIndex(index) + } +} + +impl From for u8 { + fn from(i: StringIndex) -> u8 { + i.0 + } +} diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index a704eb3b..fb846b3a 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -10,7 +10,8 @@ nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"] [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" defmt-rtt = "0.3" @@ -22,5 +23,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" -usb-device = "0.2" -usbd-serial = "0.1.1" diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs new file mode 100644 index 00000000..345d0038 --- /dev/null +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -0,0 +1,356 @@ +use core::convert::TryInto; +use core::mem; +use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; +use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; + +/// This should be used as `device_class` when building the `UsbDevice`. +pub const USB_CLASS_CDC: u8 = 0x02; + +const USB_CLASS_CDC_DATA: u8 = 0x0a; +const CDC_SUBCLASS_ACM: u8 = 0x02; +const CDC_PROTOCOL_NONE: u8 = 0x00; + +const CS_INTERFACE: u8 = 0x24; +const CDC_TYPE_HEADER: u8 = 0x00; +const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; +const CDC_TYPE_ACM: u8 = 0x02; +const CDC_TYPE_UNION: u8 = 0x06; + +const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +#[allow(unused)] +const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; +const REQ_SET_LINE_CODING: u8 = 0x20; +const REQ_GET_LINE_CODING: u8 = 0x21; +const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; + +/// Packet level implementation of a CDC-ACM serial port. +/// +/// This class can be used directly and it has the least overhead due to directly reading and +/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial +/// port. The following constraints must be followed if you use this class directly: +/// +/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the +/// method will return a `WouldBlock` error if there is no packet to be read. +/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the +/// method will return a `WouldBlock` error if the previous packet has not been sent yet. +/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the +/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) +/// can be sent if there is no other data to send. This is because USB bulk transactions must be +/// terminated with a short packet, even if the bulk endpoint is used for stream-like data. +pub struct CdcAcmClass<'d, D: Driver<'d>> { + comm_if: InterfaceNumber, + comm_ep: D::EndpointIn, + data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + line_coding: LineCoding, + dtr: bool, + rts: bool, +} + +impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { + /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For + /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. + pub fn new(builder: &mut UsbDeviceBuilder<'d, D>, max_packet_size: u16) -> Self { + let comm_if = builder.alloc_interface(); + let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); + let data_if = builder.alloc_interface(); + let read_ep = builder.alloc_bulk_endpoint_out(max_packet_size); + let write_ep = builder.alloc_bulk_endpoint_in(max_packet_size); + + builder + .config_descriptor + .iad( + comm_if, + 2, + USB_CLASS_CDC, + CDC_SUBCLASS_ACM, + CDC_PROTOCOL_NONE, + ) + .unwrap(); + + builder + .config_descriptor + .interface(comm_if, USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE) + .unwrap(); + + builder + .config_descriptor + .write( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ) + .unwrap(); + + builder + .config_descriptor + .write( + CS_INTERFACE, + &[ + CDC_TYPE_ACM, // bDescriptorSubtype + 0x00, // bmCapabilities + ], + ) + .unwrap(); + + builder + .config_descriptor + .write( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + data_if.into(), // bSubordinateInterface + ], + ) + .unwrap(); + + builder + .config_descriptor + .write( + CS_INTERFACE, + &[ + CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype + 0x00, // bmCapabilities + data_if.into(), // bDataInterface + ], + ) + .unwrap(); + + builder.config_descriptor.endpoint(comm_ep.info()).unwrap(); + + builder + .config_descriptor + .interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00) + .unwrap(); + + builder.config_descriptor.endpoint(write_ep.info()).unwrap(); + builder.config_descriptor.endpoint(read_ep.info()).unwrap(); + + CdcAcmClass { + comm_if, + comm_ep, + data_if, + read_ep, + write_ep, + line_coding: LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + }, + dtr: false, + rts: false, + } + } + + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> &LineCoding { + &self.line_coding + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.dtr + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.rts + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), WriteError> { + self.write_ep.write(data).await + } + + /// Reads a single packet from the OUT endpoint. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Gets the address of the IN endpoint. + pub(crate) fn write_ep_address(&self) -> EndpointAddress { + self.write_ep.info().addr + } +} + +/* +impl UsbClass for CdcAcmClass<'_, B> { + fn get_configuration_descriptors(&self, builder.config_descriptor: &mut Descriptorbuilder.config_descriptor) -> Result<()> { + + Ok(()) + } + + fn reset(&mut self) { + self.line_coding = LineCoding::default(); + self.dtr = false; + self.rts = false; + } + + fn control_in(&mut self, xfer: ControlIn) { + let req = xfer.request(); + + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return; + } + + match req.request { + // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. + REQ_GET_LINE_CODING if req.length == 7 => { + xfer.accept(|data| { + data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes()); + data[4] = self.line_coding.stop_bits as u8; + data[5] = self.line_coding.parity_type as u8; + data[6] = self.line_coding.data_bits; + + Ok(7) + }) + .ok(); + } + _ => { + xfer.reject().ok(); + } + } + } + + fn control_out(&mut self, xfer: ControlOut) { + let req = xfer.request(); + + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return; + } + + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + xfer.accept().ok(); + } + REQ_SET_LINE_CODING if xfer.data().len() >= 7 => { + self.line_coding.data_rate = + u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap()); + self.line_coding.stop_bits = xfer.data()[4].into(); + self.line_coding.parity_type = xfer.data()[5].into(); + self.line_coding.data_bits = xfer.data()[6]; + + xfer.accept().ok(); + } + REQ_SET_CONTROL_LINE_STATE => { + self.dtr = (req.value & 0x0001) != 0; + self.rts = (req.value & 0x0002) != 0; + + xfer.accept().ok(); + } + _ => { + xfer.reject().ok(); + } + }; + } +} + + */ + +/// Number of stop bits for LineCoding +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum StopBits { + /// 1 stop bit + One = 0, + + /// 1.5 stop bits + OnePointFive = 1, + + /// 2 stop bits + Two = 2, +} + +impl From for StopBits { + fn from(value: u8) -> Self { + if value <= 2 { + unsafe { mem::transmute(value) } + } else { + StopBits::One + } + } +} + +/// Parity for LineCoding +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum ParityType { + None = 0, + Odd = 1, + Event = 2, + Mark = 3, + Space = 4, +} + +impl From for ParityType { + fn from(value: u8) -> Self { + if value <= 4 { + unsafe { mem::transmute(value) } + } else { + ParityType::None + } + } +} + +/// Line coding parameters +/// +/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can +/// be ignored if you don't plan to interface with a physical UART. +pub struct LineCoding { + stop_bits: StopBits, + data_bits: u8, + parity_type: ParityType, + data_rate: u32, +} + +impl LineCoding { + /// Gets the number of stop bits for UART communication. + pub fn stop_bits(&self) -> StopBits { + self.stop_bits + } + + /// Gets the number of data bits for UART communication. + pub fn data_bits(&self) -> u8 { + self.data_bits + } + + /// Gets the parity type for UART communication. + pub fn parity_type(&self) -> ParityType { + self.parity_type + } + + /// Gets the data rate in bits per second for UART communication. + pub fn data_rate(&self) -> u32 { + self.data_rate + } +} + +impl Default for LineCoding { + fn default() -> Self { + LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + } + } +} diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs new file mode 100644 index 00000000..21ca2ba4 --- /dev/null +++ b/examples/nrf/src/bin/usb/main.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../../example_common.rs"] +mod example_common; + +mod cdc_acm; + +use core::mem; +use defmt::*; +use embassy::executor::Spawner; +use embassy_nrf::interrupt; +use embassy_nrf::pac; +use embassy_nrf::usb::{self, Driver}; +use embassy_nrf::Peripherals; +use embassy_usb::{Config, UsbDeviceBuilder}; + +use crate::cdc_acm::CdcAcmClass; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + let power: pac::POWER = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + info!("Waiting for vbus..."); + while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} + info!("vbus OK"); + + let irq = interrupt::take!(USBD); + let driver = Driver::new(p.USBD, irq); + let config = Config::new(0xc0de, 0xcafe); + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + + let mut builder = UsbDeviceBuilder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + ); + + let mut class = CdcAcmClass::new(&mut builder, 64); + + let mut usb = builder.build(); + usb.run().await; +} diff --git a/examples/nrf/src/bin/usb_uart.rs b/examples/nrf/src/bin/usb_uart.rs deleted file mode 100644 index d283dccd..00000000 --- a/examples/nrf/src/bin/usb_uart.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![no_std] -#![no_main] -#![feature(type_alias_impl_trait)] - -use defmt::{info, unwrap}; -use embassy::executor::Spawner; -use embassy::interrupt::InterruptExt; -use embassy::io::{AsyncBufReadExt, AsyncWriteExt}; -use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial}; -use embassy_nrf::{interrupt, Peripherals}; -use futures::pin_mut; -use usb_device::device::{UsbDeviceBuilder, UsbVidPid}; - -use defmt_rtt as _; // global logger -use panic_probe as _; // print out panic messages - -#[embassy::main] -async fn main(_spawner: Spawner, p: Peripherals) { - let mut rx_buffer = [0u8; 64]; - // we send back input + cr + lf - let mut tx_buffer = [0u8; 66]; - - let usb_bus = UsbBus::new(p.USBD); - - let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer); - - let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) - .manufacturer("Fake company") - .product("Serial port") - .serial_number("TEST") - .device_class(0x02) - .build(); - - let irq = interrupt::take!(USBD); - irq.set_priority(interrupt::Priority::P3); - - let mut state = State::new(); - let usb = unsafe { Usb::new(&mut state, device, serial, irq) }; - pin_mut!(usb); - - let (mut reader, mut writer) = usb.as_ref().take_serial_0(); - - info!("usb initialized!"); - - unwrap!( - writer - .write_all(b"\r\nInput returned upper cased on CR+LF\r\n") - .await - ); - - let mut buf = [0u8; 64]; - loop { - let mut n = 0; - - async { - loop { - let char = unwrap!(reader.read_byte().await); - - // throw away, read more on cr, exit on lf - if char == b'\r' { - continue; - } else if char == b'\n' { - break; - } - - buf[n] = char; - n += 1; - - // stop if we're out of room - if n == buf.len() { - break; - } - } - } - .await; - - if n > 0 { - for char in buf[..n].iter_mut() { - // upper case - if 0x61 <= *char && *char <= 0x7a { - *char &= !0x20; - } - } - unwrap!(writer.write_all(&buf[..n]).await); - unwrap!(writer.write_all(b"\r\n").await); - unwrap!(writer.flush().await); - } - } -} diff --git a/examples/nrf/src/bin/usb_uart_io.rs b/examples/nrf/src/bin/usb_uart_io.rs deleted file mode 100644 index ef262984..00000000 --- a/examples/nrf/src/bin/usb_uart_io.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![no_std] -#![no_main] -#![feature(type_alias_impl_trait)] - -use defmt::{info, unwrap}; -use embassy::executor::Spawner; -use embassy::interrupt::InterruptExt; -use embassy::io::{read_line, AsyncWriteExt}; -use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial}; -use embassy_nrf::{interrupt, Peripherals}; -use futures::pin_mut; -use usb_device::device::{UsbDeviceBuilder, UsbVidPid}; - -use defmt_rtt as _; // global logger -use panic_probe as _; // print out panic messages - -#[embassy::main] -async fn main(_spawner: Spawner, p: Peripherals) { - let mut rx_buffer = [0u8; 64]; - // we send back input + cr + lf - let mut tx_buffer = [0u8; 66]; - - let usb_bus = UsbBus::new(p.USBD); - - let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer); - - let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) - .manufacturer("Fake company") - .product("Serial port") - .serial_number("TEST") - .device_class(0x02) - .build(); - - let irq = interrupt::take!(USBD); - irq.set_priority(interrupt::Priority::P3); - - let mut state = State::new(); - let usb = unsafe { Usb::new(&mut state, device, serial, irq) }; - pin_mut!(usb); - - let (mut reader, mut writer) = usb.as_ref().take_serial_0(); - - info!("usb initialized!"); - - unwrap!( - writer - .write_all(b"\r\nInput returned upper cased on CR+LF\r\n") - .await - ); - - let mut buf = [0u8; 64]; - loop { - let n = unwrap!(read_line(&mut reader, &mut buf).await); - - for char in buf[..n].iter_mut() { - // upper case - if 0x61 <= *char && *char <= 0x7a { - *char &= !0x20; - } - } - - unwrap!(writer.write_all(&buf[..n]).await); - unwrap!(writer.write_all(b"\r\n").await); - unwrap!(writer.flush().await); - } -} From 77ceced036d574c7d67259b85e1d61b96e82d0d3 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 9 Mar 2022 23:06:27 +0100 Subject: [PATCH 02/50] Working CDC-ACM host->device --- embassy-nrf/src/usb.rs | 213 +++++++++++++++++++++++----- embassy-usb/Cargo.toml | 2 +- embassy-usb/src/driver.rs | 87 +++++++----- embassy-usb/src/lib.rs | 60 ++++++-- embassy-usb/src/util.rs | 45 ++++++ examples/nrf/src/bin/usb/cdc_acm.rs | 17 +-- examples/nrf/src/bin/usb/main.rs | 15 +- 7 files changed, 345 insertions(+), 94 deletions(-) create mode 100644 embassy-usb/src/util.rs diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 0f7d68d8..163b2c79 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -8,7 +8,7 @@ use embassy::time::{with_timeout, Duration}; use embassy::util::Unborrow; use embassy::waitqueue::AtomicWaker; use embassy_hal_common::unborrow; -use embassy_usb::driver::{self, ReadError, WriteError}; +use embassy_usb::driver::{self, Event, ReadError, WriteError}; use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; use futures::future::poll_fn; use futures::Future; @@ -19,7 +19,10 @@ pub use embassy_usb; use crate::interrupt::Interrupt; use crate::pac; -static EP0_WAKER: AtomicWaker = AtomicWaker::new(); +const NEW_AW: AtomicWaker = AtomicWaker::new(); +static BUS_WAKER: AtomicWaker = NEW_AW; +static EP_IN_WAKERS: [AtomicWaker; 9] = [NEW_AW; 9]; +static EP_OUT_WAKERS: [AtomicWaker; 9] = [NEW_AW; 9]; pub struct Driver<'d, T: Instance> { phantom: PhantomData<&'d mut T>, @@ -47,13 +50,48 @@ impl<'d, T: Instance> Driver<'d, T> { fn on_interrupt(_: *mut ()) { let regs = T::regs(); + if regs.events_usbreset.read().bits() != 0 { + regs.intenclr.write(|w| w.usbreset().clear()); + BUS_WAKER.wake(); + } + if regs.events_ep0setup.read().bits() != 0 { regs.intenclr.write(|w| w.ep0setup().clear()); - EP0_WAKER.wake(); + EP_OUT_WAKERS[0].wake(); } + if regs.events_ep0datadone.read().bits() != 0 { regs.intenclr.write(|w| w.ep0datadone().clear()); - EP0_WAKER.wake(); + EP_IN_WAKERS[0].wake(); + } + + // USBEVENT and EPDATA events are weird. They're the "aggregate" + // of individual bits in EVENTCAUSE and EPDATASTATUS. We handle them + // differently than events normally. + // + // They seem to be edge-triggered, not level-triggered: when an + // individual bit goes 0->1, the event fires *just once*. + // Therefore, it's fine to clear just the event, and let main thread + // check the individual bits in EVENTCAUSE and EPDATASTATUS. It + // doesn't cause an infinite irq loop. + if regs.events_usbevent.read().bits() != 0 { + regs.events_usbevent.reset(); + //regs.intenclr.write(|w| w.usbevent().clear()); + BUS_WAKER.wake(); + } + + if regs.events_epdata.read().bits() != 0 { + regs.events_epdata.reset(); + + let r = regs.epdatastatus.read().bits(); + for i in 1..=7 { + if r & (1 << i) != 0 { + EP_IN_WAKERS[i].wake(); + } + if r & (1 << (i + 16)) != 0 { + EP_OUT_WAKERS[i].wake(); + } + } } } @@ -153,6 +191,12 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { unsafe { NVIC::unmask(pac::Interrupt::USBD) }; + regs.intenset.write(|w| { + w.usbreset().set_bit(); + w.usbevent().set_bit(); + w.epdata().set_bit(); + w + }); // Enable the USB pullup, allowing enumeration. regs.usbpullup.write(|w| w.connect().enabled()); info!("enabled"); @@ -172,6 +216,49 @@ pub struct Bus<'d, T: Instance> { } impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + type PollFuture<'a> + where + Self: 'a, + = impl Future + 'a; + + fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + let regs = T::regs(); + + if regs.events_usbreset.read().bits() != 0 { + regs.events_usbreset.reset(); + regs.intenset.write(|w| w.usbreset().set()); + return Poll::Ready(Event::Reset); + } + + let r = regs.eventcause.read(); + + if r.isooutcrc().bit() { + regs.eventcause.write(|w| w.isooutcrc().set_bit()); + info!("USB event: isooutcrc"); + } + if r.usbwuallowed().bit() { + regs.eventcause.write(|w| w.usbwuallowed().set_bit()); + info!("USB event: usbwuallowed"); + } + if r.suspend().bit() { + regs.eventcause.write(|w| w.suspend().set_bit()); + info!("USB event: suspend"); + } + if r.resume().bit() { + regs.eventcause.write(|w| w.resume().set_bit()); + info!("USB event: resume"); + } + if r.ready().bit() { + regs.eventcause.write(|w| w.ready().set_bit()); + info!("USB event: ready"); + } + + Poll::Pending + }) + } + #[inline] fn reset(&mut self) { let regs = T::regs(); @@ -260,40 +347,95 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { async move { let regs = T::regs(); + let i = self.info.addr.index(); - if buf.len() == 0 { - regs.tasks_ep0status.write(|w| unsafe { w.bits(1) }); - return Ok(0); - } - - // Wait for SETUP packet - regs.events_ep0setup.reset(); - regs.intenset.write(|w| w.ep0setup().set()); - poll_fn(|cx| { - EP0_WAKER.register(cx.waker()); - if regs.events_ep0setup.read().bits() != 0 { - Poll::Ready(()) - } else { - Poll::Pending + if i == 0 { + if buf.len() == 0 { + regs.tasks_ep0status.write(|w| unsafe { w.bits(1) }); + return Ok(0); } - }) - .await; - info!("got SETUP"); - if buf.len() < 8 { - return Err(ReadError::BufferOverflow); + // Wait for SETUP packet + regs.events_ep0setup.reset(); + regs.intenset.write(|w| w.ep0setup().set()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0setup.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + info!("got SETUP"); + + if buf.len() < 8 { + return Err(ReadError::BufferOverflow); + } + + 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(); + + Ok(8) + } else { + poll_fn(|cx| { + EP_OUT_WAKERS[i].register(cx.waker()); + let regs = T::regs(); + let r = regs.epdatastatus.read().bits(); + if r & (1 << (i + 16)) != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // Clear status + regs.epdatastatus + .write(|w| unsafe { w.bits(1 << (i + 16)) }); + + // Check that the packet fits into the buffer + let size = regs.size.epout[i].read().bits(); + if size as usize > buf.len() { + return Err(ReadError::BufferOverflow); + } + + 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(); + + Ok(size as usize) } - - 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(); - - Ok(8) } } } @@ -331,7 +473,8 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { let res = with_timeout( Duration::from_millis(10), poll_fn(|cx| { - EP0_WAKER.register(cx.waker()); + EP_IN_WAKERS[0].register(cx.waker()); + let regs = T::regs(); if regs.events_ep0datadone.read().bits() != 0 { Poll::Ready(()) } else { diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index dfdc8fba..5a5a6d7a 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "embassy-usb" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] embassy = { version = "0.1.0", path = "../embassy" } diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index ed4edb57..a7b16efa 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -2,40 +2,6 @@ use core::future::Future; use super::types::*; -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EndpointAllocError; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] - -/// Operation is unsupported by the driver. -pub struct Unsupported; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] - -/// Errors returned by [`EndpointIn::write`] -pub enum WriteError { - /// The packet is too long to fit in the - /// transmission buffer. This is generally an error in the class implementation, because the - /// class shouldn't provide more data than the `max_packet_size` it specified when allocating - /// the endpoint. - BufferOverflow, -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] - -/// Errors returned by [`EndpointOut::read`] -pub enum ReadError { - /// The received packet is too long to - /// fit in `buf`. This is generally an error in the class implementation, because the class - /// should use a buffer that is large enough for the `max_packet_size` it specified when - /// allocating the endpoint. - BufferOverflow, -} - /// Driver for a specific USB peripheral. Implement this to add support for a new hardware /// platform. pub trait Driver<'a> { @@ -82,6 +48,12 @@ pub trait Driver<'a> { } pub trait Bus { + type PollFuture<'a>: Future + 'a + where + Self: 'a; + + fn poll<'a>(&'a mut self) -> Self::PollFuture<'a>; + /// Called when the host resets the device. This will be soon called after /// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Reset`]. This method should /// reset the state of all endpoints and peripheral flags back to a state suitable for @@ -158,3 +130,50 @@ pub trait EndpointIn: Endpoint { /// Writes a single packet of data to the endpoint. fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a>; } + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Event returned by [`Bus::poll`]. +pub enum Event { + /// The USB reset condition has been detected. + Reset, + + /// A USB suspend request has been detected or, in the case of self-powered devices, the device + /// has been disconnected from the USB bus. + Suspend, + + /// A USB resume request has been detected after being suspended or, in the case of self-powered + /// devices, the device has been connected to the USB bus. + Resume, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAllocError; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Operation is unsupported by the driver. +pub struct Unsupported; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Errors returned by [`EndpointIn::write`] +pub enum WriteError { + /// The packet is too long to fit in the + /// transmission buffer. This is generally an error in the class implementation, because the + /// class shouldn't provide more data than the `max_packet_size` it specified when allocating + /// the endpoint. + BufferOverflow, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Errors returned by [`EndpointOut::read`] +pub enum ReadError { + /// The received packet is too long to + /// fit in `buf`. This is generally an error in the class implementation, because the class + /// should use a buffer that is large enough for the `max_packet_size` it specified when + /// allocating the endpoint. + BufferOverflow, +} diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 397db96c..33f3d471 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -9,11 +9,13 @@ mod control; pub mod descriptor; pub mod driver; pub mod types; +mod util; use self::control::*; use self::descriptor::*; use self::driver::*; use self::types::*; +use self::util::*; pub use self::builder::Config; pub use self::builder::UsbDeviceBuilder; @@ -47,7 +49,7 @@ pub const CONFIGURATION_VALUE: u8 = 1; pub const DEFAULT_ALTERNATE_SETTING: u8 = 0; pub struct UsbDevice<'d, D: Driver<'d>> { - driver: D::Bus, + bus: D::Bus, control_in: D::EndpointIn, control_out: D::EndpointOut, @@ -93,7 +95,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { let driver = driver.enable(); Self { - driver, + bus: driver, config, control_in, control_out, @@ -108,20 +110,47 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } pub async fn run(&mut self) { + let mut buf = [0; 8]; + loop { - let mut buf = [0; 8]; - let n = self.control_out.read(&mut buf).await.unwrap(); - assert_eq!(n, 8); - let req = Request::parse(&buf).unwrap(); - info!("setup request: {:x}", req); + let control_fut = self.control_out.read(&mut buf); + let bus_fut = self.bus.poll(); + match select(bus_fut, control_fut).await { + Either::Left(evt) => match evt { + Event::Reset => { + self.bus.reset(); - // Now that we have properly parsed the setup packet, ensure the end-point is no longer in - // a stalled state. - self.control_out.set_stalled(false); + self.device_state = UsbDeviceState::Default; + self.remote_wakeup_enabled = false; + self.pending_address = 0; - match req.direction { - UsbDirection::In => self.handle_control_in(req).await, - UsbDirection::Out => self.handle_control_out(req).await, + // TODO + //self.control.reset(); + //for cls in classes { + // cls.reset(); + //} + } + Event::Resume => {} + Event::Suspend => { + self.bus.suspend(); + self.device_state = UsbDeviceState::Suspend; + } + }, + Either::Right(n) => { + let n = n.unwrap(); + assert_eq!(n, 8); + let req = Request::parse(&buf).unwrap(); + info!("control request: {:x}", req); + + // Now that we have properly parsed the setup packet, ensure the end-point is no longer in + // a stalled state. + self.control_out.set_stalled(false); + + match req.direction { + UsbDirection::In => self.handle_control_in(req).await, + UsbDirection::Out => self.handle_control_out(req).await, + } + } } } } @@ -205,7 +234,8 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } (Recipient::Endpoint, Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { - //self.bus.set_stalled(((req.index as u8) & 0x8f).into(), true); + self.bus + .set_stalled(((req.index as u8) & 0x8f).into(), true); self.control_out_accept(req).await; } @@ -266,7 +296,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { (Recipient::Endpoint, Request::GET_STATUS) => { let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into(); let mut status: u16 = 0x0000; - if self.driver.is_stalled(ep_addr) { + if self.bus.is_stalled(ep_addr) { status |= 0x0001; } self.control_in_accept(req, &status.to_le_bytes()).await; diff --git a/embassy-usb/src/util.rs b/embassy-usb/src/util.rs new file mode 100644 index 00000000..18cc875c --- /dev/null +++ b/embassy-usb/src/util.rs @@ -0,0 +1,45 @@ +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +#[derive(Debug, Clone)] +pub enum Either { + Left(A), + Right(B), +} + +pub fn select(a: A, b: B) -> Select +where + A: Future, + B: Future, +{ + Select { a, b } +} + +pub struct Select { + a: A, + b: B, +} + +impl Unpin for Select {} + +impl Future for Select +where + A: Future, + B: Future, +{ + type Output = Either; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + match a.poll(cx) { + Poll::Ready(x) => Poll::Ready(Either::Left(x)), + Poll::Pending => match b.poll(cx) { + Poll::Ready(x) => Poll::Ready(Either::Right(x)), + Poll::Pending => Poll::Pending, + }, + } + } +} diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 345d0038..b7c112ae 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -38,14 +38,15 @@ const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; /// can be sent if there is no other data to send. This is because USB bulk transactions must be /// terminated with a short packet, even if the bulk endpoint is used for stream-like data. pub struct CdcAcmClass<'d, D: Driver<'d>> { - comm_if: InterfaceNumber, - comm_ep: D::EndpointIn, - data_if: InterfaceNumber, - read_ep: D::EndpointOut, - write_ep: D::EndpointIn, - line_coding: LineCoding, - dtr: bool, - rts: bool, + // TODO not pub + pub comm_if: InterfaceNumber, + pub comm_ep: D::EndpointIn, + pub data_if: InterfaceNumber, + pub read_ep: D::EndpointOut, + pub write_ep: D::EndpointIn, + pub line_coding: LineCoding, + pub dtr: bool, + pub rts: bool, } impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 21ca2ba4..d175766b 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -14,7 +14,9 @@ use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::{self, Driver}; use embassy_nrf::Peripherals; +use embassy_usb::driver::EndpointOut; use embassy_usb::{Config, UsbDeviceBuilder}; +use futures::future::{join, select}; use crate::cdc_acm::CdcAcmClass; @@ -49,5 +51,16 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, 64); let mut usb = builder.build(); - usb.run().await; + + let fut1 = usb.run(); + let fut2 = async { + let mut buf = [0; 64]; + loop { + let n = class.read_ep.read(&mut buf).await.unwrap(); + let data = &buf[..n]; + info!("data: {:x}", data); + } + }; + + join(fut1, fut2).await; } From 0320500f0f14d03aecfe3ee7482a5cf76ec8844c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 10 Mar 2022 01:05:33 +0100 Subject: [PATCH 03/50] Working CDC-ACM device->host --- embassy-nrf/src/usb.rs | 158 +++++++++++++++++++------------ embassy-usb/src/lib.rs | 1 + examples/nrf/Cargo.toml | 2 +- examples/nrf/src/bin/usb/main.rs | 18 +++- 4 files changed, 116 insertions(+), 63 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 163b2c79..874bfe84 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -1,7 +1,8 @@ #![macro_use] use core::marker::PhantomData; -use core::sync::atomic::{compiler_fence, Ordering}; +use core::mem::MaybeUninit; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use core::task::Poll; use embassy::interrupt::InterruptExt; use embassy::time::{with_timeout, Duration}; @@ -23,6 +24,7 @@ const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; static EP_IN_WAKERS: [AtomicWaker; 9] = [NEW_AW; 9]; static EP_OUT_WAKERS: [AtomicWaker; 9] = [NEW_AW; 9]; +static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); pub struct Driver<'d, T: Instance> { phantom: PhantomData<&'d mut T>, @@ -84,6 +86,8 @@ impl<'d, T: Instance> Driver<'d, T> { regs.events_epdata.reset(); let r = regs.epdatastatus.read().bits(); + regs.epdatastatus.write(|w| unsafe { w.bits(r) }); + READY_ENDPOINTS.fetch_or(r, Ordering::AcqRel); for i in 1..=7 { if r & (1 << i) != 0 { EP_IN_WAKERS[i].wake(); @@ -143,15 +147,12 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { .alloc_in .allocate(ep_addr, ep_type, max_packet_size, interval)?; let ep_addr = EndpointAddress::from_parts(index, UsbDirection::In); - Ok(Endpoint { - _phantom: PhantomData, - info: EndpointInfo { - addr: ep_addr, - ep_type, - max_packet_size, - interval, - }, - }) + Ok(Endpoint::new(EndpointInfo { + addr: ep_addr, + ep_type, + max_packet_size, + interval, + })) } fn alloc_endpoint_out( @@ -165,15 +166,12 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { .alloc_out .allocate(ep_addr, ep_type, max_packet_size, interval)?; let ep_addr = EndpointAddress::from_parts(index, UsbDirection::Out); - Ok(Endpoint { - _phantom: PhantomData, - info: EndpointInfo { - addr: ep_addr, - ep_type, - max_packet_size, - interval, - }, - }) + Ok(Endpoint::new(EndpointInfo { + addr: ep_addr, + ep_type, + max_packet_size, + interval, + })) } fn enable(self) -> Self::Bus { @@ -284,7 +282,9 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { } } - //self.busy_in_endpoints = 0; + // IN endpoints (low bits) default to ready. + // OUT endpoints (high bits) default to NOT ready, they become ready when data comes in. + READY_ENDPOINTS.store(0x0000FFFF, Ordering::Release); } #[inline] @@ -324,6 +324,15 @@ pub struct Endpoint<'d, T: Instance, Dir> { info: EndpointInfo, } +impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> { + fn new(info: EndpointInfo) -> Self { + Self { + info, + _phantom: PhantomData, + } + } +} + impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> { fn info(&self) -> &EndpointInfo { &self.info @@ -368,7 +377,6 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { } }) .await; - info!("got SETUP"); if buf.len() < 8 { return Err(ReadError::BufferOverflow); @@ -385,10 +393,10 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { Ok(8) } else { + // Wait until ready poll_fn(|cx| { EP_OUT_WAKERS[i].register(cx.waker()); - let regs = T::regs(); - let r = regs.epdatastatus.read().bits(); + let r = READY_ENDPOINTS.load(Ordering::Acquire); if r & (1 << (i + 16)) != 0 { Poll::Ready(()) } else { @@ -397,9 +405,8 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { }) .await; - // Clear status - regs.epdatastatus - .write(|w| unsafe { w.bits(1 << (i + 16)) }); + // Mark as not ready + READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); // Check that the packet fits into the buffer let size = regs.size.epout[i].read().bits(); @@ -448,48 +455,83 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { async move { - info!("write: {:x}", buf); - let regs = T::regs(); + let i = self.info.addr.index(); - let ptr = buf.as_ptr() as u32; - let len = buf.len() as u32; - regs.epin0.ptr.write(|w| unsafe { w.bits(ptr) }); - regs.epin0.maxcnt.write(|w| unsafe { w.bits(len) }); - - regs.events_ep0datadone.reset(); - regs.events_endepin[0].reset(); - - dma_start(); - - regs.tasks_startepin[0].write(|w| unsafe { w.bits(1) }); - info!("write: waiting for endepin..."); - while regs.events_endepin[0].read().bits() == 0 {} - - dma_end(); - - info!("write: waiting for ep0datadone..."); - regs.intenset.write(|w| w.ep0datadone().set()); - let res = with_timeout( - Duration::from_millis(10), + // Wait until ready. + if i != 0 { poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { + EP_IN_WAKERS[i].register(cx.waker()); + let r = READY_ENDPOINTS.load(Ordering::Acquire); + if r & (1 << i) != 0 { Poll::Ready(()) } else { Poll::Pending } - }), - ) - .await; + }) + .await; - if res.is_err() { - // todo wrong error - return Err(driver::WriteError::BufferOverflow); + // Mark as not ready + READY_ENDPOINTS.fetch_and(!(1 << i), Ordering::AcqRel); } - info!("write done"); + if i == 0 { + regs.events_ep0datadone.reset(); + } + + assert!(buf.len() <= 64); + + // EasyDMA can't read FLASH, so we copy through RAM + let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); + let ptr = ram_buf.as_mut_ptr() as *mut u8; + unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()) }; + + 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].ptr.write(|w| w.bits(ptr as u32)); + epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8)); + } + + regs.events_endepin[i].reset(); + + dma_start(); + regs.tasks_startepin[i].write(|w| unsafe { w.bits(1) }); + while regs.events_endepin[i].read().bits() == 0 {} + dma_end(); + + if i == 0 { + regs.intenset.write(|w| w.ep0datadone().set()); + let res = with_timeout( + Duration::from_millis(10), + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }), + ) + .await; + + if res.is_err() { + // todo wrong error + return Err(driver::WriteError::BufferOverflow); + } + } Ok(()) } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 33f3d471..95f78804 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -198,6 +198,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } fn control_reject(&mut self, req: Request) { + info!("control reject"); self.control_out.set_stalled(true); } diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index fb846b3a..59e5de02 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Dario Nieuwenhuis "] -edition = "2018" +edition = "2021" name = "embassy-nrf-examples" version = "0.1.0" diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index d175766b..014ad5c6 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -10,13 +10,14 @@ mod cdc_acm; use core::mem; use defmt::*; use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; use embassy_nrf::interrupt; use embassy_nrf::pac; -use embassy_nrf::usb::{self, Driver}; +use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; -use embassy_usb::driver::EndpointOut; +use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; -use futures::future::{join, select}; +use futures::future::join3; use crate::cdc_acm::CdcAcmClass; @@ -61,6 +62,15 @@ async fn main(_spawner: Spawner, p: Peripherals) { info!("data: {:x}", data); } }; + let fut3 = async { + loop { + info!("writing..."); + class.write_ep.write(b"Hello World!\r\n").await.unwrap(); + info!("written"); - join(fut1, fut2).await; + Timer::after(Duration::from_secs(1)).await; + } + }; + + join3(fut1, fut2, fut3).await; } From 9a6d11281d33ec687e68b9b28d8489150bafff89 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 10 Mar 2022 01:10:53 +0100 Subject: [PATCH 04/50] Add some comments on the example. --- examples/nrf/src/bin/usb/main.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 014ad5c6..ecbdc346 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -34,13 +34,18 @@ async fn main(_spawner: Spawner, p: Peripherals) { while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} info!("vbus OK"); + // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let driver = Driver::new(p.USBD, irq); + + // Create embassy-usb Config let config = Config::new(0xc0de, 0xcafe); + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. let mut device_descriptor = [0; 256]; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; - let mut builder = UsbDeviceBuilder::new( driver, config, @@ -49,11 +54,16 @@ async fn main(_spawner: Spawner, p: Peripherals) { &mut bos_descriptor, ); + // Create classes on the builder. let mut class = CdcAcmClass::new(&mut builder, 64); + // Build the builder. let mut usb = builder.build(); + // Run the USB device. let fut1 = usb.run(); + + // Do stuff with the classes let fut2 = async { let mut buf = [0; 64]; loop { @@ -72,5 +82,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { } }; + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. join3(fut1, fut2, fut3).await; } From 5c0db627feae071182dd9978ffb56b0524558d93 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 11 Mar 2022 01:07:06 +0100 Subject: [PATCH 05/50] nrf/usb: update where clause syntax. --- embassy-nrf/src/usb.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 874bfe84..5df5053a 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -214,10 +214,7 @@ pub struct Bus<'d, T: Instance> { } impl<'d, T: Instance> driver::Bus for Bus<'d, T> { - type PollFuture<'a> - where - Self: 'a, - = impl Future + 'a; + type PollFuture<'a> = impl Future + 'a where Self: 'a; fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { poll_fn(|cx| { @@ -348,10 +345,7 @@ impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> { } impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { - type ReadFuture<'a> - where - Self: 'a, - = impl Future> + 'a; + type ReadFuture<'a> = impl Future> + 'a where Self: 'a; fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { async move { @@ -448,10 +442,7 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { } impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { - type WriteFuture<'a> - where - Self: 'a, - = impl Future> + 'a; + type WriteFuture<'a> = impl Future> + 'a where Self: 'a; fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { async move { From bdc6e0481c42d20d5cca19dfc8ec56306e47296e Mon Sep 17 00:00:00 2001 From: alexmoon Date: Fri, 25 Mar 2022 16:46:14 -0400 Subject: [PATCH 06/50] Add support for USB classes handling control requests. --- embassy-nrf/src/usb.rs | 432 ++++++++++++++++++---------- embassy-usb/src/builder.rs | 14 +- embassy-usb/src/class.rs | 190 ++++++++++++ embassy-usb/src/control.rs | 17 +- embassy-usb/src/driver.rs | 42 +++ embassy-usb/src/lib.rs | 169 +++++------ examples/nrf/src/bin/usb/cdc_acm.rs | 127 +++++++- examples/nrf/src/bin/usb/main.rs | 3 +- 8 files changed, 703 insertions(+), 291 deletions(-) create mode 100644 embassy-usb/src/class.rs diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 5df5053a..203f08fc 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -9,6 +9,7 @@ use embassy::time::{with_timeout, Duration}; use embassy::util::Unborrow; use embassy::waitqueue::AtomicWaker; use embassy_hal_common::unborrow; +use embassy_usb::control::Request; use embassy_usb::driver::{self, Event, ReadError, WriteError}; use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; use futures::future::poll_fn; @@ -134,6 +135,7 @@ impl<'d, T: Instance> Driver<'d, T> { impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { type EndpointOut = Endpoint<'d, T, Out>; type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; type Bus = Bus<'d, T>; fn alloc_endpoint_in( @@ -174,6 +176,19 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { })) } + fn alloc_control_pipe( + &mut self, + max_packet_size: u16, + ) -> Result { + self.alloc_endpoint_out(Some(0x00.into()), EndpointType::Control, max_packet_size, 0)?; + self.alloc_endpoint_in(Some(0x80.into()), EndpointType::Control, max_packet_size, 0)?; + Ok(ControlPipe { + _phantom: PhantomData, + max_packet_size, + request: None, + }) + } + fn enable(self) -> Self::Bus { let regs = T::regs(); @@ -344,99 +359,110 @@ impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> { } } +unsafe fn read_dma(i: usize, buf: &mut [u8]) -> Result { + let regs = T::regs(); + + // Check that the packet fits into the buffer + let size = regs.size.epout[0].read().bits() as usize; + if size > buf.len() { + return Err(ReadError::BufferOverflow); + } + + if i == 0 { + regs.events_ep0datadone.reset(); + } + + let epout = [ + ®s.epout0, + ®s.epout1, + ®s.epout2, + ®s.epout3, + ®s.epout4, + ®s.epout5, + ®s.epout6, + ®s.epout7, + ]; + epout[i].ptr.write(|w| w.bits(buf.as_ptr() as u32)); + // MAXCNT must match SIZE + epout[i].maxcnt.write(|w| w.bits(size as u32)); + + 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(); + + regs.size.epout[i].reset(); + + Ok(size) +} + +unsafe fn write_dma(i: usize, buf: &[u8]) -> Result<(), WriteError> { + let regs = T::regs(); + if buf.len() > 64 { + return Err(WriteError::BufferOverflow); + } + + // EasyDMA can't read FLASH, so we copy through RAM + let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); + let ptr = ram_buf.as_mut_ptr() as *mut u8; + core::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); + + 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. + epin[i].ptr.write(|w| w.bits(ptr as u32)); + epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8)); + + regs.events_endepin[i].reset(); + + dma_start(); + regs.tasks_startepin[i].write(|w| w.bits(1)); + while regs.events_endepin[i].read().bits() == 0 {} + dma_end(); + + Ok(()) +} + impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { type ReadFuture<'a> = impl Future> + 'a where Self: 'a; fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { async move { - let regs = T::regs(); let i = self.info.addr.index(); + assert!(i != 0); - if i == 0 { - if buf.len() == 0 { - regs.tasks_ep0status.write(|w| unsafe { w.bits(1) }); - return Ok(0); + // Wait until ready + poll_fn(|cx| { + EP_OUT_WAKERS[i].register(cx.waker()); + let r = READY_ENDPOINTS.load(Ordering::Acquire); + if r & (1 << (i + 16)) != 0 { + Poll::Ready(()) + } else { + Poll::Pending } + }) + .await; - // Wait for SETUP packet - regs.events_ep0setup.reset(); - regs.intenset.write(|w| w.ep0setup().set()); - poll_fn(|cx| { - EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0setup.read().bits() != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; + // Mark as not ready + READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); - if buf.len() < 8 { - return Err(ReadError::BufferOverflow); - } - - 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(); - - Ok(8) - } else { - // Wait until ready - poll_fn(|cx| { - EP_OUT_WAKERS[i].register(cx.waker()); - let r = READY_ENDPOINTS.load(Ordering::Acquire); - if r & (1 << (i + 16)) != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - // Mark as not ready - READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); - - // Check that the packet fits into the buffer - let size = regs.size.epout[i].read().bits(); - if size as usize > buf.len() { - return Err(ReadError::BufferOverflow); - } - - 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(); - - Ok(size as usize) - } + unsafe { read_dma::(i, buf) } } } } @@ -446,87 +472,181 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { async move { - let regs = T::regs(); let i = self.info.addr.index(); + assert!(i != 0); // Wait until ready. - if i != 0 { - poll_fn(|cx| { - EP_IN_WAKERS[i].register(cx.waker()); - let r = READY_ENDPOINTS.load(Ordering::Acquire); - if r & (1 << i) != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - // Mark as not ready - READY_ENDPOINTS.fetch_and(!(1 << i), Ordering::AcqRel); - } - - if i == 0 { - regs.events_ep0datadone.reset(); - } - - assert!(buf.len() <= 64); - - // EasyDMA can't read FLASH, so we copy through RAM - let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); - let ptr = ram_buf.as_mut_ptr() as *mut u8; - unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()) }; - - 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].ptr.write(|w| w.bits(ptr as u32)); - epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8)); - } - - regs.events_endepin[i].reset(); - - dma_start(); - regs.tasks_startepin[i].write(|w| unsafe { w.bits(1) }); - while regs.events_endepin[i].read().bits() == 0 {} - dma_end(); - - if i == 0 { - regs.intenset.write(|w| w.ep0datadone().set()); - let res = with_timeout( - Duration::from_millis(10), - poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }), - ) - .await; - - if res.is_err() { - // todo wrong error - return Err(driver::WriteError::BufferOverflow); + poll_fn(|cx| { + EP_IN_WAKERS[i].register(cx.waker()); + let r = READY_ENDPOINTS.load(Ordering::Acquire); + if r & (1 << i) != 0 { + Poll::Ready(()) + } else { + Poll::Pending } + }) + .await; + + // Mark as not ready + READY_ENDPOINTS.fetch_and(!(1 << i), Ordering::AcqRel); + + unsafe { write_dma::(i, buf) } + } + } +} + +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, + request: Option, +} + +impl<'d, T: Instance> ControlPipe<'d, T> { + async fn write(&mut self, buf: &[u8], last_chunk: bool) { + let regs = T::regs(); + regs.events_ep0datadone.reset(); + unsafe { + write_dma::(0, buf).unwrap(); + } + + regs.shorts + .modify(|_, w| w.ep0datadone_ep0status().bit(last_chunk)); + + regs.intenset.write(|w| w.ep0datadone().set()); + let res = with_timeout( + Duration::from_millis(10), + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }), + ) + .await; + + if res.is_err() { + error!("ControlPipe::write timed out."); + } + } +} + +impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { + type SetupFuture<'a> = impl Future + 'a where Self: 'a; + type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; + type AcceptInFuture<'a> = impl Future + 'a where Self: 'a; + + fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { + async move { + assert!(self.request.is_none()); + + let regs = T::regs(); + + // Wait for SETUP packet + regs.intenset.write(|w| w.ep0setup().set()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0setup.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // Reset shorts + regs.shorts + .modify(|_, w| w.ep0datadone_ep0status().clear_bit()); + regs.events_ep0setup.reset(); + + let mut buf = [0; 8]; + 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(); + + let req = Request::parse(&buf); + + if req.direction == UsbDirection::Out { + regs.tasks_ep0rcvout + .write(|w| w.tasks_ep0rcvout().set_bit()); } - Ok(()) + self.request = Some(req); + req } } + + fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a> { + async move { + let req = self.request.unwrap(); + assert_eq!(req.direction, UsbDirection::Out); + assert!(req.length > 0); + assert!(buf.len() >= usize::from(req.length)); + + let regs = T::regs(); + + // Wait until ready + regs.intenset.write(|w| w.ep0datadone().set()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs + .events_ep0datadone + .read() + .events_ep0datadone() + .bit_is_set() + { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + unsafe { read_dma::(0, buf) } + } + } + + fn accept(&mut self) { + let regs = T::regs(); + regs.tasks_ep0status + .write(|w| w.tasks_ep0status().bit(true)); + self.request = None; + } + + fn accept_in<'a>(&'a mut self, buf: &'a [u8]) -> Self::AcceptInFuture<'a> { + async move { + info!("control accept {=[u8]:x}", buf); + let req = self.request.unwrap(); + assert_eq!(req.direction, UsbDirection::In); + + let req_len = usize::from(req.length); + let len = buf.len().min(req_len); + let need_zlp = len != req_len && (len % usize::from(self.max_packet_size)) == 0; + let mut chunks = buf[0..len] + .chunks(usize::from(self.max_packet_size)) + .chain(need_zlp.then(|| -> &[u8] { &[] })); + while let Some(chunk) = chunks.next() { + self.write(chunk, chunks.size_hint().0 == 0).await; + } + + self.request = None; + } + } + + fn reject(&mut self) { + let regs = T::regs(); + regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); + self.request = None; + } } fn dma_start() { diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index e92cc8ef..f0f94b93 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,3 +1,4 @@ +use super::class::UsbClass; use super::descriptor::{BosWriter, DescriptorWriter}; use super::driver::{Driver, EndpointAllocError}; use super::types::*; @@ -174,7 +175,10 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { } /// Creates the [`UsbDevice`] instance with the configuration in this builder. - pub fn build(mut self) -> UsbDevice<'d, D> { + /// + /// If a device has mutliple [`UsbClass`]es, they can be provided as a tuple list: + /// `(class1, (class2, (class3, ()))`. + pub fn build>(mut self, classes: C) -> UsbDevice<'d, D, C> { self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); @@ -184,6 +188,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), + classes, ) } @@ -268,9 +273,10 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { /// Panics if endpoint allocation fails, because running out of endpoints or memory is not /// feasibly recoverable. #[inline] - pub fn alloc_control_endpoint_out(&mut self, max_packet_size: u16) -> D::EndpointOut { - self.alloc_endpoint_out(None, EndpointType::Control, max_packet_size, 0) - .expect("alloc_ep failed") + pub fn alloc_control_pipe(&mut self, max_packet_size: u16) -> D::ControlPipe { + self.bus + .alloc_control_pipe(max_packet_size) + .expect("alloc_control_pipe failed") } /// Allocates a bulk in endpoint. diff --git a/embassy-usb/src/class.rs b/embassy-usb/src/class.rs new file mode 100644 index 00000000..97bf7aba --- /dev/null +++ b/embassy-usb/src/class.rs @@ -0,0 +1,190 @@ +use core::future::Future; + +use crate::control::Request; +use crate::driver::{ControlPipe, Driver}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RequestStatus { + Unhandled, + Accepted, + Rejected, +} + +impl Default for RequestStatus { + fn default() -> Self { + RequestStatus::Unhandled + } +} + +/// A trait for implementing USB classes. +/// +/// All methods are optional callbacks that will be called by +/// [`UsbDevice::run()`](crate::UsbDevice::run) +pub trait UsbClass<'d, D: Driver<'d>> { + type ControlOutFuture<'a>: Future + 'a + where + Self: 'a, + 'd: 'a, + D: 'a; + + type ControlInFuture<'a>: Future + 'a + where + Self: 'a, + 'd: 'a, + D: 'a; + + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) {} + + /// Called when a control request is received with direction HostToDevice. + /// + /// All requests are passed to classes in turn, which can choose to accept, ignore or report an + /// error. Classes can even choose to override standard requests, but doing that is rarely + /// necessary. + /// + /// When implementing your own class, you should ignore any requests that are not meant for your + /// class so that any other classes in the composite device can process them. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `data` - The data from the request. + fn control_out<'a>(&'a mut self, req: Request, data: &'a [u8]) -> Self::ControlOutFuture<'a> + where + 'd: 'a, + D: 'a; + + /// Called when a control request is received with direction DeviceToHost. + /// + /// All requests are passed to classes in turn, which can choose to accept, ignore or report an + /// error. Classes can even choose to override standard requests, but doing that is rarely + /// necessary. + /// + /// See [`ControlIn`] for how to respond to the transfer. + /// + /// When implementing your own class, you should ignore any requests that are not meant for your + /// class so that any other classes in the composite device can process them. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `control` - The control pipe. + fn control_in<'a>( + &'a mut self, + req: Request, + control: ControlIn<'a, 'd, D>, + ) -> Self::ControlInFuture<'a> + where + 'd: 'a; +} + +impl<'d, D: Driver<'d>> UsbClass<'d, D> for () { + type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + + fn control_out<'a>(&'a mut self, _req: Request, _data: &'a [u8]) -> Self::ControlOutFuture<'a> + where + 'd: 'a, + D: 'a, + { + async move { RequestStatus::default() } + } + + fn control_in<'a>( + &'a mut self, + _req: Request, + control: ControlIn<'a, 'd, D>, + ) -> Self::ControlInFuture<'a> + where + 'd: 'a, + D: 'a, + { + async move { control.ignore() } + } +} + +impl<'d, D: Driver<'d>, Head, Tail> UsbClass<'d, D> for (Head, Tail) +where + Head: UsbClass<'d, D>, + Tail: UsbClass<'d, D>, +{ + type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + + fn control_out<'a>(&'a mut self, req: Request, data: &'a [u8]) -> Self::ControlOutFuture<'a> + where + 'd: 'a, + D: 'a, + { + async move { + match self.0.control_out(req, data).await { + RequestStatus::Unhandled => self.1.control_out(req, data).await, + status => status, + } + } + } + + fn control_in<'a>( + &'a mut self, + req: Request, + control: ControlIn<'a, 'd, D>, + ) -> Self::ControlInFuture<'a> + where + 'd: 'a, + { + async move { + match self + .0 + .control_in(req, ControlIn::new(control.control)) + .await + { + ControlInRequestStatus(RequestStatus::Unhandled) => { + self.1.control_in(req, control).await + } + status => status, + } + } + } +} + +/// Handle for a control IN transfer. When implementing a class, use the methods of this object to +/// response to the transfer with either data or an error (STALL condition). To ignore the request +/// and pass it on to the next class, call [`Self::ignore()`]. +pub struct ControlIn<'a, 'd: 'a, D: Driver<'d>> { + control: &'a mut D::ControlPipe, +} + +#[derive(Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControlInRequestStatus(pub(crate) RequestStatus); + +impl ControlInRequestStatus { + pub fn status(self) -> RequestStatus { + self.0 + } +} + +impl<'a, 'd: 'a, D: Driver<'d>> ControlIn<'a, 'd, D> { + pub(crate) fn new(control: &'a mut D::ControlPipe) -> Self { + ControlIn { control } + } + + /// Ignores the request and leaves it unhandled. + pub fn ignore(self) -> ControlInRequestStatus { + ControlInRequestStatus(RequestStatus::Unhandled) + } + + /// Accepts the transfer with the supplied buffer. + pub async fn accept(self, data: &[u8]) -> ControlInRequestStatus { + self.control.accept_in(data).await; + + ControlInRequestStatus(RequestStatus::Accepted) + } + + /// Rejects the transfer by stalling the pipe. + pub fn reject(self) -> ControlInRequestStatus { + self.control.reject(); + ControlInRequestStatus(RequestStatus::Rejected) + } +} diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index f1148ac7..77bc10aa 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -2,12 +2,6 @@ use core::mem; use super::types::*; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ParseError { - InvalidLength, -} - /// Control request type. #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -104,15 +98,12 @@ impl Request { /// Standard USB feature Device Remote Wakeup for Set/Clear Feature pub const FEATURE_DEVICE_REMOTE_WAKEUP: u16 = 1; - pub(crate) fn parse(buf: &[u8]) -> Result { - if buf.len() != 8 { - return Err(ParseError::InvalidLength); - } - + /// Parses a USB control request from a byte array. + pub fn parse(buf: &[u8; 8]) -> Request { let rt = buf[0]; let recipient = rt & 0b11111; - Ok(Request { + Request { direction: rt.into(), request_type: unsafe { mem::transmute((rt >> 5) & 0b11) }, recipient: if recipient <= 3 { @@ -124,7 +115,7 @@ impl Request { value: (buf[2] as u16) | ((buf[3] as u16) << 8), index: (buf[4] as u16) | ((buf[5] as u16) << 8), length: (buf[6] as u16) | ((buf[7] as u16) << 8), - }) + } } /// Gets the descriptor type and index from the value field of a GET_DESCRIPTOR request. diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index a7b16efa..1c6ba1f5 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -1,5 +1,7 @@ use core::future::Future; +use crate::control::Request; + use super::types::*; /// Driver for a specific USB peripheral. Implement this to add support for a new hardware @@ -7,6 +9,7 @@ use super::types::*; pub trait Driver<'a> { type EndpointOut: EndpointOut + 'a; type EndpointIn: EndpointIn + 'a; + type ControlPipe: ControlPipe + 'a; type Bus: Bus + 'a; /// Allocates an endpoint and specified endpoint parameters. This method is called by the device @@ -36,6 +39,11 @@ pub trait Driver<'a> { interval: u8, ) -> Result; + fn alloc_control_pipe( + &mut self, + max_packet_size: u16, + ) -> Result; + /// Enables and initializes the USB peripheral. Soon after enabling the device will be reset, so /// there is no need to perform a USB reset in this method. fn enable(self) -> Self::Bus; @@ -122,6 +130,40 @@ pub trait EndpointOut: Endpoint { fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; } +pub trait ControlPipe { + type SetupFuture<'a>: Future + 'a + where + Self: 'a; + type DataOutFuture<'a>: Future> + 'a + where + Self: 'a; + type AcceptInFuture<'a>: Future + 'a + where + Self: 'a; + + /// Reads a single setup packet from the endpoint. + fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a>; + + /// Reads the data packet of a control write sequence. + /// + /// Must be called after `setup()` for requests with `direction` of `Out` + /// and `length` greater than zero. + /// + /// `buf.len()` must be greater than or equal to the request's `length`. + fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a>; + + /// Accepts a control request. + fn accept(&mut self); + + /// Accepts a control read request with `data`. + /// + /// `data.len()` must be less than or equal to the request's `length`. + fn accept_in<'a>(&'a mut self, data: &'a [u8]) -> Self::AcceptInFuture<'a>; + + /// Rejects a control request. + fn reject(&mut self); +} + pub trait EndpointIn: Endpoint { type WriteFuture<'a>: Future> + 'a where diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 95f78804..4082868f 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -1,16 +1,19 @@ #![no_std] #![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; mod builder; -mod control; +pub mod class; +pub mod control; pub mod descriptor; pub mod driver; pub mod types; mod util; +use self::class::{RequestStatus, UsbClass}; use self::control::*; use self::descriptor::*; use self::driver::*; @@ -48,10 +51,9 @@ pub const CONFIGURATION_VALUE: u8 = 1; /// The default value for bAlternateSetting for all interfaces. pub const DEFAULT_ALTERNATE_SETTING: u8 = 0; -pub struct UsbDevice<'d, D: Driver<'d>> { +pub struct UsbDevice<'d, D: Driver<'d>, C: UsbClass<'d, D>> { bus: D::Bus, - control_in: D::EndpointIn, - control_out: D::EndpointOut, + control: D::ControlPipe, config: Config<'d>, device_descriptor: &'d [u8], @@ -62,32 +64,21 @@ pub struct UsbDevice<'d, D: Driver<'d>> { remote_wakeup_enabled: bool, self_powered: bool, pending_address: u8, + + classes: C, } -impl<'d, D: Driver<'d>> UsbDevice<'d, D> { +impl<'d, D: Driver<'d>, C: UsbClass<'d, D>> UsbDevice<'d, D, C> { pub(crate) fn build( mut driver: D, config: Config<'d>, device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], + classes: C, ) -> Self { - let control_out = driver - .alloc_endpoint_out( - Some(0x00.into()), - EndpointType::Control, - config.max_packet_size_0 as u16, - 0, - ) - .expect("failed to alloc control endpoint"); - - let control_in = driver - .alloc_endpoint_in( - Some(0x80.into()), - EndpointType::Control, - config.max_packet_size_0 as u16, - 0, - ) + let control = driver + .alloc_control_pipe(config.max_packet_size_0 as u16) .expect("failed to alloc control endpoint"); // Enable the USB bus. @@ -97,8 +88,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { Self { bus: driver, config, - control_in, - control_out, + control, device_descriptor, config_descriptor, bos_descriptor, @@ -106,14 +96,13 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { remote_wakeup_enabled: false, self_powered: false, pending_address: 0, + classes, } } pub async fn run(&mut self) { - let mut buf = [0; 8]; - loop { - let control_fut = self.control_out.read(&mut buf); + let control_fut = self.control.setup(); let bus_fut = self.bus.poll(); match select(bus_fut, control_fut).await { Either::Left(evt) => match evt { @@ -124,11 +113,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.remote_wakeup_enabled = false; self.pending_address = 0; - // TODO - //self.control.reset(); - //for cls in classes { - // cls.reset(); - //} + self.classes.reset(); } Event::Resume => {} Event::Suspend => { @@ -136,16 +121,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.device_state = UsbDeviceState::Suspend; } }, - Either::Right(n) => { - let n = n.unwrap(); - assert_eq!(n, 8); - let req = Request::parse(&buf).unwrap(); + Either::Right(req) => { info!("control request: {:x}", req); - // Now that we have properly parsed the setup packet, ensure the end-point is no longer in - // a stalled state. - self.control_out.set_stalled(false); - match req.direction { UsbDirection::In => self.handle_control_in(req).await, UsbDirection::Out => self.handle_control_out(req).await, @@ -155,36 +133,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - async fn write_chunked(&mut self, data: &[u8]) -> Result<(), driver::WriteError> { - for c in data.chunks(8) { - self.control_in.write(c).await?; - } - if data.len() % 8 == 0 { - self.control_in.write(&[]).await?; - } - Ok(()) - } - - async fn control_out_accept(&mut self, req: Request) { - info!("control out accept"); - // status phase - // todo: cleanup - self.control_out.read(&mut []).await.unwrap(); - } - - async fn control_in_accept(&mut self, req: Request, data: &[u8]) { - info!("control accept {:x}", data); - - let len = data.len().min(req.length as _); - if let Err(e) = self.write_chunked(&data[..len]).await { - info!("write_chunked failed: {:?}", e); - } - - // status phase - // todo: cleanup - self.control_out.read(&mut []).await.unwrap(); - } - async fn control_in_accept_writer( &mut self, req: Request, @@ -193,17 +141,26 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { let mut buf = [0; 256]; let mut w = DescriptorWriter::new(&mut buf); f(&mut w); - let pos = w.position(); - self.control_in_accept(req, &buf[..pos]).await; - } - - fn control_reject(&mut self, req: Request) { - info!("control reject"); - self.control_out.set_stalled(true); + let pos = w.position().min(usize::from(req.length)); + self.control.accept_in(&buf[..pos]).await; } async fn handle_control_out(&mut self, req: Request) { - // TODO actually read the data if there's an OUT data phase. + { + let mut buf = [0; 128]; + let data = if req.length > 0 { + let size = self.control.data_out(&mut buf).await.unwrap(); + &buf[0..size] + } else { + &[] + }; + + match self.classes.control_out(req, data).await { + RequestStatus::Accepted => return self.control.accept(), + RequestStatus::Rejected => return self.control.reject(), + RequestStatus::Unhandled => (), + } + } const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; @@ -217,12 +174,12 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { Request::FEATURE_DEVICE_REMOTE_WAKEUP, ) => { self.remote_wakeup_enabled = false; - self.control_out_accept(req).await; + self.control.accept(); } (Recipient::Endpoint, Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { //self.bus.set_stalled(((req.index as u8) & 0x8f).into(), false); - self.control_out_accept(req).await; + self.control.accept(); } ( @@ -231,51 +188,61 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { Request::FEATURE_DEVICE_REMOTE_WAKEUP, ) => { self.remote_wakeup_enabled = true; - self.control_out_accept(req).await; + self.control.accept(); } (Recipient::Endpoint, Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { self.bus .set_stalled(((req.index as u8) & 0x8f).into(), true); - self.control_out_accept(req).await; + self.control.accept(); } (Recipient::Device, Request::SET_ADDRESS, 1..=127) => { self.pending_address = req.value as u8; // on NRF the hardware auto-handles SET_ADDRESS. - self.control_out_accept(req).await; + self.control.accept(); } (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { self.device_state = UsbDeviceState::Configured; - self.control_out_accept(req).await; + self.control.accept(); } (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => { match self.device_state { UsbDeviceState::Default => { - self.control_out_accept(req).await; + self.control.accept(); } _ => { self.device_state = UsbDeviceState::Addressed; - self.control_out_accept(req).await; + self.control.accept(); } } } (Recipient::Interface, Request::SET_INTERFACE, DEFAULT_ALTERNATE_SETTING_U16) => { // TODO: do something when alternate settings are implemented - self.control_out_accept(req).await; + self.control.accept(); } - _ => self.control_reject(req), + _ => self.control.reject(), }, - _ => self.control_reject(req), + _ => self.control.reject(), } } async fn handle_control_in(&mut self, req: Request) { + match self + .classes + .control_in(req, class::ControlIn::new(&mut self.control)) + .await + .status() + { + RequestStatus::Accepted | RequestStatus::Rejected => return, + RequestStatus::Unhandled => (), + } + match req.request_type { RequestType::Standard => match (req.recipient, req.request) { (Recipient::Device, Request::GET_STATUS) => { @@ -286,12 +253,12 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { if self.remote_wakeup_enabled { status |= 0x0002; } - self.control_in_accept(req, &status.to_le_bytes()).await; + self.control.accept_in(&status.to_le_bytes()).await; } (Recipient::Interface, Request::GET_STATUS) => { let status: u16 = 0x0000; - self.control_in_accept(req, &status.to_le_bytes()).await; + self.control.accept_in(&status.to_le_bytes()).await; } (Recipient::Endpoint, Request::GET_STATUS) => { @@ -300,7 +267,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { if self.bus.is_stalled(ep_addr) { status |= 0x0001; } - self.control_in_accept(req, &status.to_le_bytes()).await; + self.control.accept_in(&status.to_le_bytes()).await; } (Recipient::Device, Request::GET_DESCRIPTOR) => { @@ -312,17 +279,17 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { UsbDeviceState::Configured => CONFIGURATION_VALUE, _ => CONFIGURATION_NONE, }; - self.control_in_accept(req, &status.to_le_bytes()).await; + self.control.accept_in(&status.to_le_bytes()).await; } (Recipient::Interface, Request::GET_INTERFACE) => { // TODO: change when alternate settings are implemented let status = DEFAULT_ALTERNATE_SETTING; - self.control_in_accept(req, &status.to_le_bytes()).await; + self.control.accept_in(&status.to_le_bytes()).await; } - _ => self.control_reject(req), + _ => self.control.reject(), }, - _ => self.control_reject(req), + _ => self.control.reject(), } } @@ -331,11 +298,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { let config = self.config.clone(); match dtype { - descriptor_type::BOS => self.control_in_accept(req, self.bos_descriptor).await, - descriptor_type::DEVICE => self.control_in_accept(req, self.device_descriptor).await, - descriptor_type::CONFIGURATION => { - self.control_in_accept(req, self.config_descriptor).await - } + descriptor_type::BOS => self.control.accept_in(self.bos_descriptor).await, + descriptor_type::DEVICE => self.control.accept_in(self.device_descriptor).await, + descriptor_type::CONFIGURATION => self.control.accept_in(self.config_descriptor).await, descriptor_type::STRING => { if index == 0 { self.control_in_accept_writer(req, |w| { @@ -363,11 +328,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.control_in_accept_writer(req, |w| w.string(s).unwrap()) .await; } else { - self.control_reject(req) + self.control.reject() } } } - _ => self.control_reject(req), + _ => self.control.reject(), } } } diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index b7c112ae..eebf8922 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,5 +1,8 @@ -use core::convert::TryInto; +use core::future::Future; use core::mem; +use defmt::info; +use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; +use embassy_usb::control::{self, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -39,16 +42,107 @@ const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; /// terminated with a short packet, even if the bulk endpoint is used for stream-like data. pub struct CdcAcmClass<'d, D: Driver<'d>> { // TODO not pub - pub comm_if: InterfaceNumber, pub comm_ep: D::EndpointIn, pub data_if: InterfaceNumber, pub read_ep: D::EndpointOut, pub write_ep: D::EndpointIn, + pub control: CdcAcmControl, +} + +pub struct CdcAcmControl { + pub comm_if: InterfaceNumber, pub line_coding: LineCoding, pub dtr: bool, pub rts: bool, } +impl<'d, D: Driver<'d>> UsbClass<'d, D> for CdcAcmControl { + type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + + fn reset(&mut self) { + self.line_coding = LineCoding::default(); + self.dtr = false; + self.rts = false; + } + + fn control_out<'a>( + &'a mut self, + req: control::Request, + data: &'a [u8], + ) -> Self::ControlOutFuture<'a> + where + 'd: 'a, + D: 'a, + { + async move { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return RequestStatus::Unhandled; + } + + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + RequestStatus::Accepted + } + REQ_SET_LINE_CODING if data.len() >= 7 => { + self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); + self.line_coding.stop_bits = data[4].into(); + self.line_coding.parity_type = data[5].into(); + self.line_coding.data_bits = data[6]; + info!("Set line coding to: {:?}", self.line_coding); + + RequestStatus::Accepted + } + REQ_SET_CONTROL_LINE_STATE => { + self.dtr = (req.value & 0x0001) != 0; + self.rts = (req.value & 0x0002) != 0; + info!("Set dtr {}, rts {}", self.dtr, self.rts); + + RequestStatus::Accepted + } + _ => RequestStatus::Rejected, + } + } + } + + fn control_in<'a>( + &'a mut self, + req: Request, + control: embassy_usb::class::ControlIn<'a, 'd, D>, + ) -> Self::ControlInFuture<'a> + where + 'd: 'a, + { + async move { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return control.ignore(); + } + + match req.request { + // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. + REQ_GET_LINE_CODING if req.length == 7 => { + info!("Sending line coding"); + let mut data = [0; 7]; + data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes()); + data[4] = self.line_coding.stop_bits as u8; + data[5] = self.line_coding.parity_type as u8; + data[6] = self.line_coding.data_bits; + control.accept(&data).await + } + _ => control.reject(), + } + } + } +} + impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. @@ -133,19 +227,21 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { builder.config_descriptor.endpoint(read_ep.info()).unwrap(); CdcAcmClass { - comm_if, comm_ep, data_if, read_ep, write_ep, - line_coding: LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, + control: CdcAcmControl { + comm_if, + dtr: false, + rts: false, + line_coding: LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + }, }, - dtr: false, - rts: false, } } @@ -158,17 +254,17 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// Gets the current line coding. The line coding contains information that's mainly relevant /// for USB to UART serial port emulators, and can be ignored if not relevant. pub fn line_coding(&self) -> &LineCoding { - &self.line_coding + &self.control.line_coding } /// Gets the DTR (data terminal ready) state pub fn dtr(&self) -> bool { - self.dtr + self.control.dtr } /// Gets the RTS (request to send) state pub fn rts(&self) -> bool { - self.rts + self.control.rts } /// Writes a single packet into the IN endpoint. @@ -270,7 +366,7 @@ impl UsbClass for CdcAcmClass<'_, B> { */ /// Number of stop bits for LineCoding -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] pub enum StopBits { /// 1 stop bit One = 0, @@ -293,7 +389,7 @@ impl From for StopBits { } /// Parity for LineCoding -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] pub enum ParityType { None = 0, Odd = 1, @@ -316,6 +412,7 @@ impl From for ParityType { /// /// This is provided by the host for specifying the standard UART parameters such as baud rate. Can /// be ignored if you don't plan to interface with a physical UART. +#[derive(defmt::Format)] pub struct LineCoding { stop_bits: StopBits, data_bits: u8, diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index ecbdc346..71285579 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] #[path = "../../example_common.rs"] @@ -58,7 +59,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, 64); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build(class.control); // Run the USB device. let fut1 = usb.run(); From 52c622b1cd02ba13accc84cd57e90b04797f0405 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Sun, 27 Mar 2022 17:12:57 -0400 Subject: [PATCH 07/50] Use trait objects instead of generics for UsbDevice::classes --- embassy-usb/src/builder.rs | 5 +- embassy-usb/src/class.rs | 147 +++++++--------------------- embassy-usb/src/lib.rs | 48 +++++---- examples/nrf/src/bin/usb/cdc_acm.rs | 117 ++++++++++------------ examples/nrf/src/bin/usb/main.rs | 4 +- 5 files changed, 120 insertions(+), 201 deletions(-) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index f0f94b93..bcb838ff 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -175,10 +175,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { } /// Creates the [`UsbDevice`] instance with the configuration in this builder. - /// - /// If a device has mutliple [`UsbClass`]es, they can be provided as a tuple list: - /// `(class1, (class2, (class3, ()))`. - pub fn build>(mut self, classes: C) -> UsbDevice<'d, D, C> { + pub fn build(mut self, classes: &'d mut [&'d mut dyn UsbClass]) -> UsbDevice<'d, D> { self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); diff --git a/embassy-usb/src/class.rs b/embassy-usb/src/class.rs index 97bf7aba..a0141e31 100644 --- a/embassy-usb/src/class.rs +++ b/embassy-usb/src/class.rs @@ -1,7 +1,4 @@ -use core::future::Future; - use crate::control::Request; -use crate::driver::{ControlPipe, Driver}; #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -21,19 +18,7 @@ impl Default for RequestStatus { /// /// All methods are optional callbacks that will be called by /// [`UsbDevice::run()`](crate::UsbDevice::run) -pub trait UsbClass<'d, D: Driver<'d>> { - type ControlOutFuture<'a>: Future + 'a - where - Self: 'a, - 'd: 'a, - D: 'a; - - type ControlInFuture<'a>: Future + 'a - where - Self: 'a, - 'd: 'a, - D: 'a; - +pub trait UsbClass { /// Called after a USB reset after the bus reset sequence is complete. fn reset(&mut self) {} @@ -50,10 +35,7 @@ pub trait UsbClass<'d, D: Driver<'d>> { /// /// * `req` - The request from the SETUP packet. /// * `data` - The data from the request. - fn control_out<'a>(&'a mut self, req: Request, data: &'a [u8]) -> Self::ControlOutFuture<'a> - where - 'd: 'a, - D: 'a; + fn control_out(&mut self, req: Request, data: &[u8]) -> RequestStatus; /// Called when a control request is received with direction DeviceToHost. /// @@ -71,120 +53,63 @@ pub trait UsbClass<'d, D: Driver<'d>> { /// * `req` - The request from the SETUP packet. /// * `control` - The control pipe. fn control_in<'a>( - &'a mut self, + &mut self, req: Request, - control: ControlIn<'a, 'd, D>, - ) -> Self::ControlInFuture<'a> - where - 'd: 'a; -} - -impl<'d, D: Driver<'d>> UsbClass<'d, D> for () { - type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - - fn control_out<'a>(&'a mut self, _req: Request, _data: &'a [u8]) -> Self::ControlOutFuture<'a> - where - 'd: 'a, - D: 'a, - { - async move { RequestStatus::default() } - } - - fn control_in<'a>( - &'a mut self, - _req: Request, - control: ControlIn<'a, 'd, D>, - ) -> Self::ControlInFuture<'a> - where - 'd: 'a, - D: 'a, - { - async move { control.ignore() } - } -} - -impl<'d, D: Driver<'d>, Head, Tail> UsbClass<'d, D> for (Head, Tail) -where - Head: UsbClass<'d, D>, - Tail: UsbClass<'d, D>, -{ - type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - - fn control_out<'a>(&'a mut self, req: Request, data: &'a [u8]) -> Self::ControlOutFuture<'a> - where - 'd: 'a, - D: 'a, - { - async move { - match self.0.control_out(req, data).await { - RequestStatus::Unhandled => self.1.control_out(req, data).await, - status => status, - } - } - } - - fn control_in<'a>( - &'a mut self, - req: Request, - control: ControlIn<'a, 'd, D>, - ) -> Self::ControlInFuture<'a> - where - 'd: 'a, - { - async move { - match self - .0 - .control_in(req, ControlIn::new(control.control)) - .await - { - ControlInRequestStatus(RequestStatus::Unhandled) => { - self.1.control_in(req, control).await - } - status => status, - } - } - } + control: ControlIn<'a>, + ) -> ControlInRequestStatus<'a>; } /// Handle for a control IN transfer. When implementing a class, use the methods of this object to /// response to the transfer with either data or an error (STALL condition). To ignore the request /// and pass it on to the next class, call [`Self::ignore()`]. -pub struct ControlIn<'a, 'd: 'a, D: Driver<'d>> { - control: &'a mut D::ControlPipe, +pub struct ControlIn<'a> { + buf: &'a mut [u8], } #[derive(Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControlInRequestStatus(pub(crate) RequestStatus); +pub struct ControlInRequestStatus<'a> { + pub(crate) status: RequestStatus, + pub(crate) data: &'a [u8], +} -impl ControlInRequestStatus { - pub fn status(self) -> RequestStatus { - self.0 +impl<'a> ControlInRequestStatus<'a> { + pub fn status(&self) -> RequestStatus { + self.status } } -impl<'a, 'd: 'a, D: Driver<'d>> ControlIn<'a, 'd, D> { - pub(crate) fn new(control: &'a mut D::ControlPipe) -> Self { - ControlIn { control } +impl<'a> ControlIn<'a> { + pub(crate) fn new(buf: &'a mut [u8]) -> Self { + ControlIn { buf } } /// Ignores the request and leaves it unhandled. - pub fn ignore(self) -> ControlInRequestStatus { - ControlInRequestStatus(RequestStatus::Unhandled) + pub fn ignore(self) -> ControlInRequestStatus<'a> { + ControlInRequestStatus { + status: RequestStatus::Unhandled, + data: &[], + } } /// Accepts the transfer with the supplied buffer. - pub async fn accept(self, data: &[u8]) -> ControlInRequestStatus { - self.control.accept_in(data).await; + pub fn accept(self, data: &[u8]) -> ControlInRequestStatus<'a> { + assert!(data.len() < self.buf.len()); - ControlInRequestStatus(RequestStatus::Accepted) + let buf = &mut self.buf[0..data.len()]; + buf.copy_from_slice(data); + + ControlInRequestStatus { + status: RequestStatus::Accepted, + data: buf, + } } /// Rejects the transfer by stalling the pipe. - pub fn reject(self) -> ControlInRequestStatus { - self.control.reject(); - ControlInRequestStatus(RequestStatus::Rejected) + pub fn reject(self) -> ControlInRequestStatus<'a> { + ControlInRequestStatus { + status: RequestStatus::Unhandled, + data: &[], + } } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 4082868f..ff3930af 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -13,6 +13,8 @@ pub mod driver; pub mod types; mod util; +use class::ControlInRequestStatus; + use self::class::{RequestStatus, UsbClass}; use self::control::*; use self::descriptor::*; @@ -51,7 +53,7 @@ pub const CONFIGURATION_VALUE: u8 = 1; /// The default value for bAlternateSetting for all interfaces. pub const DEFAULT_ALTERNATE_SETTING: u8 = 0; -pub struct UsbDevice<'d, D: Driver<'d>, C: UsbClass<'d, D>> { +pub struct UsbDevice<'d, D: Driver<'d>> { bus: D::Bus, control: D::ControlPipe, @@ -65,17 +67,17 @@ pub struct UsbDevice<'d, D: Driver<'d>, C: UsbClass<'d, D>> { self_powered: bool, pending_address: u8, - classes: C, + classes: &'d mut [&'d mut dyn UsbClass], } -impl<'d, D: Driver<'d>, C: UsbClass<'d, D>> UsbDevice<'d, D, C> { +impl<'d, D: Driver<'d>> UsbDevice<'d, D> { pub(crate) fn build( mut driver: D, config: Config<'d>, device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], - classes: C, + classes: &'d mut [&'d mut dyn UsbClass], ) -> Self { let control = driver .alloc_control_pipe(config.max_packet_size_0 as u16) @@ -113,7 +115,9 @@ impl<'d, D: Driver<'d>, C: UsbClass<'d, D>> UsbDevice<'d, D, C> { self.remote_wakeup_enabled = false; self.pending_address = 0; - self.classes.reset(); + for c in self.classes.iter_mut() { + c.reset(); + } } Event::Resume => {} Event::Suspend => { @@ -155,10 +159,12 @@ impl<'d, D: Driver<'d>, C: UsbClass<'d, D>> UsbDevice<'d, D, C> { &[] }; - match self.classes.control_out(req, data).await { - RequestStatus::Accepted => return self.control.accept(), - RequestStatus::Rejected => return self.control.reject(), - RequestStatus::Unhandled => (), + for c in self.classes.iter_mut() { + match c.control_out(req, data) { + RequestStatus::Accepted => return self.control.accept(), + RequestStatus::Rejected => return self.control.reject(), + RequestStatus::Unhandled => (), + } } } @@ -233,14 +239,22 @@ impl<'d, D: Driver<'d>, C: UsbClass<'d, D>> UsbDevice<'d, D, C> { } async fn handle_control_in(&mut self, req: Request) { - match self - .classes - .control_in(req, class::ControlIn::new(&mut self.control)) - .await - .status() - { - RequestStatus::Accepted | RequestStatus::Rejected => return, - RequestStatus::Unhandled => (), + let mut buf = [0; 128]; + for c in self.classes.iter_mut() { + match c.control_in(req, class::ControlIn::new(&mut buf)) { + ControlInRequestStatus { + status: RequestStatus::Accepted, + data, + } => return self.control.accept_in(data).await, + ControlInRequestStatus { + status: RequestStatus::Rejected, + .. + } => return self.control.reject(), + ControlInRequestStatus { + status: RequestStatus::Unhandled, + .. + } => (), + } } match req.request_type { diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index eebf8922..5e4abfea 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,4 +1,3 @@ -use core::future::Future; use core::mem; use defmt::info; use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; @@ -56,89 +55,71 @@ pub struct CdcAcmControl { pub rts: bool, } -impl<'d, D: Driver<'d>> UsbClass<'d, D> for CdcAcmControl { - type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - +impl UsbClass for CdcAcmControl { fn reset(&mut self) { self.line_coding = LineCoding::default(); self.dtr = false; self.rts = false; } - fn control_out<'a>( - &'a mut self, - req: control::Request, - data: &'a [u8], - ) -> Self::ControlOutFuture<'a> - where - 'd: 'a, - D: 'a, - { - async move { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return RequestStatus::Unhandled; + fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return RequestStatus::Unhandled; + } + + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + RequestStatus::Accepted } + REQ_SET_LINE_CODING if data.len() >= 7 => { + self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); + self.line_coding.stop_bits = data[4].into(); + self.line_coding.parity_type = data[5].into(); + self.line_coding.data_bits = data[6]; + info!("Set line coding to: {:?}", self.line_coding); - match req.request { - REQ_SEND_ENCAPSULATED_COMMAND => { - // We don't actually support encapsulated commands but pretend we do for standards - // compatibility. - RequestStatus::Accepted - } - REQ_SET_LINE_CODING if data.len() >= 7 => { - self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); - self.line_coding.stop_bits = data[4].into(); - self.line_coding.parity_type = data[5].into(); - self.line_coding.data_bits = data[6]; - info!("Set line coding to: {:?}", self.line_coding); - - RequestStatus::Accepted - } - REQ_SET_CONTROL_LINE_STATE => { - self.dtr = (req.value & 0x0001) != 0; - self.rts = (req.value & 0x0002) != 0; - info!("Set dtr {}, rts {}", self.dtr, self.rts); - - RequestStatus::Accepted - } - _ => RequestStatus::Rejected, + RequestStatus::Accepted } + REQ_SET_CONTROL_LINE_STATE => { + self.dtr = (req.value & 0x0001) != 0; + self.rts = (req.value & 0x0002) != 0; + info!("Set dtr {}, rts {}", self.dtr, self.rts); + + RequestStatus::Accepted + } + _ => RequestStatus::Rejected, } } fn control_in<'a>( - &'a mut self, + &mut self, req: Request, - control: embassy_usb::class::ControlIn<'a, 'd, D>, - ) -> Self::ControlInFuture<'a> - where - 'd: 'a, - { - async move { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return control.ignore(); - } + control: embassy_usb::class::ControlIn<'a>, + ) -> ControlInRequestStatus<'a> { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return control.ignore(); + } - match req.request { - // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. - REQ_GET_LINE_CODING if req.length == 7 => { - info!("Sending line coding"); - let mut data = [0; 7]; - data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes()); - data[4] = self.line_coding.stop_bits as u8; - data[5] = self.line_coding.parity_type as u8; - data[6] = self.line_coding.data_bits; - control.accept(&data).await - } - _ => control.reject(), + match req.request { + // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. + REQ_GET_LINE_CODING if req.length == 7 => { + info!("Sending line coding"); + let mut data = [0; 7]; + data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes()); + data[4] = self.line_coding.stop_bits as u8; + data[5] = self.line_coding.parity_type as u8; + data[6] = self.line_coding.data_bits; + control.accept(&data) } + _ => control.reject(), } } } diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 71285579..73ac3a21 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -16,6 +16,7 @@ use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; +use embassy_usb::class::UsbClass; use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; use futures::future::join3; @@ -59,7 +60,8 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, 64); // Build the builder. - let mut usb = builder.build(class.control); + let mut classes: [&mut dyn UsbClass; 1] = [&mut class.control]; + let mut usb = builder.build(&mut classes); // Run the USB device. let fut1 = usb.run(); From a062baae3837b453f0c05d0730ba465c6d2c2446 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 02:18:13 +0200 Subject: [PATCH 08/50] nrf/usb: fix wrong DMA read size --- embassy-nrf/src/usb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 203f08fc..2621e65f 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -363,7 +363,7 @@ unsafe fn read_dma(i: usize, buf: &mut [u8]) -> Result buf.len() { return Err(ReadError::BufferOverflow); } From a2f5763a67441e5e6c981407938c21c58e9eb669 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 02:20:01 +0200 Subject: [PATCH 09/50] usb: add `add_class` to builder, so that `FooBarClass::new(&mut builder)` can set up everything. --- embassy-usb/Cargo.toml | 1 + embassy-usb/src/builder.rs | 15 +- embassy-usb/src/lib.rs | 7 +- examples/nrf/src/bin/usb/cdc_acm.rs | 207 ++++++++++++---------------- examples/nrf/src/bin/usb/main.rs | 11 +- 5 files changed, 110 insertions(+), 131 deletions(-) diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 5a5a6d7a..af5986c1 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -10,3 +10,4 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } cortex-m = "0.7.3" num-traits = { version = "0.2.14", default-features = false } +heapless = "0.7.10" \ No newline at end of file diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index bcb838ff..491acf4d 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,8 +1,11 @@ +use heapless::Vec; + use super::class::UsbClass; use super::descriptor::{BosWriter, DescriptorWriter}; use super::driver::{Driver, EndpointAllocError}; use super::types::*; use super::UsbDevice; +use super::MAX_CLASS_COUNT; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -116,6 +119,7 @@ impl<'a> Config<'a> { /// Used to build new [`UsbDevice`]s. pub struct UsbDeviceBuilder<'d, D: Driver<'d>> { config: Config<'d>, + classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>, bus: D, next_interface_number: u8, @@ -165,6 +169,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { UsbDeviceBuilder { bus, config, + classes: Vec::new(), next_interface_number: 0, next_string_index: 4, @@ -175,7 +180,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { } /// Creates the [`UsbDevice`] instance with the configuration in this builder. - pub fn build(mut self, classes: &'d mut [&'d mut dyn UsbClass]) -> UsbDevice<'d, D> { + pub fn build(mut self) -> UsbDevice<'d, D> { self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); @@ -185,10 +190,16 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), - classes, + self.classes, ) } + pub fn add_class(&mut self, class: &'d mut dyn UsbClass) { + if self.classes.push(class).is_err() { + panic!("max class count reached") + } + } + /// Allocates a new interface number. pub fn alloc_interface(&mut self) -> InterfaceNumber { let number = self.next_interface_number; diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index ff3930af..2c00e881 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -14,6 +14,7 @@ pub mod types; mod util; use class::ControlInRequestStatus; +use heapless::Vec; use self::class::{RequestStatus, UsbClass}; use self::control::*; @@ -53,6 +54,8 @@ pub const CONFIGURATION_VALUE: u8 = 1; /// The default value for bAlternateSetting for all interfaces. pub const DEFAULT_ALTERNATE_SETTING: u8 = 0; +pub const MAX_CLASS_COUNT: usize = 4; + pub struct UsbDevice<'d, D: Driver<'d>> { bus: D::Bus, control: D::ControlPipe, @@ -67,7 +70,7 @@ pub struct UsbDevice<'d, D: Driver<'d>> { self_powered: bool, pending_address: u8, - classes: &'d mut [&'d mut dyn UsbClass], + classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>, } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { @@ -77,7 +80,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], - classes: &'d mut [&'d mut dyn UsbClass], + classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>, ) -> Self { let control = driver .alloc_control_pipe(config.max_packet_size_0 as u16) diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 5e4abfea..92cc16eb 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,5 +1,8 @@ -use core::mem; +use core::cell::{Cell, UnsafeCell}; +use core::mem::{self, MaybeUninit}; +use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; +use embassy::blocking_mutex::CriticalSectionMutex; use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; use embassy_usb::control::{self, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; @@ -25,6 +28,18 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; +pub struct State { + control: MaybeUninit, +} + +impl State { + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + } + } +} + /// Packet level implementation of a CDC-ACM serial port. /// /// This class can be used directly and it has the least overhead due to directly reading and @@ -45,21 +60,32 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { pub data_if: InterfaceNumber, pub read_ep: D::EndpointOut, pub write_ep: D::EndpointIn, - pub control: CdcAcmControl, + control: &'d ControlShared, } -pub struct CdcAcmControl { - pub comm_if: InterfaceNumber, - pub line_coding: LineCoding, - pub dtr: bool, - pub rts: bool, +struct Control { + comm_if: InterfaceNumber, + shared: UnsafeCell, } -impl UsbClass for CdcAcmControl { +/// Shared data between Control and CdcAcmClass +struct ControlShared { + line_coding: CriticalSectionMutex>, + dtr: AtomicBool, + rts: AtomicBool, +} + +impl Control { + fn shared(&mut self) -> &ControlShared { + unsafe { &*(self.shared.get() as *const _) } + } +} +impl UsbClass for Control { fn reset(&mut self) { - self.line_coding = LineCoding::default(); - self.dtr = false; - self.rts = false; + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(LineCoding::default())); + shared.dtr.store(false, Ordering::Relaxed); + shared.rts.store(false, Ordering::Relaxed); } fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { @@ -77,18 +103,25 @@ impl UsbClass for CdcAcmControl { RequestStatus::Accepted } REQ_SET_LINE_CODING if data.len() >= 7 => { - self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); - self.line_coding.stop_bits = data[4].into(); - self.line_coding.parity_type = data[5].into(); - self.line_coding.data_bits = data[6]; - info!("Set line coding to: {:?}", self.line_coding); + let coding = LineCoding { + data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), + stop_bits: data[4].into(), + parity_type: data[5].into(), + data_bits: data[6], + }; + self.shared().line_coding.lock(|x| x.set(coding)); + info!("Set line coding to: {:?}", coding); RequestStatus::Accepted } REQ_SET_CONTROL_LINE_STATE => { - self.dtr = (req.value & 0x0001) != 0; - self.rts = (req.value & 0x0002) != 0; - info!("Set dtr {}, rts {}", self.dtr, self.rts); + let dtr = (req.value & 0x0001) != 0; + let rts = (req.value & 0x0002) != 0; + + let shared = self.shared(); + shared.dtr.store(dtr, Ordering::Relaxed); + shared.rts.store(rts, Ordering::Relaxed); + info!("Set dtr {}, rts {}", dtr, rts); RequestStatus::Accepted } @@ -112,11 +145,12 @@ impl UsbClass for CdcAcmControl { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); + let coding = self.shared().line_coding.lock(|x| x.get()); let mut data = [0; 7]; - data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes()); - data[4] = self.line_coding.stop_bits as u8; - data[5] = self.line_coding.parity_type as u8; - data[6] = self.line_coding.data_bits; + data[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + data[4] = coding.stop_bits as u8; + data[5] = coding.parity_type as u8; + data[6] = coding.data_bits; control.accept(&data) } _ => control.reject(), @@ -127,7 +161,11 @@ impl UsbClass for CdcAcmControl { impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. - pub fn new(builder: &mut UsbDeviceBuilder<'d, D>, max_packet_size: u16) -> Self { + pub fn new( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State, + max_packet_size: u16, + ) -> Self { let comm_if = builder.alloc_interface(); let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); let data_if = builder.alloc_interface(); @@ -207,22 +245,29 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { builder.config_descriptor.endpoint(write_ep.info()).unwrap(); builder.config_descriptor.endpoint(read_ep.info()).unwrap(); + let control = state.control.write(Control { + comm_if, + shared: UnsafeCell::new(ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + }), + }); + + let control_shared = unsafe { &*(control.shared.get() as *const _) }; + builder.add_class(control); + CdcAcmClass { comm_ep, data_if, read_ep, write_ep, - control: CdcAcmControl { - comm_if, - dtr: false, - rts: false, - line_coding: LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - }, - }, + control: control_shared, } } @@ -234,18 +279,18 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// Gets the current line coding. The line coding contains information that's mainly relevant /// for USB to UART serial port emulators, and can be ignored if not relevant. - pub fn line_coding(&self) -> &LineCoding { - &self.control.line_coding + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(|x| x.get()) } /// Gets the DTR (data terminal ready) state pub fn dtr(&self) -> bool { - self.control.dtr + self.control.dtr.load(Ordering::Relaxed) } /// Gets the RTS (request to send) state pub fn rts(&self) -> bool { - self.control.rts + self.control.rts.load(Ordering::Relaxed) } /// Writes a single packet into the IN endpoint. @@ -264,88 +309,6 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { } } -/* -impl UsbClass for CdcAcmClass<'_, B> { - fn get_configuration_descriptors(&self, builder.config_descriptor: &mut Descriptorbuilder.config_descriptor) -> Result<()> { - - Ok(()) - } - - fn reset(&mut self) { - self.line_coding = LineCoding::default(); - self.dtr = false; - self.rts = false; - } - - fn control_in(&mut self, xfer: ControlIn) { - let req = xfer.request(); - - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return; - } - - match req.request { - // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. - REQ_GET_LINE_CODING if req.length == 7 => { - xfer.accept(|data| { - data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes()); - data[4] = self.line_coding.stop_bits as u8; - data[5] = self.line_coding.parity_type as u8; - data[6] = self.line_coding.data_bits; - - Ok(7) - }) - .ok(); - } - _ => { - xfer.reject().ok(); - } - } - } - - fn control_out(&mut self, xfer: ControlOut) { - let req = xfer.request(); - - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return; - } - - match req.request { - REQ_SEND_ENCAPSULATED_COMMAND => { - // We don't actually support encapsulated commands but pretend we do for standards - // compatibility. - xfer.accept().ok(); - } - REQ_SET_LINE_CODING if xfer.data().len() >= 7 => { - self.line_coding.data_rate = - u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap()); - self.line_coding.stop_bits = xfer.data()[4].into(); - self.line_coding.parity_type = xfer.data()[5].into(); - self.line_coding.data_bits = xfer.data()[6]; - - xfer.accept().ok(); - } - REQ_SET_CONTROL_LINE_STATE => { - self.dtr = (req.value & 0x0001) != 0; - self.rts = (req.value & 0x0002) != 0; - - xfer.accept().ok(); - } - _ => { - xfer.reject().ok(); - } - }; - } -} - - */ - /// Number of stop bits for LineCoding #[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] pub enum StopBits { @@ -393,7 +356,7 @@ impl From for ParityType { /// /// This is provided by the host for specifying the standard UART parameters such as baud rate. Can /// be ignored if you don't plan to interface with a physical UART. -#[derive(defmt::Format)] +#[derive(Clone, Copy, defmt::Format)] pub struct LineCoding { stop_bits: StopBits, data_bits: u8, diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 73ac3a21..398dd07b 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -16,12 +16,11 @@ use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; -use embassy_usb::class::UsbClass; use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; use futures::future::join3; -use crate::cdc_acm::CdcAcmClass; +use crate::cdc_acm::{CdcAcmClass, State}; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { @@ -48,6 +47,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut device_descriptor = [0; 256]; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; + + let mut state = State::new(); + let mut builder = UsbDeviceBuilder::new( driver, config, @@ -57,11 +59,10 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - let mut class = CdcAcmClass::new(&mut builder, 64); + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); // Build the builder. - let mut classes: [&mut dyn UsbClass; 1] = [&mut class.control]; - let mut usb = builder.build(&mut classes); + let mut usb = builder.build(); // Run the USB device. let fut1 = usb.run(); From 3412e5dc4a771c33dc8db644ba82c071ea486d6d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:16:45 +0200 Subject: [PATCH 10/50] usb: cleanup giant matches in control code. --- embassy-usb/src/lib.rs | 122 +++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 67 deletions(-) diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 2c00e881..7d631d17 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -175,66 +175,53 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; const DEFAULT_ALTERNATE_SETTING_U16: u16 = DEFAULT_ALTERNATE_SETTING as u16; - match req.request_type { - RequestType::Standard => match (req.recipient, req.request, req.value) { - ( - Recipient::Device, - Request::CLEAR_FEATURE, - Request::FEATURE_DEVICE_REMOTE_WAKEUP, - ) => { + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { + (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = false; self.control.accept(); } - - (Recipient::Endpoint, Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { - //self.bus.set_stalled(((req.index as u8) & 0x8f).into(), false); - self.control.accept(); - } - - ( - Recipient::Device, - Request::SET_FEATURE, - Request::FEATURE_DEVICE_REMOTE_WAKEUP, - ) => { + (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = true; self.control.accept(); } - - (Recipient::Endpoint, Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { - self.bus - .set_stalled(((req.index as u8) & 0x8f).into(), true); - self.control.accept(); - } - - (Recipient::Device, Request::SET_ADDRESS, 1..=127) => { + (Request::SET_ADDRESS, 1..=127) => { self.pending_address = req.value as u8; - - // on NRF the hardware auto-handles SET_ADDRESS. self.control.accept(); } - - (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { + (Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { self.device_state = UsbDeviceState::Configured; self.control.accept(); } - - (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => { - match self.device_state { - UsbDeviceState::Default => { - self.control.accept(); - } - _ => { - self.device_state = UsbDeviceState::Addressed; - self.control.accept(); - } + (Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => match self.device_state { + UsbDeviceState::Default => { + self.control.accept(); } - } - - (Recipient::Interface, Request::SET_INTERFACE, DEFAULT_ALTERNATE_SETTING_U16) => { + _ => { + self.device_state = UsbDeviceState::Addressed; + self.control.accept(); + } + }, + _ => self.control.reject(), + }, + (RequestType::Standard, Recipient::Interface) => match (req.request, req.value) { + (Request::SET_INTERFACE, DEFAULT_ALTERNATE_SETTING_U16) => { // TODO: do something when alternate settings are implemented self.control.accept(); } - + _ => self.control.reject(), + }, + (RequestType::Standard, Recipient::Endpoint) => match (req.request, req.value) { + (Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + let ep_addr = ((req.index as u8) & 0x8f).into(); + self.bus.set_stalled(ep_addr, true); + self.control.accept(); + } + (Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + let ep_addr = ((req.index as u8) & 0x8f).into(); + self.bus.set_stalled(ep_addr, false); + self.control.accept(); + } _ => self.control.reject(), }, _ => self.control.reject(), @@ -260,9 +247,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - match req.request_type { - RequestType::Standard => match (req.recipient, req.request) { - (Recipient::Device, Request::GET_STATUS) => { + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Device) => match req.request { + Request::GET_STATUS => { let mut status: u16 = 0x0000; if self.self_powered { status |= 0x0001; @@ -272,40 +259,41 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } self.control.accept_in(&status.to_le_bytes()).await; } - - (Recipient::Interface, Request::GET_STATUS) => { - let status: u16 = 0x0000; - self.control.accept_in(&status.to_le_bytes()).await; - } - - (Recipient::Endpoint, Request::GET_STATUS) => { - let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into(); - let mut status: u16 = 0x0000; - if self.bus.is_stalled(ep_addr) { - status |= 0x0001; - } - self.control.accept_in(&status.to_le_bytes()).await; - } - - (Recipient::Device, Request::GET_DESCRIPTOR) => { + Request::GET_DESCRIPTOR => { self.handle_get_descriptor(req).await; } - - (Recipient::Device, Request::GET_CONFIGURATION) => { + Request::GET_CONFIGURATION => { let status = match self.device_state { UsbDeviceState::Configured => CONFIGURATION_VALUE, _ => CONFIGURATION_NONE, }; self.control.accept_in(&status.to_le_bytes()).await; } - - (Recipient::Interface, Request::GET_INTERFACE) => { + _ => self.control.reject(), + }, + (RequestType::Standard, Recipient::Interface) => match req.request { + Request::GET_STATUS => { + let status: u16 = 0x0000; + self.control.accept_in(&status.to_le_bytes()).await; + } + Request::GET_INTERFACE => { // TODO: change when alternate settings are implemented let status = DEFAULT_ALTERNATE_SETTING; self.control.accept_in(&status.to_le_bytes()).await; } _ => self.control.reject(), }, + (RequestType::Standard, Recipient::Endpoint) => match req.request { + Request::GET_STATUS => { + let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into(); + let mut status: u16 = 0x0000; + if self.bus.is_stalled(ep_addr) { + status |= 0x0001; + } + self.control.accept_in(&status.to_le_bytes()).await; + } + _ => self.control.reject(), + }, _ => self.control.reject(), } } From 15cc97d794d8b4baa6c1a8f1ed6c64468701c9e7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:19:07 +0200 Subject: [PATCH 11/50] usb: associate ControlHandlers with interfaces, automatically route requests. --- embassy-usb/src/builder.rs | 32 ++++++---- embassy-usb/src/class.rs | 27 +++------ embassy-usb/src/lib.rs | 91 +++++++++++++++-------------- examples/nrf/src/bin/usb/cdc_acm.rs | 53 ++++++----------- 4 files changed, 94 insertions(+), 109 deletions(-) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 491acf4d..98b55adf 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,11 +1,11 @@ use heapless::Vec; -use super::class::UsbClass; +use super::class::ControlHandler; use super::descriptor::{BosWriter, DescriptorWriter}; use super::driver::{Driver, EndpointAllocError}; use super::types::*; use super::UsbDevice; -use super::MAX_CLASS_COUNT; +use super::MAX_INTERFACE_COUNT; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -119,7 +119,7 @@ impl<'a> Config<'a> { /// Used to build new [`UsbDevice`]s. pub struct UsbDeviceBuilder<'d, D: Driver<'d>> { config: Config<'d>, - classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>, + interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, bus: D, next_interface_number: u8, @@ -169,7 +169,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { UsbDeviceBuilder { bus, config, - classes: Vec::new(), + interfaces: Vec::new(), next_interface_number: 0, next_string_index: 4, @@ -190,16 +190,10 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), - self.classes, + self.interfaces, ) } - pub fn add_class(&mut self, class: &'d mut dyn UsbClass) { - if self.classes.push(class).is_err() { - panic!("max class count reached") - } - } - /// Allocates a new interface number. pub fn alloc_interface(&mut self) -> InterfaceNumber { let number = self.next_interface_number; @@ -208,6 +202,22 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { InterfaceNumber::new(number) } + /// Allocates a new interface number, with a handler that will be called + /// for all the control requests directed to it. + pub fn alloc_interface_with_handler( + &mut self, + handler: &'d mut dyn ControlHandler, + ) -> InterfaceNumber { + let number = self.next_interface_number; + self.next_interface_number += 1; + + if self.interfaces.push((number, handler)).is_err() { + panic!("max class count reached") + } + + InterfaceNumber::new(number) + } + /// Allocates a new string index. pub fn alloc_string(&mut self) -> StringIndex { let index = self.next_string_index; diff --git a/embassy-usb/src/class.rs b/embassy-usb/src/class.rs index a0141e31..754e3a20 100644 --- a/embassy-usb/src/class.rs +++ b/embassy-usb/src/class.rs @@ -3,22 +3,15 @@ use crate::control::Request; #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RequestStatus { - Unhandled, Accepted, Rejected, } -impl Default for RequestStatus { - fn default() -> Self { - RequestStatus::Unhandled - } -} - /// A trait for implementing USB classes. /// /// All methods are optional callbacks that will be called by /// [`UsbDevice::run()`](crate::UsbDevice::run) -pub trait UsbClass { +pub trait ControlHandler { /// Called after a USB reset after the bus reset sequence is complete. fn reset(&mut self) {} @@ -35,7 +28,9 @@ pub trait UsbClass { /// /// * `req` - The request from the SETUP packet. /// * `data` - The data from the request. - fn control_out(&mut self, req: Request, data: &[u8]) -> RequestStatus; + fn control_out(&mut self, req: Request, data: &[u8]) -> RequestStatus { + RequestStatus::Rejected + } /// Called when a control request is received with direction DeviceToHost. /// @@ -56,7 +51,9 @@ pub trait UsbClass { &mut self, req: Request, control: ControlIn<'a>, - ) -> ControlInRequestStatus<'a>; + ) -> ControlInRequestStatus<'a> { + control.reject() + } } /// Handle for a control IN transfer. When implementing a class, use the methods of this object to @@ -84,14 +81,6 @@ impl<'a> ControlIn<'a> { ControlIn { buf } } - /// Ignores the request and leaves it unhandled. - pub fn ignore(self) -> ControlInRequestStatus<'a> { - ControlInRequestStatus { - status: RequestStatus::Unhandled, - data: &[], - } - } - /// Accepts the transfer with the supplied buffer. pub fn accept(self, data: &[u8]) -> ControlInRequestStatus<'a> { assert!(data.len() < self.buf.len()); @@ -108,7 +97,7 @@ impl<'a> ControlIn<'a> { /// Rejects the transfer by stalling the pipe. pub fn reject(self) -> ControlInRequestStatus<'a> { ControlInRequestStatus { - status: RequestStatus::Unhandled, + status: RequestStatus::Rejected, data: &[], } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 7d631d17..9076123a 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -13,10 +13,9 @@ pub mod driver; pub mod types; mod util; -use class::ControlInRequestStatus; use heapless::Vec; -use self::class::{RequestStatus, UsbClass}; +use self::class::{ControlHandler, RequestStatus}; use self::control::*; use self::descriptor::*; use self::driver::*; @@ -54,7 +53,7 @@ pub const CONFIGURATION_VALUE: u8 = 1; /// The default value for bAlternateSetting for all interfaces. pub const DEFAULT_ALTERNATE_SETTING: u8 = 0; -pub const MAX_CLASS_COUNT: usize = 4; +pub const MAX_INTERFACE_COUNT: usize = 4; pub struct UsbDevice<'d, D: Driver<'d>> { bus: D::Bus, @@ -70,7 +69,7 @@ pub struct UsbDevice<'d, D: Driver<'d>> { self_powered: bool, pending_address: u8, - classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>, + interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { @@ -80,7 +79,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], - classes: Vec<&'d mut dyn UsbClass, MAX_CLASS_COUNT>, + interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, ) -> Self { let control = driver .alloc_control_pipe(config.max_packet_size_0 as u16) @@ -101,7 +100,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { remote_wakeup_enabled: false, self_powered: false, pending_address: 0, - classes, + interfaces, } } @@ -118,8 +117,8 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.remote_wakeup_enabled = false; self.pending_address = 0; - for c in self.classes.iter_mut() { - c.reset(); + for (_, h) in self.interfaces.iter_mut() { + h.reset(); } } Event::Resume => {} @@ -153,24 +152,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } async fn handle_control_out(&mut self, req: Request) { - { - let mut buf = [0; 128]; - let data = if req.length > 0 { - let size = self.control.data_out(&mut buf).await.unwrap(); - &buf[0..size] - } else { - &[] - }; - - for c in self.classes.iter_mut() { - match c.control_out(req, data) { - RequestStatus::Accepted => return self.control.accept(), - RequestStatus::Rejected => return self.control.reject(), - RequestStatus::Unhandled => (), - } - } - } - const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; const DEFAULT_ALTERNATE_SETTING_U16: u16 = DEFAULT_ALTERNATE_SETTING as u16; @@ -224,29 +205,33 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } _ => self.control.reject(), }, + (RequestType::Class, Recipient::Interface) => { + let mut buf = [0; 128]; + let data = if req.length > 0 { + let size = self.control.data_out(&mut buf).await.unwrap(); + &buf[0..size] + } else { + &[] + }; + + let handler = self + .interfaces + .iter_mut() + .find(|(i, _)| req.index == *i as _) + .map(|(_, h)| h); + match handler { + Some(handler) => match handler.control_out(req, data) { + RequestStatus::Accepted => return self.control.accept(), + RequestStatus::Rejected => return self.control.reject(), + }, + None => self.control.reject(), + } + } _ => self.control.reject(), } } async fn handle_control_in(&mut self, req: Request) { - let mut buf = [0; 128]; - for c in self.classes.iter_mut() { - match c.control_in(req, class::ControlIn::new(&mut buf)) { - ControlInRequestStatus { - status: RequestStatus::Accepted, - data, - } => return self.control.accept_in(data).await, - ControlInRequestStatus { - status: RequestStatus::Rejected, - .. - } => return self.control.reject(), - ControlInRequestStatus { - status: RequestStatus::Unhandled, - .. - } => (), - } - } - match (req.request_type, req.recipient) { (RequestType::Standard, Recipient::Device) => match req.request { Request::GET_STATUS => { @@ -294,6 +279,24 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } _ => self.control.reject(), }, + (RequestType::Class, Recipient::Interface) => { + let mut buf = [0; 128]; + let handler = self + .interfaces + .iter_mut() + .find(|(i, _)| req.index == *i as _) + .map(|(_, h)| h); + match handler { + Some(handler) => { + let resp = handler.control_in(req, class::ControlIn::new(&mut buf)); + match resp.status { + RequestStatus::Accepted => self.control.accept_in(resp.data).await, + RequestStatus::Rejected => self.control.reject(), + } + } + None => self.control.reject(), + } + } _ => self.control.reject(), } } diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 92cc16eb..f4d42979 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,7 +3,7 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; +use embassy_usb::class::{ControlHandler, ControlInRequestStatus, RequestStatus}; use embassy_usb::control::{self, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -64,7 +64,6 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { } struct Control { - comm_if: InterfaceNumber, shared: UnsafeCell, } @@ -80,7 +79,7 @@ impl Control { unsafe { &*(self.shared.get() as *const _) } } } -impl UsbClass for Control { +impl ControlHandler for Control { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -89,13 +88,6 @@ impl UsbClass for Control { } fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return RequestStatus::Unhandled; - } - match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards @@ -134,13 +126,6 @@ impl UsbClass for Control { req: Request, control: embassy_usb::class::ControlIn<'a>, ) -> ControlInRequestStatus<'a> { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return control.ignore(); - } - match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { @@ -166,7 +151,22 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { state: &'d mut State, max_packet_size: u16, ) -> Self { - let comm_if = builder.alloc_interface(); + let control = state.control.write(Control { + shared: UnsafeCell::new(ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + }), + }); + + let control_shared = unsafe { &*(control.shared.get() as *const _) }; + + let comm_if = builder.alloc_interface_with_handler(control); let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); let data_if = builder.alloc_interface(); let read_ep = builder.alloc_bulk_endpoint_out(max_packet_size); @@ -245,23 +245,6 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { builder.config_descriptor.endpoint(write_ep.info()).unwrap(); builder.config_descriptor.endpoint(read_ep.info()).unwrap(); - let control = state.control.write(Control { - comm_if, - shared: UnsafeCell::new(ControlShared { - dtr: AtomicBool::new(false), - rts: AtomicBool::new(false), - line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - })), - }), - }); - - let control_shared = unsafe { &*(control.shared.get() as *const _) }; - builder.add_class(control); - CdcAcmClass { comm_ep, data_if, From 2b547f311efc7feaa3afbb9f1bf4100c5502839e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:27:21 +0200 Subject: [PATCH 12/50] usb: move all control-related stuff to `mod control`. --- embassy-usb/src/builder.rs | 2 +- embassy-usb/src/class.rs | 104 ---------------------------- embassy-usb/src/control.rs | 103 +++++++++++++++++++++++++++ embassy-usb/src/lib.rs | 4 +- examples/nrf/src/bin/usb/cdc_acm.rs | 7 +- 5 files changed, 109 insertions(+), 111 deletions(-) delete mode 100644 embassy-usb/src/class.rs diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 98b55adf..dfd36bdb 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,6 +1,6 @@ use heapless::Vec; -use super::class::ControlHandler; +use super::control::ControlHandler; use super::descriptor::{BosWriter, DescriptorWriter}; use super::driver::{Driver, EndpointAllocError}; use super::types::*; diff --git a/embassy-usb/src/class.rs b/embassy-usb/src/class.rs deleted file mode 100644 index 754e3a20..00000000 --- a/embassy-usb/src/class.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::control::Request; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RequestStatus { - Accepted, - Rejected, -} - -/// A trait for implementing USB classes. -/// -/// All methods are optional callbacks that will be called by -/// [`UsbDevice::run()`](crate::UsbDevice::run) -pub trait ControlHandler { - /// Called after a USB reset after the bus reset sequence is complete. - fn reset(&mut self) {} - - /// Called when a control request is received with direction HostToDevice. - /// - /// All requests are passed to classes in turn, which can choose to accept, ignore or report an - /// error. Classes can even choose to override standard requests, but doing that is rarely - /// necessary. - /// - /// When implementing your own class, you should ignore any requests that are not meant for your - /// class so that any other classes in the composite device can process them. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - /// * `data` - The data from the request. - fn control_out(&mut self, req: Request, data: &[u8]) -> RequestStatus { - RequestStatus::Rejected - } - - /// Called when a control request is received with direction DeviceToHost. - /// - /// All requests are passed to classes in turn, which can choose to accept, ignore or report an - /// error. Classes can even choose to override standard requests, but doing that is rarely - /// necessary. - /// - /// See [`ControlIn`] for how to respond to the transfer. - /// - /// When implementing your own class, you should ignore any requests that are not meant for your - /// class so that any other classes in the composite device can process them. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - /// * `control` - The control pipe. - fn control_in<'a>( - &mut self, - req: Request, - control: ControlIn<'a>, - ) -> ControlInRequestStatus<'a> { - control.reject() - } -} - -/// Handle for a control IN transfer. When implementing a class, use the methods of this object to -/// response to the transfer with either data or an error (STALL condition). To ignore the request -/// and pass it on to the next class, call [`Self::ignore()`]. -pub struct ControlIn<'a> { - buf: &'a mut [u8], -} - -#[derive(Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControlInRequestStatus<'a> { - pub(crate) status: RequestStatus, - pub(crate) data: &'a [u8], -} - -impl<'a> ControlInRequestStatus<'a> { - pub fn status(&self) -> RequestStatus { - self.status - } -} - -impl<'a> ControlIn<'a> { - pub(crate) fn new(buf: &'a mut [u8]) -> Self { - ControlIn { buf } - } - - /// Accepts the transfer with the supplied buffer. - pub fn accept(self, data: &[u8]) -> ControlInRequestStatus<'a> { - assert!(data.len() < self.buf.len()); - - let buf = &mut self.buf[0..data.len()]; - buf.copy_from_slice(data); - - ControlInRequestStatus { - status: RequestStatus::Accepted, - data: buf, - } - } - - /// Rejects the transfer by stalling the pipe. - pub fn reject(self) -> ControlInRequestStatus<'a> { - ControlInRequestStatus { - status: RequestStatus::Rejected, - data: &[], - } - } -} diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 77bc10aa..ae4ad04a 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -123,3 +123,106 @@ impl Request { ((self.value >> 8) as u8, self.value as u8) } } + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RequestStatus { + Accepted, + Rejected, +} + +/// A trait for implementing USB classes. +/// +/// All methods are optional callbacks that will be called by +/// [`UsbDevice::run()`](crate::UsbDevice::run) +pub trait ControlHandler { + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) {} + + /// Called when a control request is received with direction HostToDevice. + /// + /// All requests are passed to classes in turn, which can choose to accept, ignore or report an + /// error. Classes can even choose to override standard requests, but doing that is rarely + /// necessary. + /// + /// When implementing your own class, you should ignore any requests that are not meant for your + /// class so that any other classes in the composite device can process them. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `data` - The data from the request. + fn control_out(&mut self, req: Request, data: &[u8]) -> RequestStatus { + RequestStatus::Rejected + } + + /// Called when a control request is received with direction DeviceToHost. + /// + /// All requests are passed to classes in turn, which can choose to accept, ignore or report an + /// error. Classes can even choose to override standard requests, but doing that is rarely + /// necessary. + /// + /// See [`ControlIn`] for how to respond to the transfer. + /// + /// When implementing your own class, you should ignore any requests that are not meant for your + /// class so that any other classes in the composite device can process them. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `control` - The control pipe. + fn control_in<'a>( + &mut self, + req: Request, + control: ControlIn<'a>, + ) -> ControlInRequestStatus<'a> { + control.reject() + } +} + +/// Handle for a control IN transfer. When implementing a class, use the methods of this object to +/// response to the transfer with either data or an error (STALL condition). To ignore the request +/// and pass it on to the next class, call [`Self::ignore()`]. +pub struct ControlIn<'a> { + buf: &'a mut [u8], +} + +#[derive(Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ControlInRequestStatus<'a> { + pub(crate) status: RequestStatus, + pub(crate) data: &'a [u8], +} + +impl<'a> ControlInRequestStatus<'a> { + pub fn status(&self) -> RequestStatus { + self.status + } +} + +impl<'a> ControlIn<'a> { + pub(crate) fn new(buf: &'a mut [u8]) -> Self { + ControlIn { buf } + } + + /// Accepts the transfer with the supplied buffer. + pub fn accept(self, data: &[u8]) -> ControlInRequestStatus<'a> { + assert!(data.len() < self.buf.len()); + + let buf = &mut self.buf[0..data.len()]; + buf.copy_from_slice(data); + + ControlInRequestStatus { + status: RequestStatus::Accepted, + data: buf, + } + } + + /// Rejects the transfer by stalling the pipe. + pub fn reject(self) -> ControlInRequestStatus<'a> { + ControlInRequestStatus { + status: RequestStatus::Rejected, + data: &[], + } + } +} diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 9076123a..b6c95ac6 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -6,7 +6,6 @@ pub(crate) mod fmt; mod builder; -pub mod class; pub mod control; pub mod descriptor; pub mod driver; @@ -15,7 +14,6 @@ mod util; use heapless::Vec; -use self::class::{ControlHandler, RequestStatus}; use self::control::*; use self::descriptor::*; use self::driver::*; @@ -288,7 +286,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { .map(|(_, h)| h); match handler { Some(handler) => { - let resp = handler.control_in(req, class::ControlIn::new(&mut buf)); + let resp = handler.control_in(req, ControlIn::new(&mut buf)); match resp.status { RequestStatus::Accepted => self.control.accept_in(resp.data).await, RequestStatus::Rejected => self.control.reject(), diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index f4d42979..25c3108a 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,8 +3,9 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::class::{ControlHandler, ControlInRequestStatus, RequestStatus}; -use embassy_usb::control::{self, Request}; +use embassy_usb::control::{ + self, ControlHandler, ControlIn, ControlInRequestStatus, Request, RequestStatus, +}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -124,7 +125,7 @@ impl ControlHandler for Control { fn control_in<'a>( &mut self, req: Request, - control: embassy_usb::class::ControlIn<'a>, + control: ControlIn<'a>, ) -> ControlInRequestStatus<'a> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. From bfce731982af1f053b1b727e49e920fc496a9546 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:30:08 +0200 Subject: [PATCH 13/50] usb: nicer names for control structs. --- embassy-usb/src/control.rs | 34 +++++++++++++---------------- embassy-usb/src/lib.rs | 10 ++++----- examples/nrf/src/bin/usb/cdc_acm.rs | 20 ++++++----------- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index ae4ad04a..05536dab 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -126,7 +126,7 @@ impl Request { #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum RequestStatus { +pub enum OutResponse { Accepted, Rejected, } @@ -152,8 +152,8 @@ pub trait ControlHandler { /// /// * `req` - The request from the SETUP packet. /// * `data` - The data from the request. - fn control_out(&mut self, req: Request, data: &[u8]) -> RequestStatus { - RequestStatus::Rejected + fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { + OutResponse::Rejected } /// Called when a control request is received with direction DeviceToHost. @@ -171,11 +171,7 @@ pub trait ControlHandler { /// /// * `req` - The request from the SETUP packet. /// * `control` - The control pipe. - fn control_in<'a>( - &mut self, - req: Request, - control: ControlIn<'a>, - ) -> ControlInRequestStatus<'a> { + fn control_in<'a>(&mut self, req: Request, control: ControlIn<'a>) -> InResponse<'a> { control.reject() } } @@ -189,14 +185,14 @@ pub struct ControlIn<'a> { #[derive(Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ControlInRequestStatus<'a> { - pub(crate) status: RequestStatus, +pub struct InResponse<'a> { + pub(crate) response: OutResponse, pub(crate) data: &'a [u8], } -impl<'a> ControlInRequestStatus<'a> { - pub fn status(&self) -> RequestStatus { - self.status +impl<'a> InResponse<'a> { + pub fn status(&self) -> OutResponse { + self.response } } @@ -206,22 +202,22 @@ impl<'a> ControlIn<'a> { } /// Accepts the transfer with the supplied buffer. - pub fn accept(self, data: &[u8]) -> ControlInRequestStatus<'a> { + pub fn accept(self, data: &[u8]) -> InResponse<'a> { assert!(data.len() < self.buf.len()); let buf = &mut self.buf[0..data.len()]; buf.copy_from_slice(data); - ControlInRequestStatus { - status: RequestStatus::Accepted, + InResponse { + response: OutResponse::Accepted, data: buf, } } /// Rejects the transfer by stalling the pipe. - pub fn reject(self) -> ControlInRequestStatus<'a> { - ControlInRequestStatus { - status: RequestStatus::Rejected, + pub fn reject(self) -> InResponse<'a> { + InResponse { + response: OutResponse::Rejected, data: &[], } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index b6c95ac6..8d202402 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -219,8 +219,8 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { .map(|(_, h)| h); match handler { Some(handler) => match handler.control_out(req, data) { - RequestStatus::Accepted => return self.control.accept(), - RequestStatus::Rejected => return self.control.reject(), + OutResponse::Accepted => return self.control.accept(), + OutResponse::Rejected => return self.control.reject(), }, None => self.control.reject(), } @@ -287,9 +287,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { match handler { Some(handler) => { let resp = handler.control_in(req, ControlIn::new(&mut buf)); - match resp.status { - RequestStatus::Accepted => self.control.accept_in(resp.data).await, - RequestStatus::Rejected => self.control.reject(), + match resp.response { + OutResponse::Accepted => self.control.accept_in(resp.data).await, + OutResponse::Rejected => self.control.reject(), } } None => self.control.reject(), diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 25c3108a..141c6ecd 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,9 +3,7 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::control::{ - self, ControlHandler, ControlIn, ControlInRequestStatus, Request, RequestStatus, -}; +use embassy_usb::control::{self, ControlHandler, ControlIn, InResponse, OutResponse, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -88,12 +86,12 @@ impl ControlHandler for Control { shared.rts.store(false, Ordering::Relaxed); } - fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { + fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - RequestStatus::Accepted + OutResponse::Accepted } REQ_SET_LINE_CODING if data.len() >= 7 => { let coding = LineCoding { @@ -105,7 +103,7 @@ impl ControlHandler for Control { self.shared().line_coding.lock(|x| x.set(coding)); info!("Set line coding to: {:?}", coding); - RequestStatus::Accepted + OutResponse::Accepted } REQ_SET_CONTROL_LINE_STATE => { let dtr = (req.value & 0x0001) != 0; @@ -116,17 +114,13 @@ impl ControlHandler for Control { shared.rts.store(rts, Ordering::Relaxed); info!("Set dtr {}, rts {}", dtr, rts); - RequestStatus::Accepted + OutResponse::Accepted } - _ => RequestStatus::Rejected, + _ => OutResponse::Rejected, } } - fn control_in<'a>( - &mut self, - req: Request, - control: ControlIn<'a>, - ) -> ControlInRequestStatus<'a> { + fn control_in<'a>(&mut self, req: Request, control: ControlIn<'a>) -> InResponse<'a> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { From e99a3a1da42649bb9ad9a64508d14d6461be331d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:34:24 +0200 Subject: [PATCH 14/50] usb: simplify buffer handling for Control IN transfers. --- embassy-usb/src/control.rs | 58 +++++------------------------ embassy-usb/src/lib.rs | 9 ++--- examples/nrf/src/bin/usb/cdc_acm.rs | 17 ++++----- 3 files changed, 21 insertions(+), 63 deletions(-) diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 05536dab..567a595a 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -131,6 +131,13 @@ pub enum OutResponse { Rejected, } +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InResponse { + Accepted(usize), + Rejected, +} + /// A trait for implementing USB classes. /// /// All methods are optional callbacks that will be called by @@ -171,54 +178,7 @@ pub trait ControlHandler { /// /// * `req` - The request from the SETUP packet. /// * `control` - The control pipe. - fn control_in<'a>(&mut self, req: Request, control: ControlIn<'a>) -> InResponse<'a> { - control.reject() - } -} - -/// Handle for a control IN transfer. When implementing a class, use the methods of this object to -/// response to the transfer with either data or an error (STALL condition). To ignore the request -/// and pass it on to the next class, call [`Self::ignore()`]. -pub struct ControlIn<'a> { - buf: &'a mut [u8], -} - -#[derive(Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InResponse<'a> { - pub(crate) response: OutResponse, - pub(crate) data: &'a [u8], -} - -impl<'a> InResponse<'a> { - pub fn status(&self) -> OutResponse { - self.response - } -} - -impl<'a> ControlIn<'a> { - pub(crate) fn new(buf: &'a mut [u8]) -> Self { - ControlIn { buf } - } - - /// Accepts the transfer with the supplied buffer. - pub fn accept(self, data: &[u8]) -> InResponse<'a> { - assert!(data.len() < self.buf.len()); - - let buf = &mut self.buf[0..data.len()]; - buf.copy_from_slice(data); - - InResponse { - response: OutResponse::Accepted, - data: buf, - } - } - - /// Rejects the transfer by stalling the pipe. - pub fn reject(self) -> InResponse<'a> { - InResponse { - response: OutResponse::Rejected, - data: &[], - } + fn control_in(&mut self, req: Request, resp: &mut [u8]) -> InResponse { + InResponse::Rejected } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 8d202402..9ac55db7 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -278,7 +278,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { _ => self.control.reject(), }, (RequestType::Class, Recipient::Interface) => { - let mut buf = [0; 128]; let handler = self .interfaces .iter_mut() @@ -286,10 +285,10 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { .map(|(_, h)| h); match handler { Some(handler) => { - let resp = handler.control_in(req, ControlIn::new(&mut buf)); - match resp.response { - OutResponse::Accepted => self.control.accept_in(resp.data).await, - OutResponse::Rejected => self.control.reject(), + let mut buf = [0; 128]; + match handler.control_in(req, &mut buf) { + InResponse::Accepted(len) => self.control.accept_in(&buf[..len]).await, + InResponse::Rejected => self.control.reject(), } } None => self.control.reject(), diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 141c6ecd..4be35fd3 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,7 +3,7 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::control::{self, ControlHandler, ControlIn, InResponse, OutResponse, Request}; +use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -120,20 +120,19 @@ impl ControlHandler for Control { } } - fn control_in<'a>(&mut self, req: Request, control: ControlIn<'a>) -> InResponse<'a> { + fn control_in(&mut self, req: Request, resp: &mut [u8]) -> InResponse { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); let coding = self.shared().line_coding.lock(|x| x.get()); - let mut data = [0; 7]; - data[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - data[4] = coding.stop_bits as u8; - data[5] = coding.parity_type as u8; - data[6] = coding.data_bits; - control.accept(&data) + resp[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + resp[4] = coding.stop_bits as u8; + resp[5] = coding.parity_type as u8; + resp[6] = coding.data_bits; + InResponse::Accepted(7) } - _ => control.reject(), + _ => InResponse::Rejected, } } } From d7b1f8ca57eda98f1fc8d26c23a977a173f2b3f7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:37:29 +0200 Subject: [PATCH 15/50] usb: update docs on ControlHandler. --- embassy-usb/src/control.rs | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 567a595a..cdae2d97 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -138,7 +138,7 @@ pub enum InResponse { Rejected, } -/// A trait for implementing USB classes. +/// Handler for control requests. /// /// All methods are optional callbacks that will be called by /// [`UsbDevice::run()`](crate::UsbDevice::run) @@ -148,13 +148,6 @@ pub trait ControlHandler { /// Called when a control request is received with direction HostToDevice. /// - /// All requests are passed to classes in turn, which can choose to accept, ignore or report an - /// error. Classes can even choose to override standard requests, but doing that is rarely - /// necessary. - /// - /// When implementing your own class, you should ignore any requests that are not meant for your - /// class so that any other classes in the composite device can process them. - /// /// # Arguments /// /// * `req` - The request from the SETUP packet. @@ -165,19 +158,13 @@ pub trait ControlHandler { /// Called when a control request is received with direction DeviceToHost. /// - /// All requests are passed to classes in turn, which can choose to accept, ignore or report an - /// error. Classes can even choose to override standard requests, but doing that is rarely - /// necessary. - /// - /// See [`ControlIn`] for how to respond to the transfer. - /// - /// When implementing your own class, you should ignore any requests that are not meant for your - /// class so that any other classes in the composite device can process them. + /// You should write the response to `resp`, then return `InResponse::Accepted(len)` + /// with the length of the response. /// /// # Arguments /// /// * `req` - The request from the SETUP packet. - /// * `control` - The control pipe. + /// * `resp` - The buffer for you to write the response. fn control_in(&mut self, req: Request, resp: &mut [u8]) -> InResponse { InResponse::Rejected } From 46bafecb2a543c96a65780571bf4bd541dd68ea3 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Mon, 28 Mar 2022 10:46:26 -0400 Subject: [PATCH 16/50] Only copy through RAM when needed --- embassy-nrf/src/usb.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 2621e65f..d1c94dbb 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -20,6 +20,7 @@ pub use embassy_usb; use crate::interrupt::Interrupt; use crate::pac; +use crate::util::slice_in_ram; const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; @@ -408,10 +409,15 @@ unsafe fn write_dma(i: usize, buf: &[u8]) -> Result<(), WriteError> return Err(WriteError::BufferOverflow); } - // EasyDMA can't read FLASH, so we copy through RAM let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); - let ptr = ram_buf.as_mut_ptr() as *mut u8; - core::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); + let ptr = if !slice_in_ram(buf) { + // EasyDMA can't read FLASH, so we copy through RAM + let ptr = ram_buf.as_mut_ptr() as *mut u8; + core::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); + ptr + } else { + buf.as_ptr() + }; let epin = [ ®s.epin0, From a22639ad920fccfd909602f5c69147be911650eb Mon Sep 17 00:00:00 2001 From: alexmoon Date: Mon, 28 Mar 2022 10:49:17 -0400 Subject: [PATCH 17/50] Remove UnsafeCell from cdc_acm::Control --- examples/nrf/src/bin/usb/cdc_acm.rs | 53 +++++++++++++++++------------ 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 4be35fd3..4b492593 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,4 +1,4 @@ -use core::cell::{Cell, UnsafeCell}; +use core::cell::Cell; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; @@ -27,14 +27,16 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; -pub struct State { - control: MaybeUninit, +pub struct State<'a> { + control: MaybeUninit>, + shared: ControlShared, } -impl State { +impl<'a> State<'a> { pub fn new() -> Self { Self { control: MaybeUninit::uninit(), + shared: Default::default(), } } } @@ -62,8 +64,8 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { control: &'d ControlShared, } -struct Control { - shared: UnsafeCell, +struct Control<'a> { + shared: &'a ControlShared, } /// Shared data between Control and CdcAcmClass @@ -73,12 +75,28 @@ struct ControlShared { rts: AtomicBool, } -impl Control { - fn shared(&mut self) -> &ControlShared { - unsafe { &*(self.shared.get() as *const _) } +impl Default for ControlShared { + fn default() -> Self { + ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + } } } -impl ControlHandler for Control { + +impl<'a> Control<'a> { + fn shared(&mut self) -> &'a ControlShared { + self.shared + } +} + +impl<'a> ControlHandler for Control<'a> { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -142,23 +160,14 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. pub fn new( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State, + state: &'d mut State<'d>, max_packet_size: u16, ) -> Self { let control = state.control.write(Control { - shared: UnsafeCell::new(ControlShared { - dtr: AtomicBool::new(false), - rts: AtomicBool::new(false), - line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - })), - }), + shared: &state.shared, }); - let control_shared = unsafe { &*(control.shared.get() as *const _) }; + let control_shared = &state.shared; let comm_if = builder.alloc_interface_with_handler(control); let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); From c53bb7394a20e180e2ac7e81cc468025018bd1da Mon Sep 17 00:00:00 2001 From: alexmoon Date: Mon, 28 Mar 2022 20:10:13 -0400 Subject: [PATCH 18/50] Switch to ControlHandler owned bufs for control_in() --- embassy-usb/src/control.rs | 7 +++---- embassy-usb/src/lib.rs | 11 ++++------- examples/nrf/src/bin/usb/cdc_acm.rs | 14 ++++++++------ 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index cdae2d97..195b218d 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -133,8 +133,8 @@ pub enum OutResponse { #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InResponse { - Accepted(usize), +pub enum InResponse<'a> { + Accepted(&'a [u8]), Rejected, } @@ -164,8 +164,7 @@ pub trait ControlHandler { /// # Arguments /// /// * `req` - The request from the SETUP packet. - /// * `resp` - The buffer for you to write the response. - fn control_in(&mut self, req: Request, resp: &mut [u8]) -> InResponse { + fn control_in(&mut self, req: Request) -> InResponse<'_> { InResponse::Rejected } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 9ac55db7..5a82f5ca 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -284,13 +284,10 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { .find(|(i, _)| req.index == *i as _) .map(|(_, h)| h); match handler { - Some(handler) => { - let mut buf = [0; 128]; - match handler.control_in(req, &mut buf) { - InResponse::Accepted(len) => self.control.accept_in(&buf[..len]).await, - InResponse::Rejected => self.control.reject(), - } - } + Some(handler) => match handler.control_in(req) { + InResponse::Accepted(data) => self.control.accept_in(data).await, + InResponse::Rejected => self.control.reject(), + }, None => self.control.reject(), } } diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 4b492593..2a78324f 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -66,6 +66,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { struct Control<'a> { shared: &'a ControlShared, + buf: [u8; 7], } /// Shared data between Control and CdcAcmClass @@ -138,17 +139,17 @@ impl<'a> ControlHandler for Control<'a> { } } - fn control_in(&mut self, req: Request, resp: &mut [u8]) -> InResponse { + fn control_in(&mut self, req: Request) -> InResponse<'_> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); let coding = self.shared().line_coding.lock(|x| x.get()); - resp[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - resp[4] = coding.stop_bits as u8; - resp[5] = coding.parity_type as u8; - resp[6] = coding.data_bits; - InResponse::Accepted(7) + self.buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + self.buf[4] = coding.stop_bits as u8; + self.buf[5] = coding.parity_type as u8; + self.buf[6] = coding.data_bits; + InResponse::Accepted(&self.buf) } _ => InResponse::Rejected, } @@ -165,6 +166,7 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { ) -> Self { let control = state.control.write(Control { shared: &state.shared, + buf: [0; 7], }); let control_shared = &state.shared; From 13370c28db244edd24029d6066ac9e448bd419c9 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 15:09:24 -0400 Subject: [PATCH 19/50] Add a control_buf to UsbDevice --- embassy-usb/src/builder.rs | 14 ++++++++++++++ embassy-usb/src/control.rs | 2 +- embassy-usb/src/lib.rs | 10 ++++++---- examples/nrf/src/bin/usb/cdc_acm.rs | 19 ++++++++++--------- examples/nrf/src/bin/usb/main.rs | 2 ++ 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index dfd36bdb..0c118b78 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -120,6 +120,7 @@ impl<'a> Config<'a> { pub struct UsbDeviceBuilder<'d, D: Driver<'d>> { config: Config<'d>, interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, + control_buf: &'d mut [u8], bus: D, next_interface_number: u8, @@ -133,12 +134,17 @@ pub struct UsbDeviceBuilder<'d, D: Driver<'d>> { impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { /// Creates a builder for constructing a new [`UsbDevice`]. + /// + /// `control_buf` is a buffer used for USB control request data. It should be sized + /// large enough for the length of the largest control request (in or out) + /// anticipated by any class added to the device. pub fn new( bus: D, config: Config<'d>, device_descriptor_buf: &'d mut [u8], config_descriptor_buf: &'d mut [u8], bos_descriptor_buf: &'d mut [u8], + control_buf: &'d mut [u8], ) -> Self { // Magic values specified in USB-IF ECN on IADs. if config.composite_with_iads @@ -170,6 +176,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { bus, config, interfaces: Vec::new(), + control_buf, next_interface_number: 0, next_string_index: 4, @@ -191,6 +198,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), self.interfaces, + self.control_buf, ) } @@ -202,6 +210,12 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { InterfaceNumber::new(number) } + /// Returns the size of the control request data buffer. Can be used by + /// classes to validate the buffer is large enough for their needs. + pub fn control_buf_len(&self) -> usize { + self.control_buf.len() + } + /// Allocates a new interface number, with a handler that will be called /// for all the control requests directed to it. pub fn alloc_interface_with_handler( diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 195b218d..19c2c677 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -164,7 +164,7 @@ pub trait ControlHandler { /// # Arguments /// /// * `req` - The request from the SETUP packet. - fn control_in(&mut self, req: Request) -> InResponse<'_> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { InResponse::Rejected } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 5a82f5ca..a4b7dda3 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -61,6 +61,7 @@ pub struct UsbDevice<'d, D: Driver<'d>> { device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], + control_buf: &'d mut [u8], device_state: UsbDeviceState, remote_wakeup_enabled: bool, @@ -78,6 +79,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { config_descriptor: &'d [u8], bos_descriptor: &'d [u8], interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, + control_buf: &'d mut [u8], ) -> Self { let control = driver .alloc_control_pipe(config.max_packet_size_0 as u16) @@ -94,6 +96,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { device_descriptor, config_descriptor, bos_descriptor, + control_buf, device_state: UsbDeviceState::Default, remote_wakeup_enabled: false, self_powered: false, @@ -204,10 +207,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { _ => self.control.reject(), }, (RequestType::Class, Recipient::Interface) => { - let mut buf = [0; 128]; let data = if req.length > 0 { - let size = self.control.data_out(&mut buf).await.unwrap(); - &buf[0..size] + let size = self.control.data_out(self.control_buf).await.unwrap(); + &self.control_buf[0..size] } else { &[] }; @@ -284,7 +286,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { .find(|(i, _)| req.index == *i as _) .map(|(_, h)| h); match handler { - Some(handler) => match handler.control_in(req) { + Some(handler) => match handler.control_in(req, self.control_buf) { InResponse::Accepted(data) => self.control.accept_in(data).await, InResponse::Rejected => self.control.reject(), }, diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 2a78324f..c28681dc 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -66,7 +66,6 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { struct Control<'a> { shared: &'a ControlShared, - buf: [u8; 7], } /// Shared data between Control and CdcAcmClass @@ -97,7 +96,7 @@ impl<'a> Control<'a> { } } -impl<'a> ControlHandler for Control<'a> { +impl<'d> ControlHandler for Control<'d> { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -139,17 +138,18 @@ impl<'a> ControlHandler for Control<'a> { } } - fn control_in(&mut self, req: Request) -> InResponse<'_> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); let coding = self.shared().line_coding.lock(|x| x.get()); - self.buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - self.buf[4] = coding.stop_bits as u8; - self.buf[5] = coding.parity_type as u8; - self.buf[6] = coding.data_bits; - InResponse::Accepted(&self.buf) + assert!(buf.len() >= 7); + buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + buf[4] = coding.stop_bits as u8; + buf[5] = coding.parity_type as u8; + buf[6] = coding.data_bits; + InResponse::Accepted(&buf[0..7]) } _ => InResponse::Rejected, } @@ -166,11 +166,12 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { ) -> Self { let control = state.control.write(Control { shared: &state.shared, - buf: [0; 7], }); let control_shared = &state.shared; + assert!(builder.control_buf_len() >= 7); + let comm_if = builder.alloc_interface_with_handler(control); let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); let data_if = builder.alloc_interface(); diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 398dd07b..c4b9c017 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -47,6 +47,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut device_descriptor = [0; 256]; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 7]; let mut state = State::new(); @@ -56,6 +57,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut control_buf, ); // Create classes on the builder. From d40ebcccf67c9848d9a22efd02eda9157955a4b4 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 17:13:16 -0400 Subject: [PATCH 20/50] Add handlers for standard reqs to ControlHandler --- embassy-usb/src/control.rs | 23 ++++++++++++ embassy-usb/src/lib.rs | 77 ++++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 19c2c677..b5077c73 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -1,5 +1,7 @@ use core::mem; +use crate::DEFAULT_ALTERNATE_SETTING; + use super::types::*; /// Control request type. @@ -153,6 +155,7 @@ pub trait ControlHandler { /// * `req` - The request from the SETUP packet. /// * `data` - The data from the request. fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { + let _ = (req, data); OutResponse::Rejected } @@ -165,6 +168,26 @@ pub trait ControlHandler { /// /// * `req` - The request from the SETUP packet. fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + let _ = (req, buf); InResponse::Rejected } + + fn set_interface(&mut self, alternate_setting: u16) -> OutResponse { + if alternate_setting == u16::from(DEFAULT_ALTERNATE_SETTING) { + OutResponse::Accepted + } else { + OutResponse::Rejected + } + } + + fn get_interface<'a>(&'a mut self, buf: &'a mut [u8]) -> InResponse<'a> { + buf[0] = DEFAULT_ALTERNATE_SETTING; + InResponse::Accepted(&buf[0..1]) + } + + fn get_status<'a>(&'a mut self, buf: &'a mut [u8]) -> InResponse { + let status: u16 = 0; + buf[0..2].copy_from_slice(&status.to_le_bytes()); + InResponse::Accepted(&buf[0..2]) + } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index a4b7dda3..cbb90924 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -155,7 +155,14 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { async fn handle_control_out(&mut self, req: Request) { const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; - const DEFAULT_ALTERNATE_SETTING_U16: u16 = DEFAULT_ALTERNATE_SETTING as u16; + + // If the request has a data state, we must read it. + let data = if req.length > 0 { + let size = self.control.data_out(self.control_buf).await.unwrap(); + &self.control_buf[0..size] + } else { + &[] + }; match (req.request_type, req.recipient) { (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { @@ -186,13 +193,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { }, _ => self.control.reject(), }, - (RequestType::Standard, Recipient::Interface) => match (req.request, req.value) { - (Request::SET_INTERFACE, DEFAULT_ALTERNATE_SETTING_U16) => { - // TODO: do something when alternate settings are implemented - self.control.accept(); - } - _ => self.control.reject(), - }, (RequestType::Standard, Recipient::Endpoint) => match (req.request, req.value) { (Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { let ep_addr = ((req.index as u8) & 0x8f).into(); @@ -206,24 +206,26 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } _ => self.control.reject(), }, - (RequestType::Class, Recipient::Interface) => { - let data = if req.length > 0 { - let size = self.control.data_out(self.control_buf).await.unwrap(); - &self.control_buf[0..size] - } else { - &[] - }; - + (_, Recipient::Interface) => { let handler = self .interfaces .iter_mut() .find(|(i, _)| req.index == *i as _) .map(|(_, h)| h); + match handler { - Some(handler) => match handler.control_out(req, data) { - OutResponse::Accepted => return self.control.accept(), - OutResponse::Rejected => return self.control.reject(), - }, + Some(handler) => { + let response = match (req.request_type, req.request) { + (RequestType::Standard, Request::SET_INTERFACE) => { + handler.set_interface(req.value) + } + _ => handler.control_out(req, data), + }; + match response { + OutResponse::Accepted => self.control.accept(), + OutResponse::Rejected => self.control.reject(), + } + } None => self.control.reject(), } } @@ -256,18 +258,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } _ => self.control.reject(), }, - (RequestType::Standard, Recipient::Interface) => match req.request { - Request::GET_STATUS => { - let status: u16 = 0x0000; - self.control.accept_in(&status.to_le_bytes()).await; - } - Request::GET_INTERFACE => { - // TODO: change when alternate settings are implemented - let status = DEFAULT_ALTERNATE_SETTING; - self.control.accept_in(&status.to_le_bytes()).await; - } - _ => self.control.reject(), - }, (RequestType::Standard, Recipient::Endpoint) => match req.request { Request::GET_STATUS => { let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into(); @@ -279,17 +269,30 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } _ => self.control.reject(), }, - (RequestType::Class, Recipient::Interface) => { + (_, Recipient::Interface) => { let handler = self .interfaces .iter_mut() .find(|(i, _)| req.index == *i as _) .map(|(_, h)| h); + match handler { - Some(handler) => match handler.control_in(req, self.control_buf) { - InResponse::Accepted(data) => self.control.accept_in(data).await, - InResponse::Rejected => self.control.reject(), - }, + Some(handler) => { + let response = match (req.request_type, req.request) { + (RequestType::Standard, Request::GET_STATUS) => { + handler.get_status(self.control_buf) + } + (RequestType::Standard, Request::GET_INTERFACE) => { + handler.get_interface(self.control_buf) + } + _ => handler.control_in(req, self.control_buf), + }; + + match response { + InResponse::Accepted(data) => self.control.accept_in(data).await, + InResponse::Rejected => self.control.reject(), + } + } None => self.control.reject(), } } From c06488eb2978d9eaa8dc6a2669a6ce279638f4b2 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 17:13:49 -0400 Subject: [PATCH 21/50] Support multi-frame data phase control requests --- embassy-nrf/src/usb.rs | 83 +++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index d1c94dbb..1cd5a9eb 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -508,6 +508,30 @@ pub struct ControlPipe<'d, T: Instance> { } impl<'d, T: Instance> ControlPipe<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let regs = T::regs(); + + // Wait until ready + regs.intenset.write(|w| w.ep0datadone().set()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs + .events_ep0datadone + .read() + .events_ep0datadone() + .bit_is_set() + { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + unsafe { read_dma::(0, buf) } + } + async fn write(&mut self, buf: &[u8], last_chunk: bool) { let regs = T::regs(); regs.events_ep0datadone.reset(); @@ -595,29 +619,19 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { let req = self.request.unwrap(); assert_eq!(req.direction, UsbDirection::Out); assert!(req.length > 0); - assert!(buf.len() >= usize::from(req.length)); - let regs = T::regs(); - - // Wait until ready - regs.intenset.write(|w| w.ep0datadone().set()); - poll_fn(|cx| { - EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if regs - .events_ep0datadone - .read() - .events_ep0datadone() - .bit_is_set() - { - Poll::Ready(()) - } else { - Poll::Pending + let req_length = usize::from(req.length); + let max_packet_size = usize::from(self.max_packet_size); + let mut total = 0; + for chunk in buf.chunks_mut(max_packet_size) { + let size = self.read(chunk).await?; + total += size; + if size < max_packet_size || total == req_length { + break; } - }) - .await; + } - unsafe { read_dma::(0, buf) } + Ok(total) } } @@ -697,16 +711,27 @@ impl Allocator { // Endpoint directions are allocated individually. - let alloc_index = match ep_type { - EndpointType::Isochronous => 8, - EndpointType::Control => 0, - EndpointType::Interrupt | EndpointType::Bulk => { - // Find rightmost zero bit in 1..=7 - let ones = (self.used >> 1).trailing_ones() as usize; - if ones >= 7 { - return Err(driver::EndpointAllocError); + let alloc_index = if let Some(ep_addr) = ep_addr { + match (ep_addr.index(), ep_type) { + (0, EndpointType::Control) => {} + (8, EndpointType::Isochronous) => {} + (n, EndpointType::Bulk) | (n, EndpointType::Interrupt) if n >= 1 && n <= 7 => {} + _ => return Err(driver::EndpointAllocError), + } + + ep_addr.index() + } else { + match ep_type { + EndpointType::Isochronous => 8, + EndpointType::Control => 0, + EndpointType::Interrupt | EndpointType::Bulk => { + // Find rightmost zero bit in 1..=7 + let ones = (self.used >> 1).trailing_ones() as usize; + if ones >= 7 { + return Err(driver::EndpointAllocError); + } + ones + 1 } - ones + 1 } }; From d1e4b3d7d5a931cd6e04b7e2fe467945ef862477 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 01:18:37 +0200 Subject: [PATCH 22/50] usb: add -usb-serial crate, fix warnings and stable build. --- embassy-nrf/Cargo.toml | 4 +- embassy-nrf/src/chips/nrf52820.rs | 1 + embassy-nrf/src/chips/nrf52833.rs | 1 + embassy-nrf/src/chips/nrf52840.rs | 1 + embassy-nrf/src/chips/nrf5340_app.rs | 1 + embassy-nrf/src/lib.rs | 1 + embassy-nrf/src/usb.rs | 11 +- embassy-usb-serial/Cargo.toml | 11 + embassy-usb-serial/src/fmt.rs | 225 ++++++++++++++++++ .../src/lib.rs | 13 +- embassy-usb/Cargo.toml | 2 - embassy-usb/src/descriptor.rs | 1 + embassy-usb/src/lib.rs | 10 +- examples/nrf/Cargo.toml | 1 + .../src/bin/{usb/main.rs => usb_serial.rs} | 9 +- 15 files changed, 265 insertions(+), 27 deletions(-) create mode 100644 embassy-usb-serial/Cargo.toml create mode 100644 embassy-usb-serial/src/fmt.rs rename examples/nrf/src/bin/usb/cdc_acm.rs => embassy-usb-serial/src/lib.rs (98%) rename examples/nrf/src/bin/{usb/main.rs => usb_serial.rs} (95%) diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 36c61c65..858ff1f6 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -18,7 +18,7 @@ flavors = [ [features] # Enable nightly-only features -nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async"] +nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-usb"] # Reexport the PAC for the currently enabled chip at `embassy_nrf::pac`. # This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version. @@ -64,7 +64,7 @@ _gpio-p1 = [] embassy = { version = "0.1.0", path = "../embassy" } embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["nrf"]} embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } -embassy-usb = {version = "0.1.0", path = "../embassy-usb" } +embassy-usb = {version = "0.1.0", path = "../embassy-usb", optional=true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.7", git = "https://github.com/embassy-rs/embedded-hal", branch = "embassy2", optional = true} diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index aa2b2e61..136ef4ec 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -125,6 +125,7 @@ embassy_hal_common::peripherals! { TEMP, } +#[cfg(feature = "nightly")] impl_usb!(USBD, USBD, USBD); impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 498a3c30..35cf4224 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -157,6 +157,7 @@ embassy_hal_common::peripherals! { TEMP, } +#[cfg(feature = "nightly")] impl_usb!(USBD, USBD, USBD); impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 41176814..d20abbfb 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -160,6 +160,7 @@ embassy_hal_common::peripherals! { TEMP, } +#[cfg(feature = "nightly")] impl_usb!(USBD, USBD, USBD); impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index ae6887b3..89579b69 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -348,6 +348,7 @@ embassy_hal_common::peripherals! { P1_15, } +#[cfg(feature = "nightly")] impl_usb!(USBD, USBD, USBD); impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 06e8235e..667e8ea3 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -91,6 +91,7 @@ pub mod uarte; feature = "nrf52833", feature = "nrf52840" ))] +#[cfg(feature = "nightly")] pub mod usb; #[cfg(not(feature = "_nrf5340"))] pub mod wdt; diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 1cd5a9eb..b26e4027 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -4,6 +4,7 @@ use core::marker::PhantomData; use core::mem::MaybeUninit; use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use core::task::Poll; +use cortex_m::peripheral::NVIC; use embassy::interrupt::InterruptExt; use embassy::time::{with_timeout, Duration}; use embassy::util::Unborrow; @@ -14,7 +15,6 @@ use embassy_usb::driver::{self, Event, ReadError, WriteError}; use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; use futures::future::poll_fn; use futures::Future; -use pac::NVIC; pub use embassy_usb; @@ -617,7 +617,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a> { async move { let req = self.request.unwrap(); - assert_eq!(req.direction, UsbDirection::Out); + assert!(req.direction == UsbDirection::Out); assert!(req.length > 0); let req_length = usize::from(req.length); @@ -644,9 +644,12 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { fn accept_in<'a>(&'a mut self, buf: &'a [u8]) -> Self::AcceptInFuture<'a> { async move { - info!("control accept {=[u8]:x}", buf); + #[cfg(feature = "defmt")] + info!("control accept {:x}", buf); + #[cfg(not(feature = "defmt"))] + info!("control accept {:x?}", buf); let req = self.request.unwrap(); - assert_eq!(req.direction, UsbDirection::In); + assert!(req.direction == UsbDirection::In); let req_len = usize::from(req.length); let len = buf.len().min(req_len); diff --git a/embassy-usb-serial/Cargo.toml b/embassy-usb-serial/Cargo.toml new file mode 100644 index 00000000..16e03046 --- /dev/null +++ b/embassy-usb-serial/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "embassy-usb-serial" +version = "0.1.0" +edition = "2021" + +[dependencies] +embassy = { version = "0.1.0", path = "../embassy" } +embassy-usb = { version = "0.1.0", path = "../embassy-usb" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } diff --git a/embassy-usb-serial/src/fmt.rs b/embassy-usb-serial/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-usb-serial/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/embassy-usb-serial/src/lib.rs similarity index 98% rename from examples/nrf/src/bin/usb/cdc_acm.rs rename to embassy-usb-serial/src/lib.rs index c28681dc..d6c31c86 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/embassy-usb-serial/src/lib.rs @@ -1,7 +1,13 @@ +#![no_std] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + use core::cell::Cell; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; -use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; @@ -291,11 +297,6 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { self.read_ep.read(data).await } - - /// Gets the address of the IN endpoint. - pub(crate) fn write_ep_address(&self) -> EndpointAddress { - self.write_ep.info().addr - } } /// Number of stop bits for LineCoding diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index af5986c1..01bf5ef6 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -8,6 +8,4 @@ embassy = { version = "0.1.0", path = "../embassy" } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -cortex-m = "0.7.3" -num-traits = { version = "0.2.14", default-features = false } heapless = "0.7.10" \ No newline at end of file diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index 746c6b82..5f8b0d56 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -136,6 +136,7 @@ impl<'a> DescriptorWriter<'a> { ) } + #[allow(unused)] pub(crate) fn end_class(&mut self) { self.num_endpoints_mark = None; } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index cbb90924..5a6b2190 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -302,7 +302,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { async fn handle_get_descriptor(&mut self, req: Request) { let (dtype, index) = req.descriptor_type_index(); - let config = self.config.clone(); match dtype { descriptor_type::BOS => self.control.accept_in(self.bos_descriptor).await, @@ -321,13 +320,10 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { 2 => self.config.product, 3 => self.config.serial_number, _ => { - let index = StringIndex::new(index); - let lang_id = req.index; + let _index = StringIndex::new(index); + let _lang_id = req.index; + // TODO None - //classes - // .iter() - // .filter_map(|cls| cls.get_string(index, lang_id)) - // .nth(0) } }; diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 59e5de02..58450a04 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -12,6 +12,7 @@ nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } defmt = "0.3" defmt-rtt = "0.3" diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb_serial.rs similarity index 95% rename from examples/nrf/src/bin/usb/main.rs rename to examples/nrf/src/bin/usb_serial.rs index c4b9c017..0a4ff948 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -3,11 +3,6 @@ #![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] -#[path = "../../example_common.rs"] -mod example_common; - -mod cdc_acm; - use core::mem; use defmt::*; use embassy::executor::Spawner; @@ -18,9 +13,11 @@ use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; +use embassy_usb_serial::{CdcAcmClass, State}; use futures::future::join3; -use crate::cdc_acm::{CdcAcmClass, State}; +use defmt_rtt as _; // global logger +use panic_probe as _; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { From 60d3d111972f462c1f38d1d4fd27e89713974fc6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 01:30:58 +0200 Subject: [PATCH 23/50] usb: cleanup logging. --- embassy-nrf/src/usb.rs | 18 ++++++++++-------- embassy-usb-serial/src/lib.rs | 6 +++--- embassy-usb/src/lib.rs | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index b26e4027..d9524675 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -213,7 +213,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { }); // Enable the USB pullup, allowing enumeration. regs.usbpullup.write(|w| w.connect().enabled()); - info!("enabled"); + trace!("enabled"); Bus { phantom: PhantomData, @@ -247,23 +247,23 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { if r.isooutcrc().bit() { regs.eventcause.write(|w| w.isooutcrc().set_bit()); - info!("USB event: isooutcrc"); + trace!("USB event: isooutcrc"); } if r.usbwuallowed().bit() { regs.eventcause.write(|w| w.usbwuallowed().set_bit()); - info!("USB event: usbwuallowed"); + trace!("USB event: usbwuallowed"); } if r.suspend().bit() { regs.eventcause.write(|w| w.suspend().set_bit()); - info!("USB event: suspend"); + trace!("USB event: suspend"); } if r.resume().bit() { regs.eventcause.write(|w| w.resume().set_bit()); - info!("USB event: resume"); + trace!("USB event: resume"); } if r.ready().bit() { regs.eventcause.write(|w| w.ready().set_bit()); - info!("USB event: ready"); + trace!("USB event: ready"); } Poll::Pending @@ -636,6 +636,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { } fn accept(&mut self) { + debug!("control accept"); let regs = T::regs(); regs.tasks_ep0status .write(|w| w.tasks_ep0status().bit(true)); @@ -645,9 +646,9 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { fn accept_in<'a>(&'a mut self, buf: &'a [u8]) -> Self::AcceptInFuture<'a> { async move { #[cfg(feature = "defmt")] - info!("control accept {:x}", buf); + debug!("control in accept {:x}", buf); #[cfg(not(feature = "defmt"))] - info!("control accept {:x?}", buf); + debug!("control in accept {:x?}", buf); let req = self.request.unwrap(); assert!(req.direction == UsbDirection::In); @@ -666,6 +667,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { } fn reject(&mut self) { + debug!("control reject"); let regs = T::regs(); regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); self.request = None; diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb-serial/src/lib.rs index d6c31c86..ce1fb477 100644 --- a/embassy-usb-serial/src/lib.rs +++ b/embassy-usb-serial/src/lib.rs @@ -125,7 +125,7 @@ impl<'d> ControlHandler for Control<'d> { data_bits: data[6], }; self.shared().line_coding.lock(|x| x.set(coding)); - info!("Set line coding to: {:?}", coding); + debug!("Set line coding to: {:?}", coding); OutResponse::Accepted } @@ -136,7 +136,7 @@ impl<'d> ControlHandler for Control<'d> { let shared = self.shared(); shared.dtr.store(dtr, Ordering::Relaxed); shared.rts.store(rts, Ordering::Relaxed); - info!("Set dtr {}, rts {}", dtr, rts); + debug!("Set dtr {}, rts {}", dtr, rts); OutResponse::Accepted } @@ -148,7 +148,7 @@ impl<'d> ControlHandler for Control<'d> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { - info!("Sending line coding"); + debug!("Sending line coding"); let coding = self.shared().line_coding.lock(|x| x.get()); assert!(buf.len() >= 7); buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 5a6b2190..2287267b 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -129,7 +129,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } }, Either::Right(req) => { - info!("control request: {:x}", req); + debug!("control request: {:x}", req); match req.direction { UsbDirection::In => self.handle_control_in(req).await, From a435d78cf78deb1a93682d9ff2632706eaa1b951 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 02:01:09 +0200 Subject: [PATCH 24/50] usb: cleanup and simplify error handling. --- embassy-nrf/src/usb.rs | 18 ++---- embassy-usb-serial/src/lib.rs | 117 ++++++++++++++-------------------- embassy-usb/src/builder.rs | 6 +- embassy-usb/src/descriptor.rs | 69 ++++++++------------ embassy-usb/src/lib.rs | 12 ++-- 5 files changed, 91 insertions(+), 131 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index d9524675..1057d880 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -403,11 +403,9 @@ unsafe fn read_dma(i: usize, buf: &mut [u8]) -> Result(i: usize, buf: &[u8]) -> Result<(), WriteError> { +unsafe fn write_dma(i: usize, buf: &[u8]) { let regs = T::regs(); - if buf.len() > 64 { - return Err(WriteError::BufferOverflow); - } + assert!(buf.len() <= 64); let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); let ptr = if !slice_in_ram(buf) { @@ -441,8 +439,6 @@ unsafe fn write_dma(i: usize, buf: &[u8]) -> Result<(), WriteError> regs.tasks_startepin[i].write(|w| w.bits(1)); while regs.events_endepin[i].read().bits() == 0 {} dma_end(); - - Ok(()) } impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { @@ -497,6 +493,8 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { READY_ENDPOINTS.fetch_and(!(1 << i), Ordering::AcqRel); unsafe { write_dma::(i, buf) } + + Ok(()) } } } @@ -535,9 +533,7 @@ impl<'d, T: Instance> ControlPipe<'d, T> { async fn write(&mut self, buf: &[u8], last_chunk: bool) { let regs = T::regs(); regs.events_ep0datadone.reset(); - unsafe { - write_dma::(0, buf).unwrap(); - } + unsafe { write_dma::(0, buf) } regs.shorts .modify(|_, w| w.ep0datadone_ep0status().bit(last_chunk)); @@ -616,7 +612,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a> { async move { - let req = self.request.unwrap(); + let req = unwrap!(self.request); assert!(req.direction == UsbDirection::Out); assert!(req.length > 0); @@ -649,7 +645,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { debug!("control in accept {:x}", buf); #[cfg(not(feature = "defmt"))] debug!("control in accept {:x?}", buf); - let req = self.request.unwrap(); + let req = unwrap!(self.request); assert!(req.direction == UsbDirection::In); let req_len = usize::from(req.length); diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb-serial/src/lib.rs index ce1fb477..a30cdd67 100644 --- a/embassy-usb-serial/src/lib.rs +++ b/embassy-usb-serial/src/lib.rs @@ -184,78 +184,57 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { let read_ep = builder.alloc_bulk_endpoint_out(max_packet_size); let write_ep = builder.alloc_bulk_endpoint_in(max_packet_size); - builder - .config_descriptor - .iad( - comm_if, - 2, - USB_CLASS_CDC, - CDC_SUBCLASS_ACM, - CDC_PROTOCOL_NONE, - ) - .unwrap(); + builder.config_descriptor.iad( + comm_if, + 2, + USB_CLASS_CDC, + CDC_SUBCLASS_ACM, + CDC_PROTOCOL_NONE, + ); + builder.config_descriptor.interface( + comm_if, + USB_CLASS_CDC, + CDC_SUBCLASS_ACM, + CDC_PROTOCOL_NONE, + ); + builder.config_descriptor.write( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ); + builder.config_descriptor.write( + CS_INTERFACE, + &[ + CDC_TYPE_ACM, // bDescriptorSubtype + 0x00, // bmCapabilities + ], + ); + builder.config_descriptor.write( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + data_if.into(), // bSubordinateInterface + ], + ); + builder.config_descriptor.write( + CS_INTERFACE, + &[ + CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype + 0x00, // bmCapabilities + data_if.into(), // bDataInterface + ], + ); + builder.config_descriptor.endpoint(comm_ep.info()); builder .config_descriptor - .interface(comm_if, USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE) - .unwrap(); - - builder - .config_descriptor - .write( - CS_INTERFACE, - &[ - CDC_TYPE_HEADER, // bDescriptorSubtype - 0x10, - 0x01, // bcdCDC (1.10) - ], - ) - .unwrap(); - - builder - .config_descriptor - .write( - CS_INTERFACE, - &[ - CDC_TYPE_ACM, // bDescriptorSubtype - 0x00, // bmCapabilities - ], - ) - .unwrap(); - - builder - .config_descriptor - .write( - CS_INTERFACE, - &[ - CDC_TYPE_UNION, // bDescriptorSubtype - comm_if.into(), // bControlInterface - data_if.into(), // bSubordinateInterface - ], - ) - .unwrap(); - - builder - .config_descriptor - .write( - CS_INTERFACE, - &[ - CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype - 0x00, // bmCapabilities - data_if.into(), // bDataInterface - ], - ) - .unwrap(); - - builder.config_descriptor.endpoint(comm_ep.info()).unwrap(); - - builder - .config_descriptor - .interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00) - .unwrap(); - - builder.config_descriptor.endpoint(write_ep.info()).unwrap(); - builder.config_descriptor.endpoint(read_ep.info()).unwrap(); + .interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00); + builder.config_descriptor.endpoint(write_ep.info()); + builder.config_descriptor.endpoint(read_ep.info()); CdcAcmClass { comm_ep, diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 0c118b78..c8a9db7a 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -168,9 +168,9 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { let mut config_descriptor = DescriptorWriter::new(config_descriptor_buf); let mut bos_descriptor = BosWriter::new(DescriptorWriter::new(bos_descriptor_buf)); - device_descriptor.device(&config).unwrap(); - config_descriptor.configuration(&config).unwrap(); - bos_descriptor.bos().unwrap(); + device_descriptor.device(&config); + config_descriptor.configuration(&config); + bos_descriptor.bos(); UsbDeviceBuilder { bus, diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index 5f8b0d56..ff971e12 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,13 +1,6 @@ use super::builder::Config; use super::{types::*, CONFIGURATION_VALUE, DEFAULT_ALTERNATE_SETTING}; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - BufferFull, - InvalidState, -} - /// Standard descriptor types #[allow(missing_docs)] pub mod descriptor_type { @@ -69,11 +62,11 @@ impl<'a> DescriptorWriter<'a> { } /// Writes an arbitrary (usually class-specific) descriptor. - pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) -> Result<(), Error> { + pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) { let length = descriptor.len(); if (self.position + 2 + length) > self.buf.len() || (length + 2) > 255 { - return Err(Error::BufferFull); + panic!("Descriptor buffer full"); } self.buf[self.position] = (length + 2) as u8; @@ -84,11 +77,9 @@ impl<'a> DescriptorWriter<'a> { self.buf[start..start + length].copy_from_slice(descriptor); self.position = start + length; - - Ok(()) } - pub(crate) fn device(&mut self, config: &Config) -> Result<(), Error> { + pub(crate) fn device(&mut self, config: &Config) { self.write( descriptor_type::DEVICE, &[ @@ -112,7 +103,7 @@ impl<'a> DescriptorWriter<'a> { ) } - pub(crate) fn configuration(&mut self, config: &Config) -> Result<(), Error> { + pub(crate) fn configuration(&mut self, config: &Config) { self.num_interfaces_mark = Some(self.position + 4); self.write_iads = config.composite_with_iads; @@ -168,9 +159,9 @@ impl<'a> DescriptorWriter<'a> { function_class: u8, function_sub_class: u8, function_protocol: u8, - ) -> Result<(), Error> { + ) { if !self.write_iads { - return Ok(()); + return; } self.write( @@ -183,9 +174,7 @@ impl<'a> DescriptorWriter<'a> { function_protocol, 0, ], - )?; - - Ok(()) + ); } /// Writes a interface descriptor. @@ -204,7 +193,7 @@ impl<'a> DescriptorWriter<'a> { interface_class: u8, interface_sub_class: u8, interface_protocol: u8, - ) -> Result<(), Error> { + ) { self.interface_alt( number, DEFAULT_ALTERNATE_SETTING, @@ -237,11 +226,13 @@ impl<'a> DescriptorWriter<'a> { interface_sub_class: u8, interface_protocol: u8, interface_string: Option, - ) -> Result<(), Error> { + ) { if alternate_setting == DEFAULT_ALTERNATE_SETTING { match self.num_interfaces_mark { Some(mark) => self.buf[mark] += 1, - None => return Err(Error::InvalidState), + None => { + panic!("you can only call `interface/interface_alt` after `configuration`.") + } }; } @@ -260,9 +251,7 @@ impl<'a> DescriptorWriter<'a> { interface_protocol, // bInterfaceProtocol str_index, // iInterface ], - )?; - - Ok(()) + ); } /// Writes an endpoint descriptor. @@ -271,10 +260,10 @@ impl<'a> DescriptorWriter<'a> { /// /// * `endpoint` - Endpoint previously allocated with /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). - pub fn endpoint(&mut self, endpoint: &EndpointInfo) -> Result<(), Error> { + pub fn endpoint(&mut self, endpoint: &EndpointInfo) { match self.num_endpoints_mark { Some(mark) => self.buf[mark] += 1, - None => return Err(Error::InvalidState), + None => panic!("you can only call `endpoint` after `interface/interface_alt`."), }; self.write( @@ -286,17 +275,15 @@ impl<'a> DescriptorWriter<'a> { (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize endpoint.interval, // bInterval ], - )?; - - Ok(()) + ); } /// Writes a string descriptor. - pub(crate) fn string(&mut self, string: &str) -> Result<(), Error> { + pub(crate) fn string(&mut self, string: &str) { let mut pos = self.position; if pos + 2 > self.buf.len() { - return Err(Error::BufferFull); + panic!("Descriptor buffer full"); } self.buf[pos] = 0; // length placeholder @@ -306,7 +293,7 @@ impl<'a> DescriptorWriter<'a> { for c in string.encode_utf16() { if pos >= self.buf.len() { - return Err(Error::BufferFull); + panic!("Descriptor buffer full"); } self.buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes()); @@ -316,8 +303,6 @@ impl<'a> DescriptorWriter<'a> { self.buf[self.position] = (pos - self.position) as u8; self.position = pos; - - Ok(()) } } @@ -335,7 +320,7 @@ impl<'a> BosWriter<'a> { } } - pub(crate) fn bos(&mut self) -> Result<(), Error> { + pub(crate) fn bos(&mut self) { self.num_caps_mark = Some(self.writer.position + 4); self.writer.write( descriptor_type::BOS, @@ -343,11 +328,9 @@ impl<'a> BosWriter<'a> { 0x00, 0x00, // wTotalLength 0x00, // bNumDeviceCaps ], - )?; + ); - self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4])?; - - Ok(()) + self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]); } /// Writes capability descriptor to a BOS @@ -356,17 +339,17 @@ impl<'a> BosWriter<'a> { /// /// * `capability_type` - Type of a capability /// * `data` - Binary data of the descriptor - pub fn capability(&mut self, capability_type: u8, data: &[u8]) -> Result<(), Error> { + pub fn capability(&mut self, capability_type: u8, data: &[u8]) { match self.num_caps_mark { Some(mark) => self.writer.buf[mark] += 1, - None => return Err(Error::InvalidState), + None => panic!("called `capability` not between `bos` and `end_bos`."), } let mut start = self.writer.position; let blen = data.len(); if (start + blen + 3) > self.writer.buf.len() || (blen + 3) > 255 { - return Err(Error::BufferFull); + panic!("Descriptor buffer full"); } self.writer.buf[start] = (blen + 3) as u8; @@ -376,8 +359,6 @@ impl<'a> BosWriter<'a> { start += 3; self.writer.buf[start..start + blen].copy_from_slice(data); self.writer.position = start + blen; - - Ok(()) } pub(crate) fn end_bos(&mut self) { diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 2287267b..32b67a76 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -158,7 +158,13 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { // If the request has a data state, we must read it. let data = if req.length > 0 { - let size = self.control.data_out(self.control_buf).await.unwrap(); + let size = match self.control.data_out(self.control_buf).await { + Ok(size) => size, + Err(_) => { + warn!("usb: failed to read CONTROL OUT data stage."); + return; + } + }; &self.control_buf[0..size] } else { &[] @@ -311,7 +317,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { if index == 0 { self.control_in_accept_writer(req, |w| { w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes()) - .unwrap(); }) .await } else { @@ -328,8 +333,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { }; if let Some(s) = s { - self.control_in_accept_writer(req, |w| w.string(s).unwrap()) - .await; + self.control_in_accept_writer(req, |w| w.string(s)).await; } else { self.control.reject() } From cdb7bae51a30d30eb1bc2cc0f980870c8c76e02d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 02:05:09 +0200 Subject: [PATCH 25/50] examples/nrf: don't build usb stuff in stable. --- examples/nrf/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 58450a04..aa30f3fa 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -6,13 +6,13 @@ version = "0.1.0" [features] default = ["nightly"] -nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"] +nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial"] [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } -embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } +embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true } defmt = "0.3" defmt-rtt = "0.3" From 1672fdc666188454a56b9369150e54420dc67078 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 02:16:34 +0200 Subject: [PATCH 26/50] usb-serial: make inner guts private. --- embassy-usb-serial/src/lib.rs | 19 ++++++++----------- examples/nrf/src/bin/usb_serial.rs | 24 +++++++----------------- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb-serial/src/lib.rs index a30cdd67..8418de0f 100644 --- a/embassy-usb-serial/src/lib.rs +++ b/embassy-usb-serial/src/lib.rs @@ -53,20 +53,17 @@ impl<'a> State<'a> { /// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial /// port. The following constraints must be followed if you use this class directly: /// -/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the -/// method will return a `WouldBlock` error if there is no packet to be read. -/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the -/// method will return a `WouldBlock` error if the previous packet has not been sent yet. +/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes. +/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes. /// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the /// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) /// can be sent if there is no other data to send. This is because USB bulk transactions must be /// terminated with a short packet, even if the bulk endpoint is used for stream-like data. pub struct CdcAcmClass<'d, D: Driver<'d>> { - // TODO not pub - pub comm_ep: D::EndpointIn, - pub data_if: InterfaceNumber, - pub read_ep: D::EndpointOut, - pub write_ep: D::EndpointIn, + _comm_ep: D::EndpointIn, + _data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, control: &'d ControlShared, } @@ -237,8 +234,8 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { builder.config_descriptor.endpoint(read_ep.info()); CdcAcmClass { - comm_ep, - data_if, + _comm_ep: comm_ep, + _data_if: data_if, read_ep, write_ep, control: control_shared, diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 0a4ff948..cd681c5c 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -6,15 +6,13 @@ use core::mem; use defmt::*; use embassy::executor::Spawner; -use embassy::time::{Duration, Timer}; use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; -use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; use embassy_usb_serial::{CdcAcmClass, State}; -use futures::future::join3; +use futures::future::join; use defmt_rtt as _; // global logger use panic_probe as _; @@ -64,28 +62,20 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut usb = builder.build(); // Run the USB device. - let fut1 = usb.run(); + let usb_fut = usb.run(); - // Do stuff with the classes - let fut2 = async { + // Do stuff with the class! + let echo_fut = async { let mut buf = [0; 64]; loop { - let n = class.read_ep.read(&mut buf).await.unwrap(); + let n = class.read_packet(&mut buf).await.unwrap(); let data = &buf[..n]; info!("data: {:x}", data); - } - }; - let fut3 = async { - loop { - info!("writing..."); - class.write_ep.write(b"Hello World!\r\n").await.unwrap(); - info!("written"); - - Timer::after(Duration::from_secs(1)).await; + class.write_packet(data).await.unwrap(); } }; // Run everything concurrently. // If we had made everything `'static` above instead, we could do this using separate tasks instead. - join3(fut1, fut2, fut3).await; + join(usb_fut, echo_fut).await; } From 77e0aca03b89ebc5f1e93b6c64b6c91ca10cedd1 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 20:26:30 -0400 Subject: [PATCH 27/50] Move data chunking from the driver to the lib --- embassy-nrf/src/usb.rs | 151 ++++++++++++++------------------------ embassy-usb/src/driver.rs | 23 +++--- embassy-usb/src/lib.rs | 115 +++++++++++++++++++++++------ 3 files changed, 160 insertions(+), 129 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 1057d880..570d1c95 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -186,7 +186,6 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { Ok(ControlPipe { _phantom: PhantomData, max_packet_size, - request: None, }) } @@ -502,72 +501,19 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { pub struct ControlPipe<'d, T: Instance> { _phantom: PhantomData<&'d mut T>, max_packet_size: u16, - request: Option, -} - -impl<'d, T: Instance> ControlPipe<'d, T> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - let regs = T::regs(); - - // Wait until ready - regs.intenset.write(|w| w.ep0datadone().set()); - poll_fn(|cx| { - EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if regs - .events_ep0datadone - .read() - .events_ep0datadone() - .bit_is_set() - { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - unsafe { read_dma::(0, buf) } - } - - async fn write(&mut self, buf: &[u8], last_chunk: bool) { - let regs = T::regs(); - regs.events_ep0datadone.reset(); - unsafe { write_dma::(0, buf) } - - regs.shorts - .modify(|_, w| w.ep0datadone_ep0status().bit(last_chunk)); - - regs.intenset.write(|w| w.ep0datadone().set()); - let res = with_timeout( - Duration::from_millis(10), - poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }), - ) - .await; - - if res.is_err() { - error!("ControlPipe::write timed out."); - } - } } impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { type SetupFuture<'a> = impl Future + 'a where Self: 'a; type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; - type AcceptInFuture<'a> = impl Future + 'a where Self: 'a; + type DataInFuture<'a> = impl Future + 'a where Self: 'a; + + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { async move { - assert!(self.request.is_none()); - let regs = T::regs(); // Wait for SETUP packet @@ -605,29 +551,65 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { .write(|w| w.tasks_ep0rcvout().set_bit()); } - self.request = Some(req); req } } fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a> { async move { - let req = unwrap!(self.request); - assert!(req.direction == UsbDirection::Out); - assert!(req.length > 0); + let regs = T::regs(); - let req_length = usize::from(req.length); - let max_packet_size = usize::from(self.max_packet_size); - let mut total = 0; - for chunk in buf.chunks_mut(max_packet_size) { - let size = self.read(chunk).await?; - total += size; - if size < max_packet_size || total == req_length { - break; + // Wait until ready + regs.intenset.write(|w| w.ep0datadone().set()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs + .events_ep0datadone + .read() + .events_ep0datadone() + .bit_is_set() + { + Poll::Ready(()) + } else { + Poll::Pending } + }) + .await; + + unsafe { read_dma::(0, buf) } + } + } + + fn data_in<'a>(&'a mut self, buf: &'a [u8], last_packet: bool) -> Self::DataInFuture<'a> { + async move { + let regs = T::regs(); + regs.events_ep0datadone.reset(); + unsafe { + write_dma::(0, buf); } - Ok(total) + regs.shorts + .modify(|_, w| w.ep0datadone_ep0status().bit(last_packet)); + + regs.intenset.write(|w| w.ep0datadone().set()); + let res = with_timeout( + Duration::from_millis(10), + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }), + ) + .await; + + if res.is_err() { + error!("ControlPipe::data_in timed out."); + } } } @@ -636,37 +618,12 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { let regs = T::regs(); regs.tasks_ep0status .write(|w| w.tasks_ep0status().bit(true)); - self.request = None; - } - - fn accept_in<'a>(&'a mut self, buf: &'a [u8]) -> Self::AcceptInFuture<'a> { - async move { - #[cfg(feature = "defmt")] - debug!("control in accept {:x}", buf); - #[cfg(not(feature = "defmt"))] - debug!("control in accept {:x?}", buf); - let req = unwrap!(self.request); - assert!(req.direction == UsbDirection::In); - - let req_len = usize::from(req.length); - let len = buf.len().min(req_len); - let need_zlp = len != req_len && (len % usize::from(self.max_packet_size)) == 0; - let mut chunks = buf[0..len] - .chunks(usize::from(self.max_packet_size)) - .chain(need_zlp.then(|| -> &[u8] { &[] })); - while let Some(chunk) = chunks.next() { - self.write(chunk, chunks.size_hint().0 == 0).await; - } - - self.request = None; - } } fn reject(&mut self) { debug!("control reject"); let regs = T::regs(); regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); - self.request = None; } } diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index 1c6ba1f5..82b59bd1 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -137,30 +137,35 @@ pub trait ControlPipe { type DataOutFuture<'a>: Future> + 'a where Self: 'a; - type AcceptInFuture<'a>: Future + 'a + type DataInFuture<'a>: Future + 'a where Self: 'a; + /// Maximum packet size for the control pipe + fn max_packet_size(&self) -> usize; + /// Reads a single setup packet from the endpoint. fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a>; - /// Reads the data packet of a control write sequence. + /// Reads a DATA OUT packet into `buf` in response to a control write request. /// /// Must be called after `setup()` for requests with `direction` of `Out` /// and `length` greater than zero. - /// - /// `buf.len()` must be greater than or equal to the request's `length`. fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a>; + /// Sends a DATA IN packet with `data` in response to a control read request. + /// + /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. + fn data_in<'a>(&'a mut self, data: &'a [u8], last_packet: bool) -> Self::DataInFuture<'a>; + /// Accepts a control request. + /// + /// Causes the STATUS packet for the current request to be ACKed. fn accept(&mut self); - /// Accepts a control read request with `data`. - /// - /// `data.len()` must be less than or equal to the request's `length`. - fn accept_in<'a>(&'a mut self, data: &'a [u8]) -> Self::AcceptInFuture<'a>; - /// Rejects a control request. + /// + /// Sets a STALL condition on the pipe to indicate an error. fn reject(&mut self); } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 32b67a76..77a9c33b 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -55,7 +55,7 @@ pub const MAX_INTERFACE_COUNT: usize = 4; pub struct UsbDevice<'d, D: Driver<'d>> { bus: D::Bus, - control: D::ControlPipe, + control: ControlPipe, config: Config<'d>, device_descriptor: &'d [u8], @@ -92,7 +92,10 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { Self { bus: driver, config, - control, + control: ControlPipe { + control, + request: None, + }, device_descriptor, config_descriptor, bos_descriptor, @@ -140,32 +143,19 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - async fn control_in_accept_writer( - &mut self, - req: Request, - f: impl FnOnce(&mut DescriptorWriter), - ) { - let mut buf = [0; 256]; - let mut w = DescriptorWriter::new(&mut buf); - f(&mut w); - let pos = w.position().min(usize::from(req.length)); - self.control.accept_in(&buf[..pos]).await; - } - async fn handle_control_out(&mut self, req: Request) { const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; // If the request has a data state, we must read it. let data = if req.length > 0 { - let size = match self.control.data_out(self.control_buf).await { - Ok(size) => size, + match self.control.data_out(self.control_buf).await { + Ok(data) => data, Err(_) => { warn!("usb: failed to read CONTROL OUT data stage."); return; } - }; - &self.control_buf[0..size] + } } else { &[] }; @@ -315,10 +305,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { descriptor_type::CONFIGURATION => self.control.accept_in(self.config_descriptor).await, descriptor_type::STRING => { if index == 0 { - self.control_in_accept_writer(req, |w| { - w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes()) - }) - .await + self.control + .accept_in_writer(req, |w| { + w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes()); + }) + .await } else { let s = match index { 1 => self.config.manufacturer, @@ -333,7 +324,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { }; if let Some(s) = s { - self.control_in_accept_writer(req, |w| w.string(s)).await; + self.control.accept_in_writer(req, |w| w.string(s)).await; } else { self.control.reject() } @@ -343,3 +334,81 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } } + +struct ControlPipe { + control: C, + request: Option, +} + +impl ControlPipe { + async fn setup(&mut self) -> Request { + assert!(self.request.is_none()); + let req = self.control.setup().await; + self.request = Some(req); + req + } + + async fn data_out<'a>(&mut self, buf: &'a mut [u8]) -> Result<&'a [u8], ReadError> { + let req = self.request.unwrap(); + assert_eq!(req.direction, UsbDirection::Out); + assert!(req.length > 0); + let req_length = usize::from(req.length); + + let max_packet_size = self.control.max_packet_size(); + let mut total = 0; + + for chunk in buf.chunks_mut(max_packet_size) { + let size = self.control.data_out(chunk).await?; + total += size; + if size < max_packet_size || total == req_length { + break; + } + } + + Ok(&buf[0..total]) + } + + async fn accept_in(&mut self, buf: &[u8]) -> () { + #[cfg(feature = "defmt")] + debug!("control in accept {:x}", buf); + #[cfg(not(feature = "defmt"))] + debug!("control in accept {:x?}", buf); + let req = unwrap!(self.request); + assert!(req.direction == UsbDirection::In); + + let req_len = usize::from(req.length); + let len = buf.len().min(req_len); + let max_packet_size = self.control.max_packet_size(); + let need_zlp = len != req_len && (len % usize::from(max_packet_size)) == 0; + + let mut chunks = buf[0..len] + .chunks(max_packet_size) + .chain(need_zlp.then(|| -> &[u8] { &[] })); + + while let Some(chunk) = chunks.next() { + self.control.data_in(chunk, chunks.size_hint().0 == 0).await; + } + + self.request = None; + } + + async fn accept_in_writer(&mut self, req: Request, f: impl FnOnce(&mut DescriptorWriter)) { + let mut buf = [0; 256]; + let mut w = DescriptorWriter::new(&mut buf); + f(&mut w); + let pos = w.position().min(usize::from(req.length)); + self.accept_in(&buf[..pos]).await; + } + + fn accept(&mut self) { + assert!(self.request.is_some()); + self.control.accept(); + self.request = None; + } + + fn reject(&mut self) { + assert!(self.request.is_some()); + self.control.reject(); + self.request = None; + } +} From f5ba022257ccd9ddd371f1dcd10c0775cc5a3110 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Wed, 30 Mar 2022 14:17:15 -0400 Subject: [PATCH 28/50] Refactor ControlPipe to use the typestate pattern for safety --- embassy-usb/src/control.rs | 121 ++++++++++++++++++++++++++++ embassy-usb/src/lib.rs | 158 +++++++++---------------------------- 2 files changed, 157 insertions(+), 122 deletions(-) diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index b5077c73..9f1115ff 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -1,5 +1,7 @@ use core::mem; +use crate::descriptor::DescriptorWriter; +use crate::driver::{self, ReadError}; use crate::DEFAULT_ALTERNATE_SETTING; use super::types::*; @@ -191,3 +193,122 @@ pub trait ControlHandler { InResponse::Accepted(&buf[0..2]) } } + +/// Typestate representing a ControlPipe in the DATA IN stage +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct DataInStage { + length: usize, +} + +/// Typestate representing a ControlPipe in the DATA OUT stage +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct DataOutStage { + length: usize, +} + +/// Typestate representing a ControlPipe in the STATUS stage +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct StatusStage {} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum Setup { + DataIn(Request, DataInStage), + DataOut(Request, DataOutStage), +} + +pub(crate) struct ControlPipe { + control: C, +} + +impl ControlPipe { + pub(crate) fn new(control: C) -> Self { + ControlPipe { control } + } + + pub(crate) async fn setup(&mut self) -> Setup { + let req = self.control.setup().await; + match (req.direction, req.length) { + (UsbDirection::Out, n) => Setup::DataOut( + req, + DataOutStage { + length: usize::from(n), + }, + ), + (UsbDirection::In, n) => Setup::DataIn( + req, + DataInStage { + length: usize::from(n), + }, + ), + } + } + + pub(crate) async fn data_out<'a>( + &mut self, + buf: &'a mut [u8], + stage: DataOutStage, + ) -> Result<(&'a [u8], StatusStage), ReadError> { + if stage.length == 0 { + Ok((&[], StatusStage {})) + } else { + let req_length = stage.length; + let max_packet_size = self.control.max_packet_size(); + let mut total = 0; + + for chunk in buf.chunks_mut(max_packet_size) { + let size = self.control.data_out(chunk).await?; + total += size; + if size < max_packet_size || total == req_length { + break; + } + } + + Ok((&buf[0..total], StatusStage {})) + } + } + + pub(crate) async fn accept_in(&mut self, buf: &[u8], stage: DataInStage) { + #[cfg(feature = "defmt")] + debug!("control in accept {:x}", buf); + #[cfg(not(feature = "defmt"))] + debug!("control in accept {:x?}", buf); + + let req_len = stage.length; + let len = buf.len().min(req_len); + let max_packet_size = self.control.max_packet_size(); + let need_zlp = len != req_len && (len % usize::from(max_packet_size)) == 0; + + let mut chunks = buf[0..len] + .chunks(max_packet_size) + .chain(need_zlp.then(|| -> &[u8] { &[] })); + + while let Some(chunk) = chunks.next() { + self.control.data_in(chunk, chunks.size_hint().0 == 0).await; + } + } + + pub(crate) async fn accept_in_writer( + &mut self, + req: Request, + stage: DataInStage, + f: impl FnOnce(&mut DescriptorWriter), + ) { + let mut buf = [0; 256]; + let mut w = DescriptorWriter::new(&mut buf); + f(&mut w); + let pos = w.position().min(usize::from(req.length)); + self.accept_in(&buf[..pos], stage).await + } + + pub(crate) fn accept(&mut self, _: StatusStage) { + self.control.accept(); + } + + pub(crate) fn reject(&mut self) { + self.control.reject(); + } +} diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 77a9c33b..067b5b07 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -16,7 +16,7 @@ use heapless::Vec; use self::control::*; use self::descriptor::*; -use self::driver::*; +use self::driver::{Bus, Driver, Event}; use self::types::*; use self::util::*; @@ -92,10 +92,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { Self { bus: driver, config, - control: ControlPipe { - control, - request: None, - }, + control: ControlPipe::new(control), device_descriptor, config_descriptor, bos_descriptor, @@ -134,57 +131,50 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { Either::Right(req) => { debug!("control request: {:x}", req); - match req.direction { - UsbDirection::In => self.handle_control_in(req).await, - UsbDirection::Out => self.handle_control_out(req).await, + match req { + Setup::DataIn(req, stage) => self.handle_control_in(req, stage).await, + Setup::DataOut(req, stage) => self.handle_control_out(req, stage).await, } } } } } - async fn handle_control_out(&mut self, req: Request) { + async fn handle_control_out(&mut self, req: Request, stage: DataOutStage) { const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; - // If the request has a data state, we must read it. - let data = if req.length > 0 { - match self.control.data_out(self.control_buf).await { - Ok(data) => data, - Err(_) => { - warn!("usb: failed to read CONTROL OUT data stage."); - return; - } + let (data, stage) = match self.control.data_out(self.control_buf, stage).await { + Ok(data) => data, + Err(_) => { + warn!("usb: failed to read CONTROL OUT data stage."); + return; } - } else { - &[] }; match (req.request_type, req.recipient) { (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = false; - self.control.accept(); + self.control.accept(stage) } (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = true; - self.control.accept(); + self.control.accept(stage) } (Request::SET_ADDRESS, 1..=127) => { self.pending_address = req.value as u8; - self.control.accept(); + self.control.accept(stage) } (Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { self.device_state = UsbDeviceState::Configured; - self.control.accept(); + self.control.accept(stage) } (Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => match self.device_state { - UsbDeviceState::Default => { - self.control.accept(); - } + UsbDeviceState::Default => self.control.accept(stage), _ => { self.device_state = UsbDeviceState::Addressed; - self.control.accept(); + self.control.accept(stage) } }, _ => self.control.reject(), @@ -193,12 +183,12 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { (Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { let ep_addr = ((req.index as u8) & 0x8f).into(); self.bus.set_stalled(ep_addr, true); - self.control.accept(); + self.control.accept(stage) } (Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { let ep_addr = ((req.index as u8) & 0x8f).into(); self.bus.set_stalled(ep_addr, false); - self.control.accept(); + self.control.accept(stage) } _ => self.control.reject(), }, @@ -218,7 +208,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { _ => handler.control_out(req, data), }; match response { - OutResponse::Accepted => self.control.accept(), + OutResponse::Accepted => self.control.accept(stage), OutResponse::Rejected => self.control.reject(), } } @@ -229,7 +219,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - async fn handle_control_in(&mut self, req: Request) { + async fn handle_control_in(&mut self, req: Request, stage: DataInStage) { match (req.request_type, req.recipient) { (RequestType::Standard, Recipient::Device) => match req.request { Request::GET_STATUS => { @@ -240,17 +230,15 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { if self.remote_wakeup_enabled { status |= 0x0002; } - self.control.accept_in(&status.to_le_bytes()).await; - } - Request::GET_DESCRIPTOR => { - self.handle_get_descriptor(req).await; + self.control.accept_in(&status.to_le_bytes(), stage).await } + Request::GET_DESCRIPTOR => self.handle_get_descriptor(req, stage).await, Request::GET_CONFIGURATION => { let status = match self.device_state { UsbDeviceState::Configured => CONFIGURATION_VALUE, _ => CONFIGURATION_NONE, }; - self.control.accept_in(&status.to_le_bytes()).await; + self.control.accept_in(&status.to_le_bytes(), stage).await } _ => self.control.reject(), }, @@ -261,7 +249,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { if self.bus.is_stalled(ep_addr) { status |= 0x0001; } - self.control.accept_in(&status.to_le_bytes()).await; + self.control.accept_in(&status.to_le_bytes(), stage).await } _ => self.control.reject(), }, @@ -285,7 +273,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { }; match response { - InResponse::Accepted(data) => self.control.accept_in(data).await, + InResponse::Accepted(data) => self.control.accept_in(data, stage).await, InResponse::Rejected => self.control.reject(), } } @@ -296,17 +284,19 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - async fn handle_get_descriptor(&mut self, req: Request) { + async fn handle_get_descriptor(&mut self, req: Request, stage: DataInStage) { let (dtype, index) = req.descriptor_type_index(); match dtype { - descriptor_type::BOS => self.control.accept_in(self.bos_descriptor).await, - descriptor_type::DEVICE => self.control.accept_in(self.device_descriptor).await, - descriptor_type::CONFIGURATION => self.control.accept_in(self.config_descriptor).await, + descriptor_type::BOS => self.control.accept_in(self.bos_descriptor, stage).await, + descriptor_type::DEVICE => self.control.accept_in(self.device_descriptor, stage).await, + descriptor_type::CONFIGURATION => { + self.control.accept_in(self.config_descriptor, stage).await + } descriptor_type::STRING => { if index == 0 { self.control - .accept_in_writer(req, |w| { + .accept_in_writer(req, stage, |w| { w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes()); }) .await @@ -324,7 +314,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { }; if let Some(s) = s { - self.control.accept_in_writer(req, |w| w.string(s)).await; + self.control + .accept_in_writer(req, stage, |w| w.string(s)) + .await } else { self.control.reject() } @@ -334,81 +326,3 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } } - -struct ControlPipe { - control: C, - request: Option, -} - -impl ControlPipe { - async fn setup(&mut self) -> Request { - assert!(self.request.is_none()); - let req = self.control.setup().await; - self.request = Some(req); - req - } - - async fn data_out<'a>(&mut self, buf: &'a mut [u8]) -> Result<&'a [u8], ReadError> { - let req = self.request.unwrap(); - assert_eq!(req.direction, UsbDirection::Out); - assert!(req.length > 0); - let req_length = usize::from(req.length); - - let max_packet_size = self.control.max_packet_size(); - let mut total = 0; - - for chunk in buf.chunks_mut(max_packet_size) { - let size = self.control.data_out(chunk).await?; - total += size; - if size < max_packet_size || total == req_length { - break; - } - } - - Ok(&buf[0..total]) - } - - async fn accept_in(&mut self, buf: &[u8]) -> () { - #[cfg(feature = "defmt")] - debug!("control in accept {:x}", buf); - #[cfg(not(feature = "defmt"))] - debug!("control in accept {:x?}", buf); - let req = unwrap!(self.request); - assert!(req.direction == UsbDirection::In); - - let req_len = usize::from(req.length); - let len = buf.len().min(req_len); - let max_packet_size = self.control.max_packet_size(); - let need_zlp = len != req_len && (len % usize::from(max_packet_size)) == 0; - - let mut chunks = buf[0..len] - .chunks(max_packet_size) - .chain(need_zlp.then(|| -> &[u8] { &[] })); - - while let Some(chunk) = chunks.next() { - self.control.data_in(chunk, chunks.size_hint().0 == 0).await; - } - - self.request = None; - } - - async fn accept_in_writer(&mut self, req: Request, f: impl FnOnce(&mut DescriptorWriter)) { - let mut buf = [0; 256]; - let mut w = DescriptorWriter::new(&mut buf); - f(&mut w); - let pos = w.position().min(usize::from(req.length)); - self.accept_in(&buf[..pos]).await; - } - - fn accept(&mut self) { - assert!(self.request.is_some()); - self.control.accept(); - self.request = None; - } - - fn reject(&mut self) { - assert!(self.request.is_some()); - self.control.reject(); - self.request = None; - } -} From d7d199f2acfc7c11f83dc10fbcf74641f879b0e9 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 2 Apr 2022 04:42:20 +0200 Subject: [PATCH 29/50] nrf/usb: unify in/out wakers for ep0 --- embassy-nrf/src/usb.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 570d1c95..4614389f 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -24,8 +24,9 @@ use crate::util::slice_in_ram; const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; -static EP_IN_WAKERS: [AtomicWaker; 9] = [NEW_AW; 9]; -static EP_OUT_WAKERS: [AtomicWaker; 9] = [NEW_AW; 9]; +static EP0_WAKER: AtomicWaker = NEW_AW; +static EP_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; +static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); pub struct Driver<'d, T: Instance> { @@ -61,12 +62,12 @@ impl<'d, T: Instance> Driver<'d, T> { if regs.events_ep0setup.read().bits() != 0 { regs.intenclr.write(|w| w.ep0setup().clear()); - EP_OUT_WAKERS[0].wake(); + EP0_WAKER.wake(); } if regs.events_ep0datadone.read().bits() != 0 { regs.intenclr.write(|w| w.ep0datadone().clear()); - EP_IN_WAKERS[0].wake(); + EP0_WAKER.wake(); } // USBEVENT and EPDATA events are weird. They're the "aggregate" @@ -92,10 +93,10 @@ impl<'d, T: Instance> Driver<'d, T> { READY_ENDPOINTS.fetch_or(r, Ordering::AcqRel); for i in 1..=7 { if r & (1 << i) != 0 { - EP_IN_WAKERS[i].wake(); + EP_IN_WAKERS[i - 1].wake(); } if r & (1 << (i + 16)) != 0 { - EP_OUT_WAKERS[i].wake(); + EP_OUT_WAKERS[i - 1].wake(); } } } @@ -450,7 +451,7 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { // Wait until ready poll_fn(|cx| { - EP_OUT_WAKERS[i].register(cx.waker()); + EP_OUT_WAKERS[i - 1].register(cx.waker()); let r = READY_ENDPOINTS.load(Ordering::Acquire); if r & (1 << (i + 16)) != 0 { Poll::Ready(()) @@ -478,7 +479,7 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { // Wait until ready. poll_fn(|cx| { - EP_IN_WAKERS[i].register(cx.waker()); + EP_IN_WAKERS[i - 1].register(cx.waker()); let r = READY_ENDPOINTS.load(Ordering::Acquire); if r & (1 << i) != 0 { Poll::Ready(()) @@ -519,7 +520,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { // Wait for SETUP packet regs.intenset.write(|w| w.ep0setup().set()); poll_fn(|cx| { - EP_OUT_WAKERS[0].register(cx.waker()); + EP0_WAKER.register(cx.waker()); let regs = T::regs(); if regs.events_ep0setup.read().bits() != 0 { Poll::Ready(()) @@ -562,7 +563,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { // Wait until ready regs.intenset.write(|w| w.ep0datadone().set()); poll_fn(|cx| { - EP_OUT_WAKERS[0].register(cx.waker()); + EP0_WAKER.register(cx.waker()); let regs = T::regs(); if regs .events_ep0datadone @@ -596,7 +597,7 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { let res = with_timeout( Duration::from_millis(10), poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); + EP0_WAKER.register(cx.waker()); let regs = T::regs(); if regs.events_ep0datadone.read().bits() != 0 { Poll::Ready(()) From 522a87ae429a8ca0444aee5724e10789564d8229 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 2 Apr 2022 04:53:42 +0200 Subject: [PATCH 30/50] usb: centralize all control logging in control.rs --- embassy-nrf/src/usb.rs | 2 -- embassy-usb/src/control.rs | 16 +++++++++++++--- embassy-usb/src/lib.rs | 12 ++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 4614389f..124316a2 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -615,14 +615,12 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { } fn accept(&mut self) { - debug!("control accept"); let regs = T::regs(); regs.tasks_ep0status .write(|w| w.tasks_ep0status().bit(true)); } fn reject(&mut self) { - debug!("control reject"); let regs = T::regs(); regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); } diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 9f1115ff..da48dcca 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -231,6 +231,8 @@ impl ControlPipe { pub(crate) async fn setup(&mut self) -> Setup { let req = self.control.setup().await; + trace!("control request: {:02x}", req); + match (req.direction, req.length) { (UsbDirection::Out, n) => Setup::DataOut( req, @@ -267,15 +269,21 @@ impl ControlPipe { } } - Ok((&buf[0..total], StatusStage {})) + let res = &buf[0..total]; + #[cfg(feature = "defmt")] + trace!(" control out data: {:02x}", buf); + #[cfg(not(feature = "defmt"))] + trace!(" control out data: {:02x?}", buf); + + Ok((res, StatusStage {})) } } pub(crate) async fn accept_in(&mut self, buf: &[u8], stage: DataInStage) { #[cfg(feature = "defmt")] - debug!("control in accept {:x}", buf); + trace!(" control in accept {:02x}", buf); #[cfg(not(feature = "defmt"))] - debug!("control in accept {:x?}", buf); + trace!(" control in accept {:02x?}", buf); let req_len = stage.length; let len = buf.len().min(req_len); @@ -305,10 +313,12 @@ impl ControlPipe { } pub(crate) fn accept(&mut self, _: StatusStage) { + trace!(" control accept"); self.control.accept(); } pub(crate) fn reject(&mut self) { + trace!(" control reject"); self.control.reject(); } } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 067b5b07..1b189383 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -128,14 +128,10 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.device_state = UsbDeviceState::Suspend; } }, - Either::Right(req) => { - debug!("control request: {:x}", req); - - match req { - Setup::DataIn(req, stage) => self.handle_control_in(req, stage).await, - Setup::DataOut(req, stage) => self.handle_control_out(req, stage).await, - } - } + Either::Right(req) => match req { + Setup::DataIn(req, stage) => self.handle_control_in(req, stage).await, + Setup::DataOut(req, stage) => self.handle_control_out(req, stage).await, + }, } } } From 8fe3b44d82f4f53491520898148c4ad337073593 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 2 Apr 2022 05:27:45 +0200 Subject: [PATCH 31/50] usb: log on reset, resume, suspend --- embassy-usb/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 1b189383..d2d3e5e0 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -112,6 +112,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { match select(bus_fut, control_fut).await { Either::Left(evt) => match evt { Event::Reset => { + trace!("usb: reset"); self.bus.reset(); self.device_state = UsbDeviceState::Default; @@ -122,8 +123,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { h.reset(); } } - Event::Resume => {} + Event::Resume => { + trace!("usb: resume"); + } Event::Suspend => { + trace!("usb: suspend"); self.bus.suspend(); self.device_state = UsbDeviceState::Suspend; } From 5ee7a85b33f83131fd42ce229d3aadaf2054f44a Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 15:18:43 -0400 Subject: [PATCH 32/50] Async USB HID class --- embassy-usb-hid/Cargo.toml | 18 ++ embassy-usb-hid/src/fmt.rs | 225 ++++++++++++++ embassy-usb-hid/src/lib.rs | 529 ++++++++++++++++++++++++++++++++ examples/nrf/Cargo.toml | 6 +- examples/nrf/src/bin/usb_hid.rs | 131 ++++++++ 5 files changed, 907 insertions(+), 2 deletions(-) create mode 100644 embassy-usb-hid/Cargo.toml create mode 100644 embassy-usb-hid/src/fmt.rs create mode 100644 embassy-usb-hid/src/lib.rs create mode 100644 examples/nrf/src/bin/usb_hid.rs diff --git a/embassy-usb-hid/Cargo.toml b/embassy-usb-hid/Cargo.toml new file mode 100644 index 00000000..dc3d3cd8 --- /dev/null +++ b/embassy-usb-hid/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "embassy-usb-hid" +version = "0.1.0" +edition = "2021" + +[features] +default = ["usbd-hid"] +usbd-hid = ["dep:usbd-hid", "ssmarshal"] + +[dependencies] +embassy = { version = "0.1.0", path = "../embassy" } +embassy-usb = { version = "0.1.0", path = "../embassy-usb" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +usbd-hid = { version = "0.5.2", optional = true } +ssmarshal = { version = "1.0", default-features = false, optional = true } +futures-util = { version = "0.3.21", default-features = false } diff --git a/embassy-usb-hid/src/fmt.rs b/embassy-usb-hid/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-usb-hid/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs new file mode 100644 index 00000000..c1f70c32 --- /dev/null +++ b/embassy-usb-hid/src/lib.rs @@ -0,0 +1,529 @@ +#![no_std] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +//! Implements HID functionality for a usb-device device. + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +use core::mem::MaybeUninit; + +use embassy::channel::signal::Signal; +use embassy::time::Duration; +use embassy_usb::driver::{EndpointOut, ReadError}; +use embassy_usb::{ + control::{ControlHandler, InResponse, OutResponse, Request, RequestType}, + driver::{Driver, Endpoint, EndpointIn, WriteError}, + UsbDeviceBuilder, +}; +use futures_util::future::{select, Either}; +use futures_util::pin_mut; +#[cfg(feature = "usbd-hid")] +use ssmarshal::serialize; +#[cfg(feature = "usbd-hid")] +use usbd_hid::descriptor::AsInputReport; + +const USB_CLASS_HID: u8 = 0x03; +const USB_SUBCLASS_NONE: u8 = 0x00; +const USB_PROTOCOL_NONE: u8 = 0x00; + +// HID +const HID_DESC_DESCTYPE_HID: u8 = 0x21; +const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; +const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; +const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00; + +const HID_REQ_SET_IDLE: u8 = 0x0a; +const HID_REQ_GET_IDLE: u8 = 0x02; +const HID_REQ_GET_REPORT: u8 = 0x01; +const HID_REQ_SET_REPORT: u8 = 0x09; +const HID_REQ_GET_PROTOCOL: u8 = 0x03; +const HID_REQ_SET_PROTOCOL: u8 = 0x0b; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReportId { + In(u8), + Out(u8), + Feature(u8), +} + +impl ReportId { + fn try_from(value: u16) -> Result { + match value >> 8 { + 1 => Ok(ReportId::In(value as u8)), + 2 => Ok(ReportId::Out(value as u8)), + 3 => Ok(ReportId::Feature(value as u8)), + _ => Err(()), + } + } +} + +pub struct State<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> { + control: MaybeUninit>, + out_signal: Signal<(usize, [u8; OUT_N])>, + feature_signal: Signal<(usize, [u8; FEATURE_N])>, +} + +impl<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> + State<'a, IN_N, OUT_N, FEATURE_N> +{ + pub fn new() -> Self { + State { + control: MaybeUninit::uninit(), + out_signal: Signal::new(), + feature_signal: Signal::new(), + } + } +} + +pub struct HidClass< + 'd, + D: Driver<'d>, + const IN_N: usize, + const OUT_N: usize, + const FEATURE_N: usize, +> { + input: ReportWriter<'d, D, IN_N>, + output: ReportReader<'d, D, OUT_N>, + feature: ReportReader<'d, D, FEATURE_N>, +} + +impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> + HidClass<'d, D, IN_N, OUT_N, FEATURE_N> +{ + /// Creates a new HidClass. + /// + /// poll_ms configures how frequently the host should poll for reading/writing + /// HID reports. A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + /// + /// This allocates two endpoints (IN and OUT). + /// See new_ep_in (IN endpoint only) and new_ep_out (OUT endpoint only) to only create a single + /// endpoint. + pub fn new( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + poll_ms: u8, + ) -> Self { + let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms)); + let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms)); + Self::new_inner( + builder, + state, + report_descriptor, + request_handler, + ep_out, + ep_in, + ) + } + + /// Creates a new HidClass with the provided UsbBus & HID report descriptor. + /// See new() for more details. + pub fn new_ep_in( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + poll_ms: u8, + ) -> Self { + let ep_out = None; + let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms)); + Self::new_inner( + builder, + state, + report_descriptor, + request_handler, + ep_out, + ep_in, + ) + } + + /// Creates a new HidClass with the provided UsbBus & HID report descriptor. + /// See new() for more details. + pub fn new_ep_out( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + poll_ms: u8, + ) -> Self { + let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms)); + let ep_in = None; + Self::new_inner( + builder, + state, + report_descriptor, + request_handler, + ep_out, + ep_in, + ) + } + + fn new_inner( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + ep_out: Option, + ep_in: Option, + ) -> Self { + let control = state.control.write(Control::new( + report_descriptor, + &state.out_signal, + &state.feature_signal, + request_handler, + )); + + control.build(builder, ep_out.as_ref(), ep_in.as_ref()); + + Self { + input: ReportWriter { ep_in }, + output: ReportReader { + ep_out, + receiver: &state.out_signal, + }, + feature: ReportReader { + ep_out: None, + receiver: &state.feature_signal, + }, + } + } + + /// Gets the [`ReportWriter`] for input reports. + /// + /// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out) + /// this writer will be useless as no endpoint is availabe to send reports. + pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> { + &mut self.input + } + + /// Gets the [`ReportReader`] for output reports. + pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> { + &mut self.output + } + + /// Gets the [`ReportReader`] for feature reports. + pub fn feature(&mut self) -> &mut ReportReader<'d, D, FEATURE_N> { + &mut self.feature + } + + /// Splits this `HidClass` into seperate readers/writers for each report type. + pub fn split( + self, + ) -> ( + ReportWriter<'d, D, IN_N>, + ReportReader<'d, D, OUT_N>, + ReportReader<'d, D, FEATURE_N>, + ) { + (self.input, self.output, self.feature) + } +} + +pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { + ep_in: Option, +} + +pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { + ep_out: Option, + receiver: &'d Signal<(usize, [u8; N])>, +} + +impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { + /// Tries to write an input report by serializing the given report structure. + /// + /// Panics if no endpoint is available. + #[cfg(feature = "usbd-hid")] + pub async fn serialize(&mut self, r: &IR) -> Result<(), WriteError> { + let mut buf: [u8; N] = [0; N]; + let size = match serialize(&mut buf, r) { + Ok(size) => size, + Err(_) => return Err(WriteError::BufferOverflow), + }; + self.write(&buf[0..size]).await + } + + /// Writes `report` to its interrupt endpoint. + /// + /// Panics if no endpoint is available. + pub async fn write(&mut self, report: &[u8]) -> Result<(), WriteError> { + assert!(report.len() <= N); + + let ep = self + .ep_in + .as_mut() + .expect("An IN endpoint must be allocated to write input reports."); + + let max_packet_size = usize::from(ep.info().max_packet_size); + let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); + for chunk in report.chunks(max_packet_size) { + ep.write(chunk).await?; + } + + if zlp_needed { + ep.write(&[]).await?; + } + + Ok(()) + } +} + +impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + assert!(buf.len() >= N); + if let Some(ep) = &mut self.ep_out { + let max_packet_size = usize::from(ep.info().max_packet_size); + + let mut chunks = buf.chunks_mut(max_packet_size); + + // Wait until we've received a chunk from the endpoint or a report from a SET_REPORT control request + let (mut total, data) = { + let chunk = unwrap!(chunks.next()); + let fut1 = ep.read(chunk); + pin_mut!(fut1); + match select(fut1, self.receiver.wait()).await { + Either::Left((Ok(size), _)) => (size, None), + Either::Left((Err(err), _)) => return Err(err), + Either::Right(((size, data), _)) => (size, Some(data)), + } + }; + + if let Some(data) = data { + buf[0..total].copy_from_slice(&data[0..total]); + Ok(total) + } else { + for chunk in chunks { + let size = ep.read(chunk).await?; + total += size; + if size < max_packet_size || total == N { + break; + } + } + Ok(total) + } + } else { + let (total, data) = self.receiver.wait().await; + buf[0..total].copy_from_slice(&data[0..total]); + Ok(total) + } + } +} + +pub trait RequestHandler { + /// Read the value of report `id` into `buf` returning the size. + /// + /// Returns `None` if `id` is invalid or no data is available. + fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option { + let _ = (id, buf); + None + } + + /// Set the idle rate for `id` to `dur`. + /// + /// If `id` is `None`, set the idle rate of all input reports to `dur`. If + /// an indefinite duration is requested, `dur` will be set to `Duration::MAX`. + fn set_idle(&self, id: Option, dur: Duration) { + let _ = (id, dur); + } + + /// Get the idle rate for `id`. + /// + /// If `id` is `None`, get the idle rate for all reports. Returning `None` + /// will reject the control request. Any duration above 1.020 seconds or 0 + /// will be returned as an indefinite idle rate. + fn get_idle(&self, id: Option) -> Option { + let _ = id; + None + } +} + +pub struct Control<'d, const OUT_N: usize, const FEATURE_N: usize> { + report_descriptor: &'static [u8], + out_signal: &'d Signal<(usize, [u8; OUT_N])>, + feature_signal: &'d Signal<(usize, [u8; FEATURE_N])>, + request_handler: Option<&'d dyn RequestHandler>, + hid_descriptor: [u8; 9], +} + +impl<'a, const OUT_N: usize, const FEATURE_N: usize> Control<'a, OUT_N, FEATURE_N> { + fn new( + report_descriptor: &'static [u8], + out_signal: &'a Signal<(usize, [u8; OUT_N])>, + feature_signal: &'a Signal<(usize, [u8; FEATURE_N])>, + request_handler: Option<&'a dyn RequestHandler>, + ) -> Self { + Control { + report_descriptor, + out_signal, + feature_signal, + request_handler, + hid_descriptor: [ + // Length of buf inclusive of size prefix + 9, + // Descriptor type + HID_DESC_DESCTYPE_HID, + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (report_descriptor.len() & 0xFF) as u8, + (report_descriptor.len() >> 8 & 0xFF) as u8, + ], + } + } + + fn build<'d, D: Driver<'d>>( + &'d mut self, + builder: &mut UsbDeviceBuilder<'d, D>, + ep_out: Option<&D::EndpointOut>, + ep_in: Option<&D::EndpointIn>, + ) { + let len = self.report_descriptor.len(); + let if_num = builder.alloc_interface_with_handler(self); + + builder.config_descriptor.interface( + if_num, + USB_CLASS_HID, + USB_SUBCLASS_NONE, + USB_PROTOCOL_NONE, + ); + + // HID descriptor + builder.config_descriptor.write( + HID_DESC_DESCTYPE_HID, + &[ + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (len & 0xFF) as u8, + (len >> 8 & 0xFF) as u8, + ], + ); + + if let Some(ep) = ep_out { + builder.config_descriptor.endpoint(ep.info()); + } + if let Some(ep) = ep_in { + builder.config_descriptor.endpoint(ep.info()); + } + } +} + +impl<'d, const OUT_N: usize, const FEATURE_N: usize> ControlHandler + for Control<'d, OUT_N, FEATURE_N> +{ + fn reset(&mut self) {} + + fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse { + trace!("HID control_out {:?} {=[u8]:x}", req, data); + if let RequestType::Class = req.request_type { + match req.request { + HID_REQ_SET_IDLE => { + if let Some(handler) = self.request_handler.as_ref() { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + let dur = u64::from(req.value >> 8); + let dur = if dur == 0 { + Duration::MAX + } else { + Duration::from_millis(4 * dur) + }; + handler.set_idle(id, dur); + } + OutResponse::Accepted + } + HID_REQ_SET_REPORT => match ReportId::try_from(req.value) { + Ok(ReportId::In(_)) => OutResponse::Rejected, + Ok(ReportId::Out(_id)) => { + let mut buf = [0; OUT_N]; + buf[0..data.len()].copy_from_slice(data); + self.out_signal.signal((data.len(), buf)); + OutResponse::Accepted + } + Ok(ReportId::Feature(_id)) => { + let mut buf = [0; FEATURE_N]; + buf[0..data.len()].copy_from_slice(data); + self.feature_signal.signal((data.len(), buf)); + OutResponse::Accepted + } + Err(_) => OutResponse::Rejected, + }, + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + OutResponse::Accepted + } else { + warn!("HID Boot Protocol is unsupported."); + OutResponse::Rejected // UNSUPPORTED: Boot Protocol + } + } + _ => OutResponse::Rejected, + } + } else { + OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + trace!("HID control_in {:?}", req); + match (req.request_type, req.request) { + (RequestType::Standard, Request::GET_DESCRIPTOR) => match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), + HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), + _ => InResponse::Rejected, + }, + (RequestType::Class, HID_REQ_GET_REPORT) => { + let size = match ReportId::try_from(req.value) { + Ok(id) => self + .request_handler + .as_ref() + .and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + InResponse::Accepted(&buf[0..size]) + } else { + InResponse::Rejected + } + } + (RequestType::Class, HID_REQ_GET_IDLE) => { + if let Some(handler) = self.request_handler.as_ref() { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + if let Some(dur) = handler.get_idle(id) { + let dur = u8::try_from(dur.as_millis() / 4).unwrap_or(0); + buf[0] = dur; + InResponse::Accepted(&buf[0..1]) + } else { + InResponse::Rejected + } + } else { + InResponse::Rejected + } + } + (RequestType::Class, HID_REQ_GET_PROTOCOL) => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + InResponse::Accepted(&buf[0..1]) + } + _ => InResponse::Rejected, + } + } +} diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index aa30f3fa..e944c171 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -6,13 +6,14 @@ version = "0.1.0" [features] default = ["nightly"] -nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial"] +nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid"] [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true } +embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true } defmt = "0.3" defmt-rtt = "0.3" @@ -23,4 +24,5 @@ panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" - +usbd-hid = "0.5.2" +serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs new file mode 100644 index 00000000..1fd056d0 --- /dev/null +++ b/examples/nrf/src/bin/usb_hid.rs @@ -0,0 +1,131 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use core::mem; +use defmt::*; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy_nrf::interrupt; +use embassy_nrf::pac; +use embassy_nrf::usb::Driver; +use embassy_nrf::Peripherals; +use embassy_usb::{Config, UsbDeviceBuilder}; +use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; +use futures::future::join; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + let power: pac::POWER = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + info!("Waiting for vbus..."); + while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} + info!("vbus OK"); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let driver = Driver::new(p.USBD, irq); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Tactile Engineering"); + config.product = Some("Testy"); + config.serial_number = Some("12345678"); + config.max_power = 100; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 16]; + let request_handler = MyRequestHandler {}; + + let mut state = State::<5, 0, 0>::new(); + + let mut builder = UsbDeviceBuilder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + // let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + let mut hid = HidClass::new( + &mut builder, + &mut state, + MouseReport::desc(), + Some(&request_handler), + 60, + ); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + loop { + Timer::after(Duration::from_millis(500)).await; + hid.input() + .serialize(&MouseReport { + buttons: 0, + x: 0, + y: 4, + wheel: 0, + pan: 0, + }) + .await + .unwrap(); + + Timer::after(Duration::from_millis(500)).await; + hid.input() + .serialize(&MouseReport { + buttons: 0, + x: 0, + y: -4, + wheel: 0, + pan: 0, + }) + .await + .unwrap(); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, hid_fut).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_idle(&self, id: Option, dur: Duration) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle(&self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} From cf89c855690f705744d875c4be7cebea1a133f7e Mon Sep 17 00:00:00 2001 From: alexmoon Date: Thu, 31 Mar 2022 10:04:41 -0400 Subject: [PATCH 33/50] Log when reports are dropped without being read. --- embassy-usb-hid/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index c1f70c32..14aadbc8 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -454,12 +454,18 @@ impl<'d, const OUT_N: usize, const FEATURE_N: usize> ControlHandler Ok(ReportId::Out(_id)) => { let mut buf = [0; OUT_N]; buf[0..data.len()].copy_from_slice(data); + if self.out_signal.signaled() { + warn!("Output report dropped before being read!"); + } self.out_signal.signal((data.len(), buf)); OutResponse::Accepted } Ok(ReportId::Feature(_id)) => { let mut buf = [0; FEATURE_N]; buf[0..data.len()].copy_from_slice(data); + if self.feature_signal.signaled() { + warn!("Feature report dropped before being read!"); + } self.feature_signal.signal((data.len(), buf)); OutResponse::Accepted } From a51de5a39a4bc246d6b3696ac94a200e93d918ea Mon Sep 17 00:00:00 2001 From: alexmoon Date: Thu, 31 Mar 2022 11:25:01 -0400 Subject: [PATCH 34/50] Remove the feature report reader --- embassy-usb-hid/src/lib.rs | 98 ++++++++++++--------------------- examples/nrf/src/bin/usb_hid.rs | 11 +++- 2 files changed, 43 insertions(+), 66 deletions(-) diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index 14aadbc8..0c449fa2 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -60,39 +60,26 @@ impl ReportId { } } -pub struct State<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> { - control: MaybeUninit>, +pub struct State<'a, const IN_N: usize, const OUT_N: usize> { + control: MaybeUninit>, out_signal: Signal<(usize, [u8; OUT_N])>, - feature_signal: Signal<(usize, [u8; FEATURE_N])>, } -impl<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> - State<'a, IN_N, OUT_N, FEATURE_N> -{ +impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { pub fn new() -> Self { State { control: MaybeUninit::uninit(), out_signal: Signal::new(), - feature_signal: Signal::new(), } } } -pub struct HidClass< - 'd, - D: Driver<'d>, - const IN_N: usize, - const OUT_N: usize, - const FEATURE_N: usize, -> { +pub struct HidClass<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> { input: ReportWriter<'d, D, IN_N>, output: ReportReader<'d, D, OUT_N>, - feature: ReportReader<'d, D, FEATURE_N>, } -impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> - HidClass<'d, D, IN_N, OUT_N, FEATURE_N> -{ +impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, IN_N, OUT_N> { /// Creates a new HidClass. /// /// poll_ms configures how frequently the host should poll for reading/writing @@ -105,7 +92,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: /// endpoint. pub fn new( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + state: &'d mut State<'d, IN_N, OUT_N>, report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, poll_ms: u8, @@ -126,7 +113,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: /// See new() for more details. pub fn new_ep_in( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + state: &'d mut State<'d, IN_N, OUT_N>, report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, poll_ms: u8, @@ -147,7 +134,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: /// See new() for more details. pub fn new_ep_out( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + state: &'d mut State<'d, IN_N, OUT_N>, report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, poll_ms: u8, @@ -166,7 +153,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: fn new_inner( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + state: &'d mut State<'d, IN_N, OUT_N>, report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, ep_out: Option, @@ -175,7 +162,6 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: let control = state.control.write(Control::new( report_descriptor, &state.out_signal, - &state.feature_signal, request_handler, )); @@ -187,10 +173,6 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: ep_out, receiver: &state.out_signal, }, - feature: ReportReader { - ep_out: None, - receiver: &state.feature_signal, - }, } } @@ -207,20 +189,9 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: &mut self.output } - /// Gets the [`ReportReader`] for feature reports. - pub fn feature(&mut self) -> &mut ReportReader<'d, D, FEATURE_N> { - &mut self.feature - } - - /// Splits this `HidClass` into seperate readers/writers for each report type. - pub fn split( - self, - ) -> ( - ReportWriter<'d, D, IN_N>, - ReportReader<'d, D, OUT_N>, - ReportReader<'d, D, FEATURE_N>, - ) { - (self.input, self.output, self.feature) + /// Splits this `HidClass` into seperate readers/writers for input and output reports. + pub fn split(self) -> (ReportWriter<'d, D, IN_N>, ReportReader<'d, D, OUT_N>) { + (self.input, self.output) } } @@ -314,7 +285,7 @@ impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { } pub trait RequestHandler { - /// Read the value of report `id` into `buf` returning the size. + /// Reads the value of report `id` into `buf` returning the size. /// /// Returns `None` if `id` is invalid or no data is available. fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option { @@ -322,12 +293,13 @@ pub trait RequestHandler { None } - /// Set the idle rate for `id` to `dur`. + /// Sets the value of report `id` to `data`. /// - /// If `id` is `None`, set the idle rate of all input reports to `dur`. If - /// an indefinite duration is requested, `dur` will be set to `Duration::MAX`. - fn set_idle(&self, id: Option, dur: Duration) { - let _ = (id, dur); + /// This is only called for feature or input reports. Output reports + /// are routed through [`HidClass::output()`]. + fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { + let _ = (id, data); + OutResponse::Rejected } /// Get the idle rate for `id`. @@ -339,27 +311,32 @@ pub trait RequestHandler { let _ = id; None } + + /// Set the idle rate for `id` to `dur`. + /// + /// If `id` is `None`, set the idle rate of all input reports to `dur`. If + /// an indefinite duration is requested, `dur` will be set to `Duration::MAX`. + fn set_idle(&self, id: Option, dur: Duration) { + let _ = (id, dur); + } } -pub struct Control<'d, const OUT_N: usize, const FEATURE_N: usize> { +pub struct Control<'d, const OUT_N: usize> { report_descriptor: &'static [u8], out_signal: &'d Signal<(usize, [u8; OUT_N])>, - feature_signal: &'d Signal<(usize, [u8; FEATURE_N])>, request_handler: Option<&'d dyn RequestHandler>, hid_descriptor: [u8; 9], } -impl<'a, const OUT_N: usize, const FEATURE_N: usize> Control<'a, OUT_N, FEATURE_N> { +impl<'a, const OUT_N: usize> Control<'a, OUT_N> { fn new( report_descriptor: &'static [u8], out_signal: &'a Signal<(usize, [u8; OUT_N])>, - feature_signal: &'a Signal<(usize, [u8; FEATURE_N])>, request_handler: Option<&'a dyn RequestHandler>, ) -> Self { Control { report_descriptor, out_signal, - feature_signal, request_handler, hid_descriptor: [ // Length of buf inclusive of size prefix @@ -426,9 +403,7 @@ impl<'a, const OUT_N: usize, const FEATURE_N: usize> Control<'a, OUT_N, FEATURE_ } } -impl<'d, const OUT_N: usize, const FEATURE_N: usize> ControlHandler - for Control<'d, OUT_N, FEATURE_N> -{ +impl<'d, const OUT_N: usize> ControlHandler for Control<'d, OUT_N> { fn reset(&mut self) {} fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse { @@ -450,7 +425,6 @@ impl<'d, const OUT_N: usize, const FEATURE_N: usize> ControlHandler OutResponse::Accepted } HID_REQ_SET_REPORT => match ReportId::try_from(req.value) { - Ok(ReportId::In(_)) => OutResponse::Rejected, Ok(ReportId::Out(_id)) => { let mut buf = [0; OUT_N]; buf[0..data.len()].copy_from_slice(data); @@ -460,14 +434,12 @@ impl<'d, const OUT_N: usize, const FEATURE_N: usize> ControlHandler self.out_signal.signal((data.len(), buf)); OutResponse::Accepted } - Ok(ReportId::Feature(_id)) => { - let mut buf = [0; FEATURE_N]; - buf[0..data.len()].copy_from_slice(data); - if self.feature_signal.signaled() { - warn!("Feature report dropped before being read!"); + Ok(id @ ReportId::Feature(_)) | Ok(id @ ReportId::In(_)) => { + if let Some(handler) = self.request_handler.as_ref() { + handler.set_report(id, data) + } else { + OutResponse::Rejected } - self.feature_signal.signal((data.len(), buf)); - OutResponse::Accepted } Err(_) => OutResponse::Rejected, }, diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 1fd056d0..11c2d71a 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -14,6 +14,7 @@ use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; +use embassy_usb::control::OutResponse; use embassy_usb::{Config, UsbDeviceBuilder}; use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; use futures::future::join; @@ -51,7 +52,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; - let mut state = State::<5, 0, 0>::new(); + let mut state = State::<5, 0>::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -63,8 +64,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - // let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); - let mut hid = HidClass::new( + let mut hid = HidClass::new_ep_in( &mut builder, &mut state, MouseReport::desc(), @@ -120,6 +120,11 @@ impl RequestHandler for MyRequestHandler { None } + fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + fn set_idle(&self, id: Option, dur: Duration) { info!("Set idle rate for {:?} to {:?}", id, dur); } From daf2379fa4d4ab8772982847613a09a750ac7ba8 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Fri, 1 Apr 2022 10:15:08 -0400 Subject: [PATCH 35/50] Make the interupt IN endpoint non-optional --- embassy-usb-hid/src/lib.rs | 48 ++++++++------------------------------ 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index 0c449fa2..4a7e6032 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -98,7 +98,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, I poll_ms: u8, ) -> Self { let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms)); - let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms)); + let ep_in = builder.alloc_interrupt_endpoint_in(64, poll_ms); Self::new_inner( builder, state, @@ -119,28 +119,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, I poll_ms: u8, ) -> Self { let ep_out = None; - let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms)); - Self::new_inner( - builder, - state, - report_descriptor, - request_handler, - ep_out, - ep_in, - ) - } - - /// Creates a new HidClass with the provided UsbBus & HID report descriptor. - /// See new() for more details. - pub fn new_ep_out( - builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N>, - report_descriptor: &'static [u8], - request_handler: Option<&'d dyn RequestHandler>, - poll_ms: u8, - ) -> Self { - let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms)); - let ep_in = None; + let ep_in = builder.alloc_interrupt_endpoint_in(64, poll_ms); Self::new_inner( builder, state, @@ -157,7 +136,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, I report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, ep_out: Option, - ep_in: Option, + ep_in: D::EndpointIn, ) -> Self { let control = state.control.write(Control::new( report_descriptor, @@ -165,7 +144,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, I request_handler, )); - control.build(builder, ep_out.as_ref(), ep_in.as_ref()); + control.build(builder, ep_out.as_ref(), &ep_in); Self { input: ReportWriter { ep_in }, @@ -196,7 +175,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, I } pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { - ep_in: Option, + ep_in: D::EndpointIn, } pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { @@ -224,19 +203,14 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { pub async fn write(&mut self, report: &[u8]) -> Result<(), WriteError> { assert!(report.len() <= N); - let ep = self - .ep_in - .as_mut() - .expect("An IN endpoint must be allocated to write input reports."); - - let max_packet_size = usize::from(ep.info().max_packet_size); + let max_packet_size = usize::from(self.ep_in.info().max_packet_size); let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); for chunk in report.chunks(max_packet_size) { - ep.write(chunk).await?; + self.ep_in.write(chunk).await?; } if zlp_needed { - ep.write(&[]).await?; + self.ep_in.write(&[]).await?; } Ok(()) @@ -363,7 +337,7 @@ impl<'a, const OUT_N: usize> Control<'a, OUT_N> { &'d mut self, builder: &mut UsbDeviceBuilder<'d, D>, ep_out: Option<&D::EndpointOut>, - ep_in: Option<&D::EndpointIn>, + ep_in: &D::EndpointIn, ) { let len = self.report_descriptor.len(); let if_num = builder.alloc_interface_with_handler(self); @@ -394,12 +368,10 @@ impl<'a, const OUT_N: usize> Control<'a, OUT_N> { ], ); + builder.config_descriptor.endpoint(ep_in.info()); if let Some(ep) = ep_out { builder.config_descriptor.endpoint(ep.info()); } - if let Some(ep) = ep_in { - builder.config_descriptor.endpoint(ep.info()); - } } } From c309531874052bc96ef9ce39dd8698c120cee824 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Fri, 1 Apr 2022 10:57:37 -0400 Subject: [PATCH 36/50] Remove output() and split() methods from HidClass when there is no out endpoint, and route set_report requests for output reports to RequestHandler::set_report in that case. --- embassy-usb-hid/src/lib.rs | 139 +++++++++++++++----------------- examples/nrf/src/bin/usb_hid.rs | 3 +- 2 files changed, 69 insertions(+), 73 deletions(-) diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index 4a7e6032..8bc9efdb 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -74,12 +74,55 @@ impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { } } -pub struct HidClass<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> { +pub struct HidClass<'d, D: Driver<'d>, T, const IN_N: usize> { input: ReportWriter<'d, D, IN_N>, - output: ReportReader<'d, D, OUT_N>, + output: T, } -impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, IN_N, OUT_N> { +impl<'d, D: Driver<'d>, const IN_N: usize> HidClass<'d, D, (), IN_N> { + /// Creates a new HidClass. + /// + /// poll_ms configures how frequently the host should poll for reading/writing + /// HID reports. A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + /// + /// This allocates an IN endpoint only. + pub fn new( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, 0>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + poll_ms: u8, + max_packet_size: u16, + ) -> Self { + let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); + let control = state + .control + .write(Control::new(report_descriptor, None, request_handler)); + + control.build(builder, None, &ep_in); + + Self { + input: ReportWriter { ep_in }, + output: (), + } + } +} + +impl<'d, D: Driver<'d>, T, const IN_N: usize> HidClass<'d, D, T, IN_N> { + /// Gets the [`ReportWriter`] for input reports. + /// + /// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out) + /// this writer will be useless as no endpoint is availabe to send reports. + pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> { + &mut self.input + } +} + +impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> + HidClass<'d, D, ReportReader<'d, D, OUT_N>, IN_N> +{ /// Creates a new HidClass. /// /// poll_ms configures how frequently the host should poll for reading/writing @@ -88,59 +131,20 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, I /// high performance uses, and a value of 255 is good for best-effort usecases. /// /// This allocates two endpoints (IN and OUT). - /// See new_ep_in (IN endpoint only) and new_ep_out (OUT endpoint only) to only create a single - /// endpoint. - pub fn new( + pub fn with_output_ep( builder: &mut UsbDeviceBuilder<'d, D>, state: &'d mut State<'d, IN_N, OUT_N>, report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, poll_ms: u8, + max_packet_size: u16, ) -> Self { - let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms)); - let ep_in = builder.alloc_interrupt_endpoint_in(64, poll_ms); - Self::new_inner( - builder, - state, - report_descriptor, - request_handler, - ep_out, - ep_in, - ) - } + let ep_out = Some(builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms)); + let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); - /// Creates a new HidClass with the provided UsbBus & HID report descriptor. - /// See new() for more details. - pub fn new_ep_in( - builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N>, - report_descriptor: &'static [u8], - request_handler: Option<&'d dyn RequestHandler>, - poll_ms: u8, - ) -> Self { - let ep_out = None; - let ep_in = builder.alloc_interrupt_endpoint_in(64, poll_ms); - Self::new_inner( - builder, - state, - report_descriptor, - request_handler, - ep_out, - ep_in, - ) - } - - fn new_inner( - builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N>, - report_descriptor: &'static [u8], - request_handler: Option<&'d dyn RequestHandler>, - ep_out: Option, - ep_in: D::EndpointIn, - ) -> Self { let control = state.control.write(Control::new( report_descriptor, - &state.out_signal, + Some(&state.out_signal), request_handler, )); @@ -155,14 +159,6 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> HidClass<'d, D, I } } - /// Gets the [`ReportWriter`] for input reports. - /// - /// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out) - /// this writer will be useless as no endpoint is availabe to send reports. - pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> { - &mut self.input - } - /// Gets the [`ReportReader`] for output reports. pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> { &mut self.output @@ -269,8 +265,9 @@ pub trait RequestHandler { /// Sets the value of report `id` to `data`. /// - /// This is only called for feature or input reports. Output reports - /// are routed through [`HidClass::output()`]. + /// If an output endpoint has been allocated, output reports + /// are routed through [`HidClass::output()`]. Otherwise they + /// are sent here, along with input and feature reports. fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { let _ = (id, data); OutResponse::Rejected @@ -295,9 +292,9 @@ pub trait RequestHandler { } } -pub struct Control<'d, const OUT_N: usize> { +struct Control<'d, const OUT_N: usize> { report_descriptor: &'static [u8], - out_signal: &'d Signal<(usize, [u8; OUT_N])>, + out_signal: Option<&'d Signal<(usize, [u8; OUT_N])>>, request_handler: Option<&'d dyn RequestHandler>, hid_descriptor: [u8; 9], } @@ -305,7 +302,7 @@ pub struct Control<'d, const OUT_N: usize> { impl<'a, const OUT_N: usize> Control<'a, OUT_N> { fn new( report_descriptor: &'static [u8], - out_signal: &'a Signal<(usize, [u8; OUT_N])>, + out_signal: Option<&'a Signal<(usize, [u8; OUT_N])>>, request_handler: Option<&'a dyn RequestHandler>, ) -> Self { Control { @@ -396,24 +393,22 @@ impl<'d, const OUT_N: usize> ControlHandler for Control<'d, OUT_N> { } OutResponse::Accepted } - HID_REQ_SET_REPORT => match ReportId::try_from(req.value) { - Ok(ReportId::Out(_id)) => { + HID_REQ_SET_REPORT => match ( + ReportId::try_from(req.value), + self.out_signal, + self.request_handler.as_ref(), + ) { + (Ok(ReportId::Out(_)), Some(signal), _) => { let mut buf = [0; OUT_N]; buf[0..data.len()].copy_from_slice(data); - if self.out_signal.signaled() { + if signal.signaled() { warn!("Output report dropped before being read!"); } - self.out_signal.signal((data.len(), buf)); + signal.signal((data.len(), buf)); OutResponse::Accepted } - Ok(id @ ReportId::Feature(_)) | Ok(id @ ReportId::In(_)) => { - if let Some(handler) = self.request_handler.as_ref() { - handler.set_report(id, data) - } else { - OutResponse::Rejected - } - } - Err(_) => OutResponse::Rejected, + (Ok(id), _, Some(handler)) => handler.set_report(id, data), + _ => OutResponse::Rejected, }, HID_REQ_SET_PROTOCOL => { if req.value == 1 { diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 11c2d71a..5253f225 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -64,12 +64,13 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - let mut hid = HidClass::new_ep_in( + let mut hid = HidClass::new( &mut builder, &mut state, MouseReport::desc(), Some(&request_handler), 60, + 8, ); // Build the builder. From c8ad82057d25e6b9414c7065a750761e510d84d9 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Fri, 1 Apr 2022 15:48:37 -0400 Subject: [PATCH 37/50] Reduce memory overhead and simplify logic for merging endpoint and control request output reports. --- embassy-nrf/src/usb.rs | 20 ++++-- embassy-usb-hid/src/async_lease.rs | 90 ++++++++++++++++++++++++ embassy-usb-hid/src/lib.rs | 106 ++++++++++++++--------------- embassy-usb/src/driver.rs | 8 +++ 4 files changed, 163 insertions(+), 61 deletions(-) create mode 100644 embassy-usb-hid/src/async_lease.rs diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 124316a2..df0efa51 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -443,8 +443,23 @@ unsafe fn write_dma(i: usize, buf: &[u8]) { impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { type ReadFuture<'a> = impl Future> + 'a where Self: 'a; + type DataReadyFuture<'a> = impl Future + 'a where Self: 'a; fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { + let i = self.info.addr.index(); + assert!(i != 0); + + self.wait_data_ready().await; + + // Mark as not ready + READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); + + unsafe { read_dma::(i, buf) } + } + } + + fn wait_data_ready<'a>(&'a mut self) -> Self::DataReadyFuture<'a> { async move { let i = self.info.addr.index(); assert!(i != 0); @@ -460,11 +475,6 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { } }) .await; - - // Mark as not ready - READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); - - unsafe { read_dma::(i, buf) } } } } diff --git a/embassy-usb-hid/src/async_lease.rs b/embassy-usb-hid/src/async_lease.rs new file mode 100644 index 00000000..0971daa2 --- /dev/null +++ b/embassy-usb-hid/src/async_lease.rs @@ -0,0 +1,90 @@ +use core::cell::Cell; +use core::future::Future; +use core::task::{Poll, Waker}; + +enum AsyncLeaseState { + Empty, + Waiting(*mut u8, usize, Waker), + Done(usize), +} + +impl Default for AsyncLeaseState { + fn default() -> Self { + AsyncLeaseState::Empty + } +} + +#[derive(Default)] +pub struct AsyncLease { + state: Cell, +} + +pub struct AsyncLeaseFuture<'a> { + buf: &'a mut [u8], + state: &'a Cell, +} + +impl<'a> Drop for AsyncLeaseFuture<'a> { + fn drop(&mut self) { + self.state.set(AsyncLeaseState::Empty); + } +} + +impl<'a> Future for AsyncLeaseFuture<'a> { + type Output = usize; + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> Poll { + match self.state.take() { + AsyncLeaseState::Done(len) => Poll::Ready(len), + state => { + if let AsyncLeaseState::Waiting(ptr, _, _) = state { + assert_eq!( + ptr, + self.buf.as_mut_ptr(), + "lend() called on a busy AsyncLease." + ); + } + + self.state.set(AsyncLeaseState::Waiting( + self.buf.as_mut_ptr(), + self.buf.len(), + cx.waker().clone(), + )); + Poll::Pending + } + } + } +} + +pub struct AsyncLeaseNotReady {} + +impl AsyncLease { + pub fn new() -> Self { + Default::default() + } + + pub fn try_borrow_mut usize>( + &self, + f: F, + ) -> Result<(), AsyncLeaseNotReady> { + if let AsyncLeaseState::Waiting(data, len, waker) = self.state.take() { + let buf = unsafe { core::slice::from_raw_parts_mut(data, len) }; + let len = f(buf); + self.state.set(AsyncLeaseState::Done(len)); + waker.wake(); + Ok(()) + } else { + Err(AsyncLeaseNotReady {}) + } + } + + pub fn lend<'a>(&'a self, buf: &'a mut [u8]) -> AsyncLeaseFuture<'a> { + AsyncLeaseFuture { + buf, + state: &self.state, + } + } +} diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index 8bc9efdb..43e67880 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -9,7 +9,7 @@ pub(crate) mod fmt; use core::mem::MaybeUninit; -use embassy::channel::signal::Signal; +use async_lease::AsyncLease; use embassy::time::Duration; use embassy_usb::driver::{EndpointOut, ReadError}; use embassy_usb::{ @@ -24,6 +24,8 @@ use ssmarshal::serialize; #[cfg(feature = "usbd-hid")] use usbd_hid::descriptor::AsInputReport; +mod async_lease; + const USB_CLASS_HID: u8 = 0x03; const USB_SUBCLASS_NONE: u8 = 0x00; const USB_PROTOCOL_NONE: u8 = 0x00; @@ -61,15 +63,15 @@ impl ReportId { } pub struct State<'a, const IN_N: usize, const OUT_N: usize> { - control: MaybeUninit>, - out_signal: Signal<(usize, [u8; OUT_N])>, + control: MaybeUninit>, + lease: AsyncLease, } impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { pub fn new() -> Self { State { control: MaybeUninit::uninit(), - out_signal: Signal::new(), + lease: AsyncLease::new(), } } } @@ -139,22 +141,22 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> poll_ms: u8, max_packet_size: u16, ) -> Self { - let ep_out = Some(builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms)); + let ep_out = builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms); let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); let control = state.control.write(Control::new( report_descriptor, - Some(&state.out_signal), + Some(&state.lease), request_handler, )); - control.build(builder, ep_out.as_ref(), &ep_in); + control.build(builder, Some(&ep_out), &ep_in); Self { input: ReportWriter { ep_in }, output: ReportReader { ep_out, - receiver: &state.out_signal, + lease: &state.lease, }, } } @@ -175,8 +177,8 @@ pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { } pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { - ep_out: Option, - receiver: &'d Signal<(usize, [u8; N])>, + ep_out: D::EndpointOut, + lease: &'d AsyncLease, } impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { @@ -216,41 +218,29 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { pub async fn read(&mut self, buf: &mut [u8]) -> Result { assert!(buf.len() >= N); - if let Some(ep) = &mut self.ep_out { - let max_packet_size = usize::from(ep.info().max_packet_size); - let mut chunks = buf.chunks_mut(max_packet_size); - - // Wait until we've received a chunk from the endpoint or a report from a SET_REPORT control request - let (mut total, data) = { - let chunk = unwrap!(chunks.next()); - let fut1 = ep.read(chunk); - pin_mut!(fut1); - match select(fut1, self.receiver.wait()).await { - Either::Left((Ok(size), _)) => (size, None), - Either::Left((Err(err), _)) => return Err(err), - Either::Right(((size, data), _)) => (size, Some(data)), - } - }; - - if let Some(data) = data { - buf[0..total].copy_from_slice(&data[0..total]); - Ok(total) - } else { - for chunk in chunks { - let size = ep.read(chunk).await?; - total += size; - if size < max_packet_size || total == N { - break; - } - } - Ok(total) + // Wait until a packet is ready to read from the endpoint or a SET_REPORT control request is received + { + let data_ready = self.ep_out.wait_data_ready(); + pin_mut!(data_ready); + match select(data_ready, self.lease.lend(buf)).await { + Either::Left(_) => (), + Either::Right((len, _)) => return Ok(len), } - } else { - let (total, data) = self.receiver.wait().await; - buf[0..total].copy_from_slice(&data[0..total]); - Ok(total) } + + // Read packets from the endpoint + let max_packet_size = usize::from(self.ep_out.info().max_packet_size); + let mut total = 0; + for chunk in buf.chunks_mut(max_packet_size) { + let size = self.ep_out.read(chunk).await?; + total += size; + if size < max_packet_size || total == N { + break; + } + } + + Ok(total) } } @@ -292,22 +282,22 @@ pub trait RequestHandler { } } -struct Control<'d, const OUT_N: usize> { +struct Control<'d> { report_descriptor: &'static [u8], - out_signal: Option<&'d Signal<(usize, [u8; OUT_N])>>, + out_lease: Option<&'d AsyncLease>, request_handler: Option<&'d dyn RequestHandler>, hid_descriptor: [u8; 9], } -impl<'a, const OUT_N: usize> Control<'a, OUT_N> { +impl<'a> Control<'a> { fn new( report_descriptor: &'static [u8], - out_signal: Option<&'a Signal<(usize, [u8; OUT_N])>>, + out_lease: Option<&'a AsyncLease>, request_handler: Option<&'a dyn RequestHandler>, ) -> Self { Control { report_descriptor, - out_signal, + out_lease, request_handler, hid_descriptor: [ // Length of buf inclusive of size prefix @@ -372,7 +362,7 @@ impl<'a, const OUT_N: usize> Control<'a, OUT_N> { } } -impl<'d, const OUT_N: usize> ControlHandler for Control<'d, OUT_N> { +impl<'d> ControlHandler for Control<'d> { fn reset(&mut self) {} fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse { @@ -395,17 +385,21 @@ impl<'d, const OUT_N: usize> ControlHandler for Control<'d, OUT_N> { } HID_REQ_SET_REPORT => match ( ReportId::try_from(req.value), - self.out_signal, + self.out_lease, self.request_handler.as_ref(), ) { - (Ok(ReportId::Out(_)), Some(signal), _) => { - let mut buf = [0; OUT_N]; - buf[0..data.len()].copy_from_slice(data); - if signal.signaled() { - warn!("Output report dropped before being read!"); + (Ok(ReportId::Out(_)), Some(lease), _) => { + match lease.try_borrow_mut(|buf| { + let len = buf.len().min(data.len()); + buf[0..len].copy_from_slice(&data[0..len]); + len + }) { + Ok(()) => OutResponse::Accepted, + Err(_) => { + warn!("SET_REPORT received for output report with no reader listening."); + OutResponse::Rejected + } } - signal.signal((data.len(), buf)); - OutResponse::Accepted } (Ok(id), _, Some(handler)) => handler.set_report(id, data), _ => OutResponse::Rejected, diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index 82b59bd1..03e39b8c 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -120,6 +120,9 @@ pub trait Endpoint { pub trait EndpointOut: Endpoint { type ReadFuture<'a>: Future> + 'a + where + Self: 'a; + type DataReadyFuture<'a>: Future + 'a where Self: 'a; @@ -128,6 +131,11 @@ pub trait EndpointOut: Endpoint { /// /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; + + /// Waits until a packet of data is ready to be read from the endpoint. + /// + /// A call to[`read()`](Self::read()) after this future completes should not block. + fn wait_data_ready<'a>(&'a mut self) -> Self::DataReadyFuture<'a>; } pub trait ControlPipe { From 99f95a33c30b08359fcd72123fea01f4de0903ec Mon Sep 17 00:00:00 2001 From: alexmoon Date: Sat, 2 Apr 2022 11:58:01 -0400 Subject: [PATCH 38/50] Simplify hid output report handling --- embassy-nrf/src/usb.rs | 20 +--- embassy-usb-hid/src/async_lease.rs | 90 ------------------ embassy-usb-hid/src/lib.rs | 143 ++++++++++++++--------------- embassy-usb/src/driver.rs | 8 -- examples/nrf/src/bin/usb_hid.rs | 4 +- 5 files changed, 78 insertions(+), 187 deletions(-) delete mode 100644 embassy-usb-hid/src/async_lease.rs diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index df0efa51..124316a2 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -443,23 +443,8 @@ unsafe fn write_dma(i: usize, buf: &[u8]) { impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - type DataReadyFuture<'a> = impl Future + 'a where Self: 'a; fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let i = self.info.addr.index(); - assert!(i != 0); - - self.wait_data_ready().await; - - // Mark as not ready - READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); - - unsafe { read_dma::(i, buf) } - } - } - - fn wait_data_ready<'a>(&'a mut self) -> Self::DataReadyFuture<'a> { async move { let i = self.info.addr.index(); assert!(i != 0); @@ -475,6 +460,11 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { } }) .await; + + // Mark as not ready + READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); + + unsafe { read_dma::(i, buf) } } } } diff --git a/embassy-usb-hid/src/async_lease.rs b/embassy-usb-hid/src/async_lease.rs deleted file mode 100644 index 0971daa2..00000000 --- a/embassy-usb-hid/src/async_lease.rs +++ /dev/null @@ -1,90 +0,0 @@ -use core::cell::Cell; -use core::future::Future; -use core::task::{Poll, Waker}; - -enum AsyncLeaseState { - Empty, - Waiting(*mut u8, usize, Waker), - Done(usize), -} - -impl Default for AsyncLeaseState { - fn default() -> Self { - AsyncLeaseState::Empty - } -} - -#[derive(Default)] -pub struct AsyncLease { - state: Cell, -} - -pub struct AsyncLeaseFuture<'a> { - buf: &'a mut [u8], - state: &'a Cell, -} - -impl<'a> Drop for AsyncLeaseFuture<'a> { - fn drop(&mut self) { - self.state.set(AsyncLeaseState::Empty); - } -} - -impl<'a> Future for AsyncLeaseFuture<'a> { - type Output = usize; - - fn poll( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> Poll { - match self.state.take() { - AsyncLeaseState::Done(len) => Poll::Ready(len), - state => { - if let AsyncLeaseState::Waiting(ptr, _, _) = state { - assert_eq!( - ptr, - self.buf.as_mut_ptr(), - "lend() called on a busy AsyncLease." - ); - } - - self.state.set(AsyncLeaseState::Waiting( - self.buf.as_mut_ptr(), - self.buf.len(), - cx.waker().clone(), - )); - Poll::Pending - } - } - } -} - -pub struct AsyncLeaseNotReady {} - -impl AsyncLease { - pub fn new() -> Self { - Default::default() - } - - pub fn try_borrow_mut usize>( - &self, - f: F, - ) -> Result<(), AsyncLeaseNotReady> { - if let AsyncLeaseState::Waiting(data, len, waker) = self.state.take() { - let buf = unsafe { core::slice::from_raw_parts_mut(data, len) }; - let len = f(buf); - self.state.set(AsyncLeaseState::Done(len)); - waker.wake(); - Ok(()) - } else { - Err(AsyncLeaseNotReady {}) - } - } - - pub fn lend<'a>(&'a self, buf: &'a mut [u8]) -> AsyncLeaseFuture<'a> { - AsyncLeaseFuture { - buf, - state: &self.state, - } - } -} diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index 43e67880..527f014f 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -8,24 +8,21 @@ pub(crate) mod fmt; use core::mem::MaybeUninit; +use core::ops::Range; -use async_lease::AsyncLease; use embassy::time::Duration; -use embassy_usb::driver::{EndpointOut, ReadError}; +use embassy_usb::driver::EndpointOut; use embassy_usb::{ control::{ControlHandler, InResponse, OutResponse, Request, RequestType}, driver::{Driver, Endpoint, EndpointIn, WriteError}, UsbDeviceBuilder, }; -use futures_util::future::{select, Either}; -use futures_util::pin_mut; + #[cfg(feature = "usbd-hid")] use ssmarshal::serialize; #[cfg(feature = "usbd-hid")] use usbd_hid::descriptor::AsInputReport; -mod async_lease; - const USB_CLASS_HID: u8 = 0x03; const USB_SUBCLASS_NONE: u8 = 0x00; const USB_PROTOCOL_NONE: u8 = 0x00; @@ -64,14 +61,12 @@ impl ReportId { pub struct State<'a, const IN_N: usize, const OUT_N: usize> { control: MaybeUninit>, - lease: AsyncLease, } impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { pub fn new() -> Self { State { control: MaybeUninit::uninit(), - lease: AsyncLease::new(), } } } @@ -90,9 +85,9 @@ impl<'d, D: Driver<'d>, const IN_N: usize> HidClass<'d, D, (), IN_N> { /// high performance uses, and a value of 255 is good for best-effort usecases. /// /// This allocates an IN endpoint only. - pub fn new( + pub fn new( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, 0>, + state: &'d mut State<'d, IN_N, OUT_N>, report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, poll_ms: u8, @@ -101,8 +96,7 @@ impl<'d, D: Driver<'d>, const IN_N: usize> HidClass<'d, D, (), IN_N> { let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); let control = state .control - .write(Control::new(report_descriptor, None, request_handler)); - + .write(Control::new(report_descriptor, request_handler)); control.build(builder, None, &ep_in); Self { @@ -144,20 +138,14 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> let ep_out = builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms); let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); - let control = state.control.write(Control::new( - report_descriptor, - Some(&state.lease), - request_handler, - )); - + let control = state + .control + .write(Control::new(report_descriptor, request_handler)); control.build(builder, Some(&ep_out), &ep_in); Self { input: ReportWriter { ep_in }, - output: ReportReader { - ep_out, - lease: &state.lease, - }, + output: ReportReader { ep_out, offset: 0 }, } } @@ -178,7 +166,21 @@ pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { ep_out: D::EndpointOut, - lease: &'d AsyncLease, + offset: usize, +} + +pub enum ReadError { + BufferOverflow, + Sync(Range), +} + +impl From for ReadError { + fn from(val: embassy_usb::driver::ReadError) -> Self { + use embassy_usb::driver::ReadError::*; + match val { + BufferOverflow => ReadError::BufferOverflow, + } + } } impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { @@ -216,31 +218,55 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { } impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { + /// Starts a task to deliver output reports from the Interrupt Out pipe to + /// `handler`. + pub async fn run(mut self, handler: &T) -> ! { + assert!(self.offset == 0); + let mut buf = [0; N]; + loop { + match self.read(&mut buf).await { + Ok(len) => { handler.set_report(ReportId::Out(0), &buf[0..len]); } + Err(ReadError::BufferOverflow) => warn!("Host sent output report larger than the configured maximum output report length ({})", N), + Err(ReadError::Sync(_)) => unreachable!(), + } + } + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// **Note:** Any reports sent from the host over the control pipe will be + /// passed to [`RequestHandler::set_report()`] for handling. The application + /// is responsible for ensuring output reports from both pipes are handled + /// correctly. + /// + /// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output + /// reports may be split across multiple packets) and this method's future + /// is dropped after some packets have been read, the next call to `read()` + /// will return a [`ReadError::SyncError()`]. The range in the sync error + /// indicates the portion `buf` that was filled by the current call to + /// `read()`. If the dropped future used the same `buf`, then `buf` will + /// contain the full report. pub async fn read(&mut self, buf: &mut [u8]) -> Result { assert!(buf.len() >= N); - // Wait until a packet is ready to read from the endpoint or a SET_REPORT control request is received - { - let data_ready = self.ep_out.wait_data_ready(); - pin_mut!(data_ready); - match select(data_ready, self.lease.lend(buf)).await { - Either::Left(_) => (), - Either::Right((len, _)) => return Ok(len), - } - } - // Read packets from the endpoint let max_packet_size = usize::from(self.ep_out.info().max_packet_size); - let mut total = 0; - for chunk in buf.chunks_mut(max_packet_size) { + let starting_offset = self.offset; + for chunk in buf[starting_offset..].chunks_mut(max_packet_size) { let size = self.ep_out.read(chunk).await?; - total += size; - if size < max_packet_size || total == N { + self.offset += size; + if size < max_packet_size || self.offset == N { break; } } - Ok(total) + let total = self.offset; + self.offset = 0; + if starting_offset > 0 { + Err(ReadError::Sync(starting_offset..total)) + } else { + Ok(total) + } } } @@ -254,10 +280,6 @@ pub trait RequestHandler { } /// Sets the value of report `id` to `data`. - /// - /// If an output endpoint has been allocated, output reports - /// are routed through [`HidClass::output()`]. Otherwise they - /// are sent here, along with input and feature reports. fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { let _ = (id, data); OutResponse::Rejected @@ -266,8 +288,8 @@ pub trait RequestHandler { /// Get the idle rate for `id`. /// /// If `id` is `None`, get the idle rate for all reports. Returning `None` - /// will reject the control request. Any duration above 1.020 seconds or 0 - /// will be returned as an indefinite idle rate. + /// will reject the control request. Any duration at or above 1.024 seconds + /// or below 4ms will be returned as an indefinite idle rate. fn get_idle(&self, id: Option) -> Option { let _ = id; None @@ -284,7 +306,6 @@ pub trait RequestHandler { struct Control<'d> { report_descriptor: &'static [u8], - out_lease: Option<&'d AsyncLease>, request_handler: Option<&'d dyn RequestHandler>, hid_descriptor: [u8; 9], } @@ -292,12 +313,10 @@ struct Control<'d> { impl<'a> Control<'a> { fn new( report_descriptor: &'static [u8], - out_lease: Option<&'a AsyncLease>, request_handler: Option<&'a dyn RequestHandler>, ) -> Self { Control { report_descriptor, - out_lease, request_handler, hid_descriptor: [ // Length of buf inclusive of size prefix @@ -370,7 +389,7 @@ impl<'d> ControlHandler for Control<'d> { if let RequestType::Class = req.request_type { match req.request { HID_REQ_SET_IDLE => { - if let Some(handler) = self.request_handler.as_ref() { + if let Some(handler) = self.request_handler { let id = req.value as u8; let id = (id != 0).then(|| ReportId::In(id)); let dur = u64::from(req.value >> 8); @@ -383,25 +402,8 @@ impl<'d> ControlHandler for Control<'d> { } OutResponse::Accepted } - HID_REQ_SET_REPORT => match ( - ReportId::try_from(req.value), - self.out_lease, - self.request_handler.as_ref(), - ) { - (Ok(ReportId::Out(_)), Some(lease), _) => { - match lease.try_borrow_mut(|buf| { - let len = buf.len().min(data.len()); - buf[0..len].copy_from_slice(&data[0..len]); - len - }) { - Ok(()) => OutResponse::Accepted, - Err(_) => { - warn!("SET_REPORT received for output report with no reader listening."); - OutResponse::Rejected - } - } - } - (Ok(id), _, Some(handler)) => handler.set_report(id, data), + HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { + (Ok(id), Some(handler)) => handler.set_report(id, data), _ => OutResponse::Rejected, }, HID_REQ_SET_PROTOCOL => { @@ -429,10 +431,7 @@ impl<'d> ControlHandler for Control<'d> { }, (RequestType::Class, HID_REQ_GET_REPORT) => { let size = match ReportId::try_from(req.value) { - Ok(id) => self - .request_handler - .as_ref() - .and_then(|x| x.get_report(id, buf)), + Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), Err(_) => None, }; @@ -443,7 +442,7 @@ impl<'d> ControlHandler for Control<'d> { } } (RequestType::Class, HID_REQ_GET_IDLE) => { - if let Some(handler) = self.request_handler.as_ref() { + if let Some(handler) = self.request_handler { let id = req.value as u8; let id = (id != 0).then(|| ReportId::In(id)); if let Some(dur) = handler.get_idle(id) { diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index 03e39b8c..82b59bd1 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -120,9 +120,6 @@ pub trait Endpoint { pub trait EndpointOut: Endpoint { type ReadFuture<'a>: Future> + 'a - where - Self: 'a; - type DataReadyFuture<'a>: Future + 'a where Self: 'a; @@ -131,11 +128,6 @@ pub trait EndpointOut: Endpoint { /// /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; - - /// Waits until a packet of data is ready to be read from the endpoint. - /// - /// A call to[`read()`](Self::read()) after this future completes should not block. - fn wait_data_ready<'a>(&'a mut self) -> Self::DataReadyFuture<'a>; } pub trait ControlPipe { diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 5253f225..6ffb1fd4 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -52,7 +52,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; - let mut state = State::<5, 0>::new(); + let mut control = State::<5, 0>::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -66,7 +66,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Create classes on the builder. let mut hid = HidClass::new( &mut builder, - &mut state, + &mut control, MouseReport::desc(), Some(&request_handler), 60, From 2ce435dc341c0238392df5dab5db9b80db167117 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Sat, 2 Apr 2022 16:35:03 -0400 Subject: [PATCH 39/50] Add basic device state handling for endpoints. --- embassy-nrf/src/usb.rs | 180 +++++++++++++++++++++-------- embassy-usb-serial/src/lib.rs | 5 + embassy-usb/src/driver.rs | 12 ++ embassy-usb/src/lib.rs | 7 +- examples/nrf/src/bin/usb_serial.rs | 46 ++++++-- 5 files changed, 190 insertions(+), 60 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 124316a2..c16e1be0 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -17,6 +17,7 @@ use futures::future::poll_fn; use futures::Future; pub use embassy_usb; +use pac::usbd::RegisterBlock; use crate::interrupt::Interrupt; use crate::pac; @@ -92,11 +93,11 @@ impl<'d, T: Instance> Driver<'d, T> { regs.epdatastatus.write(|w| unsafe { w.bits(r) }); READY_ENDPOINTS.fetch_or(r, Ordering::AcqRel); for i in 1..=7 { - if r & (1 << i) != 0 { - EP_IN_WAKERS[i - 1].wake(); + if r & In::mask(i) != 0 { + In::waker(i).wake(); } - if r & (1 << (i + 16)) != 0 { - EP_OUT_WAKERS[i - 1].wake(); + if r & Out::mask(i) != 0 { + Out::waker(i).wake(); } } } @@ -272,32 +273,48 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { #[inline] fn reset(&mut self) { + self.set_configured(false); + } + + #[inline] + fn set_configured(&mut self, configured: bool) { let regs = T::regs(); - // 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 unsafe { - regs.epinen.write(|w| w.bits(self.alloc_in.used.into())); - regs.epouten.write(|w| w.bits(self.alloc_out.used.into())); - } + if configured { + // TODO: Initialize ISO buffers - for i in 1..8 { - let out_enabled = self.alloc_out.used & (1 << i) != 0; + regs.epinen.write(|w| w.bits(self.alloc_in.used.into())); + regs.epouten.write(|w| w.bits(self.alloc_out.used.into())); - // 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(); + for i in 1..8 { + let out_enabled = self.alloc_out.used & (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(); + } + } + + // IN endpoints (low bits) default to ready. + // OUT endpoints (high bits) default to NOT ready, they become ready when data comes in. + READY_ENDPOINTS.store(0x0000FFFF, Ordering::Release); + } else { + // Disable all endpoints except EP0 + regs.epinen.write(|w| w.bits(0x01)); + regs.epouten.write(|w| w.bits(0x01)); + + READY_ENDPOINTS.store(In::mask(0), Ordering::Release); } } - // IN endpoints (low bits) default to ready. - // OUT endpoints (high bits) default to NOT ready, they become ready when data comes in. - READY_ENDPOINTS.store(0x0000FFFF, Ordering::Release); + for i in 1..=7 { + In::waker(i).wake(); + Out::waker(i).wake(); + } } #[inline] @@ -332,6 +349,46 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { pub enum Out {} pub enum In {} +trait EndpointDir { + fn waker(i: usize) -> &'static AtomicWaker; + fn mask(i: usize) -> u32; + fn is_enabled(regs: &RegisterBlock, i: usize) -> bool; +} + +impl EndpointDir for In { + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_IN_WAKERS[i - 1] + } + + #[inline] + fn mask(i: usize) -> u32 { + 1 << i + } + + #[inline] + fn is_enabled(regs: &RegisterBlock, i: usize) -> bool { + (regs.epinen.read().bits() & (1 << i)) != 0 + } +} + +impl EndpointDir for Out { + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_OUT_WAKERS[i - 1] + } + + #[inline] + fn mask(i: usize) -> u32 { + 1 << (i + 16) + } + + #[inline] + fn is_enabled(regs: &RegisterBlock, i: usize) -> bool { + (regs.epouten.read().bits() & (1 << i)) != 0 + } +} + pub struct Endpoint<'d, T: Instance, Dir> { _phantom: PhantomData<(&'d mut T, Dir)>, info: EndpointInfo, @@ -346,7 +403,7 @@ impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> { } } -impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> { +impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir> { fn info(&self) -> &EndpointInfo { &self.info } @@ -358,6 +415,49 @@ impl<'d, T: Instance, Dir> driver::Endpoint for Endpoint<'d, T, Dir> { fn is_stalled(&self) -> bool { Driver::::is_stalled(self.info.addr) } + + type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; + + fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { + let i = self.info.addr.index(); + assert!(i != 0); + + poll_fn(move |cx| { + Dir::waker(i).register(cx.waker()); + if Dir::is_enabled(T::regs(), i) { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + } +} + +impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> { + async fn wait_data_ready(&mut self) -> Result<(), ()> + where + Dir: EndpointDir, + { + let i = self.info.addr.index(); + assert!(i != 0); + poll_fn(|cx| { + Dir::waker(i).register(cx.waker()); + let r = READY_ENDPOINTS.load(Ordering::Acquire); + if !Dir::is_enabled(T::regs(), i) { + Poll::Ready(Err(())) + } else if r & Dir::mask(i) != 0 { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }) + .await?; + + // Mark as not ready + READY_ENDPOINTS.fetch_and(!Dir::mask(i), Ordering::AcqRel); + + Ok(()) + } } unsafe fn read_dma(i: usize, buf: &mut [u8]) -> Result { @@ -449,20 +549,9 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { let i = self.info.addr.index(); assert!(i != 0); - // Wait until ready - poll_fn(|cx| { - EP_OUT_WAKERS[i - 1].register(cx.waker()); - let r = READY_ENDPOINTS.load(Ordering::Acquire); - if r & (1 << (i + 16)) != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - // Mark as not ready - READY_ENDPOINTS.fetch_and(!(1 << (i + 16)), Ordering::AcqRel); + self.wait_data_ready() + .await + .map_err(|_| ReadError::Disabled)?; unsafe { read_dma::(i, buf) } } @@ -477,20 +566,9 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { let i = self.info.addr.index(); assert!(i != 0); - // Wait until ready. - poll_fn(|cx| { - EP_IN_WAKERS[i - 1].register(cx.waker()); - let r = READY_ENDPOINTS.load(Ordering::Acquire); - if r & (1 << i) != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - // Mark as not ready - READY_ENDPOINTS.fetch_and(!(1 << i), Ordering::AcqRel); + self.wait_data_ready() + .await + .map_err(|_| WriteError::Disabled)?; unsafe { write_dma::(i, buf) } diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb-serial/src/lib.rs index 8418de0f..07352fac 100644 --- a/embassy-usb-serial/src/lib.rs +++ b/embassy-usb-serial/src/lib.rs @@ -273,6 +273,11 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { self.read_ep.read(data).await } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await + } } /// Number of stop bits for LineCoding diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index 82b59bd1..6eaa40b0 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -72,6 +72,9 @@ pub trait Bus { /// Sets the device USB address to `addr`. fn set_device_address(&mut self, addr: u8); + /// Sets the device configured state. + fn set_configured(&mut self, configured: bool); + /// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it /// should be prepared to receive data again. Only used during control transfers. fn set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); @@ -105,6 +108,10 @@ pub trait Bus { } pub trait Endpoint { + type WaitEnabledFuture<'a>: Future + 'a + where + Self: 'a; + /// Get the endpoint address fn info(&self) -> &EndpointInfo; @@ -115,6 +122,9 @@ pub trait Endpoint { /// Gets whether the STALL condition is set for an endpoint. fn is_stalled(&self) -> bool; + /// Waits for the endpoint to be enabled. + fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_>; + // TODO enable/disable? } @@ -212,6 +222,7 @@ pub enum WriteError { /// class shouldn't provide more data than the `max_packet_size` it specified when allocating /// the endpoint. BufferOverflow, + Disabled, } #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -223,4 +234,5 @@ pub enum ReadError { /// should use a buffer that is large enough for the `max_packet_size` it specified when /// allocating the endpoint. BufferOverflow, + Disabled, } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index d2d3e5e0..cf8d1253 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -162,18 +162,21 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.remote_wakeup_enabled = true; self.control.accept(stage) } - (Request::SET_ADDRESS, 1..=127) => { - self.pending_address = req.value as u8; + (Request::SET_ADDRESS, addr @ 1..=127) => { + self.pending_address = addr as u8; + self.bus.set_device_address(self.pending_address); self.control.accept(stage) } (Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { self.device_state = UsbDeviceState::Configured; + self.bus.set_configured(true); self.control.accept(stage) } (Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => match self.device_state { UsbDeviceState::Default => self.control.accept(stage), _ => { self.device_state = UsbDeviceState::Addressed; + self.bus.set_configured(false); self.control.accept(stage) } }, diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index cd681c5c..9437e835 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -4,12 +4,13 @@ #![feature(type_alias_impl_trait)] use core::mem; -use defmt::*; +use defmt::{info, panic}; use embassy::executor::Spawner; use embassy_nrf::interrupt; use embassy_nrf::pac; -use embassy_nrf::usb::Driver; +use embassy_nrf::usb::{Driver, Instance}; use embassy_nrf::Peripherals; +use embassy_usb::driver::{ReadError, WriteError}; use embassy_usb::{Config, UsbDeviceBuilder}; use embassy_usb_serial::{CdcAcmClass, State}; use futures::future::join; @@ -66,12 +67,11 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Do stuff with the class! let echo_fut = async { - let mut buf = [0; 64]; loop { - let n = class.read_packet(&mut buf).await.unwrap(); - let data = &buf[..n]; - info!("data: {:x}", data); - class.write_packet(data).await.unwrap(); + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); } }; @@ -79,3 +79,35 @@ async fn main(_spawner: Spawner, p: Peripherals) { // If we had made everything `'static` above instead, we could do this using separate tasks instead. join(usb_fut, echo_fut).await; } + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: ReadError) -> Self { + match val { + ReadError::BufferOverflow => panic!("Buffer overflow"), + ReadError::Disabled => Disconnected {}, + } + } +} + +impl From for Disconnected { + fn from(val: WriteError) -> Self { + match val { + WriteError::BufferOverflow => panic!("Buffer overflow"), + WriteError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>( + class: &mut CdcAcmClass<'d, Driver<'d, T>>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} From fa9eadcee9cbac369195fd1356ec69ecdeda2f08 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 5 Apr 2022 21:03:21 +0200 Subject: [PATCH 40/50] Add docserver metadata. --- embassy-usb-serial/Cargo.toml | 8 ++++++++ embassy-usb/Cargo.toml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/embassy-usb-serial/Cargo.toml b/embassy-usb-serial/Cargo.toml index 16e03046..553f2a13 100644 --- a/embassy-usb-serial/Cargo.toml +++ b/embassy-usb-serial/Cargo.toml @@ -3,6 +3,14 @@ name = "embassy-usb-serial" version = "0.1.0" edition = "2021" +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-serial-v$VERSION/embassy-usb-serial/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/master/embassy-usb-serial/src/" +features = ["defmt"] +flavors = [ + { name = "default", target = "thumbv7em-none-eabihf" }, +] + [dependencies] embassy = { version = "0.1.0", path = "../embassy" } embassy-usb = { version = "0.1.0", path = "../embassy-usb" } diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 01bf5ef6..48c20506 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -3,6 +3,14 @@ name = "embassy-usb" version = "0.1.0" edition = "2021" +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/master/embassy-usb/src/" +features = ["defmt"] +flavors = [ + { name = "default", target = "thumbv7em-none-eabihf" }, +] + [dependencies] embassy = { version = "0.1.0", path = "../embassy" } From 6d514a0b31a6e480d00a36132ccd41bebfd246cc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 02:18:39 +0200 Subject: [PATCH 41/50] usb/hid: update for endpoint state changes. --- embassy-usb-hid/src/lib.rs | 5 +++++ examples/nrf/src/bin/usb_hid.rs | 40 +++++++++++++-------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index 527f014f..e29d485f 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -169,8 +169,11 @@ pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { offset: usize, } +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReadError { BufferOverflow, + Disabled, Sync(Range), } @@ -179,6 +182,7 @@ impl From for ReadError { use embassy_usb::driver::ReadError::*; match val { BufferOverflow => ReadError::BufferOverflow, + Disabled => ReadError::Disabled, } } } @@ -227,6 +231,7 @@ impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { match self.read(&mut buf).await { Ok(len) => { handler.set_report(ReportId::Out(0), &buf[0..len]); } Err(ReadError::BufferOverflow) => warn!("Host sent output report larger than the configured maximum output report length ({})", N), + Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, Err(ReadError::Sync(_)) => unreachable!(), } } diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 6ffb1fd4..741e234b 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -3,9 +3,6 @@ #![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] -#[path = "../example_common.rs"] -mod example_common; - use core::mem; use defmt::*; use embassy::executor::Spawner; @@ -20,6 +17,9 @@ use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; use futures::future::join; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; +use defmt_rtt as _; // global logger +use panic_probe as _; + #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { let clock: pac::CLOCK = unsafe { mem::transmute(()) }; @@ -81,30 +81,22 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Do stuff with the class! let hid_fut = async { + let mut y: i8 = 5; loop { Timer::after(Duration::from_millis(500)).await; - hid.input() - .serialize(&MouseReport { - buttons: 0, - x: 0, - y: 4, - wheel: 0, - pan: 0, - }) - .await - .unwrap(); - Timer::after(Duration::from_millis(500)).await; - hid.input() - .serialize(&MouseReport { - buttons: 0, - x: 0, - y: -4, - wheel: 0, - pan: 0, - }) - .await - .unwrap(); + y = -y; + let report = MouseReport { + buttons: 0, + x: 0, + y, + wheel: 0, + pan: 0, + }; + match hid.input().serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + } } }; From de9acf5d48151f1c9ca0b42d699e92b8502bebad Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 02:18:56 +0200 Subject: [PATCH 42/50] usb/hid: fix infinite loop when N=0 --- embassy-usb-hid/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index e29d485f..996de6a5 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -252,6 +252,7 @@ impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { /// `read()`. If the dropped future used the same `buf`, then `buf` will /// contain the full report. pub async fn read(&mut self, buf: &mut [u8]) -> Result { + assert!(N != 0); assert!(buf.len() >= N); // Read packets from the endpoint From 3dbb7c9e159aa456c1d85cb4d5c8d1299013d0cc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 02:24:55 +0200 Subject: [PATCH 43/50] usb/hid: add keyboard example. --- examples/nrf/src/bin/usb_hid_keyboard.rs | 148 ++++++++++++++++++ .../src/bin/{usb_hid.rs => usb_hid_mouse.rs} | 0 2 files changed, 148 insertions(+) create mode 100644 examples/nrf/src/bin/usb_hid_keyboard.rs rename examples/nrf/src/bin/{usb_hid.rs => usb_hid_mouse.rs} (100%) diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs new file mode 100644 index 00000000..af70a9a6 --- /dev/null +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,148 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +use core::mem; +use defmt::*; +use embassy::executor::Spawner; +use embassy::time::Duration; +use embassy_nrf::gpio::{Input, Pin, Pull}; +use embassy_nrf::interrupt; +use embassy_nrf::pac; +use embassy_nrf::usb::Driver; +use embassy_nrf::Peripherals; +use embassy_usb::control::OutResponse; +use embassy_usb::{Config, UsbDeviceBuilder}; +use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; +use futures::future::join; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; + +use defmt_rtt as _; // global logger +use panic_probe as _; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + let power: pac::POWER = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + info!("Waiting for vbus..."); + while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} + info!("vbus OK"); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let driver = Driver::new(p.USBD, irq); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Tactile Engineering"); + config.product = Some("Testy"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 16]; + let request_handler = MyRequestHandler {}; + + let mut state = State::<64, 64>::new(); + + let mut builder = UsbDeviceBuilder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let hid = HidClass::with_output_ep( + &mut builder, + &mut state, + KeyboardReport::desc(), + Some(&request_handler), + 60, + 64, + ); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + let mut button = Input::new(p.P0_11.degrade(), Pull::Up); + + let (mut hid_in, hid_out) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + loop { + button.wait_for_low().await; + info!("PRESSED"); + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match hid_in.serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + + button.wait_for_high().await; + info!("RELEASED"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match hid_in.serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + hid_out.run(&MyRequestHandler {}).await; + }; + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle(&self, id: Option, dur: Duration) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle(&self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid_mouse.rs similarity index 100% rename from examples/nrf/src/bin/usb_hid.rs rename to examples/nrf/src/bin/usb_hid_mouse.rs From b2e517bb2860b1ec35bb744b8a28efae50cb2d59 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 02:37:17 +0200 Subject: [PATCH 44/50] usb/serial: add multitask example. --- examples/nrf/src/bin/usb_serial_multitask.rs | 122 +++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 examples/nrf/src/bin/usb_serial_multitask.rs diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs new file mode 100644 index 00000000..bef70441 --- /dev/null +++ b/examples/nrf/src/bin/usb_serial_multitask.rs @@ -0,0 +1,122 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +use core::mem; +use defmt::{info, panic, unwrap}; +use embassy::executor::Spawner; +use embassy::util::Forever; +use embassy_nrf::pac; +use embassy_nrf::usb::Driver; +use embassy_nrf::Peripherals; +use embassy_nrf::{interrupt, peripherals}; +use embassy_usb::driver::{ReadError, WriteError}; +use embassy_usb::{Config, UsbDevice, UsbDeviceBuilder}; +use embassy_usb_serial::{CdcAcmClass, State}; + +use defmt_rtt as _; // global logger +use panic_probe as _; + +type MyDriver = Driver<'static, peripherals::USBD>; + +#[embassy::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) { + device.run().await; +} + +#[embassy::task] +async fn echo_task(mut class: CdcAcmClass<'static, MyDriver>) { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } +} + +#[embassy::main] +async fn main(spawner: Spawner, p: Peripherals) { + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + let power: pac::POWER = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + info!("Waiting for vbus..."); + while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} + info!("vbus OK"); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let driver = Driver::new(p.USBD, irq); + + // Create embassy-usb Config + let config = Config::new(0xc0de, 0xcafe); + + struct Resources { + device_descriptor: [u8; 256], + config_descriptor: [u8; 256], + bos_descriptor: [u8; 256], + control_buf: [u8; 7], + serial_state: State<'static>, + } + static RESOURCES: Forever = Forever::new(); + let res = RESOURCES.put(Resources { + device_descriptor: [0; 256], + config_descriptor: [0; 256], + bos_descriptor: [0; 256], + control_buf: [0; 7], + serial_state: State::new(), + }); + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = UsbDeviceBuilder::new( + driver, + config, + &mut res.device_descriptor, + &mut res.config_descriptor, + &mut res.bos_descriptor, + &mut res.control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + unwrap!(spawner.spawn(echo_task(class))); +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: ReadError) -> Self { + match val { + ReadError::BufferOverflow => panic!("Buffer overflow"), + ReadError::Disabled => Disconnected {}, + } + } +} + +impl From for Disconnected { + fn from(val: WriteError) -> Self { + match val { + WriteError::BufferOverflow => panic!("Buffer overflow"), + WriteError::Disabled => Disconnected {}, + } + } +} + +async fn echo(class: &mut CdcAcmClass<'static, MyDriver>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} From f6d11dfba56b2b04868e87a14d10395e1916306d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 03:02:13 +0200 Subject: [PATCH 45/50] usb: fix slow enumeration with EP0 max_packet_size of 8 or 16. --- embassy-usb/src/control.rs | 2 +- embassy-usb/src/lib.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index da48dcca..b15ba446 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -198,7 +198,7 @@ pub trait ControlHandler { #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub(crate) struct DataInStage { - length: usize, + pub(crate) length: usize, } /// Typestate representing a ControlPipe in the DATA OUT stage diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index cf8d1253..f833a86d 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -222,7 +222,20 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - async fn handle_control_in(&mut self, req: Request, stage: DataInStage) { + async fn handle_control_in(&mut self, req: Request, mut stage: DataInStage) { + // If we don't have an address yet, respond with max 1 packet. + // The host doesn't know our EP0 max packet size yet, and might assume + // a full-length packet is a short packet, thinking we're done sending data. + // See https://github.com/hathach/tinyusb/issues/184 + const DEVICE_DESCRIPTOR_LEN: u8 = 18; + if self.pending_address == 0 + && self.config.max_packet_size_0 < DEVICE_DESCRIPTOR_LEN + && (self.config.max_packet_size_0 as usize) < stage.length + { + trace!("received control req while not addressed: capping response to 1 packet."); + stage.length = self.config.max_packet_size_0 as _; + } + match (req.request_type, req.recipient) { (RequestType::Standard, Recipient::Device) => match req.request { Request::GET_STATUS => { From 22a47aeeb2bc9d459a6e83414632890164a7b448 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 03:14:22 +0200 Subject: [PATCH 46/50] usb: abort control data in/out on reset or when receiving another SETUP. This removes the horrible timeout hack. --- embassy-nrf/src/usb.rs | 71 ++++++++++++++++++++++---------------- embassy-usb/src/control.rs | 8 ++++- embassy-usb/src/driver.rs | 2 +- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index c16e1be0..b5e17307 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -6,7 +6,6 @@ use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use core::task::Poll; use cortex_m::peripheral::NVIC; use embassy::interrupt::InterruptExt; -use embassy::time::{with_timeout, Duration}; use embassy::util::Unborrow; use embassy::waitqueue::AtomicWaker; use embassy_hal_common::unborrow; @@ -59,6 +58,7 @@ impl<'d, T: Instance> Driver<'d, T> { if regs.events_usbreset.read().bits() != 0 { regs.intenclr.write(|w| w.usbreset().clear()); BUS_WAKER.wake(); + EP0_WAKER.wake(); } if regs.events_ep0setup.read().bits() != 0 { @@ -585,7 +585,7 @@ pub struct ControlPipe<'d, T: Instance> { impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { type SetupFuture<'a> = impl Future + 'a where Self: 'a; type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; - type DataInFuture<'a> = impl Future + 'a where Self: 'a; + type DataInFuture<'a> = impl Future> + 'a where Self: 'a; fn max_packet_size(&self) -> usize { usize::from(self.max_packet_size) @@ -596,7 +596,10 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { let regs = T::regs(); // Wait for SETUP packet - regs.intenset.write(|w| w.ep0setup().set()); + regs.intenset.write(|w| { + w.ep0setup().set(); + w.ep0datadone().set() + }); poll_fn(|cx| { EP0_WAKER.register(cx.waker()); let regs = T::regs(); @@ -639,22 +642,27 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { let regs = T::regs(); // Wait until ready - regs.intenset.write(|w| w.ep0datadone().set()); + regs.intenset.write(|w| { + w.usbreset().set(); + w.ep0setup().set(); + w.ep0datadone().set() + }); poll_fn(|cx| { EP0_WAKER.register(cx.waker()); let regs = T::regs(); - if regs - .events_ep0datadone - .read() - .events_ep0datadone() - .bit_is_set() - { - Poll::Ready(()) + if regs.events_usbreset.read().bits() != 0 { + trace!("aborted control data_out: usb reset"); + Poll::Ready(Err(ReadError::Disabled)) + } else if regs.events_ep0setup.read().bits() != 0 { + trace!("aborted control data_out: received another SETUP"); + Poll::Ready(Err(ReadError::Disabled)) + } else if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) } else { Poll::Pending } }) - .await; + .await?; unsafe { read_dma::(0, buf) } } @@ -671,24 +679,29 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { regs.shorts .modify(|_, w| w.ep0datadone_ep0status().bit(last_packet)); - regs.intenset.write(|w| w.ep0datadone().set()); - let res = with_timeout( - Duration::from_millis(10), - poll_fn(|cx| { - EP0_WAKER.register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }), - ) - .await; + regs.intenset.write(|w| { + w.usbreset().set(); + w.ep0setup().set(); + w.ep0datadone().set() + }); - if res.is_err() { - error!("ControlPipe::data_in timed out."); - } + poll_fn(|cx| { + cx.waker().wake_by_ref(); + EP0_WAKER.register(cx.waker()); + let regs = T::regs(); + if regs.events_usbreset.read().bits() != 0 { + trace!("aborted control data_in: usb reset"); + Poll::Ready(Err(WriteError::Disabled)) + } else if regs.events_ep0setup.read().bits() != 0 { + trace!("aborted control data_in: received another SETUP"); + Poll::Ready(Err(WriteError::Disabled)) + } else if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }) + .await } } diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index b15ba446..7c46812b 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -295,7 +295,13 @@ impl ControlPipe { .chain(need_zlp.then(|| -> &[u8] { &[] })); while let Some(chunk) = chunks.next() { - self.control.data_in(chunk, chunks.size_hint().0 == 0).await; + match self.control.data_in(chunk, chunks.size_hint().0 == 0).await { + Ok(()) => {} + Err(e) => { + warn!("control accept_in failed: {:?}", e); + return; + } + } } } diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index 6eaa40b0..d3231cb4 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -147,7 +147,7 @@ pub trait ControlPipe { type DataOutFuture<'a>: Future> + 'a where Self: 'a; - type DataInFuture<'a>: Future + 'a + type DataInFuture<'a>: Future> + 'a where Self: 'a; From a1754ac8a820d9cae97cf214969faf3090b37c76 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 5 Apr 2022 17:23:46 -0400 Subject: [PATCH 47/50] embassy-usb-hid bug fixes --- embassy-usb-hid/src/lib.rs | 87 ++++++++++++++++++------ examples/nrf/src/bin/usb_hid_keyboard.rs | 4 +- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index 996de6a5..f50c5f8c 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -9,6 +9,7 @@ pub(crate) mod fmt; use core::mem::MaybeUninit; use core::ops::Range; +use core::sync::atomic::{AtomicUsize, Ordering}; use embassy::time::Duration; use embassy_usb::driver::EndpointOut; @@ -61,12 +62,14 @@ impl ReportId { pub struct State<'a, const IN_N: usize, const OUT_N: usize> { control: MaybeUninit>, + out_report_offset: AtomicUsize, } impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { pub fn new() -> Self { State { control: MaybeUninit::uninit(), + out_report_offset: AtomicUsize::new(0), } } } @@ -94,9 +97,11 @@ impl<'d, D: Driver<'d>, const IN_N: usize> HidClass<'d, D, (), IN_N> { max_packet_size: u16, ) -> Self { let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); - let control = state - .control - .write(Control::new(report_descriptor, request_handler)); + let control = state.control.write(Control::new( + report_descriptor, + request_handler, + &state.out_report_offset, + )); control.build(builder, None, &ep_in); Self { @@ -138,14 +143,19 @@ impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> let ep_out = builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms); let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); - let control = state - .control - .write(Control::new(report_descriptor, request_handler)); + let control = state.control.write(Control::new( + report_descriptor, + request_handler, + &state.out_report_offset, + )); control.build(builder, Some(&ep_out), &ep_in); Self { input: ReportWriter { ep_in }, - output: ReportReader { ep_out, offset: 0 }, + output: ReportReader { + ep_out, + offset: &state.out_report_offset, + }, } } @@ -166,7 +176,7 @@ pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { ep_out: D::EndpointOut, - offset: usize, + offset: &'d AtomicUsize, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -188,6 +198,11 @@ impl From for ReadError { } impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { + /// Waits for the interrupt in endpoint to be enabled. + pub async fn ready(&mut self) -> () { + self.ep_in.wait_enabled().await + } + /// Tries to write an input report by serializing the given report structure. /// /// Panics if no endpoint is available. @@ -222,14 +237,27 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { } impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { + /// Waits for the interrupt out endpoint to be enabled. + pub async fn ready(&mut self) -> () { + self.ep_out.wait_enabled().await + } + /// Starts a task to deliver output reports from the Interrupt Out pipe to /// `handler`. - pub async fn run(mut self, handler: &T) -> ! { - assert!(self.offset == 0); + /// + /// Terminates when the interface becomes disabled. + /// + /// If `use_report_ids` is true, the first byte of the report will be used as + /// the `ReportId` value. Otherwise the `ReportId` value will be 0. + pub async fn run(mut self, use_report_ids: bool, handler: &T) -> ! { + let offset = self.offset.load(Ordering::Acquire); + assert!(offset == 0); let mut buf = [0; N]; loop { match self.read(&mut buf).await { - Ok(len) => { handler.set_report(ReportId::Out(0), &buf[0..len]); } + Ok(len) => { + let id = if use_report_ids { buf[0] } else { 0 }; + handler.set_report(ReportId::Out(id), &buf[..len]); } Err(ReadError::BufferOverflow) => warn!("Host sent output report larger than the configured maximum output report length ({})", N), Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, Err(ReadError::Sync(_)) => unreachable!(), @@ -257,17 +285,33 @@ impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { // Read packets from the endpoint let max_packet_size = usize::from(self.ep_out.info().max_packet_size); - let starting_offset = self.offset; - for chunk in buf[starting_offset..].chunks_mut(max_packet_size) { - let size = self.ep_out.read(chunk).await?; - self.offset += size; - if size < max_packet_size || self.offset == N { + let starting_offset = self.offset.load(Ordering::Acquire); + let mut total = starting_offset; + loop { + for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) { + match self.ep_out.read(chunk).await { + Ok(size) => { + total += size; + if size < max_packet_size || total == N { + self.offset.store(0, Ordering::Release); + break; + } else { + self.offset.store(total, Ordering::Release); + } + } + Err(err) => { + self.offset.store(0, Ordering::Release); + return Err(err.into()); + } + } + } + + // Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0. + if total > 0 { break; } } - let total = self.offset; - self.offset = 0; if starting_offset > 0 { Err(ReadError::Sync(starting_offset..total)) } else { @@ -313,6 +357,7 @@ pub trait RequestHandler { struct Control<'d> { report_descriptor: &'static [u8], request_handler: Option<&'d dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, hid_descriptor: [u8; 9], } @@ -320,10 +365,12 @@ impl<'a> Control<'a> { fn new( report_descriptor: &'static [u8], request_handler: Option<&'a dyn RequestHandler>, + out_report_offset: &'a AtomicUsize, ) -> Self { Control { report_descriptor, request_handler, + out_report_offset, hid_descriptor: [ // Length of buf inclusive of size prefix 9, @@ -388,7 +435,9 @@ impl<'a> Control<'a> { } impl<'d> ControlHandler for Control<'d> { - fn reset(&mut self) {} + fn reset(&mut self) { + self.out_report_offset.store(0, Ordering::Release); + } fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse { trace!("HID control_out {:?} {=[u8]:x}", req, data); diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index af70a9a6..51136292 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -54,7 +54,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; - let mut state = State::<64, 64>::new(); + let mut state = State::<8, 1>::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -117,7 +117,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { }; let out_fut = async { - hid_out.run(&MyRequestHandler {}).await; + hid_out.run(false, &request_handler).await; }; // Run everything concurrently. // If we had made everything `'static` above instead, we could do this using separate tasks instead. From 6abbfa9a92ec9feb03e30846bccb66272020601d Mon Sep 17 00:00:00 2001 From: alexmoon Date: Wed, 6 Apr 2022 22:10:18 -0400 Subject: [PATCH 48/50] Async-ify Driver::enable and UsbDeviceBuilder::build --- embassy-nrf/src/usb.rs | 56 ++++++++++++-------- embassy-usb/src/builder.rs | 17 +++--- embassy-usb/src/driver.rs | 3 +- embassy-usb/src/lib.rs | 8 +-- examples/nrf/src/bin/usb_hid_keyboard.rs | 2 +- examples/nrf/src/bin/usb_hid_mouse.rs | 2 +- examples/nrf/src/bin/usb_serial.rs | 2 +- examples/nrf/src/bin/usb_serial_multitask.rs | 2 +- 8 files changed, 53 insertions(+), 39 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index b5e17307..0fcbe025 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -140,6 +140,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { type EndpointIn = Endpoint<'d, T, In>; type ControlPipe = ControlPipe<'d, T>; type Bus = Bus<'d, T>; + type EnableFuture = impl Future + 'd; fn alloc_endpoint_in( &mut self, @@ -191,35 +192,46 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { }) } - fn enable(self) -> Self::Bus { - let regs = T::regs(); + fn enable(self) -> Self::EnableFuture { + async move { + let regs = T::regs(); - errata::pre_enable(); + errata::pre_enable(); - regs.enable.write(|w| w.enable().enabled()); + 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. + // Wait until the peripheral is ready. + regs.intenset.write(|w| w.usbevent().set_bit()); + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + if regs.eventcause.read().ready().is_ready() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + regs.eventcause.write(|w| w.ready().set_bit()); // Write 1 to clear. - errata::post_enable(); + errata::post_enable(); - unsafe { NVIC::unmask(pac::Interrupt::USBD) }; + unsafe { NVIC::unmask(pac::Interrupt::USBD) }; - regs.intenset.write(|w| { - w.usbreset().set_bit(); - w.usbevent().set_bit(); - w.epdata().set_bit(); - w - }); - // Enable the USB pullup, allowing enumeration. - regs.usbpullup.write(|w| w.connect().enabled()); - trace!("enabled"); + regs.intenset.write(|w| { + w.usbreset().set_bit(); + w.usbevent().set_bit(); + w.epdata().set_bit(); + w + }); + // Enable the USB pullup, allowing enumeration. + regs.usbpullup.write(|w| w.connect().enabled()); + trace!("enabled"); - Bus { - phantom: PhantomData, - alloc_in: self.alloc_in, - alloc_out: self.alloc_out, + Bus { + phantom: PhantomData, + alloc_in: self.alloc_in, + alloc_out: self.alloc_out, + } } } } diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index c8a9db7a..4bbcd3e5 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -122,7 +122,7 @@ pub struct UsbDeviceBuilder<'d, D: Driver<'d>> { interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, control_buf: &'d mut [u8], - bus: D, + driver: D, next_interface_number: u8, next_string_index: u8, @@ -139,7 +139,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { /// large enough for the length of the largest control request (in or out) /// anticipated by any class added to the device. pub fn new( - bus: D, + driver: D, config: Config<'d>, device_descriptor_buf: &'d mut [u8], config_descriptor_buf: &'d mut [u8], @@ -173,7 +173,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { bos_descriptor.bos(); UsbDeviceBuilder { - bus, + driver, config, interfaces: Vec::new(), control_buf, @@ -187,12 +187,12 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { } /// Creates the [`UsbDevice`] instance with the configuration in this builder. - pub fn build(mut self) -> UsbDevice<'d, D> { + pub async fn build(mut self) -> UsbDevice<'d, D> { self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); UsbDevice::build( - self.bus, + self.driver, self.config, self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), @@ -200,6 +200,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { self.interfaces, self.control_buf, ) + .await } /// Allocates a new interface number. @@ -251,7 +252,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { max_packet_size: u16, interval: u8, ) -> Result { - self.bus + self.driver .alloc_endpoint_in(ep_addr, ep_type, max_packet_size, interval) } @@ -266,7 +267,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { max_packet_size: u16, interval: u8, ) -> Result { - self.bus + self.driver .alloc_endpoint_out(ep_addr, ep_type, max_packet_size, interval) } @@ -306,7 +307,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { /// feasibly recoverable. #[inline] pub fn alloc_control_pipe(&mut self, max_packet_size: u16) -> D::ControlPipe { - self.bus + self.driver .alloc_control_pipe(max_packet_size) .expect("alloc_control_pipe failed") } diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index d3231cb4..01eb3d57 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -11,6 +11,7 @@ pub trait Driver<'a> { type EndpointIn: EndpointIn + 'a; type ControlPipe: ControlPipe + 'a; type Bus: Bus + 'a; + type EnableFuture: Future + 'a; /// Allocates an endpoint and specified endpoint parameters. This method is called by the device /// and class implementations to allocate endpoints, and can only be called before @@ -46,7 +47,7 @@ pub trait Driver<'a> { /// Enables and initializes the USB peripheral. Soon after enabling the device will be reset, so /// there is no need to perform a USB reset in this method. - fn enable(self) -> Self::Bus; + fn enable(self) -> Self::EnableFuture; /// Indicates that `set_device_address` must be called before accepting the corresponding /// control transfer, not after. diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index f833a86d..e98cbdee 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -72,7 +72,7 @@ pub struct UsbDevice<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { - pub(crate) fn build( + pub(crate) async fn build( mut driver: D, config: Config<'d>, device_descriptor: &'d [u8], @@ -80,17 +80,17 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { bos_descriptor: &'d [u8], interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, control_buf: &'d mut [u8], - ) -> Self { + ) -> UsbDevice<'d, D> { let control = driver .alloc_control_pipe(config.max_packet_size_0 as u16) .expect("failed to alloc control endpoint"); // Enable the USB bus. // This prevent further allocation by consuming the driver. - let driver = driver.enable(); + let bus = driver.enable().await; Self { - bus: driver, + bus, config, control: ControlPipe::new(control), device_descriptor, diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index 51136292..0812697e 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -76,7 +76,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build().await; // Run the USB device. let usb_fut = usb.run(); diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs index 741e234b..ca938382 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf/src/bin/usb_hid_mouse.rs @@ -74,7 +74,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build().await; // Run the USB device. let usb_fut = usb.run(); diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 9437e835..500be2ce 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -60,7 +60,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build().await; // Run the USB device. let usb_fut = usb.run(); diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs index bef70441..1258bc53 100644 --- a/examples/nrf/src/bin/usb_serial_multitask.rs +++ b/examples/nrf/src/bin/usb_serial_multitask.rs @@ -85,7 +85,7 @@ async fn main(spawner: Spawner, p: Peripherals) { let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64); // Build the builder. - let usb = builder.build(); + let usb = builder.build().await; unwrap!(spawner.spawn(usb_task(usb))); unwrap!(spawner.spawn(echo_task(class))); From 7f9dd1a37b98272da1adef376df4bd3c805b6101 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Thu, 7 Apr 2022 10:49:50 -0400 Subject: [PATCH 49/50] Fix spurious ControlPipe::data_in errors --- embassy-nrf/src/usb.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 0fcbe025..9f483d96 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -662,14 +662,14 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { poll_fn(|cx| { EP0_WAKER.register(cx.waker()); let regs = T::regs(); - if regs.events_usbreset.read().bits() != 0 { + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset.read().bits() != 0 { trace!("aborted control data_out: usb reset"); Poll::Ready(Err(ReadError::Disabled)) } else if regs.events_ep0setup.read().bits() != 0 { trace!("aborted control data_out: received another SETUP"); Poll::Ready(Err(ReadError::Disabled)) - } else if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(Ok(())) } else { Poll::Pending } @@ -701,14 +701,14 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { cx.waker().wake_by_ref(); EP0_WAKER.register(cx.waker()); let regs = T::regs(); - if regs.events_usbreset.read().bits() != 0 { + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset.read().bits() != 0 { trace!("aborted control data_in: usb reset"); Poll::Ready(Err(WriteError::Disabled)) } else if regs.events_ep0setup.read().bits() != 0 { trace!("aborted control data_in: received another SETUP"); Poll::Ready(Err(WriteError::Disabled)) - } else if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(Ok(())) } else { Poll::Pending } From 9252e8bb88236946597575918991b55bc10596b3 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 8 Apr 2022 00:35:00 +0200 Subject: [PATCH 50/50] Update cargo-batch. --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f1256320..6115e618 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -37,7 +37,7 @@ jobs: key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} - name: build run: | - curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.2.0/cargo-batch + curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch chmod +x /usr/local/bin/cargo-batch ./ci.sh rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* @@ -59,7 +59,7 @@ jobs: key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} - name: build run: | - curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.1.0/cargo-batch + curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch chmod +x /usr/local/bin/cargo-batch ./ci_stable.sh rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}*