From aa21aebb0b321a2085571e5be5fffcea4703584d Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 7 Feb 2023 14:19:51 -0500 Subject: [PATCH] Lazily encode UTF16 values and add docs --- embassy-usb/Cargo.toml | 3 +- embassy-usb/src/msos.rs | 318 +++++++++--------- .../nrf52840/src/bin/usb_serial_winusb.rs | 25 +- 3 files changed, 166 insertions(+), 180 deletions(-) diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 54a8f27c7..eb9ba36f4 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -13,7 +13,7 @@ target = "thumbv7em-none-eabi" [features] defmt = ["dep:defmt", "embassy-usb-driver/defmt"] usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] -msos-descriptor = ["dep:widestring"] +msos-descriptor = [] default = ["usbd-hid"] [dependencies] @@ -25,7 +25,6 @@ embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver- defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } heapless = "0.7.10" -widestring = { version = "1.0.2", default-features = false, optional = true } # for HID usbd-hid = { version = "0.6.0", optional = true } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index 360f80d91..19ed34979 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -5,16 +5,9 @@ //! use core::mem::size_of; -use core::ops::Range; - -pub use widestring::{u16cstr, u16str, U16CStr, U16Str}; use super::{capability_type, BosWriter}; -fn write_u16>(buf: &mut [u8], range: Range, data: T) { - (&mut buf[range]).copy_from_slice(data.into().to_le_bytes().as_slice()) -} - /// A serialized Microsoft OS 2.0 Descriptor set. /// /// Create with [`DeviceDescriptorSetBuilder`]. @@ -24,14 +17,17 @@ pub struct MsOsDescriptorSet<'d> { } impl<'d> MsOsDescriptorSet<'d> { + /// Gets the raw bytes of the MS OS descriptor pub fn descriptor(&self) -> &[u8] { self.descriptor } + /// Gets the vendor code used by the host to retrieve the MS OS descriptor pub fn vendor_code(&self) -> u8 { self.vendor_code } + /// Returns `true` if no MS OS descriptor data is available pub fn is_empty(&self) -> bool { self.descriptor.is_empty() } @@ -39,7 +35,7 @@ impl<'d> MsOsDescriptorSet<'d> { /// Writes a Microsoft OS 2.0 Descriptor set into a buffer. pub struct MsOsDescriptorWriter<'d> { - pub buf: &'d mut [u8], + buf: &'d mut [u8], position: usize, config_mark: Option, @@ -75,14 +71,17 @@ impl<'d> MsOsDescriptorWriter<'d> { } } + /// Returns `true` if the MS OS descriptor header has not yet been written pub fn is_empty(&self) -> bool { self.position == 0 } + /// Returns `true` if a configuration subset header has been started pub fn is_in_config_subset(&self) -> bool { self.config_mark.is_some() } + /// Returns `true` if a function subset header has been started and not yet ended pub fn is_in_function_subset(&self) -> bool { self.function_mark.is_some() } @@ -148,6 +147,7 @@ impl<'d> MsOsDescriptorWriter<'d> { self.write(desc); } + /// Ends the current function subset (if any) pub fn end_function(&mut self) { Self::end_subset::(self.buf, self.position, &mut self.function_mark); } @@ -212,29 +212,13 @@ impl<'d> MsOsDescriptorWriter<'d> { } } +/// Microsoft Windows version codes +/// +/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors. pub mod windows_version { - pub const WIN2K: u32 = 0x05000000; - pub const WIN2KSP1: u32 = 0x05000100; - pub const WIN2KSP2: u32 = 0x05000200; - pub const WIN2KSP3: u32 = 0x05000300; - pub const WIN2KSP4: u32 = 0x05000400; - - pub const WINXP: u32 = 0x05010000; - pub const WINXPSP1: u32 = 0x05010100; - pub const WINXPSP2: u32 = 0x05010200; - pub const WINXPSP3: u32 = 0x05010300; - pub const WINXPSP4: u32 = 0x05010400; - - pub const VISTA: u32 = 0x06000000; - pub const VISTASP1: u32 = 0x06000100; - pub const VISTASP2: u32 = 0x06000200; - pub const VISTASP3: u32 = 0x06000300; - pub const VISTASP4: u32 = 0x06000400; - - pub const WIN7: u32 = 0x06010000; - pub const WIN8: u32 = 0x06020000; - /// AKA `NTDDI_WINBLUE` + /// Windows 8.1 (aka `NTDDI_WINBLUE`) pub const WIN8_1: u32 = 0x06030000; + /// Windows 10 pub const WIN10: u32 = 0x0A000000; } @@ -266,7 +250,7 @@ use sealed::*; /// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::()); - assert!(buf.len() >= bytes.len(), "MSOS descriptor buffer full"); + assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full"); (&mut buf[..bytes.len()]).copy_from_slice(bytes); } @@ -274,14 +258,23 @@ unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum DescriptorType { + /// MS OS descriptor set header SetHeaderDescriptor = 0, + /// Configuration subset header SubsetHeaderConfiguration = 1, + /// Function subset header SubsetHeaderFunction = 2, + /// Compatible device ID feature descriptor FeatureCompatibleId = 3, + /// Registry property feature descriptor FeatureRegProperty = 4, + /// Minimum USB resume time feature descriptor FeatureMinResumeTime = 5, + /// Vendor revision feature descriptor FeatureModelId = 6, + /// CCGP device descriptor feature descriptor FeatureCcgpDevice = 7, + /// Vendor revision feature descriptor FeatureVendorRevision = 8, } @@ -318,6 +311,9 @@ pub struct DescriptorSetHeader { } impl DescriptorSetHeader { + /// Creates a MS OS descriptor set header. + /// + /// `windows_version` is the minimum Windows version the descriptor set can apply to. pub fn new(windows_version: u32) -> Self { DescriptorSetHeader { wLength: (size_of::() as u16).to_le(), @@ -351,6 +347,7 @@ pub struct ConfigurationSubsetHeader { } impl ConfigurationSubsetHeader { + /// Creates a configuration subset header pub fn new(config: u8) -> Self { ConfigurationSubsetHeader { wLength: (size_of::() as u16).to_le(), @@ -385,6 +382,7 @@ pub struct FunctionSubsetHeader { } impl FunctionSubsetHeader { + /// Creates a function subset header pub fn new(first_interface: u8) -> Self { FunctionSubsetHeader { wLength: (size_of::() as u16).to_le(), @@ -436,11 +434,8 @@ impl Descriptor for CompatibleIdFeatureDescriptor { } impl CompatibleIdFeatureDescriptor { - /// Creates a compatible ID descriptor that signals WINUSB driver compatiblilty. - pub fn new_winusb() -> Self { - Self::new_raw([b'W', b'I', b'N', b'U', b'S', b'B', 0, 0], [0u8; 8]) - } - + /// Creates a compatible ID feature descriptor + /// /// The ids must be 8 ASCII bytes or fewer. pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self { assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8); @@ -451,7 +446,7 @@ impl CompatibleIdFeatureDescriptor { Self::new_raw(cid, scid) } - pub fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { + fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { Self { wLength: (size_of::() as u16).to_le(), wDescriptorType: (Self::TYPE as u16).to_le(), @@ -464,129 +459,87 @@ impl CompatibleIdFeatureDescriptor { /// Table 14. Microsoft OS 2.0 registry property descriptor #[allow(non_snake_case)] pub struct RegistryPropertyFeatureDescriptor<'a> { - wLength: u16, - wDescriptorType: u16, - wPropertyDataType: u16, - PropertyName: &'a [u8], - PropertyData: &'a [u8], + name: &'a str, + data: PropertyData<'a>, } -impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} -impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} - -impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { - const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; - fn size(&self) -> usize { - 10 + self.PropertyName.len() + self.PropertyData.len() - } - fn write_to(&self, buf: &mut [u8]) { - assert!(buf.len() >= self.size(), "MSOS descriptor buffer full"); - write_u16(buf, 0..2, self.wLength); - write_u16(buf, 2..4, self.wDescriptorType); - write_u16(buf, 4..6, self.wPropertyDataType); - write_u16(buf, 6..8, (self.PropertyName.len() as u16).to_le()); - let pne = 8 + self.PropertyName.len(); - (&mut buf[8..pne]).copy_from_slice(self.PropertyName); - let pds = pne + 2; - let pde = pds + self.PropertyData.len(); - write_u16(buf, pne..pds, (self.PropertyData.len() as u16).to_le()); - (&mut buf[pds..pde]).copy_from_slice(self.PropertyData); - } -} - -impl<'a> RegistryPropertyFeatureDescriptor<'a> { - pub const DEVICE_INTERFACE_GUIDS_NAME: &U16CStr = { - // Can't use defmt::panic in constant expressions (inside u16cstr!) - macro_rules! panic { - ($($x:tt)*) => { - { - ::core::panic!($($x)*); - } - }; - } - - u16cstr!("DeviceInterfaceGUIDs") - }; - - /// A registry property. - /// - /// `name` should be a NUL-terminated 16-bit Unicode string. - pub fn new_raw<'n: 'a, 'd: 'a>(name: &'a [u8], data: &'d [u8], data_type: PropertyDataType) -> Self { - assert!(name.len() < usize::from(u16::MAX) && data.len() < usize::from(u16::MAX)); - Self { - wLength: ((10 + name.len() + data.len()) as u16).to_le(), - wDescriptorType: (Self::TYPE as u16).to_le(), - wPropertyDataType: (data_type as u16).to_le(), - PropertyName: name, - PropertyData: data, - } - } - - fn u16str_bytes(s: &U16CStr) -> &[u8] { - unsafe { core::slice::from_raw_parts(s.as_ptr() as *const u8, (s.len() + 1) * 2) } - } - - /// A registry property containing a NUL-terminated 16-bit Unicode string. - pub fn new_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { - Self::new_raw(Self::u16str_bytes(name), Self::u16str_bytes(data), PropertyDataType::Sz) - } - - /// A registry property containing a NUL-terminated 16-bit Unicode string that expands environment variables. - pub fn new_string_expand<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - Self::u16str_bytes(data), - PropertyDataType::ExpandSz, - ) - } - - /// A registry property containing a NUL-terminated 16-bit Unicode string that contains a symbolic link. - pub fn new_link<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - Self::u16str_bytes(data), - PropertyDataType::Link, - ) - } - - /// A registry property containing multiple NUL-terminated 16-bit Unicode strings. - pub fn new_multi_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u16]) -> Self { - assert!( - data.len() >= 2 && data[data.len() - 1] == 0 && data[data.len() - 2] == 0, - "multi-strings must end in double nul terminators" - ); - Self::new_raw( - Self::u16str_bytes(name), - unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) }, - PropertyDataType::RegMultiSz, - ) - } - +/// Data values that can be encoded into a registry property descriptor +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PropertyData<'a> { + /// A registry property containing a string. + Sz(&'a str), + /// A registry property containing a string that expands environment variables. + ExpandSz(&'a str), /// A registry property containing binary data. - pub fn new_binary<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u8]) -> Self { - Self::new_raw(Self::u16str_bytes(name), data, PropertyDataType::Binary) - } - - /// A registry property containing a Little-Endian 32-bit integer. - /// - /// The function assumes that `data` is already little-endian, it does not convert it. - pub fn new_dword_le<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::()) }, - PropertyDataType::DwordLittleEndian, - ) - } - + Binary(&'a [u8]), + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian(u32), /// A registry property containing a big-endian 32-bit integer. - /// - /// The function assumes that `data` is already big-endian, it does not convert it. - pub fn new_dword_be<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self { - Self::new_raw( - Self::u16str_bytes(name), - unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::()) }, - PropertyDataType::DwordBigEndian, - ) + DwordBigEndian(u32), + /// A registry property containing a string that contains a symbolic link. + Link(&'a str), + /// A registry property containing multiple strings. + RegMultiSz(&'a [&'a str]), +} + +fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize { + assert!(buf.len() >= val.len()); + buf[..val.len()].copy_from_slice(val); + val.len() +} + +fn write_utf16(val: &str, buf: &mut [u8]) -> usize { + let mut pos = 0; + for c in val.encode_utf16() { + pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) +} + +impl<'a> PropertyData<'a> { + /// Gets the `PropertyDataType` for this property value + pub fn kind(&self) -> PropertyDataType { + match self { + PropertyData::Sz(_) => PropertyDataType::Sz, + PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz, + PropertyData::Binary(_) => PropertyDataType::Binary, + PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian, + PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian, + PropertyData::Link(_) => PropertyDataType::Link, + PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz, + } + } + + /// Gets the size (in bytes) of this property value when encoded. + pub fn size(&self) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => { + core::mem::size_of::() * (val.encode_utf16().count() + 1) + } + PropertyData::Binary(val) => val.len(), + PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val), + PropertyData::RegMultiSz(val) => { + core::mem::size_of::() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::() + 1 + } + } + } + + /// Encodes the data for this property value and writes it to `buf`. + pub fn write(&self, buf: &mut [u8]) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf), + PropertyData::Binary(val) => write_bytes(val, buf), + PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf), + PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf), + PropertyData::RegMultiSz(val) => { + let mut pos = 0; + for s in *val { + pos += write_utf16(s, &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) + } + } } } @@ -594,15 +547,57 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { #[derive(Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum PropertyDataType { + /// A registry property containing a string. Sz = 1, + /// A registry property containing a string that expands environment variables. ExpandSz = 2, + /// A registry property containing binary data. Binary = 3, + /// A registry property containing a little-endian 32-bit integer. DwordLittleEndian = 4, + /// A registry property containing a big-endian 32-bit integer. DwordBigEndian = 5, + /// A registry property containing a string that contains a symbolic link. Link = 6, + /// A registry property containing multiple strings. RegMultiSz = 7, } +impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} + +impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { + const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; + + fn size(&self) -> usize { + 10 + self.name_size() + self.data.size() + } + + fn write_to(&self, buf: &mut [u8]) { + assert!(buf.len() >= self.size(), "MS OS descriptor buffer full"); + + let mut pos = 0; + pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_utf16(self.name, &mut buf[pos..]); + pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]); + self.data.write(&mut buf[pos..]); + } +} + +impl<'a> RegistryPropertyFeatureDescriptor<'a> { + /// A registry property. + pub fn new(name: &'a str, data: PropertyData<'a>) -> Self { + Self { name, data } + } + + fn name_size(&self) -> usize { + core::mem::size_of::() * (self.name.encode_utf16().count() + 1) + } +} + /// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor. #[allow(non_snake_case)] #[repr(C, packed(1))] @@ -658,15 +653,14 @@ impl Descriptor for ModelIdDescriptor { } impl ModelIdDescriptor { + /// Creates a new model ID descriptor + /// + /// `model_id` should be a uuid that uniquely identifies a physical device. pub fn new(model_id: u128) -> Self { - Self::new_bytes(model_id.to_le_bytes()) - } - - pub fn new_bytes(model_id: [u8; 16]) -> Self { Self { wLength: (size_of::() as u16).to_le(), wDescriptorType: (Self::TYPE as u16).to_le(), - modelId: model_id, + modelId: model_id.to_le_bytes(), } } } @@ -689,6 +683,7 @@ impl Descriptor for CcgpDeviceDescriptor { } impl CcgpDeviceDescriptor { + /// Creates a new CCGP device descriptor pub fn new() -> Self { Self { wLength: (size_of::() as u16).to_le(), @@ -704,7 +699,7 @@ pub struct VendorRevisionDescriptor { wLength: u16, wDescriptorType: u16, /// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or - /// other MSOS descriptor. Shell set to greater than or equal to 1. + /// other MS OS descriptor. Shell set to greater than or equal to 1. VendorRevision: u16, } @@ -719,6 +714,7 @@ impl Descriptor for VendorRevisionDescriptor { } impl VendorRevisionDescriptor { + /// Creates a new vendor revision descriptor pub fn new(revision: u16) -> Self { assert!(revision >= 1); Self { diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs index 443379a07..f4b828de6 100644 --- a/examples/nrf52840/src/bin/usb_serial_winusb.rs +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -7,7 +7,7 @@ use core::mem; use defmt::{info, panic}; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply}; +use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect}; use embassy_nrf::{interrupt, pac}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; @@ -15,17 +15,8 @@ use embassy_usb::msos::{self, windows_version}; use embassy_usb::{Builder, Config}; use {defmt_rtt as _, panic_probe as _}; -const DEVICE_INTERFACE_GUIDS: &[u16] = { - // Can't use defmt::panic in constant expressions (inside u16str!) - macro_rules! panic { - ($($x:tt)*) => { - { - ::core::panic!($($x)*); - } - }; - } - msos::u16str!("{EAA9A5DC-30BA-44BC-9232-606CDC875321}\0\0").as_slice() -}; +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"]; #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -39,7 +30,7 @@ async fn main(_spawner: Spawner) { // Create the driver, from the HAL. let irq = interrupt::take!(USBD); let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -89,9 +80,9 @@ async fn main(_spawner: Spawner) { msos_writer.configuration(0); msos_writer.function(0); msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); - msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new_multi_string( - msos::RegistryPropertyFeatureDescriptor::DEVICE_INTERFACE_GUIDS_NAME, - DEVICE_INTERFACE_GUIDS, + msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), )); // Build the builder. @@ -126,7 +117,7 @@ impl From for Disconnected { } } -async fn echo<'d, T: Instance + 'd, P: UsbSupply + 'd>( +async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, ) -> Result<(), Disconnected> { let mut buf = [0; 64];