From 37598a5b3792ec1b763b5c16fe422c9e1347d7d6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 9 Mar 2022 01:34:35 +0100 Subject: [PATCH] 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 5e69a8878..36c61c651 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 deab94544..0f7d68d8c 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 000000000..dfdc8fbac --- /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 000000000..e92cc8ef2 --- /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 000000000..f1148ac76 --- /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 000000000..746c6b828 --- /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 000000000..ed4edb576 --- /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 000000000..066970813 --- /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 000000000..397db96c4 --- /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 000000000..9d00e46cb --- /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 a704eb3bc..fb846b3a9 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 000000000..345d00389 --- /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 000000000..21ca2ba4f --- /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 d283dccd1..000000000 --- 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 ef2629844..000000000 --- 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); - } -}