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:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+381
@@ -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,
|
||||
}
|
||||
Vendored
+222
@@ -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()
|
||||
}
|
||||
}
|
||||
Vendored
+127
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
Vendored
+447
@@ -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
@@ -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);
|
||||
}
|
||||
Vendored
+686
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
Vendored
+274
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
+276
@@ -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>>();
|
||||
}
|
||||
Vendored
+24
@@ -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),*));
|
||||
}
|
||||
Vendored
+337
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user