From ca10fe7135d10084e38038f3cd433da39e505bea Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 31 Jan 2023 22:27:19 +0100 Subject: [PATCH] usb: docs --- embassy-nrf/src/usb.rs | 8 +- embassy-rp/src/usb.rs | 16 +-- embassy-stm32/src/usb/usb.rs | 16 +-- embassy-stm32/src/usb_otg/usb.rs | 16 +-- embassy-usb-driver/README.md | 32 +++++ embassy-usb-driver/src/lib.rs | 116 ++++++++++++------- embassy-usb/README.md | 22 ++++ embassy-usb/src/builder.rs | 24 ++-- embassy-usb/src/class/cdc_acm.rs | 9 ++ embassy-usb/src/class/cdc_ncm/embassy_net.rs | 11 ++ embassy-usb/src/class/cdc_ncm/mod.rs | 51 +++++--- embassy-usb/src/class/hid.rs | 21 ++++ embassy-usb/src/class/mod.rs | 1 + embassy-usb/src/control.rs | 7 ++ embassy-usb/src/descriptor.rs | 4 +- embassy-usb/src/lib.rs | 8 +- embassy-usb/src/types.rs | 2 + 17 files changed, 267 insertions(+), 97 deletions(-) create mode 100644 embassy-usb-driver/README.md create mode 100644 embassy-usb/README.md diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 6be4fec8..f030b909 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -235,7 +235,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_in.allocate(ep_type)?; let ep_addr = EndpointAddress::from_parts(index, Direction::In); @@ -243,7 +243,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } @@ -251,7 +251,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_out.allocate(ep_type)?; let ep_addr = EndpointAddress::from_parts(index, Direction::Out); @@ -259,7 +259,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 8eec55b4..2e3708ef 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -194,13 +194,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, driver::EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -281,7 +281,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, buf, }) @@ -298,18 +298,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index 03e792a2..0355c5f1 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -268,13 +268,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, driver::EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -345,7 +345,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, buf, }) @@ -362,18 +362,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs index fd2f6707..96c574ca 100644 --- a/embassy-stm32/src/usb_otg/usb.rs +++ b/embassy-stm32/src/usb_otg/usb.rs @@ -217,13 +217,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -292,7 +292,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, }) } @@ -308,18 +308,18 @@ impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { diff --git a/embassy-usb-driver/README.md b/embassy-usb-driver/README.md new file mode 100644 index 00000000..012663c3 --- /dev/null +++ b/embassy-usb-driver/README.md @@ -0,0 +1,32 @@ +# embassy-usb-driver + +This crate contains the driver traits for [`embassy-usb`]. HAL/BSP crates can implement these +traits to add support for using `embassy-usb` for a given chip/platform. + +The traits are kept in a separate crate so that breaking changes in the higher-level [`embassy-usb`] +APIs don't cause a semver-major bump of thsi crate. This allows existing HALs/BSPs to be used +with the newer `embassy-usb` without needing updates. + +If you're writing an application using USB, you should depend on the main [`embassy-usb`] crate +instead of this one. + +[`embassy-usb`]: https://crates.io/crates/embassy-usb + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs index 64d64735..e8f71a2d 100644 --- a/embassy-usb-driver/src/lib.rs +++ b/embassy-usb-driver/src/lib.rs @@ -1,6 +1,8 @@ #![no_std] #![feature(async_fn_in_trait)] #![allow(incomplete_features)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] /// 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 @@ -95,46 +97,65 @@ impl EndpointAddress { } } +/// Infomation for an endpoint. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointInfo { + /// Endpoint's address. pub addr: EndpointAddress, + /// Endpoint's type. pub ep_type: EndpointType, + /// Max packet size, in bytes. pub max_packet_size: u16, - pub interval: u8, + /// Polling interval, in milliseconds. + pub interval_ms: u8, } -/// Driver for a specific USB peripheral. Implement this to add support for a new hardware -/// platform. +/// Main USB driver trait. +/// +/// Implement this to add support for a new hardware platform. pub trait Driver<'a> { + /// Type of the OUT endpoints for this driver. type EndpointOut: EndpointOut + 'a; + /// Type of the IN endpoints for this driver. type EndpointIn: EndpointIn + 'a; + /// Type of the control pipe for this driver. type ControlPipe: ControlPipe + 'a; + /// Type for bus control for this driver. type Bus: Bus + 'a; - /// Allocates an endpoint and specified endpoint parameters. This method is called by the device - /// and class implementations to allocate endpoints, and can only be called before - /// [`start`](Self::start) is called. + /// Allocates an OUT endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) 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. + /// * `ep_type` - the endpoint's type. /// * `max_packet_size` - Maximum packet size in bytes. - /// * `interval` - Polling interval parameter for interrupt endpoints. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result; + /// Allocates an IN endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. fn alloc_endpoint_in( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result; /// Start operation of the USB device. @@ -146,35 +167,37 @@ pub trait Driver<'a> { /// This consumes the `Driver` instance, so it's no longer possible to allocate more /// endpoints. fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); - - /// 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; } +/// USB bus trait. +/// +/// This trait provides methods that act on the whole bus. It is kept owned by +/// the main USB task, and used to manage the bus. pub trait Bus { - /// Enables the USB peripheral. Soon after enabling the device will be reset, so - /// there is no need to perform a USB reset in this method. + /// Enable the USB peripheral. async fn enable(&mut self); - /// Disables and powers down the USB peripheral. + /// Disable and powers down the USB peripheral. async fn disable(&mut self); + /// Wait for a bus-related event. + /// + /// This method should asynchronously wait for an event to happen, then + /// return it. See [`Event`] for the list of events this method should return. async fn poll(&mut self) -> Event; - /// Enables or disables an endpoint. + /// Enable or disable an endpoint. fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: 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. + /// Set or clear the STALL condition for an endpoint. + /// + /// If the endpoint is an OUT endpoint, it should be prepared to receive data again. fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); - /// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. + /// Get whether the STALL condition is set for an endpoint. fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; - /// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the + /// Simulate a disconnect from the USB bus, causing the host to reset and re-enumerate the /// device. /// /// The default implementation just returns `Unsupported`. @@ -187,7 +210,7 @@ pub trait Bus { Err(Unsupported) } - /// Initiates a remote wakeup of the host by the device. + /// Initiate a remote wakeup of the host by the device. /// /// # Errors /// @@ -196,25 +219,27 @@ pub trait Bus { async fn remote_wakeup(&mut self) -> Result<(), Unsupported>; } +/// Endpoint trait, common for OUT and IN. pub trait Endpoint { /// Get the endpoint address fn info(&self) -> &EndpointInfo; - /// Waits for the endpoint to be enabled. + /// Wait for the endpoint to be enabled. async fn wait_enabled(&mut self); } +/// OUT Endpoint trait. pub trait EndpointOut: Endpoint { - /// Reads a single packet of data from the endpoint, and returns the actual length of + /// Read a single packet of data from the endpoint, and return the actual length of /// the packet. /// /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. async fn read(&mut self, buf: &mut [u8]) -> Result; } -/// Trait for USB control pipe. +/// USB control pipe trait. /// -/// The USB control pipe owns both OUT ep 0 and IN ep 0 in a single +/// The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single /// unit, and manages them together to implement the control pipe state machine. /// /// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that @@ -232,6 +257,14 @@ pub trait EndpointOut: Endpoint { /// accept() or reject() /// ``` /// +/// - control out for setting the device address: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept_set_address(addr) or reject() +/// ``` +/// /// - control out with len != 0: /// /// ```not_rust @@ -280,26 +313,26 @@ pub trait ControlPipe { /// Maximum packet size for the control pipe fn max_packet_size(&self) -> usize; - /// Reads a single setup packet from the endpoint. + /// Read a single setup packet from the endpoint. async fn setup(&mut self) -> [u8; 8]; - /// Reads a DATA OUT packet into `buf` in response to a control write request. + /// Read 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. async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result; - /// Sends a DATA IN packet with `data` in response to a control read request. + /// Send 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`. async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>; - /// Accepts a control request. + /// Accept a control request. /// /// Causes the STATUS packet for the current request to be ACKed. async fn accept(&mut self); - /// Rejects a control request. + /// Reject a control request. /// /// Sets a STALL condition on the pipe to indicate an error. async fn reject(&mut self); @@ -311,8 +344,9 @@ pub trait ControlPipe { async fn accept_set_address(&mut self, addr: u8); } +/// IN Endpoint trait. pub trait EndpointIn: Endpoint { - /// Writes a single packet of data to the endpoint. + /// Write a single packet of data to the endpoint. async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>; } @@ -338,18 +372,22 @@ pub enum Event { PowerRemoved, } +/// Allocating an endpoint failed. +/// +/// This can be due to running out of endpoints, or out of endpoint memory, +/// or because the hardware doesn't support the requested combination of features. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointAllocError; +/// Operation is unsupported by the driver. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Operation is unsupported by the driver. pub struct Unsupported; +/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] pub enum EndpointError { /// Either the packet to be written is too long to fit in the transmission /// buffer or the received packet is too long to fit in `buf`. diff --git a/embassy-usb/README.md b/embassy-usb/README.md new file mode 100644 index 00000000..581c3290 --- /dev/null +++ b/embassy-usb/README.md @@ -0,0 +1,22 @@ +# embassy-usb + +TODO crate description/ + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index fc16d2b4..48498994 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -349,11 +349,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { self.builder.config_descriptor.write(descriptor_type, descriptor) } - fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointIn { + fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { let ep = self .builder .driver - .alloc_endpoint_in(ep_type, max_packet_size, interval) + .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_in failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -361,11 +361,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { ep } - fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointOut { + fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { let ep = self .builder .driver - .alloc_endpoint_out(ep_type, max_packet_size, interval) + .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_out failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -393,25 +393,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. - pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval) + pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms) } /// Allocate a INTERRUPT OUT endpoint and write its descriptor. - pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval) + pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms) } /// Allocate a ISOCHRONOUS IN endpoint and write its descriptor. /// /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. - pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval) + pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms) } /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. - pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval) + pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms) } } diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index 84db2062..09f17456 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -1,3 +1,5 @@ +//! CDC-ACM class implementation, aka Serial over USB. + use core::cell::Cell; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; @@ -28,12 +30,14 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; +/// Internal state for CDC-ACM pub struct State<'a> { control: MaybeUninit>, shared: ControlShared, } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { control: MaybeUninit::uninit(), @@ -284,10 +288,15 @@ impl From for StopBits { #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ParityType { + /// No parity bit. None = 0, + /// Parity bit is 1 if the amount of `1` bits in the data byte is odd. Odd = 1, + /// Parity bit is 1 if the amount of `1` bits in the data byte is even. Even = 2, + /// Parity bit is always 1 Mark = 3, + /// Parity bit is always 0 Space = 4, } diff --git a/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy-usb/src/class/cdc_ncm/embassy_net.rs index 501df2d8..bc79b367 100644 --- a/embassy-usb/src/class/cdc_ncm/embassy_net.rs +++ b/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -1,3 +1,4 @@ +//! [`embassy-net`](crates.io/crates/embassy-net) driver for the CDC-NCM class. use embassy_futures::select::{select, Either}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; @@ -5,11 +6,13 @@ use embassy_usb_driver::Driver; use super::{CdcNcmClass, Receiver, Sender}; +/// Internal state for the embassy-net integration. pub struct State { ch_state: ch::State, } impl State { + /// Create a new `State`. pub const fn new() -> Self { Self { ch_state: ch::State::new(), @@ -17,6 +20,9 @@ impl State, const MTU: usize> { tx_usb: Sender<'d, D>, rx_usb: Receiver<'d, D>, @@ -24,6 +30,9 @@ pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { } impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { + /// Run the CDC-NCM class. + /// + /// You must call this in a background task for the class to operate. pub async fn run(mut self) -> ! { let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); let rx_fut = async move { @@ -66,9 +75,11 @@ impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { // would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug? //pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd; +/// Type alias for the embassy-net driver for CDC-NCM. pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>; impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Obtain a driver for using the CDC-NCM class with [`embassy-net`](crates.io/crates/embassy-net). pub fn into_embassy_net_device( self, state: &'d mut State, diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs index 4954a65b..5e59b72f 100644 --- a/embassy-usb/src/class/cdc_ncm/mod.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -1,18 +1,19 @@ -/// CDC-NCM, aka Ethernet over USB. -/// -/// # Compatibility -/// -/// Windows: NOT supported in Windows 10. Supported in Windows 11. -/// -/// Linux: Well-supported since forever. -/// -/// Android: Support for CDC-NCM is spotty and varies across manufacturers. -/// -/// - On Pixel 4a, it refused to work on Android 11, worked on Android 12. -/// - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), -/// it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. -/// This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 -/// and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 +//! CDC-NCM class implementation, aka Ethernet over USB. +//! +//! # Compatibility +//! +//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11. +//! +//! Linux: Well-supported since forever. +//! +//! Android: Support for CDC-NCM is spotty and varies across manufacturers. +//! +//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12. +//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), +//! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. +//! This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 +//! and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 + use core::intrinsics::copy_nonoverlapping; use core::mem::{size_of, MaybeUninit}; @@ -114,6 +115,7 @@ fn byteify(buf: &mut [u8], data: T) -> &[u8] { &buf[..len] } +/// Internal state for the CDC-NCM class. pub struct State<'a> { comm_control: MaybeUninit>, data_control: MaybeUninit, @@ -121,6 +123,7 @@ pub struct State<'a> { } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { comm_control: MaybeUninit::uninit(), @@ -223,6 +226,7 @@ impl ControlHandler for DataControl { } } +/// CDC-NCM class pub struct CdcNcmClass<'d, D: Driver<'d>> { _comm_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -235,6 +239,7 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Create a new CDC NCM class. pub fn new( builder: &mut Builder<'d, D>, state: &'d mut State<'d>, @@ -319,6 +324,9 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { ( Sender { @@ -334,12 +342,18 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } +/// CDC NCM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcNcmClass::split`] pub struct Sender<'d, D: Driver<'d>> { write_ep: D::EndpointIn, seq: u16, } impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Write a packet. + /// + /// This waits until the packet is succesfully stored in the CDC-NCM endpoint buffers. pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { let seq = self.seq; self.seq = self.seq.wrapping_add(1); @@ -393,6 +407,9 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { } } +/// CDC NCM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcNcmClass::split`] pub struct Receiver<'d, D: Driver<'d>> { data_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -400,7 +417,9 @@ pub struct Receiver<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> Receiver<'d, D> { - /// Reads a single packet from the OUT endpoint. + /// Write a network packet. + /// + /// This waits until a packet is succesfully received from the endpoint buffers. pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { // Retry loop loop { diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs index b967aba0..2493061b 100644 --- a/embassy-usb/src/class/hid.rs +++ b/embassy-usb/src/class/hid.rs @@ -1,3 +1,5 @@ +//! USB HID (Human Interface Device) class implementation. + use core::mem::MaybeUninit; use core::ops::Range; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -28,6 +30,7 @@ const HID_REQ_SET_REPORT: u8 = 0x09; const HID_REQ_GET_PROTOCOL: u8 = 0x03; const HID_REQ_SET_PROTOCOL: u8 = 0x0b; +/// Configuration for the HID class. pub struct Config<'d> { /// HID report descriptor. pub report_descriptor: &'d [u8], @@ -46,11 +49,15 @@ pub struct Config<'d> { pub max_packet_size: u16, } +/// Report ID #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReportId { + /// IN report In(u8), + /// OUT report Out(u8), + /// Feature report Feature(u8), } @@ -65,12 +72,14 @@ impl ReportId { } } +/// Internal state for USB HID. pub struct State<'d> { control: MaybeUninit>, out_report_offset: AtomicUsize, } impl<'d> State<'d> { + /// Create a new `State`. pub fn new() -> Self { State { control: MaybeUninit::uninit(), @@ -79,6 +88,7 @@ impl<'d> State<'d> { } } +/// USB HID reader/writer. pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { reader: HidReader<'d, D, READ_N>, writer: HidWriter<'d, D, WRITE_N>, @@ -180,20 +190,30 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit } } +/// USB HID writer. +/// +/// You can obtain a `HidWriter` using [`HidReaderWriter::split`]. pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { ep_in: D::EndpointIn, } +/// USB HID reader. +/// +/// You can obtain a `HidReader` using [`HidReaderWriter::split`]. pub struct HidReader<'d, D: Driver<'d>, const N: usize> { ep_out: D::EndpointOut, offset: &'d AtomicUsize, } +/// Error when reading a HID report. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReadError { + /// The given buffer was too small to read the received report. BufferOverflow, + /// The endpoint is disabled. Disabled, + /// The report was only partially read. See [`HidReader::read`] for details. Sync(Range), } @@ -344,6 +364,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { } } +/// Handler for HID-related control requests. pub trait RequestHandler { /// Reads the value of report `id` into `buf` returning the size. /// diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index af27577a..b23e03d4 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs @@ -1,3 +1,4 @@ +//! Implementations of well-known USB classes. pub mod cdc_acm; pub mod cdc_ncm; pub mod hid; diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index d6d0c656..39b499f0 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -126,17 +126,23 @@ impl Request { } } +/// Response for a CONTROL OUT request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OutResponse { + /// The request was accepted. Accepted, + /// The request was rejected. Rejected, } +/// Response for a CONTROL IN request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InResponse<'a> { + /// The request was accepted. The buffer contains the response data. Accepted(&'a [u8]), + /// The request was rejected. Rejected, } @@ -148,6 +154,7 @@ pub trait ControlHandler { /// Called after a USB reset after the bus reset sequence is complete. fn reset(&mut self) {} + /// Called when a "set alternate setting" control request is done on the interface. fn set_alternate_setting(&mut self, alternate_setting: u8) { let _ = alternate_setting; } diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index 497f0319..ae38e26c 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,3 +1,5 @@ +//! Utilities for writing USB descriptors. + use crate::builder::Config; use crate::driver::EndpointInfo; use crate::types::*; @@ -236,7 +238,7 @@ impl<'a> DescriptorWriter<'a> { endpoint.ep_type as u8, // bmAttributes endpoint.max_packet_size as u8, (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize - endpoint.interval, // bInterval + endpoint.interval_ms, // bInterval ], ); } diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 096e8b07..2656af29 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] #![feature(type_alias_impl_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; @@ -46,10 +48,13 @@ pub enum UsbDeviceState { Configured, } +/// Error returned by [`UsbDevice::remote_wakeup`]. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RemoteWakeupError { + /// The USB device is not suspended, or remote wakeup was not enabled. InvalidState, + /// The underlying driver doesn't support remote wakeup. Unsupported, } @@ -65,6 +70,7 @@ pub const CONFIGURATION_NONE: u8 = 0; /// The bConfiguration value for the single configuration supported by this device. pub const CONFIGURATION_VALUE: u8 = 1; +/// Maximum interface count, configured at compile time. pub const MAX_INTERFACE_COUNT: usize = 4; const STRING_INDEX_MANUFACTURER: u8 = 1; @@ -100,6 +106,7 @@ struct Interface<'d> { num_strings: u8, } +/// Main struct for the USB device stack. pub struct UsbDevice<'d, D: Driver<'d>> { control_buf: &'d mut [u8], control: D::ControlPipe, @@ -489,7 +496,6 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { .unwrap(); // TODO check it is valid (not out of range) - // TODO actually enable/disable endpoints. if let Some(handler) = &mut iface.handler { handler.set_alternate_setting(new_altsetting); diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs index aeab063d..1743e61f 100644 --- a/embassy-usb/src/types.rs +++ b/embassy-usb/src/types.rs @@ -1,3 +1,5 @@ +//! USB types. + /// A handle for a USB interface that contains its number. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))]