435 lines
15 KiB
Rust
435 lines
15 KiB
Rust
//! Utilities for writing USB descriptors.
|
||
use embassy_usb_driver::EndpointType;
|
||
|
||
use crate::builder::Config;
|
||
use crate::driver::EndpointInfo;
|
||
use crate::types::{InterfaceNumber, StringIndex};
|
||
use crate::CONFIGURATION_VALUE;
|
||
|
||
/// 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 DEVICE_QUALIFIER: u8 = 6;
|
||
pub const OTHER_SPEED_CONFIGURATION: u8 = 7;
|
||
pub const IAD: u8 = 11;
|
||
pub const BOS: u8 = 15;
|
||
pub const CAPABILITY: u8 = 16;
|
||
}
|
||
|
||
/// String descriptor language IDs.
|
||
pub mod lang_id {
|
||
/// English (US)
|
||
///
|
||
/// Recommended for use as the first language ID for compatibility.
|
||
pub const ENGLISH_US: u16 = 0x0409;
|
||
}
|
||
|
||
/// 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;
|
||
}
|
||
|
||
/// USB endpoint synchronization type. The values of this enum can be directly
|
||
/// cast into `u8` to get the bmAttributes synchronization type bits.
|
||
/// Values other than `NoSynchronization` are only allowed on isochronous endpoints.
|
||
#[repr(u8)]
|
||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||
pub enum SynchronizationType {
|
||
/// No synchronization is used.
|
||
NoSynchronization = 0b00,
|
||
/// Unsynchronized, although sinks provide data rate feedback.
|
||
Asynchronous = 0b01,
|
||
/// Synchronized using feedback or feedforward data rate information.
|
||
Adaptive = 0b10,
|
||
/// Synchronized to the USB’s SOF.
|
||
Synchronous = 0b11,
|
||
}
|
||
|
||
/// USB endpoint usage type. The values of this enum can be directly cast into
|
||
/// `u8` to get the bmAttributes usage type bits.
|
||
/// Values other than `DataEndpoint` are only allowed on isochronous endpoints.
|
||
#[repr(u8)]
|
||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||
pub enum UsageType {
|
||
/// Use the endpoint for regular data transfer.
|
||
DataEndpoint = 0b00,
|
||
/// Endpoint conveys explicit feedback information for one or more data endpoints.
|
||
FeedbackEndpoint = 0b01,
|
||
/// A data endpoint that also serves as an implicit feedback endpoint for one or more data endpoints.
|
||
ImplicitFeedbackDataEndpoint = 0b10,
|
||
/// Reserved usage type.
|
||
Reserved = 0b11,
|
||
}
|
||
|
||
/// A writer for USB descriptors.
|
||
pub(crate) struct DescriptorWriter<'a> {
|
||
pub buf: &'a mut [u8],
|
||
position: usize,
|
||
num_interfaces_mark: Option<usize>,
|
||
num_endpoints_mark: Option<usize>,
|
||
}
|
||
|
||
impl<'a> DescriptorWriter<'a> {
|
||
pub(crate) fn new(buf: &'a mut [u8]) -> Self {
|
||
DescriptorWriter {
|
||
buf,
|
||
position: 0,
|
||
num_interfaces_mark: None,
|
||
num_endpoints_mark: None,
|
||
}
|
||
}
|
||
|
||
pub fn into_buf(self) -> &'a mut [u8] {
|
||
&mut self.buf[..self.position]
|
||
}
|
||
|
||
/// Gets the current position in the buffer, i.e. the number of bytes written so far.
|
||
pub const fn position(&self) -> usize {
|
||
self.position
|
||
}
|
||
|
||
/// Writes an arbitrary (usually class-specific) descriptor with optional extra fields.
|
||
pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8], extra_fields: &[u8]) {
|
||
let descriptor_length = descriptor.len();
|
||
let extra_fields_length = extra_fields.len();
|
||
let total_length = descriptor_length + extra_fields_length;
|
||
|
||
assert!(
|
||
(self.position + 2 + total_length) <= self.buf.len() && (total_length + 2) <= 255,
|
||
"Descriptor buffer full"
|
||
);
|
||
|
||
self.buf[self.position] = (total_length + 2) as u8;
|
||
self.buf[self.position + 1] = descriptor_type;
|
||
|
||
let start = self.position + 2;
|
||
|
||
self.buf[start..start + descriptor_length].copy_from_slice(descriptor);
|
||
self.buf[start + descriptor_length..start + total_length].copy_from_slice(extra_fields);
|
||
|
||
self.position = start + total_length;
|
||
}
|
||
|
||
pub(crate) fn configuration(&mut self, config: &Config) {
|
||
self.num_interfaces_mark = Some(self.position + 4);
|
||
|
||
self.write(
|
||
descriptor_type::CONFIGURATION,
|
||
&[
|
||
0,
|
||
0, // wTotalLength
|
||
0, // bNumInterfaces
|
||
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 / 2) as u8, // bMaxPower
|
||
],
|
||
&[],
|
||
);
|
||
}
|
||
|
||
#[allow(unused)]
|
||
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
|
||
/// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::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.
|
||
pub fn iad(
|
||
&mut self,
|
||
first_interface: InterfaceNumber,
|
||
interface_count: u8,
|
||
function_class: u8,
|
||
function_sub_class: u8,
|
||
function_protocol: u8,
|
||
) {
|
||
self.write(
|
||
descriptor_type::IAD,
|
||
&[
|
||
first_interface.into(), // bFirstInterface
|
||
interface_count, // bInterfaceCount
|
||
function_class,
|
||
function_sub_class,
|
||
function_protocol,
|
||
0,
|
||
],
|
||
&[],
|
||
);
|
||
}
|
||
|
||
/// Writes a interface descriptor with a specific alternate setting and
|
||
/// interface string identifier.
|
||
///
|
||
/// # Arguments
|
||
///
|
||
/// * `number` - Interface number previously allocated with
|
||
/// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::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>,
|
||
) {
|
||
if alternate_setting == 0 {
|
||
match self.num_interfaces_mark {
|
||
Some(mark) => self.buf[mark] += 1,
|
||
None => {
|
||
panic!("you can only call `interface/interface_alt` after `configuration`.")
|
||
}
|
||
};
|
||
}
|
||
|
||
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
|
||
],
|
||
&[],
|
||
);
|
||
}
|
||
|
||
/// Writes an endpoint descriptor.
|
||
///
|
||
/// # Arguments
|
||
///
|
||
/// * `endpoint` - Endpoint previously allocated with
|
||
/// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder).
|
||
/// * `synchronization_type` - The synchronization type of the endpoint.
|
||
/// * `usage_type` - The usage type of the endpoint.
|
||
/// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor.
|
||
pub fn endpoint(
|
||
&mut self,
|
||
endpoint: &EndpointInfo,
|
||
synchronization_type: SynchronizationType,
|
||
usage_type: UsageType,
|
||
extra_fields: &[u8],
|
||
) {
|
||
match self.num_endpoints_mark {
|
||
Some(mark) => self.buf[mark] += 1,
|
||
None => panic!("you can only call `endpoint` after `interface/interface_alt`."),
|
||
};
|
||
|
||
let mut bm_attributes = endpoint.ep_type as u8;
|
||
|
||
// Synchronization types other than `NoSynchronization`,
|
||
// and usage types other than `DataEndpoint`
|
||
// are only allowed for isochronous endpoints.
|
||
if endpoint.ep_type != EndpointType::Isochronous {
|
||
assert_eq!(synchronization_type, SynchronizationType::NoSynchronization);
|
||
assert_eq!(usage_type, UsageType::DataEndpoint);
|
||
} else {
|
||
if usage_type == UsageType::FeedbackEndpoint {
|
||
assert_eq!(synchronization_type, SynchronizationType::NoSynchronization)
|
||
}
|
||
|
||
let synchronization_bm_attibutes: u8 = (synchronization_type as u8) << 2;
|
||
let usage_bm_attibutes: u8 = (usage_type as u8) << 4;
|
||
|
||
bm_attributes |= usage_bm_attibutes | synchronization_bm_attibutes;
|
||
}
|
||
|
||
self.write(
|
||
descriptor_type::ENDPOINT,
|
||
&[
|
||
endpoint.addr.into(), // bEndpointAddress
|
||
bm_attributes, // bmAttributes
|
||
endpoint.max_packet_size as u8,
|
||
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
|
||
endpoint.interval_ms, // bInterval
|
||
],
|
||
extra_fields,
|
||
);
|
||
}
|
||
|
||
/// Writes a string descriptor.
|
||
#[allow(unused)]
|
||
pub(crate) fn string(&mut self, string: &str) {
|
||
let mut pos = self.position;
|
||
|
||
assert!(pos + 2 <= self.buf.len(), "Descriptor buffer full");
|
||
|
||
self.buf[pos] = 0; // length placeholder
|
||
self.buf[pos + 1] = descriptor_type::STRING;
|
||
|
||
pos += 2;
|
||
|
||
for c in string.encode_utf16() {
|
||
assert!(pos < self.buf.len(), "Descriptor buffer full");
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// Create a new Device Descriptor array.
|
||
///
|
||
/// All device descriptors are always 18 bytes, so there's no need for
|
||
/// a variable-length buffer or DescriptorWriter.
|
||
pub(crate) fn device_descriptor(config: &Config) -> [u8; 18] {
|
||
[
|
||
18, // bLength
|
||
0x01, // bDescriptorType
|
||
config.bcd_usb as u8,
|
||
(config.bcd_usb as u16 >> 8) as u8, // bcdUSB
|
||
config.device_class, // bDeviceClass
|
||
config.device_sub_class, // bDeviceSubClass
|
||
config.device_protocol, // bDeviceProtocol
|
||
config.max_packet_size_0, // bMaxPacketSize0
|
||
config.vendor_id as u8,
|
||
(config.vendor_id >> 8) as u8, // idVendor
|
||
config.product_id as u8,
|
||
(config.product_id >> 8) as u8, // idProduct
|
||
config.device_release as u8,
|
||
(config.device_release >> 8) as u8, // bcdDevice
|
||
config.manufacturer.map_or(0, |_| 1), // iManufacturer
|
||
config.product.map_or(0, |_| 2), // iProduct
|
||
config.serial_number.map_or(0, |_| 3), // iSerialNumber
|
||
1, // bNumConfigurations
|
||
]
|
||
}
|
||
|
||
/// Create a new Device Qualifier Descriptor array.
|
||
///
|
||
/// All device qualifier descriptors are always 10 bytes, so there's no need for
|
||
/// a variable-length buffer or DescriptorWriter.
|
||
pub(crate) fn device_qualifier_descriptor(config: &Config) -> [u8; 10] {
|
||
[
|
||
10, // bLength
|
||
0x06, // bDescriptorType
|
||
config.bcd_usb as u8,
|
||
(config.bcd_usb 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
|
||
1, // bNumConfigurations
|
||
0, // Reserved
|
||
]
|
||
}
|
||
|
||
/// A writer for Binary Object Store descriptor.
|
||
pub struct BosWriter<'a> {
|
||
pub(crate) writer: DescriptorWriter<'a>,
|
||
num_caps_mark: Option<usize>,
|
||
}
|
||
|
||
impl<'a> BosWriter<'a> {
|
||
pub(crate) const fn new(writer: DescriptorWriter<'a>) -> Self {
|
||
Self {
|
||
writer,
|
||
num_caps_mark: None,
|
||
}
|
||
}
|
||
|
||
pub(crate) fn bos(&mut self) {
|
||
if (self.writer.buf.len() - self.writer.position) < 5 {
|
||
return;
|
||
}
|
||
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]);
|
||
}
|
||
|
||
/// 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]) {
|
||
match self.num_caps_mark {
|
||
Some(mark) => self.writer.buf[mark] += 1,
|
||
None => panic!("called `capability` not between `bos` and `end_bos`."),
|
||
}
|
||
|
||
let mut start = self.writer.position;
|
||
let blen = data.len();
|
||
|
||
assert!(
|
||
(start + blen + 3) <= self.writer.buf.len() && (blen + 3) <= 255,
|
||
"Descriptor buffer full"
|
||
);
|
||
|
||
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;
|
||
}
|
||
|
||
pub(crate) fn end_bos(&mut self) {
|
||
if self.writer.position == 0 {
|
||
return;
|
||
}
|
||
self.num_caps_mark = None;
|
||
let position = self.writer.position as u16;
|
||
self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes());
|
||
}
|
||
}
|