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}* diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 5e69a887..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,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", 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} @@ -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/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 0004eb58..3b180902 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 deab9454..9f483d96 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -1,43 +1,810 @@ #![macro_use] +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::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; +use futures::Future; + +pub use embassy_usb; +use pac::usbd::RegisterBlock; + use crate::interrupt::Interrupt; use crate::pac; +use crate::util::slice_in_ram; -use core::marker::PhantomData; -use embassy::util::Unborrow; -use nrf_usbd::{UsbPeripheral, Usbd}; -use usb_device::bus::UsbBusAllocator; +const NEW_AW: AtomicWaker = AtomicWaker::new(); +static BUS_WAKER: AtomicWaker = NEW_AW; +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 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_usbreset.read().bits() != 0 { + regs.intenclr.write(|w| w.usbreset().clear()); + BUS_WAKER.wake(); + EP0_WAKER.wake(); + } + + 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(); + } + + // 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(); + regs.epdatastatus.write(|w| unsafe { w.bits(r) }); + READY_ENDPOINTS.fetch_or(r, Ordering::AcqRel); + for i in 1..=7 { + if r & In::mask(i) != 0 { + In::waker(i).wake(); + } + if r & Out::mask(i) != 0 { + Out::waker(i).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(), + } + } +} + +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>; + type EnableFuture = impl Future + 'd; + + 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::new(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::new(EndpointInfo { + addr: ep_addr, + ep_type, + max_packet_size, + interval, + })) + } + + 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, + }) + } + + fn enable(self) -> Self::EnableFuture { + async move { + let regs = T::regs(); + + errata::pre_enable(); + + regs.enable.write(|w| w.enable().enabled()); + + // 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(); + + 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"); + + 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> { + type PollFuture<'a> = impl Future + 'a where Self: '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()); + trace!("USB event: isooutcrc"); + } + if r.usbwuallowed().bit() { + regs.eventcause.write(|w| w.usbwuallowed().set_bit()); + trace!("USB event: usbwuallowed"); + } + if r.suspend().bit() { + regs.eventcause.write(|w| w.suspend().set_bit()); + trace!("USB event: suspend"); + } + if r.resume().bit() { + regs.eventcause.write(|w| w.resume().set_bit()); + trace!("USB event: resume"); + } + if r.ready().bit() { + regs.eventcause.write(|w| w.ready().set_bit()); + trace!("USB event: ready"); + } + + Poll::Pending + }) + } + + #[inline] + fn reset(&mut self) { + self.set_configured(false); + } + + #[inline] + fn set_configured(&mut self, configured: bool) { + let regs = T::regs(); + + unsafe { + if configured { + // TODO: Initialize ISO buffers + + 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(); + } + } + + // 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); + } + } + + for i in 1..=7 { + In::waker(i).wake(); + Out::waker(i).wake(); + } + } + + #[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 {} + +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, +} + +impl<'d, T: Instance, Dir> Endpoint<'d, T, Dir> { + fn new(info: EndpointInfo) -> Self { + Self { + info, + _phantom: PhantomData, + } + } +} + +impl<'d, T: Instance, Dir: EndpointDir> 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) + } + + 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 + } }) } } -unsafe impl embassy_hal_common::usb::USBInterrupt for crate::interrupt::USBD {} +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 { + let regs = T::regs(); + + // Check that the packet fits into the buffer + let size = regs.size.epout[i].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]) { + let regs = T::regs(); + assert!(buf.len() <= 64); + + let mut ram_buf: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); + 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, + ®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(); +} + +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 i = self.info.addr.index(); + assert!(i != 0); + + self.wait_data_ready() + .await + .map_err(|_| ReadError::Disabled)?; + + unsafe { read_dma::(i, buf) } + } + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + type WriteFuture<'a> = impl Future> + 'a where Self: 'a; + + fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + let i = self.info.addr.index(); + assert!(i != 0); + + self.wait_data_ready() + .await + .map_err(|_| WriteError::Disabled)?; + + unsafe { write_dma::(i, buf) } + + Ok(()) + } + } +} + +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, +} + +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; + + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } + + fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { + async move { + let regs = T::regs(); + + // Wait for SETUP packet + regs.intenset.write(|w| { + w.ep0setup().set(); + w.ep0datadone().set() + }); + poll_fn(|cx| { + EP0_WAKER.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()); + } + + req + } + } + + fn data_out<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::DataOutFuture<'a> { + async move { + let regs = T::regs(); + + // Wait until ready + 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().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 { + 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); + } + + regs.shorts + .modify(|_, w| w.ep0datadone_ep0status().bit(last_packet)); + + regs.intenset.write(|w| { + w.usbreset().set(); + w.ep0setup().set(); + w.ep0datadone().set() + }); + + poll_fn(|cx| { + cx.waker().wake_by_ref(); + EP0_WAKER.register(cx.waker()); + let regs = T::regs(); + 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 { + Poll::Pending + } + }) + .await + } + } + + fn accept(&mut self) { + let regs = T::regs(); + regs.tasks_ep0status + .write(|w| w.tasks_ep0status().bit(true)); + } + + fn reject(&mut self) { + let regs = T::regs(); + regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); + } +} + +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 = 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 + } + } + }; + + 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 +830,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-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..f50c5f8c --- /dev/null +++ b/embassy-usb-hid/src/lib.rs @@ -0,0 +1,522 @@ +#![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 core::ops::Range; +use core::sync::atomic::{AtomicUsize, Ordering}; + +use embassy::time::Duration; +use embassy_usb::driver::EndpointOut; +use embassy_usb::{ + control::{ControlHandler, InResponse, OutResponse, Request, RequestType}, + driver::{Driver, Endpoint, EndpointIn, WriteError}, + UsbDeviceBuilder, +}; + +#[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> { + 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), + } + } +} + +pub struct HidClass<'d, D: Driver<'d>, T, const IN_N: usize> { + input: ReportWriter<'d, D, IN_N>, + output: T, +} + +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, OUT_N>, + 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, + request_handler, + &state.out_report_offset, + )); + 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 + /// 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). + 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 = 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, + &state.out_report_offset, + )); + control.build(builder, Some(&ep_out), &ep_in); + + Self { + input: ReportWriter { ep_in }, + output: ReportReader { + ep_out, + offset: &state.out_report_offset, + }, + } + } + + /// Gets the [`ReportReader`] for output reports. + pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> { + &mut self.output + } + + /// 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) + } +} + +pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { + ep_in: D::EndpointIn, +} + +pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { + ep_out: D::EndpointOut, + offset: &'d AtomicUsize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReadError { + BufferOverflow, + Disabled, + Sync(Range), +} + +impl From for ReadError { + fn from(val: embassy_usb::driver::ReadError) -> Self { + use embassy_usb::driver::ReadError::*; + match val { + BufferOverflow => ReadError::BufferOverflow, + Disabled => ReadError::Disabled, + } + } +} + +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. + #[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 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) { + self.ep_in.write(chunk).await?; + } + + if zlp_needed { + self.ep_in.write(&[]).await?; + } + + Ok(()) + } +} + +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`. + /// + /// 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) => { + 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!(), + } + } + } + + /// 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!(N != 0); + assert!(buf.len() >= N); + + // Read packets from the endpoint + let max_packet_size = usize::from(self.ep_out.info().max_packet_size); + 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; + } + } + + if starting_offset > 0 { + Err(ReadError::Sync(starting_offset..total)) + } else { + Ok(total) + } + } +} + +pub trait RequestHandler { + /// 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 { + let _ = (id, buf); + None + } + + /// Sets the value of report `id` to `data`. + fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { + let _ = (id, data); + OutResponse::Rejected + } + + /// 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 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 + } + + /// 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); + } +} + +struct Control<'d> { + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + hid_descriptor: [u8; 9], +} + +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, + // 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: &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, + ], + ); + + builder.config_descriptor.endpoint(ep_in.info()); + if let Some(ep) = ep_out { + builder.config_descriptor.endpoint(ep.info()); + } + } +} + +impl<'d> ControlHandler for Control<'d> { + 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); + if let RequestType::Class = req.request_type { + match req.request { + HID_REQ_SET_IDLE => { + 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); + 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), self.request_handler) { + (Ok(id), Some(handler)) => handler.set_report(id, data), + _ => 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.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 { + 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/embassy-usb-serial/Cargo.toml b/embassy-usb-serial/Cargo.toml new file mode 100644 index 00000000..553f2a13 --- /dev/null +++ b/embassy-usb-serial/Cargo.toml @@ -0,0 +1,19 @@ +[package] +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" } + +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/embassy-usb-serial/src/lib.rs b/embassy-usb-serial/src/lib.rs new file mode 100644 index 00000000..07352fac --- /dev/null +++ b/embassy-usb-serial/src/lib.rs @@ -0,0 +1,369 @@ +#![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 embassy::blocking_mutex::CriticalSectionMutex; +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}; + +/// 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; + +pub struct State<'a> { + control: MaybeUninit>, + shared: ControlShared, +} + +impl<'a> State<'a> { + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + shared: Default::default(), + } + } +} + +/// 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. +/// - `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>> { + _comm_ep: D::EndpointIn, + _data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + control: &'d ControlShared, +} + +struct Control<'a> { + shared: &'a ControlShared, +} + +/// Shared data between Control and CdcAcmClass +struct ControlShared { + line_coding: CriticalSectionMutex>, + dtr: AtomicBool, + rts: AtomicBool, +} + +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<'a> Control<'a> { + fn shared(&mut self) -> &'a ControlShared { + self.shared + } +} + +impl<'d> ControlHandler for Control<'d> { + fn reset(&mut self) { + 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]) -> OutResponse { + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + OutResponse::Accepted + } + REQ_SET_LINE_CODING if data.len() >= 7 => { + 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)); + debug!("Set line coding to: {:?}", coding); + + OutResponse::Accepted + } + REQ_SET_CONTROL_LINE_STATE => { + 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); + debug!("Set dtr {}, rts {}", dtr, rts); + + OutResponse::Accepted + } + _ => OutResponse::Rejected, + } + } + + 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 => { + 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()); + 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, + } + } +} + +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>, + state: &'d mut State<'d>, + max_packet_size: u16, + ) -> Self { + let control = state.control.write(Control { + shared: &state.shared, + }); + + 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(); + 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, + ); + 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(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: comm_ep, + _data_if: data_if, + read_ep, + write_ep, + control: control_shared, + } + } + + /// 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.control.line_coding.lock(|x| x.get()) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// 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 + } + + /// 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 +#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] +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, defmt::Format)] +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. +#[derive(Clone, Copy, defmt::Format)] +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/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml new file mode 100644 index 00000000..48c20506 --- /dev/null +++ b/embassy-usb/Cargo.toml @@ -0,0 +1,19 @@ +[package] +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" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +heapless = "0.7.10" \ No newline at end of file diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs new file mode 100644 index 00000000..4bbcd3e5 --- /dev/null +++ b/embassy-usb/src/builder.rs @@ -0,0 +1,386 @@ +use heapless::Vec; + +use super::control::ControlHandler; +use super::descriptor::{BosWriter, DescriptorWriter}; +use super::driver::{Driver, EndpointAllocError}; +use super::types::*; +use super::UsbDevice; +use super::MAX_INTERFACE_COUNT; + +#[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>, + interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, + control_buf: &'d mut [u8], + + driver: 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`]. + /// + /// `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( + driver: 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 + && (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); + config_descriptor.configuration(&config); + bos_descriptor.bos(); + + UsbDeviceBuilder { + driver, + config, + interfaces: Vec::new(), + control_buf, + 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 async fn build(mut self) -> UsbDevice<'d, D> { + self.config_descriptor.end_configuration(); + self.bos_descriptor.end_bos(); + + UsbDevice::build( + self.driver, + self.config, + self.device_descriptor.into_buf(), + self.config_descriptor.into_buf(), + self.bos_descriptor.writer.into_buf(), + self.interfaces, + self.control_buf, + ) + .await + } + + /// 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) + } + + /// 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( + &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; + 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.driver + .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.driver + .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_pipe(&mut self, max_packet_size: u16) -> D::ControlPipe { + self.driver + .alloc_control_pipe(max_packet_size) + .expect("alloc_control_pipe 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..7c46812b --- /dev/null +++ b/embassy-usb/src/control.rs @@ -0,0 +1,330 @@ +use core::mem; + +use crate::descriptor::DescriptorWriter; +use crate::driver::{self, ReadError}; +use crate::DEFAULT_ALTERNATE_SETTING; + +use super::types::*; + +/// 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; + + /// Parses a USB control request from a byte array. + pub fn parse(buf: &[u8; 8]) -> Request { + let rt = buf[0]; + let recipient = rt & 0b11111; + + 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) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OutResponse { + Accepted, + Rejected, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InResponse<'a> { + Accepted(&'a [u8]), + Rejected, +} + +/// Handler for control requests. +/// +/// 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. + /// + /// # Arguments + /// + /// * `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 + } + + /// Called when a control request is received with direction DeviceToHost. + /// + /// 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. + 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]) + } +} + +/// Typestate representing a ControlPipe in the DATA IN stage +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct DataInStage { + pub(crate) 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; + trace!("control request: {:02x}", req); + + 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; + } + } + + 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")] + trace!(" control in accept {:02x}", buf); + #[cfg(not(feature = "defmt"))] + trace!(" control in accept {:02x?}", 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() { + match self.control.data_in(chunk, chunks.size_hint().0 == 0).await { + Ok(()) => {} + Err(e) => { + warn!("control accept_in failed: {:?}", e); + return; + } + } + } + } + + 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) { + trace!(" control accept"); + self.control.accept(); + } + + pub(crate) fn reject(&mut self) { + trace!(" control reject"); + self.control.reject(); + } +} diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs new file mode 100644 index 00000000..ff971e12 --- /dev/null +++ b/embassy-usb/src/descriptor.rs @@ -0,0 +1,369 @@ +use super::builder::Config; +use super::{types::*, CONFIGURATION_VALUE, DEFAULT_ALTERNATE_SETTING}; + +/// 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]) { + let length = descriptor.len(); + + if (self.position + 2 + length) > self.buf.len() || (length + 2) > 255 { + panic!("Descriptor buffer full"); + } + + 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; + } + + pub(crate) fn device(&mut self, config: &Config) { + 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) { + 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 + ], + ) + } + + #[allow(unused)] + 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, + ) { + if !self.write_iads { + return; + } + + self.write( + descriptor_type::IAD, + &[ + first_interface.into(), // bFirstInterface + interface_count, // bInterfaceCount + function_class, + function_sub_class, + function_protocol, + 0, + ], + ); + } + + /// 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, + ) { + 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, + ) { + if alternate_setting == DEFAULT_ALTERNATE_SETTING { + match self.num_interfaces_mark { + Some(mark) => self.buf[mark] += 1, + None => { + panic!("you can only call `interface/interface_alt` after `configuration`.") + } + }; + } + + 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 + ], + ); + } + + /// Writes an endpoint descriptor. + /// + /// # Arguments + /// + /// * `endpoint` - Endpoint previously allocated with + /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). + pub fn endpoint(&mut self, endpoint: &EndpointInfo) { + match self.num_endpoints_mark { + Some(mark) => self.buf[mark] += 1, + None => panic!("you can only call `endpoint` after `interface/interface_alt`."), + }; + + 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 + ], + ); + } + + /// Writes a string descriptor. + pub(crate) fn string(&mut self, string: &str) { + let mut pos = self.position; + + if pos + 2 > self.buf.len() { + panic!("Descriptor buffer full"); + } + + 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() { + panic!("Descriptor buffer full"); + } + + 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; + } +} + +/// 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) { + 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]); + } + + /// 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]) { + match self.num_caps_mark { + Some(mark) => self.writer.buf[mark] += 1, + 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 { + panic!("Descriptor buffer full"); + } + + 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; + } + + 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..01eb3d57 --- /dev/null +++ b/embassy-usb/src/driver.rs @@ -0,0 +1,239 @@ +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 +/// platform. +pub trait Driver<'a> { + type EndpointOut: EndpointOut + '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 + /// [`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; + + 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::EnableFuture; + + /// 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 { + 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 + /// 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 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); + + /// 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 { + type WaitEnabledFuture<'a>: Future + 'a + where + Self: 'a; + + /// 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; + + /// Waits for the endpoint to be enabled. + fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_>; + + // 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 ControlPipe { + type SetupFuture<'a>: Future + 'a + where + Self: 'a; + type DataOutFuture<'a>: Future> + 'a + where + Self: '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 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. + 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); + + /// Rejects a control request. + /// + /// Sets a STALL condition on the pipe to indicate an error. + fn reject(&mut self); +} + +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>; +} + +#[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, + Disabled, +} + +#[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, + Disabled, +} 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..e98cbdee --- /dev/null +++ b/embassy-usb/src/lib.rs @@ -0,0 +1,344 @@ +#![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; +pub mod control; +pub mod descriptor; +pub mod driver; +pub mod types; +mod util; + +use heapless::Vec; + +use self::control::*; +use self::descriptor::*; +use self::driver::{Bus, Driver, Event}; +use self::types::*; +use self::util::*; + +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 const MAX_INTERFACE_COUNT: usize = 4; + +pub struct UsbDevice<'d, D: Driver<'d>> { + bus: D::Bus, + control: ControlPipe, + + config: Config<'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, + self_powered: bool, + pending_address: u8, + + interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, +} + +impl<'d, D: Driver<'d>> UsbDevice<'d, D> { + pub(crate) async fn build( + mut driver: D, + config: Config<'d>, + device_descriptor: &'d [u8], + config_descriptor: &'d [u8], + bos_descriptor: &'d [u8], + interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, + control_buf: &'d mut [u8], + ) -> 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 bus = driver.enable().await; + + Self { + bus, + config, + control: ControlPipe::new(control), + device_descriptor, + config_descriptor, + bos_descriptor, + control_buf, + device_state: UsbDeviceState::Default, + remote_wakeup_enabled: false, + self_powered: false, + pending_address: 0, + interfaces, + } + } + + pub async fn run(&mut self) { + loop { + let control_fut = self.control.setup(); + let bus_fut = self.bus.poll(); + 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; + self.remote_wakeup_enabled = false; + self.pending_address = 0; + + for (_, h) in self.interfaces.iter_mut() { + h.reset(); + } + } + Event::Resume => { + trace!("usb: resume"); + } + Event::Suspend => { + trace!("usb: suspend"); + self.bus.suspend(); + self.device_state = UsbDeviceState::Suspend; + } + }, + 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, + }, + } + } + } + + 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; + + 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; + } + }; + + 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(stage) + } + (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { + self.remote_wakeup_enabled = true; + self.control.accept(stage) + } + (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) + } + }, + _ => 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(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(stage) + } + _ => self.control.reject(), + }, + (_, Recipient::Interface) => { + let handler = self + .interfaces + .iter_mut() + .find(|(i, _)| req.index == *i as _) + .map(|(_, h)| h); + + match handler { + 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(stage), + OutResponse::Rejected => self.control.reject(), + } + } + None => self.control.reject(), + } + } + _ => self.control.reject(), + } + } + + 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 => { + let mut status: u16 = 0x0000; + if self.self_powered { + status |= 0x0001; + } + if self.remote_wakeup_enabled { + status |= 0x0002; + } + 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(), stage).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(), stage).await + } + _ => self.control.reject(), + }, + (_, Recipient::Interface) => { + let handler = self + .interfaces + .iter_mut() + .find(|(i, _)| req.index == *i as _) + .map(|(_, h)| h); + + match handler { + 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, stage).await, + InResponse::Rejected => self.control.reject(), + } + } + None => self.control.reject(), + } + } + _ => self.control.reject(), + } + } + + 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, 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, stage, |w| { + w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes()); + }) + .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; + // TODO + None + } + }; + + if let Some(s) = s { + self.control + .accept_in_writer(req, stage, |w| w.string(s)) + .await + } else { + self.control.reject() + } + } + } + _ => self.control.reject(), + } + } +} 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/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/Cargo.toml b/examples/nrf/Cargo.toml index a704eb3b..e944c171 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -1,16 +1,19 @@ [package] authors = ["Dario Nieuwenhuis "] -edition = "2018" +edition = "2021" name = "embassy-nrf-examples" 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", "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"] } +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" @@ -21,6 +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" - -usb-device = "0.2" -usbd-serial = "0.1.1" +usbd-hid = "0.5.2" +serde = { version = "1.0.136", default-features = false } 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..0812697e --- /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::<8, 1>::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().await; + + // 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(false, &request_handler).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_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs new file mode 100644 index 00000000..ca938382 --- /dev/null +++ b/examples/nrf/src/bin/usb_hid_mouse.rs @@ -0,0 +1,129 @@ +#![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, Timer}; +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::{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(()) }; + 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 control = State::<5, 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 hid = HidClass::new( + &mut builder, + &mut control, + MouseReport::desc(), + Some(&request_handler), + 60, + 8, + ); + + // Build the builder. + let mut usb = builder.build().await; + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + let mut y: i8 = 5; + loop { + Timer::after(Duration::from_millis(500)).await; + + 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), + } + } + }; + + // 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_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_serial.rs b/examples/nrf/src/bin/usb_serial.rs new file mode 100644 index 00000000..500be2ce --- /dev/null +++ b/examples/nrf/src/bin/usb_serial.rs @@ -0,0 +1,113 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +use core::mem; +use defmt::{info, panic}; +use embassy::executor::Spawner; +use embassy_nrf::interrupt; +use embassy_nrf::pac; +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; + +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 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 control_buf = [0; 7]; + + let mut state = State::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); + + // Build the builder. + let mut usb = builder.build().await; + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // 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?; + } +} 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..1258bc53 --- /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().await; + + 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?; + } +} 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); - } -}