From ca10fe7135d10084e38038f3cd433da39e505bea Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 31 Jan 2023 22:27:19 +0100 Subject: usb: docs --- 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 ++ 10 files changed, 108 insertions(+), 30 deletions(-) (limited to 'embassy-usb/src') diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index fc16d2b41..484989949 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 84db20621..09f17456c 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 501df2d8c..bc79b3671 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 4954a65bc..5e59b72fe 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 b967aba0e..2493061b0 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 af27577a6..b23e03d40 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 d6d0c6565..39b499f03 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 497f03196..ae38e26ca 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 096e8b07a..2656af29d 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 aeab063d1..1743e61ff 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))] -- cgit