feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"a6cd31fe0c31ffdf94e25d5c26aab7fbe8c5da2e3931dd3939a886d7c526bb1c",".github/workflows/ci.yml":"4098afd3f2b24cfb7cbd68097d3eae6701791c8b9568d9e6a42cefe99c187459",".github/workflows/rustfmt.yml":"857f3a7c57f72c870050bd4596ccb415182d0511c189df98fadb0240eaa8aba1","CHANGELOG.md":"8321ffdbc1e80b12ed325c4240430400690dd65fa8601e87d46fa079f12123f6","Cargo.toml":"52eef3cec0409879207b02f713a0ce35bfb4d6bd8478e80912d82443e5e0a222","Cargo.toml.orig":"c5b83bf7d6d4a80e323a30e2d5b66a2b68b9ed0bff6c7a86023d5e25726b358d","LICENSE":"afddff8c3601bc0f9329fe9a8159df16f1961633c1489f3bdcad007ad2a33c44","README.md":"3d8f0d44a6a5e004802277334a112e0b959b40d14ea5fd6f511d79f80687b145","src/bus.rs":"3d210914cb67df4cbb305d4e47453f601898037d8a7b4edcd415a19c366c9c04","src/class.rs":"0e4707a441d5a032f794bfb024c7759dab87555adea054a8838064eec1b4b15d","src/control.rs":"434301e4a6c6bc858b7e0432f85944bf0bc20c93654cbe0a76e191daac868f41","src/control_pipe.rs":"e9d32c446c92965fb1c57e404c8136f134399fb75279ee5c1cff16ca1a323f53","src/descriptor.rs":"e8bdd0396ea0c6f2f2b478b5227de4f23976981d4a60304ecc11a1289d6f94e9","src/descriptor/lang_id.rs":"c648b091b177fcc7264c352eb2ace8c712e58c03681afd551119237f051c6d0d","src/device.rs":"f4ca7d506caf237e2ee2afd484f5cd19bfe0297bb09a07adcdf326f3dffa0279","src/device_builder.rs":"0ddb8278ee20b68165f6d7c1c2b025903143f2cfb1ff7c98c2c49e76827aa622","src/endpoint.rs":"5a8d60c0267dc1b2f5ef7179f44e0ab4bbde8f3ec1b778d084eac85ae94d3a7b","src/lib.rs":"de0ea23060b2c211c10823fcf3e4bc043ccd847bb79e1bf7d73a12031cec4198","src/macros.rs":"1a51bc19d43007659191d3d2c5debcf71a3b13782f70cd02f2c7436e298aca4b","src/test_class.rs":"a7b63ef02565815f0b5d58e76f99f130cbd8f00045fe9ffeef31023ee6ab8406","tests/test_class_host/99-usb-test-class.rules":"cb5d018cef26b7cf78cb48092c44aedfe2d2071889ca2f090cf73f2eec22bd3f","tests/test_class_host/device.rs":"e4b64297111b3b1e672e3e743bdd02b366566cdde82dc599aa1fa1208058de41","tests/test_class_host/main.rs":"a9120acdb09a2bd49b268d1d7467184cf095bd497c00c0ea1174cda2fe4af755","tests/test_class_host/tests.rs":"32345ce984e746457fc47def60c6f011fd848a2bfcd7722f5e4a07a5fbd29b3f"},"package":"98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "6a44a84166f2e3799a14fbb30e8f9fc0f08b54d3"
},
"path_in_vcs": ""
}
+35
View File
@@ -0,0 +1,35 @@
on:
push:
branches: [ staging, trying, master ]
pull_request:
name: CI
jobs:
ci-linux:
name: CI
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable]
include:
# Test nightly but don't fail
- rust: nightly
experimental: true
TARGET: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.TARGET }}
- name: Install libusb library
run: sudo apt-get install -y libusb-1.0.0-dev
- run: cargo check --all-targets
- run: cargo check --features control-buffer-256
- run: cargo check --features defmt
+21
View File
@@ -0,0 +1,21 @@
on:
push:
branches: [ staging, trying, master ]
pull_request:
name: Code formatting check
jobs:
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- 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
+99
View File
@@ -0,0 +1,99 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [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<StringIndex>` 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))
* 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
## [0.2.7] - 2020-10-03
## [0.2.6] - 2020-09-22
## [0.2.5] - 2020-02-10
## [0.2.4] - 2020-02-01
## [0.2.3] - 2019-08-28
## [0.2.2] - 2019-07-27
## [0.2.1] - 2019-06-07
## [0.2.0] - 2019-06-07
## 0.1.0 - 2019-06-07
This is the initial release to crates.io.
[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
[0.2.6]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.5...v0.2.6
[0.2.5]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.3...v0.2.4
[0.2.3]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.2...v0.2.3
[0.2.2]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/rust-embedded-community/usb-device/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/rust-embedded-community/usb-device/compare/v0.1.0...v0.2.0
+57
View File
@@ -0,0 +1,57 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "usb-device"
version = "0.3.2"
authors = ["Matti Virkkunen <mvirkkunen@gmail.com>"]
description = "USB stack for embedded devices."
readme = "README.md"
keywords = [
"no-std",
"embedded",
"usb",
]
license = "MIT"
repository = "https://github.com/rust-embedded-community/usb-device"
[[test]]
name = "test_class_host"
path = "tests/test_class_host/main.rs"
harness = false
[dependencies.defmt]
version = "0.3"
optional = true
[dependencies.heapless]
version = "0.8"
[dependencies.log]
version = "0.4"
optional = true
default-features = false
[dependencies.portable-atomic]
version = "1.2.0"
default-features = false
[dev-dependencies.rand]
version = "0.8.5"
[dev-dependencies.rusb]
version = "0.9.1"
[features]
control-buffer-256 = []
log = ["dep:log"]
test-class-high-speed = []
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Matti Virkkunen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+48
View File
@@ -0,0 +1,48 @@
usb-device
==========
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 driver to implement the USB device.
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.
The UsbBus trait is intended to be implemented by device-specific crates to provide a driver for
each device's USB peripheral.
Hardware driver crates
----------------------
* [atsam4](https://github.com/atsam-rs/atsam4-hal) - device-driver implementation for atsam4e & atsam4s microcontrollers (UDP). Examples can be found [here](https://github.com/atsam-rs/sam_xplained). While not expressly supported with this crate, atsam3s and atsam55g could also be supported with a similar code-base.
* [atsamd](https://github.com/atsamd-rs/atsamd) - device-driver implementation for samd21 & samd51 microcontrollers. An example for the
itsybitsy_m4 board from Adafruit can be found [here](https://github.com/atsamd-rs/atsamd/blob/master/boards/itsybitsy_m4/examples/usb_serial.rs).
* [imxrt-usbd](https://github.com/imxrt-rs/imxrt-usbd) - device-driver implementation for NXP i.MX RT microcontrollers. Examples for
i.MX RT boards, like the Teensy 4, are maintained with the driver.
* [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-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
* The [Embassy](https://github.com/embassy-rs/embassy) project has an async USB stack, embassy-usb.
+381
View File
@@ -0,0 +1,381 @@
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 portable_atomic::{AtomicPtr, Ordering};
/// A trait for device-specific USB peripherals. Implement this to add support for a new hardware
/// platform.
///
/// The UsbBus is shared by reference between the global [`UsbDevice`](crate::device::UsbDevice) as
/// well as [`UsbClass`](crate::class::UsbClass)es, and therefore any required mutability must be
/// implemented using interior mutability. Most operations that may mutate the bus object itself
/// take place before [`enable`](UsbBus::enable) is called. After the bus is enabled, in practice
/// most access won't mutate the object itself but only endpoint-specific registers and buffers, the
/// access to which is mostly arbitrated by endpoint handles.
pub trait UsbBus: Sync + Sized {
/// Allocates an endpoint and specified endpoint parameters. This method is called by the device
/// and class implementations to allocate endpoints, and can only be called before
/// [`enable`](UsbBus::enable) is called.
///
/// # Arguments
///
/// * `ep_dir` - The endpoint direction.
/// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should
/// attempt to return an endpoint with the specified address. If None, the implementation
/// should return the next available one.
/// * `max_packet_size` - Maximum packet size in bytes.
/// * `interval` - Polling interval parameter for interrupt endpoints.
///
/// # Errors
///
/// * [`EndpointOverflow`](crate::UsbError::EndpointOverflow) - Available total number of
/// endpoints, endpoints of the specified type, or endpoind packet memory has been exhausted.
/// This is generally caused when a user tries to add too many classes to a composite device.
/// * [`InvalidEndpoint`](crate::UsbError::InvalidEndpoint) - A specific `ep_addr` was specified
/// but the endpoint in question has already been allocated.
fn alloc_ep(
&mut self,
ep_dir: UsbDirection,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<EndpointAddress>;
/// Enables and initializes the USB peripheral. Soon after enabling the device will be reset, so
/// there is no need to perform a USB reset in this method.
fn enable(&mut self);
/// Called when the host resets the device. This will be soon called after
/// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Reset`]. This method should
/// reset the state of all endpoints and peripheral flags back to a state suitable for
/// enumeration, as well as ensure that all endpoints previously allocated with alloc_ep are
/// initialized as specified.
fn reset(&self);
/// Sets the device USB address to `addr`.
fn set_device_address(&self, addr: u8);
/// Writes a single packet of data to the specified endpoint and returns number of bytes
/// actually written.
///
/// The only reason for a short write is if the caller passes a slice larger than the amount of
/// memory allocated earlier, and this is generally an error in the class implementation.
///
/// # Errors
///
/// * [`InvalidEndpoint`](crate::UsbError::InvalidEndpoint) - The `ep_addr` does not point to a
/// valid endpoint that was previously allocated with [`UsbBus::alloc_ep`].
/// * [`WouldBlock`](crate::UsbError::WouldBlock) - A previously written packet is still pending
/// to be sent.
/// * [`BufferOverflow`](crate::UsbError::BufferOverflow) - The packet is too long to fit in the
/// transmission buffer. This is generally an error in the class implementation, because the
/// class shouldn't provide more data than the `max_packet_size` it specified when allocating
/// the endpoint.
///
/// Implementations may also return other errors if applicable.
fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> Result<usize>;
/// Reads a single packet of data from the specified endpoint and returns the actual length of
/// the packet.
///
/// This should also clear any NAK flags and prepare the endpoint to receive the next packet.
///
/// # Errors
///
/// * [`InvalidEndpoint`](crate::UsbError::InvalidEndpoint) - The `ep_addr` does not point to a
/// valid endpoint that was previously allocated with [`UsbBus::alloc_ep`].
/// * [`WouldBlock`](crate::UsbError::WouldBlock) - There is no packet to be read. Note that
/// this is different from a received zero-length packet, which is valid in USB. A zero-length
/// packet will return `Ok(0)`.
/// * [`BufferOverflow`](crate::UsbError::BufferOverflow) - The received packet is too long to
/// fit in `buf`. This is generally an error in the class implementation, because the class
/// should use a buffer that is large enough for the `max_packet_size` it specified when
/// allocating the endpoint.
///
/// Implementations may also return other errors if applicable.
fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> Result<usize>;
/// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it
/// should be prepared to receive data again.
fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool);
/// Gets whether the STALL condition is set for an endpoint.
fn is_stalled(&self, ep_addr: EndpointAddress) -> bool;
/// Causes the USB peripheral to enter USB suspend mode, lowering power consumption and
/// preparing to detect a USB wakeup event. This will be called after
/// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Suspend`]. The device will
/// continue be polled, and it shall return a value other than `Suspend` from `poll` when it no
/// longer detects the suspend condition.
fn suspend(&self);
/// Resumes from suspend mode. This may only be called after the peripheral has been previously
/// suspended.
fn resume(&self);
/// Gets information about events and incoming data. Usually called in a loop or from an
/// interrupt handler. See the [`PollResult`] struct for more information.
fn poll(&self) -> PollResult;
/// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the
/// device.
///
/// The default implementation just returns `Unsupported`.
///
/// # Errors
///
/// * [`Unsupported`](crate::UsbError::Unsupported) - This UsbBus implementation doesn't support
/// simulating a disconnect or it has not been enabled at creation time.
fn force_reset(&self) -> Result<()> {
Err(UsbError::Unsupported)
}
/// Indicates that `set_device_address` must be called before accepting the corresponding
/// control transfer, not after.
///
/// The default value for this constant is `false`, which corresponds to the USB 2.0 spec, 9.4.6
const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false;
}
struct AllocatorState {
next_interface_number: u8,
next_string_index: u8,
}
/// Helper type used for UsbBus resource allocation and initialization.
pub struct UsbBusAllocator<B: UsbBus> {
bus: RefCell<B>,
bus_ptr: AtomicPtr<B>,
state: RefCell<AllocatorState>,
}
impl<B: UsbBus> UsbBusAllocator<B> {
/// Creates a new [`UsbBusAllocator`] that wraps the provided [`UsbBus`]. Usually only called by
/// USB driver implementations.
pub fn new(bus: B) -> UsbBusAllocator<B> {
UsbBusAllocator {
bus: RefCell::new(bus),
bus_ptr: AtomicPtr::new(ptr::null_mut()),
state: RefCell::new(AllocatorState {
next_interface_number: 0,
next_string_index: 4,
}),
}
}
pub(crate) fn freeze(&self) -> &B {
// Prevent further allocation by borrowing the allocation state permanently.
mem::forget(self.state.borrow_mut());
// Enable the USB bus
self.bus.borrow_mut().enable();
// An AtomicPtr is used for the reference from Endpoints to UsbBus, in order to ensure that
// Endpoints stay Sync (if the Endpoints had a reference to a RefCell, they would not be
// Sync) Set the pointer used by the Endpoints to access the UsbBus to point to the UsbBus
// in the RefCell.
let mut bus_ref = self.bus.borrow_mut();
let bus_ptr_v = &mut *bus_ref as *mut B;
self.bus_ptr.store(bus_ptr_v, Ordering::SeqCst);
// And then leave the RefCell borrowed permanently so that it cannot be borrowed mutably
// anymore.
mem::forget(bus_ref);
// Return the reference to the UsbBus, for use by UsbDevice.
unsafe { &*bus_ptr_v }
}
/// Allocates a new interface number.
pub fn interface(&self) -> InterfaceNumber {
let mut state = self.state.borrow_mut();
let number = state.next_interface_number;
state.next_interface_number += 1;
InterfaceNumber(number)
}
/// Allocates a new string index.
pub fn string(&self) -> StringIndex {
let mut state = self.state.borrow_mut();
let index = state.next_string_index;
state.next_string_index += 1;
StringIndex(index)
}
/// Allocates an endpoint with the specified direction and address.
///
/// This directly delegates to [`UsbBus::alloc_ep`], so see that method for details. In most
/// cases classes should call the endpoint type specific methods instead.
pub fn alloc<D: EndpointDirection>(
&self,
ep_addr: Option<EndpointAddress>,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Result<Endpoint<'_, B, D>> {
self.bus
.borrow_mut()
.alloc_ep(D::DIRECTION, ep_addr, ep_type, max_packet_size, interval)
.map(|a| Endpoint::new(&self.bus_ptr, a, ep_type, max_packet_size, interval))
}
/// Allocates a control endpoint.
///
/// This crate implements the control state machine only for endpoint 0. If classes want to
/// support control requests in other endpoints, the state machine must be implemented manually.
/// This should rarely be needed by classes.
///
/// # Arguments
///
/// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn control<D: EndpointDirection>(&self, max_packet_size: u16) -> Endpoint<'_, B, D> {
self.alloc(None, EndpointType::Control, max_packet_size, 0)
.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<D: EndpointDirection>(
&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
///
/// * `max_packet_size` - Maximum packet size in bytes. Must be one of 8, 16, 32 or 64.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn bulk<D: EndpointDirection>(&self, max_packet_size: u16) -> Endpoint<'_, B, D> {
self.alloc(None, EndpointType::Bulk, max_packet_size, 0)
.expect("alloc_ep failed")
}
/// Allocates an interrupt endpoint.
///
/// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes.
/// * `interval` - Polling interval.
///
/// # Panics
///
/// Panics if endpoint allocation fails, because running out of endpoints or memory is not
/// feasibly recoverable.
#[inline]
pub fn interrupt<D: EndpointDirection>(
&self,
max_packet_size: u16,
interval: u8,
) -> Endpoint<'_, B, D> {
self.alloc(None, EndpointType::Interrupt, max_packet_size, interval)
.expect("alloc_ep failed")
}
}
/// 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(pub(crate) u8);
impl From<InterfaceNumber> for u8 {
fn from(n: InterfaceNumber) -> u8 {
n.0
}
}
/// A handle for a USB string descriptor that contains its index.
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct StringIndex(u8);
impl StringIndex {
pub(crate) fn new(index: u8) -> StringIndex {
StringIndex(index)
}
}
impl From<StringIndex> for u8 {
fn from(i: StringIndex) -> u8 {
i.0
}
}
/// Event and incoming packet information returned by [`UsbBus::poll`].
pub enum PollResult {
/// No events or packets to report.
None,
/// The USB reset condition has been detected.
Reset,
/// USB packets have been received or sent. Each data field is a bit-field where the least
/// significant bit represents endpoint 0 etc., and a set bit signifies the event has occurred
/// for the corresponding endpoint.
Data {
/// An OUT packet has been received. This event should continue to be reported until the
/// packet is read.
ep_out: u16,
/// An IN packet has finished transmitting. This event should only be reported once for each
/// completed transfer.
ep_in_complete: u16,
/// A SETUP packet has been received. This event should continue to be reported until the
/// packet is read. The corresponding bit in `ep_out` may also be set but is ignored.
ep_setup: u16,
},
/// A USB suspend request has been detected or, in the case of self-powered devices, the device
/// has been disconnected from the USB bus.
Suspend,
/// A USB resume request has been detected after being suspended or, in the case of self-powered
/// devices, the device has been connected to the USB bus.
Resume,
}
+222
View File
@@ -0,0 +1,222 @@
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};
/// A trait for implementing USB classes.
///
/// All methods are optional callbacks that will be called by
/// [UsbBus::poll](crate::bus::UsbBus::poll)
pub trait UsbClass<B: UsbBus> {
/// Called when a GET_DESCRIPTOR request is received for a configuration descriptor. When
/// called, the implementation should write its interface, endpoint and any extra class
/// descriptors into `writer`. The configuration descriptor itself will be written by
/// [UsbDevice](crate::device::UsbDevice) and shouldn't be written by classes.
///
/// # Errors
///
/// Generally errors returned by `DescriptorWriter`. Implementors should propagate any errors
/// using `?`.
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> {
let _ = writer;
Ok(())
}
/// Called when a GET_DESCRIPTOR request is received for a BOS descriptor.
/// When called, the implementation should write its blobs such as capability
/// descriptors into `writer`. The BOS descriptor itself will be written by
/// [UsbDevice](crate::device::UsbDevice) and shouldn't be written by classes.
fn get_bos_descriptors(&self, writer: &mut BosWriter) -> Result<()> {
let _ = writer;
Ok(())
}
/// Gets a class-specific string descriptor.
///
/// Note: All string descriptor requests are passed to all classes in turn, so implementations
/// should return [`None`] if an unknown index is requested.
///
/// # Arguments
///
/// * `index` - A string index allocated earlier with
/// [`UsbAllocator`](crate::bus::UsbBusAllocator).
/// * `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
}
/// Called after a USB reset after the bus reset sequence is complete.
fn reset(&mut self) {}
/// Called whenever the `UsbDevice` is polled.
fn poll(&mut self) {}
/// Called when a control request is received with direction HostToDevice.
///
/// All requests are passed to classes in turn, which can choose to accept, ignore or report an
/// error. Classes can even choose to override standard requests, but doing that is rarely
/// necessary.
///
/// See [`ControlOut`] for how to respond to the transfer.
///
/// When implementing your own class, you should ignore any requests that are not meant for your
/// class so that any other classes in the composite device can process them.
///
/// # Arguments
///
/// * `req` - The request from the SETUP packet.
/// * `xfer` - A handle to the transfer.
fn control_out(&mut self, xfer: ControlOut<B>) {
let _ = xfer;
}
/// Called when a control request is received with direction DeviceToHost.
///
/// All requests are passed to classes in turn, which can choose to accept, ignore or report an
/// error. Classes can even choose to override standard requests, but doing that is rarely
/// necessary.
///
/// See [`ControlIn`] for how to respond to the transfer.
///
/// When implementing your own class, you should ignore any requests that are not meant for your
/// class so that any other classes in the composite device can process them.
///
/// # Arguments
///
/// * `req` - The request from the SETUP packet.
/// * `data` - Data to send in the DATA stage of the control transfer.
fn control_in(&mut self, xfer: ControlIn<B>) {
let _ = xfer;
}
/// Called when endpoint with address `addr` has received a SETUP packet. Implementing this
/// shouldn't be necessary in most cases, but is provided for completeness' sake.
///
/// Note: This method may be called for an endpoint address you didn't allocate, and in that
/// case you should ignore the event.
fn endpoint_setup(&mut self, addr: EndpointAddress) {
let _ = addr;
}
/// Called when endpoint with address `addr` has received data (OUT packet).
///
/// Note: This method may be called for an endpoint address you didn't allocate, and in that
/// case you should ignore the event.
fn endpoint_out(&mut self, addr: EndpointAddress) {
let _ = addr;
}
/// Called when endpoint with address `addr` has completed transmitting data (IN packet).
///
/// Note: This method may be called for an endpoint address you didn't allocate, and in that
/// case you should ignore the event.
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<u8> {
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
/// response to the transfer with either data or an error (STALL condition). To ignore the request
/// and pass it on to the next class, simply don't call any method.
pub struct ControlIn<'a, 'p, 'r, B: UsbBus> {
pipe: &'p mut ControlPipe<'a, B>,
req: &'r control::Request,
}
impl<'a, 'p, 'r, B: UsbBus> ControlIn<'a, 'p, 'r, B> {
pub(crate) fn new(pipe: &'p mut ControlPipe<'a, B>, req: &'r control::Request) -> Self {
ControlIn { pipe, req }
}
/// Gets the request from the SETUP packet.
pub fn request(&self) -> &control::Request {
self.req
}
/// Accepts the transfer with the supplied buffer.
pub fn accept_with(self, data: &[u8]) -> Result<()> {
self.pipe.accept_in(|buf| {
if data.len() > buf.len() {
return Err(UsbError::BufferOverflow);
}
buf[..data.len()].copy_from_slice(data);
Ok(data.len())
})
}
/// Accepts the transfer with the supplied static buffer.
/// This method is useful when you have a large static descriptor to send as one packet.
pub fn accept_with_static(self, data: &'static [u8]) -> Result<()> {
self.pipe.accept_in_static(data)
}
/// Accepts the transfer with a callback that can write to the internal buffer of the control
/// pipe. Can be used to avoid an extra copy.
pub fn accept(self, f: impl FnOnce(&mut [u8]) -> Result<usize>) -> Result<()> {
self.pipe.accept_in(f)
}
/// Rejects the transfer by stalling the pipe.
pub fn reject(self) -> Result<()> {
self.pipe.reject()
}
}
/// Handle for a control OUT transfer. When implementing a class, use the methods of this object to
/// response to the transfer with an ACT or an error (STALL condition). To ignore the request and
/// pass it on to the next class, simply don't call any method.
pub struct ControlOut<'a, 'p, 'r, B: UsbBus> {
pipe: &'p mut ControlPipe<'a, B>,
req: &'r control::Request,
}
impl<'a, 'p, 'r, B: UsbBus> ControlOut<'a, 'p, 'r, B> {
pub(crate) fn new(pipe: &'p mut ControlPipe<'a, B>, req: &'r control::Request) -> Self {
ControlOut { pipe, req }
}
/// Gets the request from the SETUP packet.
pub fn request(&self) -> &control::Request {
self.req
}
/// Gets the data from the data stage of the request. May be empty if there was no data stage.
pub fn data(&self) -> &[u8] {
self.pipe.data()
}
/// Accepts the transfer by succesfully responding to the status stage.
pub fn accept(self) -> Result<()> {
self.pipe.accept_out()
}
/// Rejects the transfer by stalling the pipe.
pub fn reject(self) -> Result<()> {
self.pipe.reject()
}
}
+127
View File
@@ -0,0 +1,127 @@
use crate::{Result, UsbDirection, UsbError};
use core::mem;
/// Control request type.
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RequestType {
/// Request is a USB standard request. Usually handled by
/// [`UsbDevice`](crate::device::UsbDevice).
Standard = 0,
/// Request is intended for a USB class.
Class = 1,
/// Request is vendor-specific.
Vendor = 2,
/// Reserved.
Reserved = 3,
}
/// Control request recipient.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Recipient {
/// Request is intended for the entire device.
Device = 0,
/// Request is intended for an interface. Generally, the `index` field of the request specifies
/// the interface number.
Interface = 1,
/// Request is intended for an endpoint. Generally, the `index` field of the request specifies
/// the endpoint address.
Endpoint = 2,
/// None of the above.
Other = 3,
/// Reserved.
Reserved = 4,
}
/// A control request read from a SETUP packet.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Request {
/// Direction of the request.
pub direction: UsbDirection,
/// Type of the request.
pub request_type: RequestType,
/// Recipient of the request.
pub recipient: Recipient,
/// Request code. The meaning of the value depends on the previous fields.
pub request: u8,
/// Request value. The meaning of the value depends on the previous fields.
pub value: u16,
/// Request index. The meaning of the value depends on the previous fields.
pub index: u16,
/// Length of the DATA stage. For control OUT transfers this is the exact length of the data the
/// host sent. For control IN transfers this is the maximum length of data the device should
/// return.
pub length: u16,
}
impl Request {
/// Standard USB control request Get Status
pub const GET_STATUS: u8 = 0;
/// Standard USB control request Clear Feature
pub const CLEAR_FEATURE: u8 = 1;
/// Standard USB control request Set Feature
pub const SET_FEATURE: u8 = 3;
/// Standard USB control request Set Address
pub const SET_ADDRESS: u8 = 5;
/// Standard USB control request Get Descriptor
pub const GET_DESCRIPTOR: u8 = 6;
/// Standard USB control request Set Descriptor
pub const SET_DESCRIPTOR: u8 = 7;
/// Standard USB control request Get Configuration
pub const GET_CONFIGURATION: u8 = 8;
/// Standard USB control request Set Configuration
pub const SET_CONFIGURATION: u8 = 9;
/// Standard USB control request Get Interface
pub const GET_INTERFACE: u8 = 10;
/// Standard USB control request Set Interface
pub const SET_INTERFACE: u8 = 11;
/// Standard USB control request Synch Frame
pub const SYNCH_FRAME: u8 = 12;
/// Standard USB feature Endpoint Halt for Set/Clear Feature
pub const FEATURE_ENDPOINT_HALT: u16 = 0;
/// Standard USB feature Device Remote Wakeup for Set/Clear Feature
pub const FEATURE_DEVICE_REMOTE_WAKEUP: u16 = 1;
pub(crate) fn parse(buf: &[u8]) -> Result<Request> {
if buf.len() != 8 {
return Err(UsbError::ParseError);
}
let rt = buf[0];
let recipient = rt & 0b11111;
Ok(Request {
direction: rt.into(),
request_type: unsafe { mem::transmute((rt >> 5) & 0b11) },
recipient: if recipient <= 3 {
unsafe { mem::transmute(recipient) }
} else {
Recipient::Reserved
},
request: buf[1],
value: (buf[2] as u16) | ((buf[3] as u16) << 8),
index: (buf[4] as u16) | ((buf[5] as u16) << 8),
length: (buf[6] as u16) | ((buf[7] as u16) << 8),
})
}
/// Gets the descriptor type and index from the value field of a GET_DESCRIPTOR request.
pub fn descriptor_type_index(&self) -> (u8, u8) {
((self.value >> 8) as u8, self.value as u8)
}
}
+317
View File
@@ -0,0 +1,317 @@
use crate::bus::UsbBus;
use crate::control::Request;
use crate::endpoint::{EndpointIn, EndpointOut};
use crate::{Result, UsbDirection, UsbError};
use core::cmp::min;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(unused)]
enum ControlState {
Idle,
DataIn,
DataInZlp,
DataInLast,
CompleteIn(Request),
StatusOut,
CompleteOut,
DataOut(Request),
StatusIn,
Error,
}
// Maximum length of control transfer data stage in bytes. 128 bytes by default. You can define the
// feature "control-buffer-256" to make it 256 bytes if you have larger control transfers.
#[cfg(not(feature = "control-buffer-256"))]
const CONTROL_BUF_LEN: usize = 128;
#[cfg(feature = "control-buffer-256")]
const CONTROL_BUF_LEN: usize = 256;
/// Buffers and parses USB control transfers.
pub struct ControlPipe<'a, B: UsbBus> {
ep_out: EndpointOut<'a, B>,
ep_in: EndpointIn<'a, B>,
state: ControlState,
buf: [u8; CONTROL_BUF_LEN],
static_in_buf: Option<&'static [u8]>,
i: usize,
len: usize,
}
impl<B: UsbBus> ControlPipe<'_, B> {
pub fn new<'a>(ep_out: EndpointOut<'a, B>, ep_in: EndpointIn<'a, B>) -> ControlPipe<'a, B> {
ControlPipe {
ep_out,
ep_in,
state: ControlState::Idle,
buf: [0; CONTROL_BUF_LEN],
static_in_buf: None,
i: 0,
len: 0,
}
}
pub fn waiting_for_response(&self) -> bool {
matches!(
self.state,
ControlState::CompleteOut | ControlState::CompleteIn(_)
)
}
pub fn data(&self) -> &[u8] {
&self.buf[0..self.len]
}
pub fn reset(&mut self) {
usb_trace!("Control pipe reset");
self.state = ControlState::Idle;
}
pub fn handle_setup(&mut self) -> Option<Request> {
let count = match self.ep_out.read(&mut self.buf[..]) {
Ok(count) => {
usb_trace!("Read {} bytes on EP0-OUT: {:?}", count, &self.buf[..count]);
count
}
Err(UsbError::WouldBlock) => return None,
Err(_) => {
return None;
}
};
let req = match Request::parse(&self.buf[0..count]) {
Ok(req) => req,
Err(_) => {
// Failed to parse SETUP packet. We are supposed to silently ignore this.
return None;
}
};
// Now that we have properly parsed the setup packet, ensure the end-point is no longer in
// 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,
self.state);*/
if req.direction == UsbDirection::Out {
// OUT transfer
if req.length > 0 {
// Has data stage
if req.length as usize > self.buf.len() {
// Data stage won't fit in buffer
return None;
}
self.i = 0;
self.len = req.length as usize;
self.state = ControlState::DataOut(req);
} else {
// No data stage
self.len = 0;
self.state = ControlState::CompleteOut;
return Some(req);
}
} else {
// IN transfer
self.state = ControlState::CompleteIn(req);
return Some(req);
}
None
}
pub fn handle_out(&mut self) -> Result<Option<Request>> {
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 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 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 Ok(Some(req));
}
}
// The host may terminate a DATA stage early by sending a zero-length status packet
// acknowledging the data we sent it.
ControlState::DataIn
| ControlState::DataInLast
| ControlState::DataInZlp
| ControlState::StatusOut => {
usb_debug!(
"Control transfer completed. Current state: {:?}",
self.state
);
self.ep_out.read(&mut [])?;
self.state = ControlState::Idle;
}
_ => {
// Discard the packet
usb_debug!(
"Discarding EP0 data due to unexpected state. Current state: {:?}",
self.state
);
self.ep_out.read(&mut [])?;
// Unexpected OUT packet
self.set_error()
}
}
Ok(None)
}
pub fn handle_in_complete(&mut self) -> Result<bool> {
match self.state {
ControlState::DataIn => {
self.write_in_chunk()?;
}
ControlState::DataInZlp => {
self.ep_in.write(&[])?;
usb_trace!("wrote EP0: ZLP");
self.state = ControlState::DataInLast;
}
ControlState::DataInLast => {
self.ep_out.unstall();
self.state = ControlState::StatusOut;
}
ControlState::StatusIn => {
self.state = ControlState::Idle;
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.
}
_ => {
// 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.
}
};
Ok(false)
}
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 = self.ep_in.write(&buffer[self.i..(self.i + count)])?;
usb_trace!("wrote EP0: {:?}", &buffer[self.i..(self.i + count)]);
self.i += count;
if self.i >= self.len {
self.static_in_buf = None;
self.state = if count == self.ep_in.max_packet_size() as usize {
ControlState::DataInZlp
} else {
ControlState::DataInLast
};
}
Ok(())
}
pub fn accept_out(&mut self) -> Result<()> {
match self.state {
ControlState::CompleteOut => {}
_ => {
usb_debug!("Cannot ACK, invalid state: {:?}", self.state);
return Err(UsbError::InvalidState);
}
};
self.ep_in.write(&[])?;
self.state = ControlState::StatusIn;
Ok(())
}
pub fn accept_in(&mut self, f: impl FnOnce(&mut [u8]) -> Result<usize>) -> Result<()> {
let req = match self.state {
ControlState::CompleteIn(req) => req,
_ => {
usb_debug!("EP0-IN cannot ACK, invalid state: {:?}", self.state);
return Err(UsbError::InvalidState);
}
};
let len = f(&mut self.buf[..])?;
if len > self.buf.len() {
self.set_error();
return Err(UsbError::BufferOverflow);
}
self.start_in_transfer(req, len)
}
pub fn accept_in_static(&mut self, data: &'static [u8]) -> Result<()> {
let req = match self.state {
ControlState::CompleteIn(req) => req,
_ => {
usb_debug!("EP0-IN cannot ACK, invalid state: {:?}", self.state);
return Err(UsbError::InvalidState);
}
};
self.static_in_buf = Some(data);
self.start_in_transfer(req, data.len())
}
fn start_in_transfer(&mut self, req: Request, data_len: usize) -> Result<()> {
self.len = min(data_len, req.length as usize);
self.i = 0;
self.state = ControlState::DataIn;
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);
}
self.set_error();
Ok(())
}
fn set_error(&mut self) {
usb_debug!("EP0 stalled - error");
self.state = ControlState::Error;
self.ep_out.stall();
self.ep_in.stall();
}
}
+447
View File
@@ -0,0 +1,447 @@
use core::cmp::min;
use crate::bus::{InterfaceNumber, StringIndex, UsbBus};
use crate::device;
use crate::endpoint::{Endpoint, EndpointDirection};
use crate::{Result, UsbError};
/// Standard descriptor types
#[allow(missing_docs)]
pub mod descriptor_type {
pub const DEVICE: u8 = 1;
pub const CONFIGURATION: u8 = 2;
pub const STRING: u8 = 3;
pub const INTERFACE: u8 = 4;
pub const ENDPOINT: u8 = 5;
pub const IAD: u8 = 11;
pub const BOS: u8 = 15;
pub const CAPABILITY: u8 = 16;
}
/// String descriptor language IDs.
pub mod lang_id;
/// Standard capability descriptor types
#[allow(missing_docs)]
pub mod capability_type {
pub const WIRELESS_USB: u8 = 1;
pub const USB_2_0_EXTENSION: u8 = 2;
pub const SS_USB_DEVICE: u8 = 3;
pub const CONTAINER_ID: u8 = 4;
pub const PLATFORM: u8 = 5;
}
/// A writer for USB descriptors.
pub struct DescriptorWriter<'a> {
buf: &'a mut [u8],
position: usize,
num_interfaces_mark: Option<usize>,
num_endpoints_mark: Option<usize>,
write_iads: bool,
}
impl DescriptorWriter<'_> {
pub(crate) fn new(buf: &mut [u8]) -> DescriptorWriter<'_> {
DescriptorWriter {
buf,
position: 0,
num_interfaces_mark: None,
num_endpoints_mark: None,
write_iads: false,
}
}
/// Gets the current position in the buffer, i.e. the number of bytes written so far.
pub fn position(&self) -> usize {
self.position
}
/// Writes an arbitrary (usually class-specific) descriptor.
pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) -> Result<()> {
self.write_with(descriptor_type, |buf| {
if descriptor.len() > buf.len() {
return Err(UsbError::BufferOverflow);
}
buf[..descriptor.len()].copy_from_slice(descriptor);
Ok(descriptor.len())
})
}
/// Writes an arbitrary (usually class-specific) descriptor by using a callback function.
///
/// The callback function gets a reference to the remaining buffer space, and it should write
/// the descriptor into it and return the number of bytes written. If the descriptor doesn't
/// fit, the function should return `Err(UsbError::BufferOverflow)`. That and any error returned
/// by it will be propagated up.
pub fn write_with(
&mut self,
descriptor_type: u8,
f: impl FnOnce(&mut [u8]) -> Result<usize>,
) -> Result<()> {
if self.position + 2 > self.buf.len() {
return Err(UsbError::BufferOverflow);
}
let data_end = min(self.buf.len(), self.position + 256);
let data_buf = &mut self.buf[self.position + 2..data_end];
let total_len = f(data_buf)? + 2;
if self.position + total_len > self.buf.len() {
return Err(UsbError::BufferOverflow);
}
self.buf[self.position] = total_len as u8;
self.buf[self.position + 1] = descriptor_type;
self.position += total_len;
Ok(())
}
pub(crate) fn device(&mut self, config: &device::Config) -> Result<()> {
self.write(
descriptor_type::DEVICE,
&[
(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.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
],
)
}
pub(crate) fn configuration(&mut self, config: &device::Config) -> Result<()> {
self.num_interfaces_mark = Some(self.position + 4);
self.write_iads = config.composite_with_iads;
self.write(
descriptor_type::CONFIGURATION,
&[
0,
0, // wTotalLength
0, // bNumInterfaces
device::CONFIGURATION_VALUE, // bConfigurationValue
0, // iConfiguration
0x80 | if config.self_powered { 0x40 } else { 0x00 }
| if config.supports_remote_wakeup {
0x20
} else {
0x00
}, // bmAttributes
config.max_power, // bMaxPower
],
)
}
pub(crate) fn end_class(&mut self) {
self.num_endpoints_mark = None;
}
pub(crate) fn end_configuration(&mut self) {
let position = self.position as u16;
self.buf[2..4].copy_from_slice(&position.to_le_bytes());
}
/// Writes a interface association descriptor. Call from `UsbClass::get_configuration_descriptors`
/// before writing the USB class or function's interface descriptors if your class has more than
/// one interface and wants to play nicely with composite devices on Windows. If the USB device
/// hosting the class was not configured as composite with IADs enabled, calling this function
/// does nothing, so it is safe to call from libraries.
///
/// # Arguments
///
/// * `first_interface` - Number of the function's first interface, previously allocated with
/// [`UsbBusAllocator::interface`](crate::bus::UsbBusAllocator::interface).
/// * `interface_count` - Number of interfaces in the function.
/// * `function_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
/// that do not conform to any class.
/// * `function_sub_class` - Sub-class code. Depends on class.
/// * `function_protocol` - Protocol code. Depends on class and sub-class.
/// * `function_string` - Index of string descriptor describing this function
pub fn iad(
&mut self,
first_interface: InterfaceNumber,
interface_count: u8,
function_class: u8,
function_sub_class: u8,
function_protocol: u8,
function_string: Option<StringIndex>,
) -> Result<()> {
if !self.write_iads {
return Ok(());
}
let str_index = function_string.map_or(0, Into::into);
self.write(
descriptor_type::IAD,
&[
first_interface.into(), // bFirstInterface
interface_count, // bInterfaceCount
function_class,
function_sub_class,
function_protocol,
str_index,
],
)?;
Ok(())
}
/// Writes a interface descriptor.
///
/// # Arguments
///
/// * `number` - Interface number previously allocated with
/// [`UsbBusAllocator::interface`](crate::bus::UsbBusAllocator::interface).
/// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
/// that do not conform to any class.
/// * `interface_sub_class` - Sub-class code. Depends on class.
/// * `interface_protocol` - Protocol code. Depends on class and sub-class.
pub fn interface(
&mut self,
number: InterfaceNumber,
interface_class: u8,
interface_sub_class: u8,
interface_protocol: u8,
) -> Result<()> {
self.interface_alt(
number,
device::DEFAULT_ALTERNATE_SETTING,
interface_class,
interface_sub_class,
interface_protocol,
None,
)
}
/// Writes a interface descriptor with a specific alternate setting and
/// interface string identifier.
///
/// # Arguments
///
/// * `number` - Interface number previously allocated with
/// [`UsbBusAllocator::interface`](crate::bus::UsbBusAllocator::interface).
/// * `alternate_setting` - Number of the alternate setting
/// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
/// that do not conform to any class.
/// * `interface_sub_class` - Sub-class code. Depends on class.
/// * `interface_protocol` - Protocol code. Depends on class and sub-class.
/// * `interface_string` - Index of string descriptor describing this interface
pub fn interface_alt(
&mut self,
number: InterfaceNumber,
alternate_setting: u8,
interface_class: u8,
interface_sub_class: u8,
interface_protocol: u8,
interface_string: Option<StringIndex>,
) -> Result<()> {
if alternate_setting == device::DEFAULT_ALTERNATE_SETTING {
match self.num_interfaces_mark {
Some(mark) => self.buf[mark] += 1,
None => return Err(UsbError::InvalidState),
};
}
let str_index = interface_string.map_or(0, Into::into);
self.num_endpoints_mark = Some(self.position + 4);
self.write(
descriptor_type::INTERFACE,
&[
number.into(), // bInterfaceNumber
alternate_setting, // bAlternateSetting
0, // bNumEndpoints
interface_class, // bInterfaceClass
interface_sub_class, // bInterfaceSubClass
interface_protocol, // bInterfaceProtocol
str_index, // iInterface
],
)?;
Ok(())
}
/// Writes an endpoint descriptor.
///
/// # Arguments
///
/// * `endpoint` - Endpoint previously allocated with
/// [`UsbBusAllocator`](crate::bus::UsbBusAllocator).
pub fn endpoint<B: UsbBus, D: EndpointDirection>(
&mut self,
endpoint: &Endpoint<'_, B, D>,
) -> Result<()> {
self.endpoint_ex(endpoint, |_| Ok(0))
}
/// Writes an endpoint descriptor with extra trailing data.
///
/// This is rarely needed and shouldn't be used except for compatibility with standard USB
/// classes that require it. Extra data is normally written in a separate class specific
/// descriptor.
///
/// # Arguments
///
/// * `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<B: UsbBus, D: EndpointDirection>(
&mut self,
endpoint: &Endpoint<'_, B, D>,
f: impl FnOnce(&mut [u8]) -> Result<usize>,
) -> Result<()> {
match self.num_endpoints_mark {
Some(mark) => self.buf[mark] += 1,
None => return Err(UsbError::InvalidState),
};
self.write_with(descriptor_type::ENDPOINT, |buf| {
if buf.len() < 5 {
return Err(UsbError::BufferOverflow);
}
let mps = endpoint.max_packet_size();
buf[0] = endpoint.address().into();
buf[1] = endpoint.ep_type().to_bm_attributes();
buf[2] = mps as u8;
buf[3] = (mps >> 8) as u8;
buf[4] = endpoint.interval();
Ok(f(&mut buf[5..])? + 5)
})
}
/// Writes a string descriptor.
pub(crate) fn string(&mut self, string: &str) -> Result<()> {
let mut pos = self.position;
if pos + 2 > self.buf.len() {
return Err(UsbError::BufferOverflow);
}
self.buf[pos] = 0; // length placeholder
self.buf[pos + 1] = descriptor_type::STRING;
pos += 2;
for c in string.encode_utf16() {
if pos >= self.buf.len() {
return Err(UsbError::BufferOverflow);
}
self.buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes());
pos += 2;
}
self.buf[self.position] = (pos - self.position) as u8;
self.position = pos;
Ok(())
}
}
/// A writer for Binary Object Store descriptor.
pub struct BosWriter<'w, 'a: 'w> {
writer: &'w mut DescriptorWriter<'a>,
num_caps_mark: Option<usize>,
}
impl<'w, 'a: 'w> BosWriter<'w, 'a> {
pub(crate) fn new(writer: &'w mut DescriptorWriter<'a>) -> Self {
Self {
writer,
num_caps_mark: None,
}
}
pub(crate) fn bos(&mut self) -> Result<()> {
self.num_caps_mark = Some(self.writer.position + 4);
self.writer.write(
descriptor_type::BOS,
&[
0x00, 0x00, // wTotalLength
0x00, // bNumDeviceCaps
],
)?;
self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4])?;
Ok(())
}
/// Writes capability descriptor to a BOS
///
/// # Arguments
///
/// * `capability_type` - Type of a capability
/// * `data` - Binary data of the descriptor
pub fn capability(&mut self, capability_type: u8, data: &[u8]) -> Result<()> {
match self.num_caps_mark {
Some(mark) => self.writer.buf[mark] += 1,
None => return Err(UsbError::InvalidState),
}
let mut start = self.writer.position;
let blen = data.len();
if (start + blen + 3) > self.writer.buf.len() || (blen + 3) > 255 {
return Err(UsbError::BufferOverflow);
}
self.writer.buf[start] = (blen + 3) as u8;
self.writer.buf[start + 1] = descriptor_type::CAPABILITY;
self.writer.buf[start + 2] = capability_type;
start += 3;
self.writer.buf[start..start + blen].copy_from_slice(data);
self.writer.position = start + blen;
Ok(())
}
pub(crate) fn end_bos(&mut self) {
self.num_caps_mark = None;
let position = self.writer.position as u16;
self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes());
}
}
+479
View File
@@ -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<LangID> for u16 {
fn from(lang_id: LangID) -> Self {
let LangID(id) = lang_id;
id
}
}
impl From<u16> 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);
}
+686
View File
@@ -0,0 +1,686 @@
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::LangID, BosWriter, DescriptorWriter};
pub use crate::device_builder::{StringDescriptors, UsbDeviceBuilder, UsbVidPid};
use crate::endpoint::{EndpointAddress, EndpointType};
use crate::{Result, UsbDirection};
/// The global state of the USB device.
///
/// In general class traffic is only possible in the `Configured` state.
#[repr(u8)]
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UsbDeviceState {
/// The USB device has just been created or reset.
Default,
/// The USB device has received an address from the host.
Addressed,
/// The USB device has been configured and is fully functional.
Configured,
/// The USB device has been suspended by the host or it has been unplugged from the USB bus.
Suspend,
}
// 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,
config: Config<'a>,
control: ControlPipe<'a, B>,
device_state: UsbDeviceState,
remote_wakeup_enabled: bool,
self_powered: bool,
suspended_device_state: Option<UsbDeviceState>,
pending_address: u8,
}
pub(crate) struct Config<'a> {
pub device_class: u8,
pub device_sub_class: u8,
pub device_protocol: u8,
pub max_packet_size_0: u8,
pub vendor_id: u16,
pub product_id: u16,
pub usb_rev: UsbRev,
pub device_release: u16,
pub string_descriptors: heapless::Vec<StringDescriptors<'a>, 16>,
pub self_powered: bool,
pub supports_remote_wakeup: bool,
pub composite_with_iads: bool,
pub max_power: u8,
}
/// The bConfiguration value for the not configured state.
pub const CONFIGURATION_NONE: u8 = 0;
/// The bConfiguration value for the single configuration supported by this device.
pub const CONFIGURATION_VALUE: u8 = 1;
/// The default value for bAlternateSetting for all interfaces.
pub const DEFAULT_ALTERNATE_SETTING: u8 = 0;
type ClassList<'a, B> = [&'a mut dyn UsbClass<B>];
impl<B: UsbBus> UsbDevice<'_, B> {
pub(crate) fn build<'a>(alloc: &'a UsbBusAllocator<B>, config: Config<'a>) -> UsbDevice<'a, B> {
let control_out = alloc
.alloc(
Some(0x00.into()),
EndpointType::Control,
config.max_packet_size_0 as u16,
0,
)
.expect("failed to alloc control endpoint");
let control_in = alloc
.alloc(
Some(0x80.into()),
EndpointType::Control,
config.max_packet_size_0 as u16,
0,
)
.expect("failed to alloc control endpoint");
let bus = alloc.freeze();
UsbDevice {
bus,
config,
control: ControlPipe::new(control_out, control_in),
device_state: UsbDeviceState::Default,
remote_wakeup_enabled: false,
self_powered: false,
suspended_device_state: None,
pending_address: 0,
}
}
/// Gets a reference to the [`UsbBus`] implementation used by this `UsbDevice`. You can use this
/// to call platform-specific methods on the `UsbBus`.
///
/// While it is also possible to call the standard `UsbBus` trait methods through this
/// reference, this is not recommended as it can cause the device to misbehave.
pub fn bus(&self) -> &B {
self.bus
}
/// Gets the current state of the device.
///
/// In general class traffic is only possible in the `Configured` state.
pub fn state(&self) -> UsbDeviceState {
self.device_state
}
/// Gets whether host remote wakeup has been enabled by the host.
pub fn remote_wakeup_enabled(&self) -> bool {
self.remote_wakeup_enabled
}
/// Gets whether the device is currently self powered.
pub fn self_powered(&self) -> bool {
self.self_powered
}
/// Sets whether the device is currently self powered.
pub fn set_self_powered(&mut self, is_self_powered: bool) {
self.self_powered = is_self_powered;
}
/// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the
/// device.
///
/// Mostly useful for development. Calling this at the start of your program ensures that the
/// host re-enumerates your device after a new program has been flashed.
pub fn force_reset(&mut self) -> Result<()> {
self.bus.force_reset()
}
/// Polls the [`UsbBus`] for new events and dispatches them to the provided classes. Returns
/// true if one of the classes may have data available for reading or be ready for writing,
/// false otherwise. This should be called periodically as often as possible for the best data
/// rate, or preferably from an interrupt handler. Must be called at least once every 10
/// milliseconds while connected to the USB host to be USB compliant.
///
/// Note: The list of classes passed in must be the same classes in the same order for every
/// call while the device is configured, or the device may enumerate incorrectly or otherwise
/// misbehave. The easiest way to do this is to call the `poll` method in only one place in your
/// code, as follows:
///
/// ``` ignore
/// usb_dev.poll(&mut [&mut class1, &mut class2]);
/// ```
///
/// Strictly speaking the list of classes is allowed to change between polls if the device has
/// been reset, which is indicated by `state` being equal to [`UsbDeviceState::Default`].
pub fn poll(&mut self, classes: &mut ClassList<'_, B>) -> bool {
let pr = self.bus.poll();
if self.device_state == UsbDeviceState::Suspend {
match pr {
PollResult::Suspend | PollResult::None => {
return false;
}
_ => {
self.bus.resume();
self.device_state = self
.suspended_device_state
.expect("Unknown state before suspend");
self.suspended_device_state = None;
}
}
}
match pr {
PollResult::None => {}
PollResult::Reset => self.reset(classes),
PollResult::Data {
ep_out,
ep_in_complete,
ep_setup,
} => {
// Combine bit fields for quick tests
let mut eps = ep_out | ep_in_complete | ep_setup;
// Pending events for endpoint 0?
if (eps & 1) != 0 {
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 {
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 => {
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 => {
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;
}
}
_ => (),
};
eps &= !1;
}
// Pending events for other endpoints?
if eps != 0 {
let mut bit = 2u16;
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,
UsbDirection::In,
));
}
}
eps &= !bit;
if eps == 0 {
// No more pending events for higher endpoints
break;
}
bit <<= 1;
}
}
for cls in classes.iter_mut() {
cls.poll();
}
return true;
}
PollResult::Resume => {}
PollResult::Suspend => {
usb_debug!("Suspending bus");
self.bus.suspend();
self.suspended_device_state = Some(self.device_state);
self.device_state = UsbDeviceState::Suspend;
}
}
false
}
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 Ok(());
}
}
if req.request_type == control::RequestType::Standard {
let xfer = ControlIn::new(&mut self.control, &req);
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
} else {
0x0000
};
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())?;
}
(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) {
0x0001
} else {
0x0000
};
xfer.accept_with(&status.to_le_bytes())?;
}
(Recipient::Device, Request::GET_DESCRIPTOR) => {
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())?;
}
(Recipient::Interface, Request::GET_INTERFACE) => {
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() {
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) -> Result<()> {
use crate::control::{Recipient, Request};
for cls in classes.iter_mut() {
cls.control_out(ControlOut::new(&mut self.control, &req));
if !self.control.waiting_for_response() {
return Ok(());
}
}
if req.request_type == control::RequestType::Standard {
let xfer = ControlOut::new(&mut self.control, &req);
const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16;
const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16;
const DEFAULT_ALTERNATE_SETTING_U16: u16 = DEFAULT_ALTERNATE_SETTING as u16;
match (req.recipient, req.request, req.value) {
(
Recipient::Device,
Request::CLEAR_FEATURE,
Request::FEATURE_DEVICE_REMOTE_WAKEUP,
) => {
usb_debug!("Remote wakeup disabled");
self.remote_wakeup_enabled = false;
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()?;
}
(
Recipient::Device,
Request::SET_FEATURE,
Request::FEATURE_DEVICE_REMOTE_WAKEUP,
) => {
usb_debug!("Remote wakeup enabled");
self.remote_wakeup_enabled = true;
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()?;
}
(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()?;
}
(Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => {
usb_debug!("Device configured");
self.device_state = UsbDeviceState::Configured;
xfer.accept()?;
}
(Recipient::Device, Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => {
usb_debug!("Device deconfigured");
match self.device_state {
UsbDeviceState::Default => {
xfer.accept()?;
}
_ => {
self.device_state = UsbDeviceState::Addressed;
xfer.accept()?;
}
}
}
(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()?;
return Ok(());
}
}
}
if self.control.waiting_for_response() {
usb_debug!("Rejecting control transfer due to waiting response");
self.control.reject()?;
}
Ok(())
}
fn get_descriptor(
config: &Config,
classes: &mut ClassList<'_, B>,
xfer: ControlIn<B>,
) -> Result<()> {
let req = *xfer.request();
let (dtype, index) = req.descriptor_type_index();
fn accept_writer<B: UsbBus>(
xfer: ControlIn<B>,
f: impl FnOnce(&mut DescriptorWriter) -> Result<()>,
) -> Result<()> {
xfer.accept(|buf| {
let mut writer = DescriptorWriter::new(buf);
f(&mut writer)?;
Ok(writer.position())
})?;
Ok(())
}
match dtype {
descriptor_type::BOS if config.usb_rev > UsbRev::Usb200 => accept_writer(xfer, |w| {
let mut bw = BosWriter::new(w);
bw.bos()?;
for cls in classes {
cls.get_bos_descriptors(&mut bw)?;
}
bw.end_bos();
Ok(())
})?,
descriptor_type::DEVICE => accept_writer(xfer, |w| w.device(config))?,
descriptor_type::CONFIGURATION => accept_writer(xfer, |w| {
w.configuration(config)?;
for cls in classes {
cls.get_configuration_descriptors(w)?;
w.end_class();
}
w.end_configuration();
Ok(())
})?,
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_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);
classes
.iter()
.find_map(|cls| cls.get_string(index, lang_id))
}
};
if let Some(string_descriptor) = string {
accept_writer(xfer, |w| w.string(string_descriptor))?;
} else {
xfer.reject()?;
}
}
},
_ => {
xfer.reject()?;
}
};
Ok(())
}
fn reset(&mut self, classes: &mut ClassList<'_, B>) {
self.bus.reset();
self.device_state = UsbDeviceState::Default;
self.suspended_device_state = None; // We may reset during Suspend
self.remote_wakeup_enabled = false;
self.pending_address = 0;
self.control.reset();
for cls in classes {
cls.reset();
}
}
}
+211
View File
@@ -0,0 +1,211 @@
use crate::bus::{UsbBus, UsbBusAllocator};
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);
/// Used to build new [`UsbDevice`]s.
pub struct UsbDeviceBuilder<'a, B: UsbBus> {
alloc: &'a UsbBusAllocator<B>,
config: Config<'a>,
}
macro_rules! builder_fields {
( $( $(#[$meta:meta])* $name:ident: $type:ty, )* ) => {
$(
$(#[$meta])*
pub fn $name(mut self, $name: $type) -> Self {
self.config.$name = $name;
self
}
)*
}
}
#[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<B>, vid_pid: UsbVidPid) -> UsbDeviceBuilder<'a, B> {
UsbDeviceBuilder {
alloc,
config: Config {
device_class: 0x00,
device_sub_class: 0x00,
device_protocol: 0x00,
max_packet_size_0: 8,
vendor_id: vid_pid.0,
product_id: vid_pid.1,
usb_rev: UsbRev::Usb210,
device_release: 0x0010,
string_descriptors: heapless::Vec::new(),
self_powered: false,
supports_remote_wakeup: false,
composite_with_iads: false,
max_power: 50,
},
}
}
/// Creates the [`UsbDevice`] instance with the configuration in this builder.
pub fn build(self) -> UsbDevice<'a, B> {
UsbDevice::build(self.alloc, self.config)
}
builder_fields! {
/// Sets the device class code assigned by USB.org. Set to `0xff` for vendor-specific
/// devices that do not conform to any class.
///
/// Default: `0x00` (class code specified by interfaces)
device_class: u8,
/// Sets the device sub-class code. Depends on class.
///
/// Default: `0x00`
device_sub_class: u8,
/// Sets the device protocol code. Depends on class and sub-class.
///
/// Default: `0x00`
device_protocol: u8,
/// Sets the device release version in BCD.
///
/// Default: `0x0010` ("0.1")
device_release: u16,
/// Sets whether the device may have an external power source.
///
/// This should be set to `true` even if the device is sometimes self-powered and may not
/// always draw power from the USB bus.
///
/// Default: `false`
///
/// See also: `max_power`
self_powered: bool,
/// Sets whether the device supports remotely waking up the host is requested.
///
/// 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.
pub fn composite_with_iads(mut self) -> Self {
// Magic values specified in USB-IF ECN on IADs.
self.config.device_class = 0xEF;
self.config.device_sub_class = 0x02;
self.config.device_protocol = 0x01;
self.config.composite_with_iads = true;
self
}
/// Specify the strings for the device.
///
/// # Note
/// Up to 16 languages may be provided.
pub fn strings(mut self, descriptors: &[StringDescriptors<'a>]) -> Result<Self, BuilderError> {
// 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.
///
/// Valid values are 8, 16, 32 and 64. There's generally no need to change this from the default
/// value of 8 bytes unless a class uses control transfers for sending large amounts of data, in
/// which case using a larger packet size may be more efficient.
///
/// Default: 8 bytes
pub fn max_packet_size_0(mut self, max_packet_size_0: u8) -> Result<Self, BuilderError> {
match max_packet_size_0 {
8 | 16 | 32 | 64 => {}
_ => return Err(BuilderError::InvalidPacketSize),
}
self.config.max_packet_size_0 = max_packet_size_0;
Ok(self)
}
/// Sets the maximum current drawn from the USB bus by the device in milliamps.
///
/// The default is 100 mA. If your device always uses an external power source and never draws
/// power from the USB bus, this can be set to 0.
///
/// See also: `self_powered`
///
/// Default: 100mA
pub fn max_power(mut self, max_power_ma: usize) -> Result<Self, BuilderError> {
if max_power_ma > 500 {
return Err(BuilderError::PowerTooHigh);
}
self.config.max_power = (max_power_ma / 2) as u8;
Ok(self)
}
}
+274
View File
@@ -0,0 +1,274 @@
use crate::bus::UsbBus;
use crate::{Result, UsbDirection};
use core::marker::PhantomData;
use portable_atomic::{AtomicPtr, Ordering};
/// Trait for endpoint type markers.
pub trait EndpointDirection {
/// Direction value of the marker type.
const DIRECTION: UsbDirection;
}
/// Marker type for OUT endpoints.
pub struct Out;
impl EndpointDirection for Out {
const DIRECTION: UsbDirection = UsbDirection::Out;
}
/// Marker type for IN endpoints.
pub struct In;
impl EndpointDirection for In {
const DIRECTION: UsbDirection = UsbDirection::In;
}
/// A host-to-device (OUT) endpoint.
pub type EndpointOut<'a, B> = Endpoint<'a, B, Out>;
/// A device-to-host (IN) endpoint.
pub type EndpointIn<'a, B> = Endpoint<'a, B, In>;
/// 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,
/// 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,
/// Interrupt endpoint. Used for small amounts of time-critical reliable data.
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
/// must be either `In` or `Out`.
pub struct Endpoint<'a, B: UsbBus, D: EndpointDirection> {
bus_ptr: &'a AtomicPtr<B>,
address: EndpointAddress,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
_marker: PhantomData<D>,
}
impl<B: UsbBus, D: EndpointDirection> Endpoint<'_, B, D> {
pub(crate) fn new(
bus_ptr: &AtomicPtr<B>,
address: EndpointAddress,
ep_type: EndpointType,
max_packet_size: u16,
interval: u8,
) -> Endpoint<'_, B, D> {
Endpoint {
bus_ptr,
address,
ep_type,
max_packet_size,
interval,
_marker: PhantomData,
}
}
fn bus(&self) -> &B {
let bus_ptr = self.bus_ptr.load(Ordering::SeqCst);
if bus_ptr.is_null() {
panic!("UsbBus initialization not complete");
}
unsafe { &*bus_ptr }
}
/// Gets the endpoint address including direction bit.
pub fn address(&self) -> EndpointAddress {
self.address
}
/// Gets the endpoint transfer type.
pub fn ep_type(&self) -> EndpointType {
self.ep_type
}
/// Gets the maximum packet size for the endpoint.
pub fn max_packet_size(&self) -> u16 {
self.max_packet_size
}
/// Gets the poll interval for interrupt endpoints.
pub fn interval(&self) -> u8 {
self.interval
}
/// Sets the STALL condition for the endpoint.
pub fn stall(&self) {
self.bus().set_stalled(self.address, true);
}
/// Clears the STALL condition of the endpoint.
pub fn unstall(&self) {
self.bus().set_stalled(self.address, false);
}
}
impl<B: UsbBus> Endpoint<'_, B, In> {
/// Writes a single packet of data to the specified endpoint and returns number of bytes
/// actually written. The buffer must not be longer than the `max_packet_size` specified when
/// allocating the endpoint.
///
/// # Errors
///
/// Note: USB bus implementation errors are directly passed through, so be prepared to handle
/// other errors as well.
///
/// * [`WouldBlock`](crate::UsbError::WouldBlock) - The transmission buffer of the USB
/// peripheral is full and the packet cannot be sent now. A peripheral may or may not support
/// concurrent transmission of packets.
/// * [`BufferOverflow`](crate::UsbError::BufferOverflow) - The data is longer than the
/// `max_packet_size` specified when allocating the endpoint. This is generally an error in
/// the class implementation.
pub fn write(&self, data: &[u8]) -> Result<usize> {
self.bus().write(self.address, data)
}
}
impl<B: UsbBus> Endpoint<'_, B, Out> {
/// Reads a single packet of data from the specified endpoint and returns the actual length of
/// the packet. The buffer should be large enough to fit at least as many bytes as the
/// `max_packet_size` specified when allocating the endpoint.
///
/// # Errors
///
/// Note: USB bus implementation errors are directly passed through, so be prepared to handle
/// other errors as well.
///
/// * [`WouldBlock`](crate::UsbError::WouldBlock) - There is no packet to be read. Note that
/// this is different from a received zero-length packet, which is valid and significant in
/// USB. A zero-length packet will return `Ok(0)`.
/// * [`BufferOverflow`](crate::UsbError::BufferOverflow) - The received packet is too long to
/// fit in `data`. This is generally an error in the class implementation.
pub fn read(&self, data: &mut [u8]) -> Result<usize> {
self.bus().read(self.address, data)
}
}
/// Type-safe endpoint address.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EndpointAddress(u8);
impl From<u8> for EndpointAddress {
#[inline]
fn from(addr: u8) -> EndpointAddress {
EndpointAddress(addr)
}
}
impl From<EndpointAddress> for u8 {
#[inline]
fn from(addr: EndpointAddress) -> u8 {
addr.0
}
}
impl EndpointAddress {
const INBITS: u8 = UsbDirection::In as u8;
/// Constructs a new EndpointAddress with the given index and direction.
#[inline]
pub fn from_parts(index: usize, dir: UsbDirection) -> Self {
EndpointAddress(index as u8 | dir as u8)
}
/// Gets the direction part of the address.
#[inline]
pub fn direction(&self) -> UsbDirection {
if (self.0 & Self::INBITS) != 0 {
UsbDirection::In
} else {
UsbDirection::Out
}
}
/// Returns true if the direction is IN, otherwise false.
#[inline]
pub fn is_in(&self) -> bool {
(self.0 & Self::INBITS) != 0
}
/// Returns true if the direction is OUT, otherwise false.
#[inline]
pub fn is_out(&self) -> bool {
(self.0 & Self::INBITS) == 0
}
/// Gets the index part of the endpoint address.
#[inline]
pub fn index(&self) -> usize {
(self.0 & !Self::INBITS) as usize
}
}
+276
View File
@@ -0,0 +1,276 @@
//! Experimental device-side USB stack for embedded devices.
//!
//! ## Implementing a USB device
//!
//! A USB device consists of a [`UsbDevice`](device::UsbDevice) instance, one or more
//! [`UsbClass`](crate::class::UsbClass)es, and a platform-specific [`UsbBus`](bus::UsbBus)
//! implementation which together form a USB composite device.
//!
//! In the future USB device implementors will be able to use pre-existing peripheral driver crates
//! and USB class implementation crates. The necessary types for the basic USB composite device
//! implementation are available with:
//!
//! `use usb_device::prelude::*`.
//!
//! See the [`device`] module for a more complete example.
//!
//! ## USB classes
//!
//! For information on how to implement new USB classes, see the [`class`] module and the
//! [`TestClass`](test_class::TestClass) source code for an example of a custom USB device class
//! implementation. The necessary types for creating new classes are available with:
//!
//! `use usb_device::class_prelude::*`.
//!
//! ## USB peripheral drivers
//!
//! New peripheral driver crates can be created by implementing the [`UsbBus`](bus::UsbBus) trait.
//!
//! # Note about terminology
//!
//! This crate uses standard host-centric USB terminology for transfer directions. Therefore an OUT
//! transfer refers to a host-to-device transfer, and an IN transfer refers to a device-to-host
//! transfer. This is mainly a concern for implementing new USB peripheral drivers and USB classes,
//! and people doing that should be familiar with the USB standard.
#![no_std]
#![warn(missing_docs)]
#[macro_use]
mod macros;
/// A USB stack error.
#[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.
WouldBlock,
/// Parsing failed due to invalid input.
ParseError,
/// A buffer too short for the data to read was passed, or provided data cannot fit within
/// length constraints.
BufferOverflow,
/// Classes attempted to allocate more endpoints than the peripheral supports.
EndpointOverflow,
/// Classes attempted to allocate more packet buffer memory than the peripheral supports. This
/// can be caused by either a single class trying to allocate a packet buffer larger than the
/// peripheral supports per endpoint, or multiple allocated endpoints together using more memory
/// than the peripheral has available for the buffers.
EndpointMemoryOverflow,
/// The endpoint address is invalid or already used.
InvalidEndpoint,
/// Operation is not supported by device or configuration.
Unsupported,
/// Operation is not valid in the current state of the object.
InvalidState,
}
/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from
/// the perspective of the host, which is backward for devices, but the standard directions are used
/// for consistency.
///
/// The values of the enum also match the direction bit used in endpoint addresses and control
/// request types.
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UsbDirection {
/// Host to device (OUT)
Out = 0x00,
/// Device to host (IN)
In = 0x80,
}
impl From<u8> for UsbDirection {
fn from(value: u8) -> Self {
unsafe { core::mem::transmute(value & 0x80) }
}
}
/// Result for USB operations.
pub type Result<T> = core::result::Result<T, UsbError>;
/// USB control transfers and the SETUP packet.
pub mod control;
/// For implementing peripheral drivers.
pub mod bus;
/// For implementing standard as well as vendor-specific USB classes.
///
/// To implement a new class, implement the [`UsbClass`](class::UsbClass) trait. The trait contains
/// numerous callbacks that you can use to respond to USB events. None of the methods are required,
/// and you only need to override the ones that your specific class needs to function. See the trait
/// documentation for more information on the callback methods.
///
/// Your class should *not* hold a direct reference to the [`UsbBus`](bus::UsbBus) object. Rather it
/// should take a temporary reference to the [`UsbBusAllocator`](bus::UsbBusAllocator) object
/// exposed by the bus in its constructor, and use that to allocate endpoints, as well as interface
/// and string handles. Using the [`Endpoint`](endpoint::Endpoint) handles which wrap a reference to
/// the `UsbBus` instance ensures that classes cannot inadvertently access an endpoint owned by
/// another class.
///
/// In addition to implementing the trait, add struct methods for the end-user to send and receive
/// data via your class. For example, a serial port class might have class-specific methods `read`
/// and `write` to read and write data.
pub mod class;
/// USB endpoints.
pub mod endpoint;
/// USB composite device.
///
/// The [UsbDevice](device::UsbDevice) type in this module is the core of this crate. It combines
/// multiple USB class implementations and the USB bus driver and dispatches bus state changes and
/// control messages between them.
///
/// To implement USB support for your own project, the required code is usually as follows:
///
/// ``` ignore
/// use usb_device::prelude::*;
/// use usb_serial; // example class crate (not included)
///
/// // Create the device-specific USB peripheral driver. The exact name and arguments are device
/// // specific, so check the documentation for your device driver crate.
/// let usb_bus = device_specific_usb::UsbBus::new(...);
///
/// // Create one or more USB class implementation. The name and arguments depend on the class,
/// // however most classes require the UsbAllocator as the first argument in order to allocate
/// // the required shared resources.
/// let mut serial = usb_serial::SerialPort::new(&usb_bus.allocator());
///
/// // Build the final [UsbDevice](device::UsbDevice) instance. The required arguments are a
/// // reference to the peripheral driver created earlier, as well as a USB vendor ID/product ID
/// // pair. Additional builder arguments can specify parameters such as device class code or
/// // 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))
/// .strings(&[StringDescriptors::new(LangID::EN)
/// .product("Serial port")])
/// .expect("Failed to set strings")
/// .device_class(usb_serial::DEVICE_CLASS)
/// .build();
///
/// // At this point the USB peripheral is enabled and a connected host will attempt to enumerate
/// // it.
/// loop {
/// // Must be called more often than once every 10ms to handle events and stay USB compilant,
/// // or from a device-specific interrupt handler.
/// if (usb_dev.poll(&mut [&mut serial])) {
/// // Call class-specific methods here
/// serial.read(...);
/// }
/// }
/// ```
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.
pub mod test_class;
mod control_pipe;
mod device_builder;
/// Prelude for device implementors.
pub mod prelude {
pub use crate::device::{
StringDescriptors, UsbDevice, UsbDeviceBuilder, UsbDeviceState, UsbVidPid,
};
pub use crate::device_builder::BuilderError;
pub use crate::LangID;
pub use crate::UsbError;
}
/// Prelude for class implementors.
pub mod class_prelude {
pub use crate::bus::{InterfaceNumber, StringIndex, UsbBus, UsbBusAllocator};
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, IsochronousSynchronizationType,
IsochronousUsageType,
};
pub use crate::LangID;
pub use crate::UsbError;
}
fn _ensure_sync() {
use crate::bus::PollResult;
use crate::class_prelude::*;
struct DummyBus<'a> {
_a: &'a str,
}
impl UsbBus for DummyBus<'_> {
fn alloc_ep(
&mut self,
_ep_dir: UsbDirection,
_ep_addr: Option<EndpointAddress>,
_ep_type: EndpointType,
_max_packet_size: u16,
_interval: u8,
) -> Result<EndpointAddress> {
Err(UsbError::EndpointOverflow)
}
fn enable(&mut self) {}
fn reset(&self) {}
fn set_device_address(&self, _addr: u8) {}
fn write(&self, _ep_addr: EndpointAddress, _buf: &[u8]) -> Result<usize> {
Err(UsbError::InvalidEndpoint)
}
fn read(&self, _ep_addr: EndpointAddress, _buf: &mut [u8]) -> Result<usize> {
Err(UsbError::InvalidEndpoint)
}
fn set_stalled(&self, _ep_addr: EndpointAddress, _stalled: bool) {}
fn is_stalled(&self, _ep_addr: EndpointAddress) -> bool {
false
}
fn suspend(&self) {}
fn resume(&self) {}
fn poll(&self) -> PollResult {
PollResult::None
}
}
struct DummyClass<'a, B: UsbBus> {
_ep: crate::endpoint::EndpointIn<'a, B>,
}
impl<B: UsbBus> DummyClass<'_, B> {
fn _new(alloc: &UsbBusAllocator<B>) -> DummyClass<'_, B> {
DummyClass {
_ep: alloc.bulk(64),
}
}
}
impl<B: UsbBus> UsbClass<B> for DummyClass<'_, B> {}
fn ensure_sync<T: Sync + Send>() {}
ensure_sync::<DummyBus>();
ensure_sync::<crate::endpoint::EndpointIn<DummyBus>>();
ensure_sync::<crate::endpoint::EndpointOut<DummyBus>>();
ensure_sync::<DummyClass<'_, DummyBus>>();
}
+24
View File
@@ -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),*));
}
+337
View File
@@ -0,0 +1,337 @@
#![allow(missing_docs)]
use crate::class_prelude::*;
use crate::descriptor::lang_id::LangID;
use crate::device::{StringDescriptors, UsbDevice, UsbDeviceBuilder, UsbVidPid};
use crate::Result;
use core::cmp;
#[cfg(feature = "test-class-high-speed")]
mod sizes {
pub const BUFFER: usize = 2048;
pub const CONTROL_ENDPOINT: u8 = 64;
pub const BULK_ENDPOINT: u16 = 512;
pub const INTERRUPT_ENDPOINT: u16 = 1024;
}
#[cfg(not(feature = "test-class-high-speed"))]
mod sizes {
pub const BUFFER: usize = 256;
pub const CONTROL_ENDPOINT: u8 = 8;
pub const BULK_ENDPOINT: u16 = 64;
pub const INTERRUPT_ENDPOINT: u16 = 31;
}
/// Test USB class for testing USB driver implementations. Supports various endpoint types and
/// requests for testing USB peripheral drivers on actual hardware.
pub struct TestClass<'a, B: UsbBus> {
custom_string: StringIndex,
interface_string: StringIndex,
iface: InterfaceNumber,
ep_bulk_in: EndpointIn<'a, B>,
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],
len: usize,
i: usize,
bench: bool,
expect_bulk_in_complete: bool,
expect_bulk_out: bool,
expect_interrupt_in_complete: bool,
expect_interrupt_out: bool,
}
pub const VID: u16 = 0x16c0;
pub const PID: u16 = 0x05dc;
pub const MANUFACTURER: &str = "TestClass Manufacturer";
pub const PRODUCT: &str = "virkkunen.net usb-device TestClass";
pub const SERIAL_NUMBER: &str = "TestClass Serial";
pub const CUSTOM_STRING: &str = "TestClass Custom String";
pub const INTERFACE_STRING: &str = "TestClass Interface";
pub const REQ_STORE_REQUEST: u8 = 1;
pub const REQ_READ_BUFFER: u8 = 2;
pub const REQ_WRITE_BUFFER: u8 = 3;
pub const REQ_SET_BENCH_ENABLED: u8 = 4;
pub const REQ_READ_LONG_DATA: u8 = 5;
pub const REQ_UNKNOWN: u8 = 42;
pub const LONG_DATA: &[u8] = &[0x17; 257];
impl<B: UsbBus> TestClass<'_, B> {
/// Creates a new TestClass.
pub fn new(alloc: &UsbBusAllocator<B>) -> TestClass<'_, B> {
TestClass {
custom_string: alloc.string(),
interface_string: alloc.string(),
iface: alloc.interface(),
ep_bulk_in: alloc.bulk(sizes::BULK_ENDPOINT),
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],
len: 0,
i: 0,
bench: false,
expect_bulk_in_complete: false,
expect_bulk_out: false,
expect_interrupt_in_complete: false,
expect_interrupt_out: false,
}
}
/// Convenience method to create a UsbDevice that is configured correctly for TestClass.
pub fn make_device<'a>(&self, usb_bus: &'a UsbBusAllocator<B>) -> UsbDevice<'a, B> {
self.make_device_builder(usb_bus).build()
}
/// Convenience method to create a UsbDeviceBuilder that is configured correctly for TestClass.
///
/// The methods sets
///
/// - manufacturer
/// - product
/// - serial number
/// - max_packet_size_0
///
/// 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>(
&self,
usb_bus: &'a UsbBusAllocator<B>,
) -> UsbDeviceBuilder<'a, B> {
UsbDeviceBuilder::new(usb_bus, UsbVidPid(VID, PID))
.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.
pub fn poll(&mut self) {
if self.bench {
match self.ep_bulk_out.read(&mut self.bulk_buf) {
Ok(_) | Err(UsbError::WouldBlock) => {}
Err(err) => panic!("bulk bench read {:?}", err),
};
match self
.ep_bulk_in
.write(&self.bulk_buf[0..self.ep_bulk_in.max_packet_size() as usize])
{
Ok(_) | Err(UsbError::WouldBlock) => {}
Err(err) => panic!("bulk bench write {:?}", err),
};
return;
}
let temp_i = self.i;
match self.ep_bulk_out.read(&mut self.bulk_buf[temp_i..]) {
Ok(count) => {
if self.expect_bulk_out {
self.expect_bulk_out = false;
} else {
panic!("unexpectedly read data from bulk out endpoint");
}
self.i += count;
if count < self.ep_bulk_out.max_packet_size() as usize {
self.len = self.i;
self.i = 0;
self.write_bulk_in(count == 0);
}
}
Err(UsbError::WouldBlock) => {}
Err(err) => panic!("bulk read {:?}", err),
};
match self.ep_interrupt_out.read(&mut self.interrupt_buf) {
Ok(count) => {
if self.expect_interrupt_out {
self.expect_interrupt_out = false;
} else {
panic!("unexpectedly read data from interrupt out endpoint");
}
self.ep_interrupt_in
.write(&self.interrupt_buf[0..count])
.expect("interrupt write");
self.expect_interrupt_in_complete = true;
}
Err(UsbError::WouldBlock) => {}
Err(err) => panic!("interrupt read {:?}", err),
};
}
fn write_bulk_in(&mut self, write_empty: bool) {
let to_write = cmp::min(
self.len - self.i,
self.ep_bulk_in.max_packet_size() as usize,
);
if to_write == 0 && !write_empty {
self.len = 0;
self.i = 0;
return;
}
match self
.ep_bulk_in
.write(&self.bulk_buf[self.i..self.i + to_write])
{
Ok(count) => {
assert_eq!(count, to_write);
self.expect_bulk_in_complete = true;
self.i += count;
}
Err(UsbError::WouldBlock) => {}
Err(err) => panic!("bulk write {:?}", err),
};
}
}
impl<B: UsbBus> UsbClass<B> for TestClass<'_, B> {
fn reset(&mut self) {
self.len = 0;
self.i = 0;
self.bench = false;
self.expect_bulk_in_complete = false;
self.expect_bulk_out = false;
self.expect_interrupt_in_complete = false;
self.expect_interrupt_out = false;
}
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> {
writer.interface(self.iface, 0xff, 0x00, 0x00)?;
writer.endpoint(&self.ep_bulk_in)?;
writer.endpoint(&self.ep_bulk_out)?;
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: LangID) -> Option<&str> {
if lang_id == LangID::EN_US {
if index == self.custom_string {
return Some(CUSTOM_STRING);
} else if index == self.interface_string {
return Some(INTERFACE_STRING);
}
}
None
}
fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
if self.bench {
return;
}
if addr == self.ep_bulk_in.address() {
if self.expect_bulk_in_complete {
self.expect_bulk_in_complete = false;
self.write_bulk_in(false);
} else {
panic!("unexpected endpoint_in_complete");
}
} else if addr == self.ep_interrupt_in.address() {
if self.expect_interrupt_in_complete {
self.expect_interrupt_in_complete = false;
} else {
panic!("unexpected endpoint_in_complete");
}
}
}
fn endpoint_out(&mut self, addr: EndpointAddress) {
if addr == self.ep_bulk_out.address() {
self.expect_bulk_out = true;
} else if addr == self.ep_interrupt_out.address() {
self.expect_interrupt_out = true;
}
}
fn control_in(&mut self, xfer: ControlIn<B>) {
let req = *xfer.request();
if !(req.request_type == control::RequestType::Vendor
&& req.recipient == control::Recipient::Device)
{
return;
}
match req.request {
REQ_READ_BUFFER if req.length as usize <= self.control_buf.len() => xfer
.accept_with(&self.control_buf[0..req.length as usize])
.expect("control_in REQ_READ_BUFFER failed"),
REQ_READ_LONG_DATA => xfer
.accept_with_static(LONG_DATA)
.expect("control_in REQ_READ_LONG_DATA failed"),
_ => xfer.reject().expect("control_in reject failed"),
}
}
fn control_out(&mut self, xfer: ControlOut<B>) {
let req = *xfer.request();
if !(req.request_type == control::RequestType::Vendor
&& req.recipient == control::Recipient::Device)
{
return;
}
match req.request {
REQ_STORE_REQUEST => {
self.control_buf[0] =
(req.direction as u8) | (req.request_type as u8) << 5 | (req.recipient as u8);
self.control_buf[1] = req.request;
self.control_buf[2..4].copy_from_slice(&req.value.to_le_bytes());
self.control_buf[4..6].copy_from_slice(&req.index.to_le_bytes());
self.control_buf[6..8].copy_from_slice(&req.length.to_le_bytes());
xfer.accept().expect("control_out REQ_STORE_REQUEST failed");
}
REQ_WRITE_BUFFER if xfer.data().len() <= self.control_buf.len() => {
assert_eq!(
xfer.data().len(),
req.length as usize,
"xfer data len == req.length"
);
self.control_buf[0..xfer.data().len()].copy_from_slice(xfer.data());
xfer.accept().expect("control_out REQ_WRITE_BUFFER failed");
}
REQ_SET_BENCH_ENABLED => {
self.bench = req.value != 0;
xfer.accept()
.expect("control_out REQ_SET_BENCH_ENABLED failed");
}
_ => xfer.reject().expect("control_out reject failed"),
}
}
}
@@ -0,0 +1 @@
SUBSYSTEM=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", ATTRS{serial}=="TestClass Serial", MODE="0666"
+87
View File
@@ -0,0 +1,87 @@
use rusb::{ConfigDescriptor, Context, DeviceDescriptor, DeviceHandle, Language, UsbContext as _};
use std::time::Duration;
use usb_device::test_class;
pub const TIMEOUT: Duration = Duration::from_secs(1);
pub const EN_US: u16 = 0x0409;
pub struct DeviceHandles {
pub device_descriptor: DeviceDescriptor,
pub config_descriptor: ConfigDescriptor,
pub handle: DeviceHandle<Context>,
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<Context>;
fn deref(&self) -> &DeviceHandle<Context> {
&self.handle
}
}
impl ::std::ops::DerefMut for DeviceHandles {
fn deref_mut(&mut self) -> &mut DeviceHandle<Context> {
&mut self.handle
}
}
pub fn open_device(ctx: &Context) -> rusb::Result<DeviceHandles> {
for device in ctx.devices()?.iter() {
let device_descriptor = device.device_descriptor()?;
if !(device_descriptor.vendor_id() == test_class::VID
&& device_descriptor.product_id() == test_class::PID)
{
continue;
}
let mut handle = device.open()?;
let langs = handle.read_languages(TIMEOUT)?;
if langs.is_empty() || langs[0].lang_id() != EN_US {
continue;
}
let prod = handle.read_product_string(langs[0], &device_descriptor, TIMEOUT)?;
if prod == test_class::PRODUCT {
handle.reset()?;
let config_descriptor = device.config_descriptor(0)?;
return Ok(DeviceHandles {
device_descriptor,
config_descriptor,
handle,
en_us: langs[0],
});
}
}
Err(rusb::Error::NoDevice)
}
+116
View File
@@ -0,0 +1,116 @@
mod device;
/// Runs tests against a TestClass device running on actual hardware using libusb.
///
/// This is implemented as an example as opposed to a test because the Rust test runner system is
/// not well suited for running tests that must depend on outside resources such as hardware and
/// cannot be run in parallel.
mod tests;
use crate::device::open_device;
use crate::tests::{get_tests, TestFn};
use std::io::prelude::*;
use std::io::stdout;
use std::panic;
use std::thread;
use std::time::Duration;
use usb_device::device::CONFIGURATION_VALUE;
fn main() {
let tests = get_tests();
run_tests(&tests[..]);
}
fn run_tests(tests: &[(&str, TestFn)]) {
const INTERFACE: u8 = 0;
println!("test_class_host starting");
println!("looking for device...");
let ctx = rusb::Context::new().expect("create libusb context");
// Look for the device for about 5 seconds in case it hasn't finished enumerating yet
let mut dev = Err(rusb::Error::NoDevice);
for _ in 0..50 {
dev = open_device(&ctx);
if dev.is_ok() {
break;
}
thread::sleep(Duration::from_millis(100));
}
let mut dev = match dev {
Ok(d) => d,
Err(err) => {
println!("Did not find a TestClass device. Make sure the device is correctly programmed and plugged in. Last error: {}", err);
return;
}
};
println!("\nrunning {} tests", tests.len());
let mut success = 0;
for (name, test) in tests {
if let Err(err) = dev.reset() {
println!("Failed to reset the device: {}", err);
return;
}
if let Err(err) = dev.set_active_configuration(CONFIGURATION_VALUE) {
println!("Failed to set active configuration: {}", err);
return;
}
if let Err(err) = dev.claim_interface(INTERFACE) {
println!("Failed to claim interface: {}", err);
return;
}
print!("test {} ... ", name);
let _ = stdout().flush();
let mut out = String::new();
let res = {
let hook = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
test(&mut dev, &mut out);
}));
panic::set_hook(hook);
res
};
dev.release_interface(INTERFACE)
.expect("failed to release interface");
if let Err(err) = res {
let err = if let Some(err) = err.downcast_ref::<&'static str>() {
String::from(*err)
} else if let Some(err) = err.downcast_ref::<String>() {
err.clone()
} else {
String::from("???")
};
println!("FAILED\nerror: {}\n", err);
} else {
println!("ok");
if !out.is_empty() {
print!("{}", out);
}
success += 1;
}
}
println!("{} failed, {} succeeded", tests.len() - success, success);
if success == tests.len() {
println!("\nALL TESTS PASSED!");
} else {
std::process::exit(1);
}
}
+323
View File
@@ -0,0 +1,323 @@
use crate::device::*;
use rand::prelude::*;
use rusb::{request_type, Direction, Recipient, RequestType, TransferType};
use std::cmp::max;
use std::fmt::Write;
use std::time::{Duration, Instant};
use usb_device::test_class;
pub type TestFn = fn(&mut DeviceHandles, &mut String) -> ();
const BENCH_TIMEOUT: Duration = Duration::from_secs(10);
macro_rules! tests {
{ $(fn $name:ident($dev:ident, $out:ident) $body:expr)* } => {
pub fn get_tests() -> Vec<(&'static str, TestFn)> {
let mut tests: Vec<(&'static str, TestFn)> = Vec::new();
$(
fn $name($dev: &mut DeviceHandles, $out: &mut String) {
$body
}
tests.push((stringify!($name), $name));
)*
tests
}
}
}
tests! {
fn control_request(dev, _out) {
let mut rng = rand::thread_rng();
let value: u16 = rng.gen();
let index: u16 = rng.gen();
let data = random_data(rng.gen_range(0..16));
let mut expected = [0u8; 8];
expected[0] = 0x02_u8 << 5;
expected[1] = test_class::REQ_STORE_REQUEST;
expected[2..4].copy_from_slice(&value.to_le_bytes());
expected[4..6].copy_from_slice(&index.to_le_bytes());
expected[6..8].copy_from_slice(&(data.len() as u16).to_le_bytes());
assert_eq!(
dev.write_control(
request_type(Direction::Out, RequestType::Vendor, Recipient::Device),
test_class::REQ_STORE_REQUEST, value, index,
&data, TIMEOUT).expect("control write"),
data.len());
let mut response = [0u8; 8];
assert_eq!(
dev.read_control(
request_type(Direction::In, RequestType::Vendor, Recipient::Device),
test_class::REQ_READ_BUFFER, 0, 0,
&mut response, TIMEOUT).expect("control read"),
response.len());
assert_eq!(&response, &expected);
}
fn control_data(dev, _out) {
for len in &[0, 7, 8, 9, 15, 16, 17] {
let data = random_data(*len);
assert_eq!(
dev.write_control(
request_type(Direction::Out, RequestType::Vendor, Recipient::Device),
test_class::REQ_WRITE_BUFFER, 0, 0,
&data, TIMEOUT).unwrap_or_else(|_| panic!("control write len {}", len)),
data.len());
let mut response = vec![0u8; *len];
assert_eq!(
dev.read_control(
request_type(Direction::In, RequestType::Vendor, Recipient::Device),
test_class::REQ_READ_BUFFER, 0, 0,
&mut response, TIMEOUT).unwrap_or_else(|_| panic!("control read len {}", len)),
data.len());
assert_eq!(&response, &data);
}
}
fn control_data_static(dev, _out) {
let mut response = [0u8; 257];
assert_eq!(
dev.read_control(
request_type(Direction::In, RequestType::Vendor, Recipient::Device),
test_class::REQ_READ_LONG_DATA, 0, 0,
&mut response, TIMEOUT).expect("control read"),
response.len());
assert_eq!(&response[..], test_class::LONG_DATA);
}
fn control_error(dev, _out) {
let res = dev.write_control(
request_type(Direction::Out, RequestType::Vendor, Recipient::Device),
test_class::REQ_UNKNOWN, 0, 0,
&[], TIMEOUT);
if res.is_ok() {
panic!("unknown control request succeeded");
}
}
fn string_descriptors(dev, _out) {
assert_eq!(
dev.read_product_string(dev.en_us, &dev.device_descriptor, TIMEOUT)
.expect("read product string"),
test_class::PRODUCT);
assert_eq!(
dev.read_manufacturer_string(dev.en_us, &dev.device_descriptor, TIMEOUT)
.expect("read manufacturer string"),
test_class::MANUFACTURER);
assert_eq!(
dev.read_serial_number_string(dev.en_us, &dev.device_descriptor, TIMEOUT)
.expect("read serial number string"),
test_class::SERIAL_NUMBER);
assert_eq!(
dev.read_string_descriptor(dev.en_us, 4, TIMEOUT)
.expect("read custom string"),
test_class::CUSTOM_STRING);
}
fn interface_descriptor(dev, _out) {
let iface = dev.config_descriptor
.interfaces()
.find(|i| i.number() == 0)
.expect("interface not found");
let default_alt_setting = iface.descriptors()
.find(|i| i.setting_number() == 0)
.expect("default alt setting not found");
assert_eq!(default_alt_setting.description_string_index(), None);
assert_eq!(default_alt_setting.class_code(), 0xff);
assert_eq!(default_alt_setting.sub_class_code(), 0x00);
let second_alt_setting = iface.descriptors()
.find(|i| i.setting_number() == 1)
.expect("second alt setting not found");
assert_eq!(second_alt_setting.class_code(), 0xff);
assert_eq!(second_alt_setting.sub_class_code(), 0x01);
let string_index = second_alt_setting.description_string_index()
.expect("second alt setting string is undefined");
assert_eq!(
dev.read_string_descriptor(dev.en_us, string_index, TIMEOUT)
.expect("read interface string"),
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) {
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!(
dev.write_bulk(0x01, &data, TIMEOUT)
.unwrap_or_else(|_| panic!("bulk write len {}", len)),
data.len(),
"bulk write len {}", len);
if *len > 0 && *len % max_packet_size == 0 {
assert_eq!(
dev.write_bulk(0x01, &[], TIMEOUT)
.expect("bulk write zero-length packet"),
0,
"bulk write zero-length packet");
}
// Prevent libusb from instantaneously reading an empty packet on Windows when
// zero-sized buffer is passed.
let mut response = vec![0u8; max(*len, 1)];
assert_eq!(
dev.read_bulk(0x81, &mut response, TIMEOUT)
.unwrap_or_else(|_| panic!("bulk read len {}", len)),
data.len(),
"bulk read len {}", len);
assert_eq!(&response[..*len], &data[..]);
}
}
fn interrupt_loopback(dev, _out) {
for len in &[0, 1, 2, 15, 31] {
let data = random_data(*len);
assert_eq!(
dev.write_interrupt(0x02, &data, TIMEOUT)
.unwrap_or_else(|_| panic!("interrupt write len {}", len)),
data.len(),
"interrupt write len {}", len);
// Prevent libusb from instantaneously reading an empty packet on Windows when
// zero-sized buffer is passed.
let mut response = vec![0u8; max(*len, 1)];
assert_eq!(
dev.read_interrupt(0x82, &mut response, TIMEOUT)
.unwrap_or_else(|_| panic!("interrupt read len {}", len)),
data.len(),
"interrupt read len {}", len);
assert_eq!(&response[..*len], &data[..]);
}
}
fn bench_bulk_write(dev, out) {
run_bench(dev, out, |data| {
assert_eq!(
dev.write_bulk(0x01, data, BENCH_TIMEOUT)
.expect("bulk write"),
data.len(),
"bulk write");
});
}
fn bench_bulk_read(dev, out) {
run_bench(dev, out, |data| {
assert_eq!(
dev.read_bulk(0x81, data, BENCH_TIMEOUT)
.expect("bulk read"),
data.len(),
"bulk read");
});
}
}
fn run_bench(dev: &DeviceHandles, out: &mut String, f: impl Fn(&mut [u8])) {
const TRANSFER_BYTES: usize = 64 * 1024;
const TRANSFERS: usize = 16;
const TOTAL_BYTES: usize = TRANSFER_BYTES * TRANSFERS;
dev.write_control(
request_type(Direction::Out, RequestType::Vendor, Recipient::Device),
test_class::REQ_SET_BENCH_ENABLED,
1,
0,
&[],
TIMEOUT,
)
.expect("enable bench mode");
let mut data = random_data(TRANSFER_BYTES);
let start = Instant::now();
for _ in 0..TRANSFERS {
f(&mut data);
}
let elapsed = start.elapsed();
let elapsed = elapsed.as_secs() as f64 + (elapsed.subsec_micros() as f64) * 0.000_001;
let throughput = (TOTAL_BYTES * 8) as f64 / 1_000_000.0 / elapsed;
writeln!(
out,
" {} transfers of {} bytes in {:.3}s -> {:.3}Mbit/s",
TRANSFERS, TRANSFER_BYTES, elapsed, throughput
)
.expect("write failed");
}
fn random_data(len: usize) -> Vec<u8> {
let mut data = vec![0u8; len];
rand::thread_rng().fill(data.as_mut_slice());
data
}