diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc8ea8e..4b806d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,29 +21,15 @@ jobs: TARGET: x86_64-unknown-linux-gnu steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - target: ${{ matrix.TARGET }} - override: true + targets: ${{ matrix.TARGET }} - name: Install libusb library run: sudo apt-get install -y libusb-1.0.0-dev - - uses: actions-rs/cargo@v1 - with: - command: check - args: --all-targets - - - uses: actions-rs/cargo@v1 - with: - command: check - args: --features control-buffer-256 - - - uses: actions-rs/cargo@v1 - with: - command: check - args: --features defmt + - run: cargo check --all-targets + - run: cargo check --features control-buffer-256 + - run: cargo check --features defmt diff --git a/.github/workflows/rustfmt.yml b/.github/workflows/rustfmt.yml index 9a55c00..3ebfa79 100644 --- a/.github/workflows/rustfmt.yml +++ b/.github/workflows/rustfmt.yml @@ -10,14 +10,12 @@ jobs: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable - override: true components: rustfmt - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + - run: cargo fmt --all -- --check + - run: cargo clippy --all-features + - run: cargo clippy --features defmt + - run: cargo clippy --features control-buffer-256 + - run: cargo clippy diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d7f026..dfaba74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,58 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -... +## [0.3.2] - 2024-03-06 + +### Added +* A new `log` feature can be enabled to provide logging and tracing information about the USB +interface. + +### Changed +* [breaking] LangIDs no longer implement `TryFromPrimitive`. The minor version has not been bumped +as this was not intended to be used in the public API. + - If this is problematic, please open an issue in the main `usb-device` repository. +* Changed handling of EP0 state to eliminate unexpected issues with device enumeration + +## [0.3.1] - 2023-11-15 + +### Added +* `BuilderError`, `LangID`, `StringDescriptors` now in `prelude` +* `LangID` now in `class_prelude` + +### Changed +* Updated documentation, including example code + +## [0.3.0] - 2023-11-13 + +### Fixed +* Fixed a defect where enumeration may fail due to timing-related issues ([#128](https://github.com/rust-embedded-community/usb-device/issues/128)) + +### Added +* New enums and allocators for Isochronous endpoints ([#60](https://github.com/rust-embedded-community/usb-device/pull/60)). +* Ability to select USB revision ([#116](https://github.com/rust-embedded-community/usb-device/pull/116)). +* Added support for alternate settings on interfaces ([#114](https://github.com/rust-embedded-community/usb-device/pull/114)). +* Added support for architectures without atomics ([#115](https://github.com/rust-embedded-community/usb-device/pull/115)). +* Added support for multi-language STRING desc ([#122](https://github.com/rust-embedded-community/usb-device/pull/122)). + * `UsbDeviceBuilder` has a public `.extra_lang_ids()` method to specify LANGIDs besides ENGLISH_US(0x0409) + +### Breaking +* Acess numeric form of `EndpointType` variants now require a `.to_bm_attributes()`. ([#60](https://github.com/rust-embedded-community/usb-device/pull/60)) +* `DescriptorWriter::iad()` now requires a `Option` to optionally specify a string for describing the function ([#121](https://github.com/rust-embedded-community/usb-device/pull/121)) +* `.manufacturer()`, `.product()` and `.serial_number()` of `UsbDeviceBuilder` are now replaced with the `strings()` function that accepts a `StringDescriptor` list to allow multilanguage support ([#122](https://github.com/rust-embedded-community/usb-device/pull/122)) +* Various methods of the `UsbDeviceBuilder` now return `Result<>` types instead of internally panicking. + +### Changed +* `EndpointType` enum now has fields for isochronous synchronization and usage ([#60](https://github.com/rust-embedded-community/usb-device/pull/60)). +* `descriptor_type::STRING` of `fn get_descriptor()` will send the LANGIDs supported by device, and respond STRING Request with specified LANGID. ([#122](https://github.com/rust-embedded-community/usb-device/pull/122)) +* `UsbError` is now copyable and comparable ([#127](https://github.com/rust-embedded-community/usb-device/pull/127)) ## [0.2.9] - 2022-08-02 +### Added +* Optional support for defmt ([#76](https://github.com/rust-embedded-community/usb-device/pull/76)). + ### Fixed * Fixed an issue where USB devices were not enumerating on Windows ([#32](https://github.com/rust-embedded-community/usb-device/issues/82)) -* Add optional support for defmt ([#76](https://github.com/rust-embedded-community/usb-device/pull/76)) * Fixed Suspend state transition so it goes back to the previous state, not just Default ([#97](https://github.com/rust-embedded-community/usb-device/pull/97)) ## [0.2.8] - 2021-03-13 @@ -38,7 +83,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. This is the initial release to crates.io. -[Unreleased]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.9...HEAD +[Unreleased]: https://github.com/rust-embedded-community/usb-device/compare/v0.3.2...HEAD +[0.3.2]: https://github.com/rust-embedded-community/usb-device/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/rust-embedded-community/usb-device/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.9...v0.3.0 [0.2.9]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.8...v0.2.9 [0.2.8]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.7...v0.2.8 [0.2.7]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.6...v0.2.7 diff --git a/Cargo.toml b/Cargo.toml index e1da13e..d5c2c6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,19 @@ [package] name = "usb-device" -description = "Experimental device-side USB stack for embedded devices." -version = "0.2.9" +description = "USB stack for embedded devices." +version = "0.3.2" edition = "2018" readme = "README.md" keywords = ["no-std", "embedded", "usb"] license = "MIT" authors = ["Matti Virkkunen "] -repository = "https://github.com/mvirkkunen/usb-device" +repository = "https://github.com/rust-embedded-community/usb-device" [dependencies] defmt = { version = "0.3", optional = true } +portable-atomic = { version = "1.2.0", default-features = false } +heapless = "0.8" +log = { version = "0.4", default-features = false, optional = true} [dev-dependencies] rusb = "0.9.1" @@ -20,6 +23,9 @@ rand = "0.8.5" # Use a 256 byte buffer for control transfers instead of 128. control-buffer-256 = [] +# Enable logging and tracing via the `log` crate +log = ["dep:log"] + # Use larger endpoint buffers for highspeed operation (default fullspeed) # # Note: usb-device doesn't truly support high speed enumeration yet, so setting this will make diff --git a/README.md b/README.md index 048c6a2..df04b8b 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ usb-device ========== -Experimental device-side USB stack for embedded devices in Rust. - -This crate is still under development and should not be considered production ready or even USB -compliant. +USB stack for embedded devices in Rust. The UsbDevice object represents a composite USB device and is the most important object for application implementors. The UsbDevice combines a number of UsbClasses (either custom ones, or -pre-existing ones provided by other crates) and a UsbBus device drives to implement the USB device. +pre-existing ones provided by other crates) and a UsbBus device driver to implement the USB device. -The UsbClass trait can be used to implemented USB classes such as a HID device or a serial port. An +The UsbClass trait can be used to implement USB classes such as a HID device or a serial port. An implementation may also use a custom class if the required functionality isn't covered by a standard class. @@ -31,15 +28,21 @@ Hardware driver crates * [stm32-usbd](https://github.com/stm32-rs/stm32-usbd) - device-driver implementation for multiple STM32 microcontroller families. Examples can be found in each individual HAL crate that implements the USB peripheral. +* [rp2040-hal](https://github.com/rp-rs/rp-hal) - device-driver implementation for the raspberry pi RP2040 microcontroller. Examples can be found + in the various boards crates [here](https://github.com/rp-rs/rp-hal-boards). + Class crates ------------ * [usbd-hid](https://github.com/twitchyliquid64/usbd-hid) [![Crates.io](https://img.shields.io/crates/v/usbd-hid.svg)](https://crates.io/crates/usbd-hid) - HID class -* [usbd-serial](https://github.com/mvirkkunen/usbd-serial) [![Crates.io](https://img.shields.io/crates/v/usbd-serial.svg)](https://crates.io/crates/usbd-serial) - CDC-ACM serial port class - +* [usbd-human-device-interface](https://github.com/dlkj/usbd-human-interface-device) [![Crates.io](https://img.shields.io/crates/v/usbd-human-interface-device.svg)](https://crates.io/crates/usbd-human-device-interface) - HID class +* [usbd-serial](https://github.com/rust-embedded-community/usbd-serial) [![Crates.io](https://img.shields.io/crates/v/usbd-serial.svg)](https://crates.io/crates/usbd-serial) - CDC-ACM serial port class +* [usbd-storage](https://github.com/apohrebniak/usbd-storage) [![Crates.io](https://img.shields.io/crates/v/usbd-storage.svg)](https://crates.io/crates/usbd-storage) - (Experimental) Mass storage port class +* [usbd-dfu](https://github.com/vitalyvb/usbd-dfu) [![Crates.io](https://img.shields.io/crates/v/usbd-dfu.svg)](https://crates.io/crates/usbd-dfu) - Device Firmware Upgrade class +* [usbd-picotool-reset](https://github.com/ithinuel/usbd-picotool-reset) [![Crates.io](https://img.shields.io/crates/v/usbd-picotool-reset.svg)](https://crates.io/crates/usbd-picotool-reset) - picotool-reset class Others ------ Other implementations for USB in Rust -* [embassy-usb](https://github.com/embassy-rs/embassy/blob/master/embassy-usb/src/driver.rs), an async variant. \ No newline at end of file +* The [Embassy](https://github.com/embassy-rs/embassy) project has an async USB stack, embassy-usb. diff --git a/src/bus.rs b/src/bus.rs index 9461412..398b192 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,9 +1,12 @@ -use crate::endpoint::{Endpoint, EndpointAddress, EndpointDirection, EndpointType}; +use crate::endpoint::{ + Endpoint, EndpointAddress, EndpointDirection, EndpointType, IsochronousSynchronizationType, + IsochronousUsageType, +}; use crate::{Result, UsbDirection, UsbError}; use core::cell::RefCell; use core::mem; use core::ptr; -use core::sync::atomic::{AtomicPtr, Ordering}; +use portable_atomic::{AtomicPtr, Ordering}; /// A trait for device-specific USB peripherals. Implement this to add support for a new hardware /// platform. @@ -244,6 +247,41 @@ impl UsbBusAllocator { .expect("alloc_ep failed") } + /// Allocates an isochronous endpoint. + /// + /// # Arguments + /// + /// * `synchronization` - Type of synchronization used by the endpoint + /// * `usage` - Whether the endpoint is data, explicit feedback, or data+implicit feedback + /// * `payload_size` - Payload size in bytes. + /// * `interval` - Interval for polling, expressed in frames/microframes. + /// + /// See USB 2.0 section 9.6.6. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn isochronous( + &self, + synchronization: IsochronousSynchronizationType, + usage: IsochronousUsageType, + payload_size: u16, + interval: u8, + ) -> Endpoint<'_, B, D> { + self.alloc( + None, + EndpointType::Isochronous { + synchronization, + usage, + }, + payload_size, + interval, + ) + .expect("alloc_ep failed") + } + /// Allocates a bulk endpoint. /// /// # Arguments @@ -263,6 +301,7 @@ impl UsbBusAllocator { /// Allocates an interrupt endpoint. /// /// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes. + /// * `interval` - Polling interval. /// /// # Panics /// @@ -282,7 +321,7 @@ impl UsbBusAllocator { /// 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); +pub struct InterfaceNumber(pub(crate) u8); impl From for u8 { fn from(n: InterfaceNumber) -> u8 { diff --git a/src/class.rs b/src/class.rs index 5337e97..4d70c8e 100644 --- a/src/class.rs +++ b/src/class.rs @@ -1,6 +1,7 @@ -use crate::bus::{StringIndex, UsbBus}; +use crate::bus::{InterfaceNumber, StringIndex, UsbBus}; use crate::control; use crate::control_pipe::ControlPipe; +use crate::descriptor::lang_id::LangID; use crate::descriptor::{BosWriter, DescriptorWriter}; use crate::endpoint::EndpointAddress; use crate::{Result, UsbError}; @@ -42,8 +43,9 @@ pub trait UsbClass { /// /// * `index` - A string index allocated earlier with /// [`UsbAllocator`](crate::bus::UsbBusAllocator). - /// * `lang_id` - The language ID for the string to retrieve. - fn get_string(&self, index: StringIndex, lang_id: u16) -> Option<&str> { + /// * `lang_id` - The language ID for the string to retrieve. If the requested lang_id is not + /// valid it will default to EN_US. + fn get_string(&self, index: StringIndex, lang_id: LangID) -> Option<&str> { let _ = (index, lang_id); None } @@ -116,6 +118,24 @@ pub trait UsbClass { fn endpoint_in_complete(&mut self, addr: EndpointAddress) { let _ = addr; } + + /// Called when the interfaces alternate setting state is requested. + /// + /// Note: This method may be called on interfaces, that are not relevant to this class. + /// You should return `None, if `interface` belongs to an interface you don't know. + fn get_alt_setting(&mut self, interface: InterfaceNumber) -> Option { + let _ = interface; + None + } + + /// Called when the interfaces alternate setting state is altered. + /// + /// Note: This method may be called on interfaces, that are not relevant to this class. + /// You should return `false`, if `interface` belongs to an interface you don't know. + fn set_alt_setting(&mut self, interface: InterfaceNumber, alternative: u8) -> bool { + let _ = (interface, alternative); + false + } } /// Handle for a control IN transfer. When implementing a class, use the methods of this object to diff --git a/src/control_pipe.rs b/src/control_pipe.rs index 17d5ff3..1dd66ca 100644 --- a/src/control_pipe.rs +++ b/src/control_pipe.rs @@ -63,15 +63,18 @@ impl ControlPipe<'_, B> { } pub fn reset(&mut self) { + usb_trace!("Control pipe reset"); self.state = ControlState::Idle; } pub fn handle_setup(&mut self) -> Option { let count = match self.ep_out.read(&mut self.buf[..]) { - Ok(count) => count, + Ok(count) => { + usb_trace!("Read {} bytes on EP0-OUT: {:?}", count, &self.buf[..count]); + count + } Err(UsbError::WouldBlock) => return None, Err(_) => { - self.set_error(); return None; } }; @@ -79,8 +82,7 @@ impl ControlPipe<'_, B> { let req = match Request::parse(&self.buf[0..count]) { Ok(req) => req, Err(_) => { - // Failed to parse SETUP packet - self.set_error(); + // Failed to parse SETUP packet. We are supposed to silently ignore this. return None; } }; @@ -89,6 +91,8 @@ impl ControlPipe<'_, B> { // a stalled state. self.ep_out.unstall(); + usb_debug!("EP0 request received: {:?}", req); + /*sprintln!("SETUP {:?} {:?} {:?} req:{} val:{} idx:{} len:{} {:?}", req.direction, req.request_type, req.recipient, req.request, req.value, req.index, req.length, @@ -102,7 +106,6 @@ impl ControlPipe<'_, B> { if req.length as usize > self.buf.len() { // Data stage won't fit in buffer - self.set_error(); return None; } @@ -126,26 +129,33 @@ impl ControlPipe<'_, B> { None } - pub fn handle_out(&mut self) -> Option { + pub fn handle_out(&mut self) -> Result> { match self.state { ControlState::DataOut(req) => { let i = self.i; let count = match self.ep_out.read(&mut self.buf[i..]) { Ok(count) => count, - Err(UsbError::WouldBlock) => return None, - Err(_) => { + Err(UsbError::WouldBlock) => return Ok(None), + Err(_err) => { // Failed to read or buffer overflow (overflow is only possible if the host // sends more data than it indicated in the SETUP request) + usb_debug!("Failed EP0 read: {:?}", _err); self.set_error(); - return None; + return Ok(None); } }; + usb_trace!( + "Read {} bytes on EP0-OUT: {:?}", + count, + &self.buf[i..(i + count)] + ); self.i += count; if self.i >= self.len { + usb_debug!("Request OUT complete: {:?}", req); self.state = ControlState::CompleteOut; - return Some(req); + return Ok(Some(req)); } } // The host may terminate a DATA stage early by sending a zero-length status packet @@ -154,33 +164,37 @@ impl ControlPipe<'_, B> { | ControlState::DataInLast | ControlState::DataInZlp | ControlState::StatusOut => { - self.ep_out.read(&mut []).ok(); + usb_debug!( + "Control transfer completed. Current state: {:?}", + self.state + ); + self.ep_out.read(&mut [])?; self.state = ControlState::Idle; } _ => { // Discard the packet - self.ep_out.read(&mut []).ok(); + usb_debug!( + "Discarding EP0 data due to unexpected state. Current state: {:?}", + self.state + ); + self.ep_out.read(&mut [])?; // Unexpected OUT packet self.set_error() } } - None + Ok(None) } - pub fn handle_in_complete(&mut self) -> bool { + pub fn handle_in_complete(&mut self) -> Result { match self.state { ControlState::DataIn => { - self.write_in_chunk(); + self.write_in_chunk()?; } ControlState::DataInZlp => { - if self.ep_in.write(&[]).is_err() { - // There isn't much we can do if the write fails, except to wait for another - // poll or for the host to resend the request. - return false; - } - + self.ep_in.write(&[])?; + usb_trace!("wrote EP0: ZLP"); self.state = ControlState::DataInLast; } ControlState::DataInLast => { @@ -189,27 +203,29 @@ impl ControlPipe<'_, B> { } ControlState::StatusIn => { self.state = ControlState::Idle; - return true; + return Ok(true); + } + ControlState::Idle => { + // If we received a message on EP0 while sending the last portion of an IN + // transfer, we may have already transitioned to IDLE without getting the last + // IN-complete status. Just ignore this indication. } _ => { - // Unexpected IN packet - self.set_error(); + // If we get IN-COMPLETE indications in unexpected states, it's generally because + // of control flow in previous phases updating after our packet was successfully + // sent. Ignore these indications if they don't drive any further behavior. } }; - false + Ok(false) } - fn write_in_chunk(&mut self) { + fn write_in_chunk(&mut self) -> Result<()> { let count = min(self.len - self.i, self.ep_in.max_packet_size() as usize); let buffer = self.static_in_buf.unwrap_or(&self.buf); - let count = match self.ep_in.write(&buffer[self.i..(self.i + count)]) { - Ok(c) => c, - // There isn't much we can do if the write fails, except to wait for another poll or for - // the host to resend the request. - Err(_) => return, - }; + let count = self.ep_in.write(&buffer[self.i..(self.i + count)])?; + usb_trace!("wrote EP0: {:?}", &buffer[self.i..(self.i + count)]); self.i += count; @@ -222,15 +238,20 @@ impl ControlPipe<'_, B> { ControlState::DataInLast }; } + + Ok(()) } pub fn accept_out(&mut self) -> Result<()> { match self.state { ControlState::CompleteOut => {} - _ => return Err(UsbError::InvalidState), + _ => { + usb_debug!("Cannot ACK, invalid state: {:?}", self.state); + return Err(UsbError::InvalidState); + } }; - self.ep_in.write(&[]).ok(); + self.ep_in.write(&[])?; self.state = ControlState::StatusIn; Ok(()) } @@ -238,7 +259,10 @@ impl ControlPipe<'_, B> { pub fn accept_in(&mut self, f: impl FnOnce(&mut [u8]) -> Result) -> Result<()> { let req = match self.state { ControlState::CompleteIn(req) => req, - _ => return Err(UsbError::InvalidState), + _ => { + usb_debug!("EP0-IN cannot ACK, invalid state: {:?}", self.state); + return Err(UsbError::InvalidState); + } }; let len = f(&mut self.buf[..])?; @@ -254,7 +278,10 @@ impl ControlPipe<'_, B> { pub fn accept_in_static(&mut self, data: &'static [u8]) -> Result<()> { let req = match self.state { ControlState::CompleteIn(req) => req, - _ => return Err(UsbError::InvalidState), + _ => { + usb_debug!("EP0-IN cannot ACK, invalid state: {:?}", self.state); + return Err(UsbError::InvalidState); + } }; self.static_in_buf = Some(data); @@ -266,12 +293,13 @@ impl ControlPipe<'_, B> { self.len = min(data_len, req.length as usize); self.i = 0; self.state = ControlState::DataIn; - self.write_in_chunk(); + self.write_in_chunk()?; Ok(()) } pub fn reject(&mut self) -> Result<()> { + usb_debug!("EP0 transfer rejected"); if !self.waiting_for_response() { return Err(UsbError::InvalidState); } @@ -281,6 +309,7 @@ impl ControlPipe<'_, B> { } fn set_error(&mut self) { + usb_debug!("EP0 stalled - error"); self.state = ControlState::Error; self.ep_out.stall(); self.ep_in.stall(); diff --git a/src/descriptor.rs b/src/descriptor.rs index 9bb7361..a1a2a17 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -19,12 +19,7 @@ pub mod descriptor_type { } /// 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; -} +pub mod lang_id; /// Standard capability descriptor types #[allow(missing_docs)] @@ -110,22 +105,40 @@ impl DescriptorWriter<'_> { 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.usb_rev as u16) as u8, + (config.usb_rev as u16 >> 8) as u8, // bcdUSB + 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 + (config.device_release >> 8) as u8, // bcdDevice + config.string_descriptors.first().map_or(0, |lang| { + if lang.manufacturer.is_some() { + 1 + } else { + 0 + } + }), + config.string_descriptors.first().map_or(0, |lang| { + if lang.product.is_some() { + 2 + } else { + 0 + } + }), + config.string_descriptors.first().map_or(0, |lang| { + if lang.serial.is_some() { + 3 + } else { + 0 + } + }), + 1, // bNumConfigurations ], ) } @@ -178,6 +191,7 @@ impl DescriptorWriter<'_> { /// 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. + /// * `function_string` - Index of string descriptor describing this function pub fn iad( &mut self, first_interface: InterfaceNumber, @@ -185,11 +199,14 @@ impl DescriptorWriter<'_> { function_class: u8, function_sub_class: u8, function_protocol: u8, + function_string: Option, ) -> Result<()> { if !self.write_iads { return Ok(()); } + let str_index = function_string.map_or(0, Into::into); + self.write( descriptor_type::IAD, &[ @@ -198,7 +215,7 @@ impl DescriptorWriter<'_> { function_class, function_sub_class, function_protocol, - 0, + str_index, ], )?; @@ -288,9 +305,9 @@ impl DescriptorWriter<'_> { /// /// * `endpoint` - Endpoint previously allocated with /// [`UsbBusAllocator`](crate::bus::UsbBusAllocator). - pub fn endpoint<'e, B: UsbBus, D: EndpointDirection>( + pub fn endpoint( &mut self, - endpoint: &Endpoint<'e, B, D>, + endpoint: &Endpoint<'_, B, D>, ) -> Result<()> { self.endpoint_ex(endpoint, |_| Ok(0)) } @@ -306,9 +323,9 @@ impl DescriptorWriter<'_> { /// * `endpoint` - Endpoint previously allocated with /// [`UsbBusAllocator`](crate::bus::UsbBusAllocator). /// * `f` - Callback for the extra data. See `write_with` for more information. - pub fn endpoint_ex<'e, B: UsbBus, D: EndpointDirection>( + pub fn endpoint_ex( &mut self, - endpoint: &Endpoint<'e, B, D>, + endpoint: &Endpoint<'_, B, D>, f: impl FnOnce(&mut [u8]) -> Result, ) -> Result<()> { match self.num_endpoints_mark { @@ -324,7 +341,7 @@ impl DescriptorWriter<'_> { let mps = endpoint.max_packet_size(); buf[0] = endpoint.address().into(); - buf[1] = endpoint.ep_type() as u8; + buf[1] = endpoint.ep_type().to_bm_attributes(); buf[2] = mps as u8; buf[3] = (mps >> 8) as u8; buf[4] = endpoint.interval(); diff --git a/src/descriptor/lang_id.rs b/src/descriptor/lang_id.rs new file mode 100644 index 0000000..298da80 --- /dev/null +++ b/src/descriptor/lang_id.rs @@ -0,0 +1,479 @@ +#![allow(non_upper_case_globals, non_camel_case_types)] + +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LangID(u16); + +impl From for u16 { + fn from(lang_id: LangID) -> Self { + let LangID(id) = lang_id; + id + } +} + +impl From for LangID { + fn from(id: u16) -> Self { + LangID(id) + } +} + +#[allow(missing_docs)] +impl LangID { + pub const AR: LangID = LangID(0x0001); + pub const BG: LangID = LangID(0x0002); + pub const CA: LangID = LangID(0x0003); + pub const ZH_HANS: LangID = LangID(0x0004); + pub const CS: LangID = LangID(0x0005); + pub const DA: LangID = LangID(0x0006); + pub const DE: LangID = LangID(0x0007); + pub const EL: LangID = LangID(0x0008); + pub const EN: LangID = LangID(0x0009); + pub const ES: LangID = LangID(0x000A); + pub const FI: LangID = LangID(0x000B); + pub const FR: LangID = LangID(0x000C); + pub const HE: LangID = LangID(0x000D); + pub const HU: LangID = LangID(0x000E); + pub const IS: LangID = LangID(0x000F); + pub const IT: LangID = LangID(0x0010); + pub const JA: LangID = LangID(0x0011); + pub const KO: LangID = LangID(0x0012); + pub const NL: LangID = LangID(0x0013); + pub const NO: LangID = LangID(0x0014); + pub const PL: LangID = LangID(0x0015); + pub const PT: LangID = LangID(0x0016); + pub const RM: LangID = LangID(0x0017); + pub const RO: LangID = LangID(0x0018); + pub const RU: LangID = LangID(0x0019); + pub const HR: LangID = LangID(0x001A); + pub const SK: LangID = LangID(0x001B); + pub const SQ: LangID = LangID(0x001C); + pub const SV: LangID = LangID(0x001D); + pub const TH: LangID = LangID(0x001E); + pub const TR: LangID = LangID(0x001F); + pub const UR: LangID = LangID(0x0020); + pub const ID: LangID = LangID(0x0021); + pub const UK: LangID = LangID(0x0022); + pub const BE: LangID = LangID(0x0023); + pub const SL: LangID = LangID(0x0024); + pub const ET: LangID = LangID(0x0025); + pub const LV: LangID = LangID(0x0026); + pub const LT: LangID = LangID(0x0027); + pub const TG: LangID = LangID(0x0028); + pub const FA: LangID = LangID(0x0029); + pub const VI: LangID = LangID(0x002A); + pub const HY: LangID = LangID(0x002B); + pub const AZ: LangID = LangID(0x002C); + pub const EU: LangID = LangID(0x002D); + pub const HSB: LangID = LangID(0x002E); + pub const MK: LangID = LangID(0x002F); + pub const ST: LangID = LangID(0x0030); + pub const TS: LangID = LangID(0x0031); + pub const TN: LangID = LangID(0x0032); + pub const VE: LangID = LangID(0x0033); + pub const XH: LangID = LangID(0x0034); + pub const ZU: LangID = LangID(0x0035); + pub const AF: LangID = LangID(0x0036); + pub const KA: LangID = LangID(0x0037); + pub const FO: LangID = LangID(0x0038); + pub const HI: LangID = LangID(0x0039); + pub const MT: LangID = LangID(0x003A); + pub const SE: LangID = LangID(0x003B); + pub const GA: LangID = LangID(0x003C); + pub const YI: LangID = LangID(0x003D); + pub const MS: LangID = LangID(0x003E); + pub const KK: LangID = LangID(0x003F); + pub const KY: LangID = LangID(0x0040); + pub const SW: LangID = LangID(0x0041); + pub const TK: LangID = LangID(0x0042); + pub const UZ: LangID = LangID(0x0043); + pub const TT: LangID = LangID(0x0044); + pub const BN: LangID = LangID(0x0045); + pub const PA: LangID = LangID(0x0046); + pub const GU: LangID = LangID(0x0047); + pub const OR: LangID = LangID(0x0048); + pub const TA: LangID = LangID(0x0049); + pub const TE: LangID = LangID(0x004A); + pub const KN: LangID = LangID(0x004B); + pub const ML: LangID = LangID(0x004C); + pub const AS: LangID = LangID(0x004D); + pub const MR: LangID = LangID(0x004E); + pub const SA: LangID = LangID(0x004F); + pub const MN: LangID = LangID(0x0050); + pub const BO: LangID = LangID(0x0051); + pub const CY: LangID = LangID(0x0052); + pub const KM: LangID = LangID(0x0053); + pub const LO: LangID = LangID(0x0054); + pub const MY: LangID = LangID(0x0055); + pub const GL: LangID = LangID(0x0056); + pub const KOK: LangID = LangID(0x0057); + pub const MNI: LangID = LangID(0x0058); + pub const SD: LangID = LangID(0x0059); + pub const SYR: LangID = LangID(0x005A); + pub const SI: LangID = LangID(0x005B); + pub const CHR: LangID = LangID(0x005C); + pub const IU: LangID = LangID(0x005D); + pub const AM: LangID = LangID(0x005E); + pub const TZM: LangID = LangID(0x005F); + pub const KS: LangID = LangID(0x0060); + pub const NE: LangID = LangID(0x0061); + pub const FY: LangID = LangID(0x0062); + pub const PS: LangID = LangID(0x0063); + pub const FIL: LangID = LangID(0x0064); + pub const DV: LangID = LangID(0x0065); + pub const BIN: LangID = LangID(0x0066); + pub const FF: LangID = LangID(0x0067); + pub const HA: LangID = LangID(0x0068); + pub const IBB: LangID = LangID(0x0069); + pub const YO: LangID = LangID(0x006A); + pub const QUZ: LangID = LangID(0x006B); + pub const NSO: LangID = LangID(0x006C); + pub const BA: LangID = LangID(0x006D); + pub const LB: LangID = LangID(0x006E); + pub const KL: LangID = LangID(0x006F); + pub const IG: LangID = LangID(0x0070); + pub const KR: LangID = LangID(0x0071); + pub const OM: LangID = LangID(0x0072); + pub const TI: LangID = LangID(0x0073); + pub const GN: LangID = LangID(0x0074); + pub const HAW: LangID = LangID(0x0075); + pub const LA: LangID = LangID(0x0076); + pub const SO: LangID = LangID(0x0077); + pub const II: LangID = LangID(0x0078); + pub const PAP: LangID = LangID(0x0079); + pub const ARN: LangID = LangID(0x007A); + pub const MOH: LangID = LangID(0x007C); + pub const BR: LangID = LangID(0x007E); + pub const UG: LangID = LangID(0x0080); + pub const MI: LangID = LangID(0x0081); + pub const OC: LangID = LangID(0x0082); + pub const CO: LangID = LangID(0x0083); + pub const GSW: LangID = LangID(0x0084); + pub const SAH: LangID = LangID(0x0085); + pub const QUT: LangID = LangID(0x0086); + pub const RW: LangID = LangID(0x0087); + pub const WO: LangID = LangID(0x0088); + pub const PRS: LangID = LangID(0x008C); + pub const GD: LangID = LangID(0x0091); + pub const KU: LangID = LangID(0x0092); + pub const QUC: LangID = LangID(0x0093); + pub const AR_SA: LangID = LangID(0x0401); + pub const BG_BG: LangID = LangID(0x0402); + pub const CA_ES: LangID = LangID(0x0403); + pub const ZH_TW: LangID = LangID(0x0404); + pub const CS_CZ: LangID = LangID(0x0405); + pub const DA_DK: LangID = LangID(0x0406); + pub const DE_DE: LangID = LangID(0x0407); + pub const EL_GR: LangID = LangID(0x0408); + pub const EN_US: LangID = LangID(0x0409); + pub const ES_ES_TRADNL: LangID = LangID(0x040A); + pub const FI_FI: LangID = LangID(0x040B); + pub const FR_FR: LangID = LangID(0x040C); + pub const HE_IL: LangID = LangID(0x040D); + pub const HU_HU: LangID = LangID(0x040E); + pub const IS_IS: LangID = LangID(0x040F); + pub const IT_IT: LangID = LangID(0x0410); + pub const JA_JP: LangID = LangID(0x0411); + pub const KO_KR: LangID = LangID(0x0412); + pub const NL_NL: LangID = LangID(0x0413); + pub const NB_NO: LangID = LangID(0x0414); + pub const PL_PL: LangID = LangID(0x0415); + pub const PT_BR: LangID = LangID(0x0416); + pub const RM_CH: LangID = LangID(0x0417); + pub const RO_RO: LangID = LangID(0x0418); + pub const RU_RU: LangID = LangID(0x0419); + pub const HR_HR: LangID = LangID(0x041A); + pub const SK_SK: LangID = LangID(0x041B); + pub const SQ_AL: LangID = LangID(0x041C); + pub const SV_SE: LangID = LangID(0x041D); + pub const TH_TH: LangID = LangID(0x041E); + pub const TR_TR: LangID = LangID(0x041F); + pub const UR_PK: LangID = LangID(0x0420); + pub const ID_ID: LangID = LangID(0x0421); + pub const UK_UA: LangID = LangID(0x0422); + pub const BE_BY: LangID = LangID(0x0423); + pub const SL_SI: LangID = LangID(0x0424); + pub const ET_EE: LangID = LangID(0x0425); + pub const LV_LV: LangID = LangID(0x0426); + pub const LT_LT: LangID = LangID(0x0427); + pub const TG_CYRL_TJ: LangID = LangID(0x0428); + pub const FA_IR: LangID = LangID(0x0429); + pub const VI_VN: LangID = LangID(0x042A); + pub const HY_AM: LangID = LangID(0x042B); + pub const AZ_LATN_AZ: LangID = LangID(0x042C); + pub const EU_ES: LangID = LangID(0x042D); + pub const HSB_DE: LangID = LangID(0x042E); + pub const MK_MK: LangID = LangID(0x042F); + pub const ST_ZA: LangID = LangID(0x0430); + pub const TS_ZA: LangID = LangID(0x0431); + pub const TN_ZA: LangID = LangID(0x0432); + pub const VE_ZA: LangID = LangID(0x0433); + pub const XH_ZA: LangID = LangID(0x0434); + pub const ZU_ZA: LangID = LangID(0x0435); + pub const AF_ZA: LangID = LangID(0x0436); + pub const KA_GE: LangID = LangID(0x0437); + pub const FO_FO: LangID = LangID(0x0438); + pub const HI_IN: LangID = LangID(0x0439); + pub const MT_MT: LangID = LangID(0x043A); + pub const SE_NO: LangID = LangID(0x043B); + pub const YI_001: LangID = LangID(0x043D); + pub const MS_MY: LangID = LangID(0x043E); + pub const KK_KZ: LangID = LangID(0x043F); + pub const KY_KG: LangID = LangID(0x0440); + pub const SW_KE: LangID = LangID(0x0441); + pub const TK_TM: LangID = LangID(0x0442); + pub const UZ_LATN_UZ: LangID = LangID(0x0443); + pub const TT_RU: LangID = LangID(0x0444); + pub const BN_IN: LangID = LangID(0x0445); + pub const PA_IN: LangID = LangID(0x0446); + pub const GU_IN: LangID = LangID(0x0447); + pub const OR_IN: LangID = LangID(0x0448); + pub const TA_IN: LangID = LangID(0x0449); + pub const TE_IN: LangID = LangID(0x044A); + pub const KN_IN: LangID = LangID(0x044B); + pub const ML_IN: LangID = LangID(0x044C); + pub const AS_IN: LangID = LangID(0x044D); + pub const MR_IN: LangID = LangID(0x044E); + pub const SA_IN: LangID = LangID(0x044F); + pub const MN_MN: LangID = LangID(0x0450); + pub const BO_CN: LangID = LangID(0x0451); + pub const CY_GB: LangID = LangID(0x0452); + pub const KM_KH: LangID = LangID(0x0453); + pub const LO_LA: LangID = LangID(0x0454); + pub const MY_MM: LangID = LangID(0x0455); + pub const GL_ES: LangID = LangID(0x0456); + pub const KOK_IN: LangID = LangID(0x0457); + pub const MNI_IN: LangID = LangID(0x0458); + pub const SD_DEVA_IN: LangID = LangID(0x0459); + pub const SYR_SY: LangID = LangID(0x045A); + pub const SI_LK: LangID = LangID(0x045B); + pub const CHR_CHER_US: LangID = LangID(0x045C); + pub const IU_CANS_CA: LangID = LangID(0x045D); + pub const AM_ET: LangID = LangID(0x045E); + pub const TZM_ARAB_MA: LangID = LangID(0x045F); + pub const KS_ARAB: LangID = LangID(0x0460); + pub const NE_NP: LangID = LangID(0x0461); + pub const FY_NL: LangID = LangID(0x0462); + pub const PS_AF: LangID = LangID(0x0463); + pub const FIL_PH: LangID = LangID(0x0464); + pub const DV_MV: LangID = LangID(0x0465); + pub const BIN_NG: LangID = LangID(0x0466); + pub const FF_NG__FF_LATN_NG: LangID = LangID(0x0467); + pub const HA_LATN_NG: LangID = LangID(0x0468); + pub const IBB_NG: LangID = LangID(0x0469); + pub const YO_NG: LangID = LangID(0x046A); + pub const QUZ_BO: LangID = LangID(0x046B); + pub const NSO_ZA: LangID = LangID(0x046C); + pub const BA_RU: LangID = LangID(0x046D); + pub const LB_LU: LangID = LangID(0x046E); + pub const KL_GL: LangID = LangID(0x046F); + pub const IG_NG: LangID = LangID(0x0470); + pub const KR_LATN_NG: LangID = LangID(0x0471); + pub const OM_ET: LangID = LangID(0x0472); + pub const TI_ET: LangID = LangID(0x0473); + pub const GN_PY: LangID = LangID(0x0474); + pub const HAW_US: LangID = LangID(0x0475); + pub const LA_VA: LangID = LangID(0x0476); + pub const SO_SO: LangID = LangID(0x0477); + pub const II_CN: LangID = LangID(0x0478); + pub const PAP_029: LangID = LangID(0x0479); + pub const ARN_CL: LangID = LangID(0x047A); + pub const MOH_CA: LangID = LangID(0x047C); + pub const BR_FR: LangID = LangID(0x047E); + pub const UG_CN: LangID = LangID(0x0480); + pub const MI_NZ: LangID = LangID(0x0481); + pub const OC_FR: LangID = LangID(0x0482); + pub const CO_FR: LangID = LangID(0x0483); + pub const GSW_FR: LangID = LangID(0x0484); + pub const SAH_RU: LangID = LangID(0x0485); + pub const QUT_GT: LangID = LangID(0x0486); + pub const RW_RW: LangID = LangID(0x0487); + pub const WO_SN: LangID = LangID(0x0488); + pub const PRS_AF: LangID = LangID(0x048C); + pub const PLT_MG: LangID = LangID(0x048D); + pub const ZH_YUE_HK: LangID = LangID(0x048E); + pub const TDD_TALE_CN: LangID = LangID(0x048F); + pub const KHB_TALU_CN: LangID = LangID(0x0490); + pub const GD_GB: LangID = LangID(0x0491); + pub const KU_ARAB_IQ: LangID = LangID(0x0492); + pub const QUC_CO: LangID = LangID(0x0493); + pub const QPS_PLOC: LangID = LangID(0x0501); + pub const QPS_PLOCA: LangID = LangID(0x05FE); + pub const AR_IQ: LangID = LangID(0x0801); + pub const CA_ES_VALENCIA: LangID = LangID(0x0803); + pub const ZH_CN: LangID = LangID(0x0804); + pub const DE_CH: LangID = LangID(0x0807); + pub const EN_GB: LangID = LangID(0x0809); + pub const ES_MX: LangID = LangID(0x080A); + pub const FR_BE: LangID = LangID(0x080C); + pub const IT_CH: LangID = LangID(0x0810); + pub const JA_PLOC_JP: LangID = LangID(0x0811); + pub const NL_BE: LangID = LangID(0x0813); + pub const NN_NO: LangID = LangID(0x0814); + pub const PT_PT: LangID = LangID(0x0816); + pub const RO_MD: LangID = LangID(0x0818); + pub const RU_MD: LangID = LangID(0x0819); + pub const SR_LATN_CS: LangID = LangID(0x081A); + pub const SV_FI: LangID = LangID(0x081D); + pub const UR_IN: LangID = LangID(0x0820); + pub const AZ_CYRL_AZ: LangID = LangID(0x082C); + pub const DSB_DE: LangID = LangID(0x082E); + pub const TN_BW: LangID = LangID(0x0832); + pub const SE_SE: LangID = LangID(0x083B); + pub const GA_IE: LangID = LangID(0x083C); + pub const MS_BN: LangID = LangID(0x083E); + pub const KK_LATN_KZ: LangID = LangID(0x083F); + pub const UZ_CYRL_UZ: LangID = LangID(0x0843); + pub const BN_BD: LangID = LangID(0x0845); + pub const PA_ARAB_PK: LangID = LangID(0x0846); + pub const TA_LK: LangID = LangID(0x0849); + pub const MN_MONG_CN: LangID = LangID(0x0850); + pub const BO_BT: LangID = LangID(0x0851); + pub const SD_ARAB_PK: LangID = LangID(0x0859); + pub const IU_LATN_CA: LangID = LangID(0x085D); + pub const TZM_LATN_DZ: LangID = LangID(0x085F); + pub const KS_DEVA_IN: LangID = LangID(0x0860); + pub const NE_IN: LangID = LangID(0x0861); + pub const FF_LATN_SN: LangID = LangID(0x0867); + pub const QUZ_EC: LangID = LangID(0x086B); + pub const TI_ER: LangID = LangID(0x0873); + pub const QPS_PLOCM: LangID = LangID(0x09FF); + pub const LOCALE_CUSTOM_USER_DEFAULT: LangID = LangID(0x0C00); + pub const AR_EG: LangID = LangID(0x0C01); + pub const ZH_HK: LangID = LangID(0x0C04); + pub const DE_AT: LangID = LangID(0x0C07); + pub const EN_AU: LangID = LangID(0x0C09); + pub const ES_ES: LangID = LangID(0x0C0A); + pub const FR_CA: LangID = LangID(0x0C0C); + pub const SR_CYRL_CS: LangID = LangID(0x0C1A); + pub const SE_FI: LangID = LangID(0x0C3B); + pub const MN_MONG_MN: LangID = LangID(0x0C50); + pub const DZ_BT: LangID = LangID(0x0C51); + pub const TMZ_MA: LangID = LangID(0x0C5F); + pub const QUZ_PE: LangID = LangID(0x0C6B); + pub const LOCALE_CUSTOM_UNSPECIFIED: LangID = LangID(0x1000); + pub const AR_LY: LangID = LangID(0x1001); + pub const ZH_SG: LangID = LangID(0x1004); + pub const DE_LU: LangID = LangID(0x1007); + pub const EN_CA: LangID = LangID(0x1009); + pub const ES_GT: LangID = LangID(0x100A); + pub const FR_CH: LangID = LangID(0x100C); + pub const HR_BA: LangID = LangID(0x101A); + pub const SMJ_NO: LangID = LangID(0x103B); + pub const TZM_TFNG_MA: LangID = LangID(0x105F); + pub const AR_DZ: LangID = LangID(0x1401); + pub const ZH_MO: LangID = LangID(0x1404); + pub const DE_LI: LangID = LangID(0x1407); + pub const EN_NZ: LangID = LangID(0x1409); + pub const ES_CR: LangID = LangID(0x140A); + pub const FR_LU: LangID = LangID(0x140C); + pub const BS_LATN_BA: LangID = LangID(0x141A); + pub const SMJ_SE: LangID = LangID(0x143B); + pub const AR_MA: LangID = LangID(0x1801); + pub const EN_IE: LangID = LangID(0x1809); + pub const ES_PA: LangID = LangID(0x180A); + pub const FR_MC: LangID = LangID(0x180C); + pub const SR_LATN_BA: LangID = LangID(0x181A); + pub const SMA_NO: LangID = LangID(0x183B); + pub const AR_TN: LangID = LangID(0x1C01); + pub const EN_ZA: LangID = LangID(0x1C09); + pub const ES_DO: LangID = LangID(0x1C0A); + pub const FR_029: LangID = LangID(0x1C0C); + pub const SR_CYRL_BA: LangID = LangID(0x1C1A); + pub const SMA_SE: LangID = LangID(0x1C3B); + pub const AR_OM: LangID = LangID(0x2001); + pub const EN_JM: LangID = LangID(0x2009); + pub const ES_VE: LangID = LangID(0x200A); + pub const FR_RE: LangID = LangID(0x200C); + pub const BS_CYRL_BA: LangID = LangID(0x201A); + pub const SMS_FI: LangID = LangID(0x203B); + pub const AR_YE: LangID = LangID(0x2401); + pub const EN_029: LangID = LangID(0x2409); + pub const ES_CO: LangID = LangID(0x240A); + pub const FR_CD: LangID = LangID(0x240C); + pub const SR_LATN_RS: LangID = LangID(0x241A); + pub const SMN_FI: LangID = LangID(0x243B); + pub const AR_SY: LangID = LangID(0x2801); + pub const EN_BZ: LangID = LangID(0x2809); + pub const ES_PE: LangID = LangID(0x280A); + pub const FR_SN: LangID = LangID(0x280C); + pub const SR_CYRL_RS: LangID = LangID(0x281A); + pub const AR_JO: LangID = LangID(0x2C01); + pub const EN_TT: LangID = LangID(0x2C09); + pub const ES_AR: LangID = LangID(0x2C0A); + pub const FR_CM: LangID = LangID(0x2C0C); + pub const SR_LATN_ME: LangID = LangID(0x2C1A); + pub const AR_LB: LangID = LangID(0x3001); + pub const EN_ZW: LangID = LangID(0x3009); + pub const ES_EC: LangID = LangID(0x300A); + pub const FR_CI: LangID = LangID(0x300C); + pub const SR_CYRL_ME: LangID = LangID(0x301A); + pub const AR_KW: LangID = LangID(0x3401); + pub const EN_PH: LangID = LangID(0x3409); + pub const ES_CL: LangID = LangID(0x340A); + pub const FR_ML: LangID = LangID(0x340C); + pub const AR_AE: LangID = LangID(0x3801); + pub const EN_ID: LangID = LangID(0x3809); + pub const ES_UY: LangID = LangID(0x380A); + pub const FR_MA: LangID = LangID(0x380C); + pub const AR_BH: LangID = LangID(0x3C01); + pub const EN_HK: LangID = LangID(0x3C09); + pub const ES_PY: LangID = LangID(0x3C0A); + pub const FR_HT: LangID = LangID(0x3C0C); + pub const AR_QA: LangID = LangID(0x4001); + pub const EN_IN: LangID = LangID(0x4009); + pub const ES_BO: LangID = LangID(0x400A); + pub const AR_PLOC_SA: LangID = LangID(0x4401); + pub const EN_MY: LangID = LangID(0x4409); + pub const ES_SV: LangID = LangID(0x440A); + pub const AR_145: LangID = LangID(0x4801); + pub const EN_SG: LangID = LangID(0x4809); + pub const ES_HN: LangID = LangID(0x480A); + pub const EN_AE: LangID = LangID(0x4C09); + pub const ES_NI: LangID = LangID(0x4C0A); + pub const EN_BH: LangID = LangID(0x5009); + pub const ES_PR: LangID = LangID(0x500A); + pub const EN_EG: LangID = LangID(0x5409); + pub const ES_US: LangID = LangID(0x540A); + pub const EN_JO: LangID = LangID(0x5809); + pub const ES_419: LangID = LangID(0x580A); + pub const EN_KW: LangID = LangID(0x5C09); + pub const ES_CU: LangID = LangID(0x5C0A); + pub const EN_TR: LangID = LangID(0x6009); + pub const EN_YE: LangID = LangID(0x6409); + pub const BS_CYRL: LangID = LangID(0x641A); + pub const BS_LATN: LangID = LangID(0x681A); + pub const SR_CYRL: LangID = LangID(0x6C1A); + pub const SR_LATN: LangID = LangID(0x701A); + pub const SMN: LangID = LangID(0x703B); + pub const AZ_CYRL: LangID = LangID(0x742C); + pub const SMS: LangID = LangID(0x743B); + pub const ZH: LangID = LangID(0x7804); + pub const NN: LangID = LangID(0x7814); + pub const BS: LangID = LangID(0x781A); + pub const AZ_LATN: LangID = LangID(0x782C); + pub const SMA: LangID = LangID(0x783B); + pub const KK_CYRL: LangID = LangID(0x783F); + pub const UZ_CYRL: LangID = LangID(0x7843); + pub const MN_CYRL: LangID = LangID(0x7850); + pub const IU_CANS: LangID = LangID(0x785D); + pub const TZM_TFNG: LangID = LangID(0x785F); + pub const ZH_HANT: LangID = LangID(0x7C04); + pub const NB: LangID = LangID(0x7C14); + pub const SR: LangID = LangID(0x7C1A); + pub const TG_CYRL: LangID = LangID(0x7C28); + pub const DSB: LangID = LangID(0x7C2E); + pub const SMJ: LangID = LangID(0x7C3B); + pub const KK_LATN: LangID = LangID(0x7C3F); + pub const UZ_LATN: LangID = LangID(0x7C43); + pub const PA_ARAB: LangID = LangID(0x7C46); + pub const MN_MONG: LangID = LangID(0x7C50); + pub const SD_ARAB: LangID = LangID(0x7C59); + pub const CHR_CHER: LangID = LangID(0x7C5C); + pub const IU_LATN: LangID = LangID(0x7C5D); + pub const TZM_LATN: LangID = LangID(0x7C5F); + pub const FF_LATN: LangID = LangID(0x7C67); + pub const HA_LATN: LangID = LangID(0x7C68); + pub const KU_ARAB: LangID = LangID(0x7C92); + pub const FR_015: LangID = LangID(0xE40C); +} diff --git a/src/device.rs b/src/device.rs index b1eb203..e5b49df 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,9 +1,9 @@ -use crate::bus::{PollResult, StringIndex, UsbBus, UsbBusAllocator}; +use crate::bus::{InterfaceNumber, PollResult, StringIndex, UsbBus, UsbBusAllocator}; use crate::class::{ControlIn, ControlOut, UsbClass}; use crate::control; use crate::control_pipe::ControlPipe; -use crate::descriptor::{descriptor_type, lang_id, BosWriter, DescriptorWriter}; -pub use crate::device_builder::{UsbDeviceBuilder, UsbVidPid}; +use crate::descriptor::{descriptor_type, lang_id::LangID, BosWriter, DescriptorWriter}; +pub use crate::device_builder::{StringDescriptors, UsbDeviceBuilder, UsbVidPid}; use crate::endpoint::{EndpointAddress, EndpointType}; use crate::{Result, UsbDirection}; @@ -30,6 +30,18 @@ pub enum UsbDeviceState { // Maximum number of endpoints in one direction. Specified by the USB specification. const MAX_ENDPOINTS: usize = 16; +/// Usb spec revision. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u16)] +pub enum UsbRev { + /// USB 2.0 compliance + Usb200 = 0x200, + /// USB 2.1 compliance. + /// + /// Typically adds support for BOS requests. + Usb210 = 0x210, +} + /// A USB device consisting of one or more device classes. pub struct UsbDevice<'a, B: UsbBus> { bus: &'a B, @@ -49,10 +61,9 @@ pub(crate) struct Config<'a> { pub max_packet_size_0: u8, pub vendor_id: u16, pub product_id: u16, + pub usb_rev: UsbRev, pub device_release: u16, - pub manufacturer: Option<&'a str>, - pub product: Option<&'a str>, - pub serial_number: Option<&'a str>, + pub string_descriptors: heapless::Vec, 16>, pub self_powered: bool, pub supports_remote_wakeup: bool, pub composite_with_iads: bool, @@ -171,6 +182,7 @@ impl UsbDevice<'_, B> { } _ => { self.bus.resume(); + self.device_state = self .suspended_device_state .expect("Unknown state before suspend"); @@ -192,39 +204,71 @@ impl UsbDevice<'_, B> { // Pending events for endpoint 0? if (eps & 1) != 0 { - // Handle EP0-IN conditions first. When both EP0-IN and EP0-OUT have completed, - // it is possible that EP0-OUT is a zero-sized out packet to complete the STATUS - // phase of the control transfer. We have to process EP0-IN first to update our - // internal state properly. - if (ep_in_complete & 1) != 0 { - let completed = self.control.handle_in_complete(); - - if !B::QUIRK_SET_ADDRESS_BEFORE_STATUS - && completed - && self.pending_address != 0 - { - self.bus.set_device_address(self.pending_address); - self.pending_address = 0; - - self.device_state = UsbDeviceState::Addressed; - } - } + usb_debug!( + "EP0: setup={}, in_complete={}, out={}", + ep_setup & 1, + ep_in_complete & 1, + ep_out & 1 + ); let req = if (ep_setup & 1) != 0 { self.control.handle_setup() } else if (ep_out & 1) != 0 { - self.control.handle_out() + match self.control.handle_out() { + Ok(req) => req, + Err(_err) => { + // TODO: Propagate error out of `poll()` + usb_debug!("Failed to handle EP0: {:?}", _err); + None + } + } } else { None }; match req { Some(req) if req.direction == UsbDirection::In => { - self.control_in(classes, req) + if let Err(_err) = self.control_in(classes, req) { + // TODO: Propagate error out of `poll()` + usb_debug!("Failed to handle input control request: {:?}", _err); + } } Some(req) if req.direction == UsbDirection::Out => { - self.control_out(classes, req) + if let Err(_err) = self.control_out(classes, req) { + // TODO: Propagate error out of `poll()` + usb_debug!("Failed to handle output control request: {:?}", _err); + } } + + None if ((ep_in_complete & 1) != 0) => { + // We only handle EP0-IN completion if there's no other request being + // processed. EP0-IN tokens may be issued due to completed STATUS + // phases of the control transfer. If we just got a SETUP packet or + // an OUT token, we can safely ignore the IN-COMPLETE indication and + // continue with the next transfer. + let completed = match self.control.handle_in_complete() { + Ok(completed) => completed, + Err(_err) => { + // TODO: Propagate this out of `poll()` + usb_debug!( + "Failed to process control-input complete: {:?}", + _err + ); + false + } + }; + + if !B::QUIRK_SET_ADDRESS_BEFORE_STATUS + && completed + && self.pending_address != 0 + { + self.bus.set_device_address(self.pending_address); + self.pending_address = 0; + + self.device_state = UsbDeviceState::Addressed; + } + } + _ => (), }; @@ -238,18 +282,21 @@ impl UsbDevice<'_, B> { for i in 1..MAX_ENDPOINTS { if (ep_setup & bit) != 0 { for cls in classes.iter_mut() { + usb_trace!("Handling EP{}-SETUP", i); cls.endpoint_setup(EndpointAddress::from_parts( i, UsbDirection::Out, )); } } else if (ep_out & bit) != 0 { + usb_trace!("Handling EP{}-OUT", i); for cls in classes.iter_mut() { cls.endpoint_out(EndpointAddress::from_parts(i, UsbDirection::Out)); } } if (ep_in_complete & bit) != 0 { + usb_trace!("Handling EP{}-IN", i); for cls in classes.iter_mut() { cls.endpoint_in_complete(EndpointAddress::from_parts( i, @@ -277,6 +324,7 @@ impl UsbDevice<'_, B> { } PollResult::Resume => {} PollResult::Suspend => { + usb_debug!("Suspending bus"); self.bus.suspend(); self.suspended_device_state = Some(self.device_state); self.device_state = UsbDeviceState::Suspend; @@ -286,14 +334,14 @@ impl UsbDevice<'_, B> { false } - fn control_in(&mut self, classes: &mut ClassList<'_, B>, req: control::Request) { + fn control_in(&mut self, classes: &mut ClassList<'_, B>, req: control::Request) -> Result<()> { use crate::control::{Recipient, Request}; for cls in classes.iter_mut() { cls.control_in(ControlIn::new(&mut self.control, &req)); if !self.control.waiting_for_response() { - return; + return Ok(()); } } @@ -302,6 +350,7 @@ impl UsbDevice<'_, B> { match (req.recipient, req.request) { (Recipient::Device, Request::GET_STATUS) => { + usb_trace!("Processing Device::GetStatus"); let status: u16 = if self.self_powered { 0x0001 } else { 0x0000 } | if self.remote_wakeup_enabled { 0x0002 @@ -309,16 +358,18 @@ impl UsbDevice<'_, B> { 0x0000 }; - xfer.accept_with(&status.to_le_bytes()).ok(); + xfer.accept_with(&status.to_le_bytes())?; } (Recipient::Interface, Request::GET_STATUS) => { + usb_trace!("Processing Interface::GetStatus"); let status: u16 = 0x0000; - xfer.accept_with(&status.to_le_bytes()).ok(); + xfer.accept_with(&status.to_le_bytes())?; } (Recipient::Endpoint, Request::GET_STATUS) => { + usb_trace!("Processing EP::GetStatus"); let ep_addr = ((req.index as u8) & 0x8f).into(); let status: u16 = if self.bus.is_stalled(ep_addr) { @@ -327,45 +378,64 @@ impl UsbDevice<'_, B> { 0x0000 }; - xfer.accept_with(&status.to_le_bytes()).ok(); + xfer.accept_with(&status.to_le_bytes())?; } (Recipient::Device, Request::GET_DESCRIPTOR) => { - UsbDevice::get_descriptor(&self.config, classes, xfer) + usb_trace!("Processing Device::GetDescriptor"); + UsbDevice::get_descriptor(&self.config, classes, xfer)?; } (Recipient::Device, Request::GET_CONFIGURATION) => { + usb_trace!("Processing Device::GetConfiguration"); let config = match self.device_state { UsbDeviceState::Configured => CONFIGURATION_VALUE, _ => CONFIGURATION_NONE, }; - xfer.accept_with(&config.to_le_bytes()).ok(); + xfer.accept_with(&config.to_le_bytes())?; } (Recipient::Interface, Request::GET_INTERFACE) => { - // TODO: change when alternate settings are implemented - xfer.accept_with(&DEFAULT_ALTERNATE_SETTING.to_le_bytes()) - .ok(); + usb_trace!("Processing Interface::GetInterface"); + // Reject interface numbers bigger than 255 + if req.index > core::u8::MAX.into() { + return xfer.reject(); + } + + // Ask class implementations, whether they know the alternate setting + // of the interface in question + for cls in classes { + if let Some(setting) = cls.get_alt_setting(InterfaceNumber(req.index as u8)) + { + return xfer.accept_with(&setting.to_le_bytes()); + } + } + + // If no class returned an alternate setting, return the default value + xfer.accept_with(&DEFAULT_ALTERNATE_SETTING.to_le_bytes())?; } - _ => (), + _ => {} }; } if self.control.waiting_for_response() { - self.control.reject().ok(); + usb_debug!("Rejecting control transfer because we were waiting for a response"); + self.control.reject()?; } + + Ok(()) } - fn control_out(&mut self, classes: &mut ClassList<'_, B>, req: control::Request) { + fn control_out(&mut self, classes: &mut ClassList<'_, B>, req: control::Request) -> Result<()> { use crate::control::{Recipient, Request}; - for cls in classes { + for cls in classes.iter_mut() { cls.control_out(ControlOut::new(&mut self.control, &req)); if !self.control.waiting_for_response() { - return; + return Ok(()); } } @@ -382,14 +452,16 @@ impl UsbDevice<'_, B> { Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP, ) => { + usb_debug!("Remote wakeup disabled"); self.remote_wakeup_enabled = false; - xfer.accept().ok(); + xfer.accept()?; } (Recipient::Endpoint, Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + usb_debug!("EP{} halt removed", req.index & 0x8f); self.bus .set_stalled(((req.index as u8) & 0x8f).into(), false); - xfer.accept().ok(); + xfer.accept()?; } ( @@ -397,61 +469,94 @@ impl UsbDevice<'_, B> { Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP, ) => { + usb_debug!("Remote wakeup enabled"); self.remote_wakeup_enabled = true; - xfer.accept().ok(); + xfer.accept()?; } (Recipient::Endpoint, Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + usb_debug!("EP{} halted", req.index & 0x8f); self.bus .set_stalled(((req.index as u8) & 0x8f).into(), true); - xfer.accept().ok(); + xfer.accept()?; } (Recipient::Device, Request::SET_ADDRESS, 1..=127) => { + usb_debug!("Setting device address to {}", req.value); if B::QUIRK_SET_ADDRESS_BEFORE_STATUS { self.bus.set_device_address(req.value as u8); self.device_state = UsbDeviceState::Addressed; } else { self.pending_address = req.value as u8; } - xfer.accept().ok(); + xfer.accept()?; } (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { + usb_debug!("Device configured"); self.device_state = UsbDeviceState::Configured; - xfer.accept().ok(); + xfer.accept()?; } (Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => { + usb_debug!("Device deconfigured"); match self.device_state { UsbDeviceState::Default => { - xfer.reject().ok(); + xfer.accept()?; } _ => { self.device_state = UsbDeviceState::Addressed; - xfer.accept().ok(); + xfer.accept()?; } } } - (Recipient::Interface, Request::SET_INTERFACE, DEFAULT_ALTERNATE_SETTING_U16) => { - // TODO: do something when alternate settings are implemented - xfer.accept().ok(); + (Recipient::Interface, Request::SET_INTERFACE, alt_setting) => { + // Reject interface numbers and alt settings bigger than 255 + if req.index > core::u8::MAX.into() || alt_setting > core::u8::MAX.into() { + xfer.reject()?; + return Ok(()); + } + + // Ask class implementations, whether they accept the alternate interface setting. + for cls in classes { + if cls.set_alt_setting(InterfaceNumber(req.index as u8), alt_setting as u8) + { + xfer.accept()?; + return Ok(()); + } + } + + // Default behaviour, if no class implementation accepted the alternate setting. + if alt_setting == DEFAULT_ALTERNATE_SETTING_U16 { + usb_debug!("Accepting unused alternate settings"); + xfer.accept()?; + } else { + usb_debug!("Rejecting unused alternate settings"); + xfer.reject()?; + } } _ => { - xfer.reject().ok(); - return; + xfer.reject()?; + return Ok(()); } } } if self.control.waiting_for_response() { - self.control.reject().ok(); + usb_debug!("Rejecting control transfer due to waiting response"); + self.control.reject()?; } + + Ok(()) } - fn get_descriptor(config: &Config, classes: &mut ClassList<'_, B>, xfer: ControlIn) { + fn get_descriptor( + config: &Config, + classes: &mut ClassList<'_, B>, + xfer: ControlIn, + ) -> Result<()> { let req = *xfer.request(); let (dtype, index) = req.descriptor_type_index(); @@ -459,17 +564,18 @@ impl UsbDevice<'_, B> { fn accept_writer( xfer: ControlIn, f: impl FnOnce(&mut DescriptorWriter) -> Result<()>, - ) { + ) -> Result<()> { xfer.accept(|buf| { let mut writer = DescriptorWriter::new(buf); f(&mut writer)?; Ok(writer.position()) - }) - .ok(); + })?; + + Ok(()) } match dtype { - descriptor_type::BOS => accept_writer(xfer, |w| { + descriptor_type::BOS if config.usb_rev > UsbRev::Usb200 => accept_writer(xfer, |w| { let mut bw = BosWriter::new(w); bw.bos()?; @@ -480,9 +586,9 @@ impl UsbDevice<'_, B> { bw.end_bos(); Ok(()) - }), + })?, - descriptor_type::DEVICE => accept_writer(xfer, |w| w.device(config)), + descriptor_type::DEVICE => accept_writer(xfer, |w| w.device(config))?, descriptor_type::CONFIGURATION => accept_writer(xfer, |w| { w.configuration(config)?; @@ -495,41 +601,72 @@ impl UsbDevice<'_, B> { w.end_configuration(); Ok(()) - }), - - descriptor_type::STRING => { - if index == 0 { + })?, + + descriptor_type::STRING => match index { + // first STRING Request + 0 => { + let mut lang_id_bytes = [0u8; 32]; + for (lang, buf) in config + .string_descriptors + .iter() + .zip(lang_id_bytes.chunks_exact_mut(2)) + { + buf.copy_from_slice(&u16::from(lang.id).to_le_bytes()); + } accept_writer(xfer, |w| { - w.write(descriptor_type::STRING, &lang_id::ENGLISH_US.to_le_bytes()) - }) - } else { - let s = match index { - 1 => config.manufacturer, - 2 => config.product, - 3 => config.serial_number, + w.write( + descriptor_type::STRING, + &lang_id_bytes[..config.string_descriptors.len() * 2], + ) + })?; + } + + // rest STRING Requests + _ => { + let lang_id = LangID::from(req.index); + + let string = match index { + // Manufacturer, product, and serial are handled directly here. + 1..=3 => { + let Some(lang) = config + .string_descriptors + .iter() + .find(|lang| lang.id == lang_id) + else { + xfer.reject()?; + return Ok(()); + }; + + match index { + 1 => lang.manufacturer, + 2 => lang.product, + 3 => lang.serial, + _ => unreachable!(), + } + } _ => { let index = StringIndex::new(index); - let lang_id = req.index; - classes .iter() - .filter_map(|cls| cls.get_string(index, lang_id)) - .next() + .find_map(|cls| cls.get_string(index, lang_id)) } }; - if let Some(s) = s { - accept_writer(xfer, |w| w.string(s)); + if let Some(string_descriptor) = string { + accept_writer(xfer, |w| w.string(string_descriptor))?; } else { - xfer.reject().ok(); + xfer.reject()?; } } - } + }, _ => { - xfer.reject().ok(); + xfer.reject()?; } - } + }; + + Ok(()) } fn reset(&mut self, classes: &mut ClassList<'_, B>) { diff --git a/src/device_builder.rs b/src/device_builder.rs index 944a45f..89de6cf 100644 --- a/src/device_builder.rs +++ b/src/device_builder.rs @@ -1,5 +1,6 @@ use crate::bus::{UsbBus, UsbBusAllocator}; -use crate::device::{Config, UsbDevice}; +use crate::descriptor::lang_id::LangID; +use crate::device::{Config, UsbDevice, UsbRev}; /// A USB vendor ID and product ID pair. pub struct UsbVidPid(pub u16, pub u16); @@ -22,6 +23,63 @@ macro_rules! builder_fields { } } +#[derive(Copy, Clone, Debug, PartialEq)] +/// Error type for the USB device builder +pub enum BuilderError { + /// String descriptors were provided in more languages than are supported + TooManyLanguages, + /// Control endpoint can only be 8, 16, 32, or 64 byte max packet size + InvalidPacketSize, + /// Configuration specifies higher USB power draw than allowed + PowerTooHigh, +} + +/// Provides basic string descriptors about the device, including the manufacturer, product name, +/// and serial number of the device in a specified language. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct StringDescriptors<'a> { + pub(crate) id: LangID, + pub(crate) serial: Option<&'a str>, + pub(crate) product: Option<&'a str>, + pub(crate) manufacturer: Option<&'a str>, +} + +impl<'a> Default for StringDescriptors<'a> { + fn default() -> Self { + Self::new(LangID::EN_US) + } +} + +impl<'a> StringDescriptors<'a> { + /// Create a new descriptor list with the provided language. + pub fn new(lang_id: LangID) -> Self { + Self { + id: lang_id, + serial: None, + product: None, + manufacturer: None, + } + } + + /// Specify the serial number for this language. + pub fn serial_number(mut self, serial: &'a str) -> Self { + self.serial.replace(serial); + self + } + + /// Specify the manufacturer name for this language. + pub fn manufacturer(mut self, manufacturer: &'a str) -> Self { + self.manufacturer.replace(manufacturer); + self + } + + /// Specify the product name for this language. + pub fn product(mut self, product: &'a str) -> Self { + self.product.replace(product); + self + } +} + impl<'a, B: UsbBus> UsbDeviceBuilder<'a, B> { /// Creates a builder for constructing a new [`UsbDevice`]. pub fn new(alloc: &'a UsbBusAllocator, vid_pid: UsbVidPid) -> UsbDeviceBuilder<'a, B> { @@ -34,10 +92,9 @@ impl<'a, B: UsbBus> UsbDeviceBuilder<'a, B> { max_packet_size_0: 8, vendor_id: vid_pid.0, product_id: vid_pid.1, + usb_rev: UsbRev::Usb210, device_release: 0x0010, - manufacturer: None, - product: None, - serial_number: None, + string_descriptors: heapless::Vec::new(), self_powered: false, supports_remote_wakeup: false, composite_with_iads: false, @@ -87,6 +144,11 @@ impl<'a, B: UsbBus> UsbDeviceBuilder<'a, B> { /// /// Default: `false` supports_remote_wakeup: bool, + + /// Sets which Usb 2 revision to comply to. + /// + /// Default: `UsbRev::Usb210` + usb_rev: UsbRev, } /// Configures the device as a composite device with interface association descriptors. @@ -100,28 +162,17 @@ impl<'a, B: UsbBus> UsbDeviceBuilder<'a, B> { self } - /// Sets the manufacturer name string descriptor. - /// - /// Default: (none) - pub fn manufacturer(mut self, manufacturer: &'a str) -> Self { - self.config.manufacturer = Some(manufacturer); - self - } - - /// Sets the product name string descriptor. + /// Specify the strings for the device. /// - /// Default: (none) - pub fn product(mut self, product: &'a str) -> Self { - self.config.product = Some(product); - self - } - - /// Sets the serial number string descriptor. - /// - /// Default: (none) - pub fn serial_number(mut self, serial_number: &'a str) -> Self { - self.config.serial_number = Some(serial_number); - self + /// # Note + /// Up to 16 languages may be provided. + pub fn strings(mut self, descriptors: &[StringDescriptors<'a>]) -> Result { + // The 16 language limit comes from the size of the buffer used to provide the list of + // language descriptors to the host. + self.config.string_descriptors = + heapless::Vec::from_slice(descriptors).map_err(|_| BuilderError::TooManyLanguages)?; + + Ok(self) } /// Sets the maximum packet size in bytes for the control endpoint 0. @@ -131,14 +182,14 @@ impl<'a, B: UsbBus> UsbDeviceBuilder<'a, B> { /// which case using a larger packet size may be more efficient. /// /// Default: 8 bytes - pub fn max_packet_size_0(mut self, max_packet_size_0: u8) -> Self { + pub fn max_packet_size_0(mut self, max_packet_size_0: u8) -> Result { match max_packet_size_0 { 8 | 16 | 32 | 64 => {} - _ => panic!("invalid max_packet_size_0"), + _ => return Err(BuilderError::InvalidPacketSize), } self.config.max_packet_size_0 = max_packet_size_0; - self + Ok(self) } /// Sets the maximum current drawn from the USB bus by the device in milliamps. @@ -149,12 +200,12 @@ impl<'a, B: UsbBus> UsbDeviceBuilder<'a, B> { /// See also: `self_powered` /// /// Default: 100mA - pub fn max_power(mut self, max_power_ma: usize) -> Self { + pub fn max_power(mut self, max_power_ma: usize) -> Result { if max_power_ma > 500 { - panic!("max_power is too much") + return Err(BuilderError::PowerTooHigh); } self.config.max_power = (max_power_ma / 2) as u8; - self + Ok(self) } } diff --git a/src/endpoint.rs b/src/endpoint.rs index d761ed1..2b31eec 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -1,7 +1,7 @@ use crate::bus::UsbBus; use crate::{Result, UsbDirection}; use core::marker::PhantomData; -use core::sync::atomic::{AtomicPtr, Ordering}; +use portable_atomic::{AtomicPtr, Ordering}; /// Trait for endpoint type markers. pub trait EndpointDirection { @@ -29,21 +29,81 @@ pub type EndpointOut<'a, B> = Endpoint<'a, B, Out>; /// A device-to-host (IN) endpoint. pub type EndpointIn<'a, B> = Endpoint<'a, B, In>; -/// 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)] +/// Isochronous transfers employ one of three synchronization schemes. See USB 2.0 spec 5.12.4.1. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum IsochronousSynchronizationType { + /// Synchronization is not implemented for this endpoint. + NoSynchronization, + /// Source and Sink sample clocks are free running. + Asynchronous, + /// Source sample clock is locked to Sink, Sink sample clock is locked to data flow. + Adaptive, + /// Source and Sink sample clocks are locked to USB SOF. + Synchronous, +} + +/// Intended use of an isochronous endpoint, see USB 2.0 spec sections 5.12 and 9.6.6. +/// Associations between data and feedback endpoints are described in section 9.6.6. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum IsochronousUsageType { + /// Endpoint is used for isochronous data. + Data, + /// Feedback for synchronization. + Feedback, + /// Endpoint is data and provides implicit feedback for synchronization. + ImplicitFeedbackData, +} + +/// USB endpoint transfer type. #[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, + Control, + /// Isochronous endpoint. Used for time-critical unreliable data. + /// + /// See USB 2.0 spec section 5.12 "Special Considerations for Isochronous Transfers" + Isochronous { + /// Synchronization model used for the data stream that this endpoint relates to. + synchronization: IsochronousSynchronizationType, + /// Endpoint's role in the synchronization model selected by [Self::Isochronous::synchronization]. + usage: IsochronousUsageType, + }, /// Bulk endpoint. Used for large amounts of best-effort reliable data. - Bulk = 0b10, + Bulk, /// Interrupt endpoint. Used for small amounts of time-critical reliable data. - Interrupt = 0b11, + Interrupt, +} + +impl EndpointType { + /// Format EndpointType for use in bmAttributes transfer type field USB 2.0 spec section 9.6.6 + pub fn to_bm_attributes(&self) -> u8 { + match self { + EndpointType::Control => 0b00, + EndpointType::Isochronous { + synchronization, + usage, + } => { + let sync_bits = match synchronization { + IsochronousSynchronizationType::NoSynchronization => 0b00, + IsochronousSynchronizationType::Asynchronous => 0b01, + IsochronousSynchronizationType::Adaptive => 0b10, + IsochronousSynchronizationType::Synchronous => 0b11, + }; + let usage_bits = match usage { + IsochronousUsageType::Data => 0b00, + IsochronousUsageType::Feedback => 0b01, + IsochronousUsageType::ImplicitFeedbackData => 0b10, + }; + (usage_bits << 4) | (sync_bits << 2) | 0b01 + } + EndpointType::Bulk => 0b10, + EndpointType::Interrupt => 0b11, + } + } } /// Handle for a USB endpoint. The endpoint direction is constrained by the `D` type argument, which diff --git a/src/lib.rs b/src/lib.rs index a8ea2cb..b33c4b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,8 +36,11 @@ #![no_std] #![warn(missing_docs)] +#[macro_use] +mod macros; + /// A USB stack error. -#[derive(Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum UsbError { /// An operation would block because the device is currently busy or there is no data available. @@ -149,7 +152,9 @@ pub mod endpoint; /// // product name. If using an existing class, remember to check the class crate documentation /// // for correct values. /// let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x5824, 0x27dd)) -/// .product("Serial port") +/// .strings(&[StringDescriptors::new(LangID::EN) +/// .product("Serial port")]) +/// .expect("Failed to set strings") /// .device_class(usb_serial::DEVICE_CLASS) /// .build(); /// @@ -169,6 +174,8 @@ pub mod device; /// Creating USB descriptors pub mod descriptor; +pub use descriptor::lang_id::LangID; + /// Test USB class for testing USB driver implementations. Peripheral driver implementations should /// include an example called "test_class" that creates a device with this class to enable the /// driver to be tested with the test_class_host example in this crate. @@ -180,7 +187,11 @@ mod device_builder; /// Prelude for device implementors. pub mod prelude { - pub use crate::device::{UsbDevice, UsbDeviceBuilder, UsbDeviceState, UsbVidPid}; + pub use crate::device::{ + StringDescriptors, UsbDevice, UsbDeviceBuilder, UsbDeviceState, UsbVidPid, + }; + pub use crate::device_builder::BuilderError; + pub use crate::LangID; pub use crate::UsbError; } @@ -190,16 +201,20 @@ pub mod class_prelude { pub use crate::class::{ControlIn, ControlOut, UsbClass}; pub use crate::control; pub use crate::descriptor::{BosWriter, DescriptorWriter}; - pub use crate::endpoint::{EndpointAddress, EndpointIn, EndpointOut, EndpointType}; + pub use crate::endpoint::{ + EndpointAddress, EndpointIn, EndpointOut, EndpointType, IsochronousSynchronizationType, + IsochronousUsageType, + }; + pub use crate::LangID; pub use crate::UsbError; } fn _ensure_sync() { - use crate::bus::{PollResult, UsbBus, UsbBusAllocator}; + use crate::bus::PollResult; use crate::class_prelude::*; struct DummyBus<'a> { - a: &'a str, + _a: &'a str, } impl UsbBus for DummyBus<'_> { @@ -239,12 +254,14 @@ fn _ensure_sync() { } struct DummyClass<'a, B: UsbBus> { - ep: crate::endpoint::EndpointIn<'a, B>, + _ep: crate::endpoint::EndpointIn<'a, B>, } impl DummyClass<'_, B> { - fn new(alloc: &UsbBusAllocator) -> DummyClass<'_, B> { - DummyClass { ep: alloc.bulk(64) } + fn _new(alloc: &UsbBusAllocator) -> DummyClass<'_, B> { + DummyClass { + _ep: alloc.bulk(64), + } } } diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..ff96a6d --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,24 @@ +#[cfg(all(feature = "log", not(feature = "defmt")))] +macro_rules! usb_log { + (trace, $($arg:expr),*) => { log::trace!($($arg),*) }; + (debug, $($arg:expr),*) => { log::debug!($($arg),*) }; +} + +#[cfg(feature = "defmt")] +macro_rules! usb_log { + (trace, $($arg:expr),*) => { defmt::trace!($($arg),*) }; + (debug, $($arg:expr),*) => { defmt::debug!($($arg),*) }; +} + +#[cfg(not(any(feature = "log", feature = "defmt")))] +macro_rules! usb_log { + ($level:ident, $($arg:expr),*) => {{ $( let _ = $arg; )* }} +} + +macro_rules! usb_trace { + ($($arg:expr),*) => (usb_log!(trace, $($arg),*)); +} + +macro_rules! usb_debug { + ($($arg:expr),*) => (usb_log!(debug, $($arg),*)); +} diff --git a/src/test_class.rs b/src/test_class.rs index 3713856..ec26f77 100644 --- a/src/test_class.rs +++ b/src/test_class.rs @@ -1,8 +1,8 @@ #![allow(missing_docs)] use crate::class_prelude::*; -use crate::descriptor; -use crate::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; +use crate::descriptor::lang_id::LangID; +use crate::device::{StringDescriptors, UsbDevice, UsbDeviceBuilder, UsbVidPid}; use crate::Result; use core::cmp; @@ -32,6 +32,7 @@ pub struct TestClass<'a, B: UsbBus> { ep_bulk_out: EndpointOut<'a, B>, ep_interrupt_in: EndpointIn<'a, B>, ep_interrupt_out: EndpointOut<'a, B>, + ep_iso_in: EndpointIn<'a, B>, control_buf: [u8; sizes::BUFFER], bulk_buf: [u8; sizes::BUFFER], interrupt_buf: [u8; sizes::BUFFER], @@ -72,6 +73,12 @@ impl TestClass<'_, B> { ep_bulk_out: alloc.bulk(sizes::BULK_ENDPOINT), ep_interrupt_in: alloc.interrupt(sizes::INTERRUPT_ENDPOINT, 1), ep_interrupt_out: alloc.interrupt(sizes::INTERRUPT_ENDPOINT, 1), + ep_iso_in: alloc.isochronous( + IsochronousSynchronizationType::Asynchronous, + IsochronousUsageType::ImplicitFeedbackData, + 500, // These last two args are arbitrary in this usage, they + 1, // let the host know how much bandwidth to reserve. + ), control_buf: [0; sizes::BUFFER], bulk_buf: [0; sizes::BUFFER], interrupt_buf: [0; sizes::BUFFER], @@ -86,7 +93,7 @@ impl TestClass<'_, B> { } /// Convenience method to create a UsbDevice that is configured correctly for TestClass. - pub fn make_device<'a, 'b>(&'a self, usb_bus: &'b UsbBusAllocator) -> UsbDevice<'b, B> { + pub fn make_device<'a>(&self, usb_bus: &'a UsbBusAllocator) -> UsbDevice<'a, B> { self.make_device_builder(usb_bus).build() } @@ -101,15 +108,18 @@ impl TestClass<'_, B> { /// /// on the returned builder. If you change the manufacturer, product, or serial number fields, /// the test host may misbehave. - pub fn make_device_builder<'a, 'b>( - &'a self, - usb_bus: &'b UsbBusAllocator, - ) -> UsbDeviceBuilder<'b, B> { + pub fn make_device_builder<'a>( + &self, + usb_bus: &'a UsbBusAllocator, + ) -> UsbDeviceBuilder<'a, B> { UsbDeviceBuilder::new(usb_bus, UsbVidPid(VID, PID)) - .manufacturer(MANUFACTURER) - .product(PRODUCT) - .serial_number(SERIAL_NUMBER) + .strings(&[StringDescriptors::default() + .manufacturer(MANUFACTURER) + .product(PRODUCT) + .serial_number(SERIAL_NUMBER)]) + .unwrap() .max_packet_size_0(sizes::CONTROL_ENDPOINT) + .unwrap() } /// Must be called after polling the UsbDevice. @@ -218,12 +228,12 @@ impl UsbClass for TestClass<'_, B> { writer.endpoint(&self.ep_interrupt_in)?; writer.endpoint(&self.ep_interrupt_out)?; writer.interface_alt(self.iface, 1, 0xff, 0x01, 0x00, Some(self.interface_string))?; - + writer.endpoint(&self.ep_iso_in)?; Ok(()) } - fn get_string(&self, index: StringIndex, lang_id: u16) -> Option<&str> { - if lang_id == descriptor::lang_id::ENGLISH_US { + fn get_string(&self, index: StringIndex, lang_id: LangID) -> Option<&str> { + if lang_id == LangID::EN_US { if index == self.custom_string { return Some(CUSTOM_STRING); } else if index == self.interface_string { @@ -304,7 +314,7 @@ impl UsbClass for TestClass<'_, B> { xfer.accept().expect("control_out REQ_STORE_REQUEST failed"); } - REQ_WRITE_BUFFER if xfer.data().len() as usize <= self.control_buf.len() => { + REQ_WRITE_BUFFER if xfer.data().len() <= self.control_buf.len() => { assert_eq!( xfer.data().len(), req.length as usize, diff --git a/tests/test_class_host/device.rs b/tests/test_class_host/device.rs index 25824a2..d204985 100644 --- a/tests/test_class_host/device.rs +++ b/tests/test_class_host/device.rs @@ -12,6 +12,30 @@ pub struct DeviceHandles { pub en_us: Language, } +impl DeviceHandles { + /// Indicates if this device is (true) or isn't (false) a + /// high-speed device. + pub fn is_high_speed(&self) -> bool { + self.handle.device().speed() == rusb::Speed::High + } + /// Returns the max packet size for the `TestClass` bulk endpoint(s). + pub fn bulk_max_packet_size(&self) -> u16 { + self.config_descriptor + .interfaces() + .flat_map(|intf| intf.descriptors()) + .flat_map(|desc| { + desc.endpoint_descriptors() + .find(|ep| { + // Assumes that IN and OUT endpoint MPSes are the same. + ep.transfer_type() == rusb::TransferType::Bulk + }) + .map(|ep| ep.max_packet_size()) + }) + .next() + .expect("TestClass has at least one bulk endpoint") + } +} + impl ::std::ops::Deref for DeviceHandles { type Target = DeviceHandle; diff --git a/tests/test_class_host/main.rs b/tests/test_class_host/main.rs index 4d816e2..9cab6cb 100644 --- a/tests/test_class_host/main.rs +++ b/tests/test_class_host/main.rs @@ -67,7 +67,7 @@ fn run_tests(tests: &[(&str, TestFn)]) { } print!("test {} ... ", name); - stdout().flush().ok(); + let _ = stdout().flush(); let mut out = String::new(); diff --git a/tests/test_class_host/tests.rs b/tests/test_class_host/tests.rs index 93a3360..f745a1d 100644 --- a/tests/test_class_host/tests.rs +++ b/tests/test_class_host/tests.rs @@ -1,6 +1,6 @@ use crate::device::*; use rand::prelude::*; -use rusb::{request_type, Direction, Recipient, RequestType}; +use rusb::{request_type, Direction, Recipient, RequestType, TransferType}; use std::cmp::max; use std::fmt::Write; use std::time::{Duration, Instant}; @@ -163,8 +163,48 @@ fn interface_descriptor(dev, _out) { test_class::INTERFACE_STRING); } +fn iso_endpoint_descriptors(dev, _out) { + // Tests that an isochronous endpoint descriptor is present in the first + // alternate setting, but not in the default setting. + let iface = dev.config_descriptor + .interfaces() + .find(|i| i.number() == 0) + .expect("interface not found"); + + let mut iso_ep_count = 0; + for iface_descriptor in iface.descriptors() { + if iface_descriptor.setting_number() == 0 { + // Default setting - no isochronous endpoints allowed. Per USB 2.0 + // spec rev 2.0, 5.6.3 Isochronous Transfer Packet Size Constraints: + // + // All device default interface settings must not include any + // isochronous endpoints with non-zero data payload sizes (specified + // via wMaxPacketSize in the endpoint descriptor) + let issue = iface_descriptor + .endpoint_descriptors() + .find(|ep| ep.transfer_type() == TransferType::Isochronous + && ep.max_packet_size() != 0); + if let Some(ep) = issue { + panic!("Endpoint {} is isochronous and in the default setting", + ep.number()); + } + } else { + iso_ep_count += iface_descriptor.endpoint_descriptors() + .filter(|ep| ep.transfer_type() == TransferType::Isochronous) + .count(); + } + } + assert!(iso_ep_count > 0, "At least one isochronous endpoint is expected"); +} + fn bulk_loopback(dev, _out) { - for len in &[0, 1, 2, 32, 63, 64, 65, 127, 128, 129] { + let mut lens = vec![0, 1, 2, 32, 63, 64, 65, 127, 128, 129]; + if dev.is_high_speed() { + lens.extend([255, 256, 257, 511, 512, 513, 1023, 1024, 1025]); + } + + let max_packet_size: usize = dev.bulk_max_packet_size().into(); + for len in &lens { let data = random_data(*len); assert_eq!( @@ -173,7 +213,7 @@ fn bulk_loopback(dev, _out) { data.len(), "bulk write len {}", len); - if *len > 0 && *len % 64 == 0 { + if *len > 0 && *len % max_packet_size == 0 { assert_eq!( dev.write_bulk(0x01, &[], TIMEOUT) .expect("bulk write zero-length packet"),