diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 31d1f4ca..54a8f27c 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -13,6 +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"] default = ["usbd-hid"] [dependencies] @@ -24,7 +25,7 @@ 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 } +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/builder.rs b/embassy-usb/src/builder.rs index 2c42fd64..d1cbf674 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -3,6 +3,8 @@ use heapless::Vec; use crate::control::ControlHandler; use crate::descriptor::{BosWriter, DescriptorWriter}; use crate::driver::{Driver, Endpoint, EndpointType}; +#[cfg(feature = "msos-descriptor")] +use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; use crate::types::*; use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; @@ -130,7 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> { device_descriptor: DescriptorWriter<'d>, config_descriptor: DescriptorWriter<'d>, bos_descriptor: BosWriter<'d>, - msos_descriptor: Option>, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter<'d>, } impl<'d, D: Driver<'d>> Builder<'d, D> { @@ -145,6 +149,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { device_descriptor_buf: &'d mut [u8], config_descriptor_buf: &'d mut [u8], bos_descriptor_buf: &'d mut [u8], + #[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8], control_buf: &'d mut [u8], handler: Option<&'d dyn DeviceStateHandler>, ) -> Self { @@ -183,12 +188,17 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { device_descriptor, config_descriptor, bos_descriptor, - msos_descriptor: None, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf), } } /// Creates the [`UsbDevice`] instance with the configuration in this builder. pub fn build(mut self) -> UsbDevice<'d, D> { + #[cfg(feature = "msos-descriptor")] + let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor); + self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); @@ -201,7 +211,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { self.bos_descriptor.writer.into_buf(), self.interfaces, self.control_buf, - self.msos_descriptor, + #[cfg(feature = "msos-descriptor")] + msos_descriptor, ) } @@ -218,14 +229,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { /// /// If it's not set, no IAD descriptor is added. pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { + let first_interface = InterfaceNumber::new(self.interfaces.len() as u8); let iface_count_index = if self.config.composite_with_iads { - self.config_descriptor.iad( - InterfaceNumber::new(self.interfaces.len() as _), - 0, - class, - subclass, - protocol, - ); + self.config_descriptor + .iad(first_interface, 0, class, subclass, protocol); Some(self.config_descriptor.position() - 5) } else { @@ -235,19 +242,31 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { FunctionBuilder { builder: self, iface_count_index, + + #[cfg(feature = "msos-descriptor")] + first_interface, } } + #[cfg(feature = "msos-descriptor")] /// Add an MS OS 2.0 Descriptor Set. /// /// Panics if called more than once. - pub fn msos_descriptor(&mut self, msos_descriptor: crate::msos::MsOsDescriptorSet<'d>) { - if self.msos_descriptor.is_some() { - panic!("msos_descriptor already set"); - } - self.msos_descriptor - .insert(msos_descriptor) - .write_bos_capability(&mut self.bos_descriptor); + pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) { + self.msos_descriptor.header(windows_version, vendor_code); + } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Device Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + self.msos_descriptor.device_feature(desc); + } + + #[cfg(feature = "msos-descriptor")] + /// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that + /// do not add their own. + pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> { + &mut self.msos_descriptor } } @@ -259,6 +278,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { builder: &'a mut Builder<'d, D>, iface_count_index: Option, + + #[cfg(feature = "msos-descriptor")] + first_interface: InterfaceNumber, +} + +impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> { + fn drop(&mut self) { + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.end_function(); + } } impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { @@ -288,6 +317,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { next_alt_setting_number: 0, } } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Function Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + if !self.builder.msos_descriptor.is_in_config_subset() { + self.builder.msos_descriptor.configuration(0); + } + + if !self.builder.msos_descriptor.is_in_function_subset() { + self.builder.msos_descriptor.function(self.first_interface.0); + } + + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.function_feature(desc); + } } /// Interface builder. diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 948b8d52..aec18524 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -136,8 +136,8 @@ struct Inner<'d, D: Driver<'d>> { set_address_pending: bool, interfaces: Vec, MAX_INTERFACE_COUNT>, - - msos_descriptor: Option>, + #[cfg(feature = "msos-descriptor")] + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { @@ -150,7 +150,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { bos_descriptor: &'d [u8], interfaces: Vec, MAX_INTERFACE_COUNT>, control_buf: &'d mut [u8], - msos_descriptor: Option>, + #[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, ) -> UsbDevice<'d, D> { // Start the USB bus. // This prevent further allocation by consuming the driver. @@ -174,6 +174,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { address: 0, set_address_pending: false, interfaces, + #[cfg(feature = "msos-descriptor")] msos_descriptor, }, } @@ -608,11 +609,12 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { None => InResponse::Rejected, } } + #[cfg(feature = "msos-descriptor")] (RequestType::Vendor, Recipient::Device) => { - if let Some(msos) = &self.msos_descriptor { - if req.request == msos.vendor_code() && req.index == 7 { + if !self.msos_descriptor.is_empty() { + if req.request == self.msos_descriptor.vendor_code() && req.index == 7 { // Index 7 retrieves the MS OS Descriptor Set - InResponse::Accepted(msos.descriptor()) + InResponse::Accepted(self.msos_descriptor.descriptor()) } else { InResponse::Rejected } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index df846125..360f80d9 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "msos-descriptor")] + //! Microsoft OS Descriptors //! //! @@ -5,10 +7,9 @@ use core::mem::size_of; use core::ops::Range; -pub use widestring::{u16cstr, U16CStr}; +pub use widestring::{u16cstr, u16str, U16CStr, U16Str}; -use crate::descriptor::{capability_type, BosWriter}; -use crate::types::InterfaceNumber; +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()) @@ -17,13 +18,12 @@ fn write_u16>(buf: &mut [u8], range: Range, data: T) { /// A serialized Microsoft OS 2.0 Descriptor set. /// /// Create with [`DeviceDescriptorSetBuilder`]. -pub struct MsOsDescriptorSet<'a> { - descriptor: &'a [u8], - windows_version: u32, +pub struct MsOsDescriptorSet<'d> { + descriptor: &'d [u8], vendor_code: u8, } -impl<'a> MsOsDescriptorSet<'a> { +impl<'d> MsOsDescriptorSet<'d> { pub fn descriptor(&self) -> &[u8] { self.descriptor } @@ -32,9 +32,150 @@ impl<'a> MsOsDescriptorSet<'a> { self.vendor_code } - pub fn write_bos_capability(&self, bos: &mut BosWriter) { - let windows_version = self.windows_version.to_le_bytes(); - let len = self.descriptor.len().to_le_bytes(); + pub fn is_empty(&self) -> bool { + self.descriptor.is_empty() + } +} + +/// Writes a Microsoft OS 2.0 Descriptor set into a buffer. +pub struct MsOsDescriptorWriter<'d> { + pub buf: &'d mut [u8], + + position: usize, + config_mark: Option, + function_mark: Option, + vendor_code: u8, +} + +impl<'d> MsOsDescriptorWriter<'d> { + pub(crate) fn new(buf: &'d mut [u8]) -> Self { + MsOsDescriptorWriter { + buf, + position: 0, + config_mark: None, + function_mark: None, + vendor_code: 0, + } + } + + pub(crate) fn build(mut self, bos: &mut BosWriter) -> MsOsDescriptorSet<'d> { + self.end(); + + if self.is_empty() { + MsOsDescriptorSet { + descriptor: &[], + vendor_code: 0, + } + } else { + self.write_bos(bos); + MsOsDescriptorSet { + descriptor: &self.buf[..self.position], + vendor_code: self.vendor_code, + } + } + } + + pub fn is_empty(&self) -> bool { + self.position == 0 + } + + pub fn is_in_config_subset(&self) -> bool { + self.config_mark.is_some() + } + + pub fn is_in_function_subset(&self) -> bool { + self.function_mark.is_some() + } + + /// Write the MS OS descriptor set header. + /// + /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] + /// module. + /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. + pub fn header(&mut self, windows_version: u32, vendor_code: u8) { + assert!(self.is_empty(), "You can only call MsOsDescriptorWriter::header once"); + self.write(DescriptorSetHeader::new(windows_version)); + self.vendor_code = vendor_code; + } + + /// Add a device level feature descriptor. + /// + /// Note that some feature descriptors may only be used at the device level in non-composite devices. + /// Those features must be written before the first call to [`Self::configuration`]. + pub fn device_feature(&mut self, desc: T) { + assert!( + !self.is_empty(), + "device features may only be added after the header is written" + ); + assert!( + self.config_mark.is_none(), + "device features must be added before the first configuration subset" + ); + self.write(desc); + } + + /// Add a configuration subset. + pub fn configuration(&mut self, config: u8) { + assert!( + !self.is_empty(), + "MsOsDescriptorWriter: configuration must be called after header" + ); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + self.config_mark = Some(self.position); + self.write(ConfigurationSubsetHeader::new(config)); + } + + /// Add a function subset. + pub fn function(&mut self, first_interface: u8) { + assert!( + self.config_mark.is_some(), + "MsOsDescriptorWriter: function subset requires a configuration subset" + ); + self.end_function(); + self.function_mark = Some(self.position); + self.write(FunctionSubsetHeader::new(first_interface)); + } + + /// Add a function level feature descriptor. + /// + /// Note that some features may only be used at the function level. Those features must be written after a call + /// to [`Self::function`]. + pub fn function_feature(&mut self, desc: T) { + assert!( + self.function_mark.is_some(), + "function features may only be added to a function subset" + ); + self.write(desc); + } + + pub fn end_function(&mut self) { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + } + + fn write(&mut self, desc: T) { + desc.write_to(&mut self.buf[self.position..]); + self.position += desc.size(); + } + + fn end_subset(buf: &mut [u8], position: usize, mark: &mut Option) { + if let Some(mark) = mark.take() { + let len = position - mark; + let p = mark + T::LENGTH_OFFSET; + buf[p..(p + 2)].copy_from_slice(&(len as u16).to_le_bytes()); + } + } + + fn end(&mut self) { + if self.position > 0 { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + Self::end_subset::(self.buf, self.position, &mut Some(0)); + } + } + + fn write_bos(&mut self, bos: &mut BosWriter) { + let windows_version = &self.buf[4..8]; + let len = (self.position as u16).to_le_bytes(); bos.capability( capability_type::PLATFORM, &[ @@ -67,30 +208,7 @@ impl<'a> MsOsDescriptorSet<'a> { self.vendor_code, 0x0, // Device does not support alternate enumeration ], - ) - } -} - -/// A helper struct to implement the different descriptor set builders. -struct DescriptorSetBuilder<'a> { - used: usize, - buf: &'a mut [u8], -} - -impl<'a> DescriptorSetBuilder<'a> { - pub fn descriptor(&mut self, desc: T) - where - T: Descriptor + 'a, - { - let size = desc.size(); - let start = self.used; - let end = start + size; - desc.write_to(&mut self.buf[start..end]); - self.used += size; - } - - pub fn remaining(&mut self) -> &mut [u8] { - &mut self.buf[self.used..] + ); } } @@ -120,182 +238,27 @@ pub mod windows_version { pub const WIN10: u32 = 0x0A000000; } -/// Helps build a Microsoft OS 2.0 Descriptor set. -/// -/// # Example -/// ```rust -/// # use embassy_usb::types::InterfaceNumber; -/// # use embassy_usb::msos::*; -/// # let cdc_interface = unsafe { core::mem::transmute::(0) }; -/// # let dfu_interface = unsafe { core::mem::transmute::(1) }; -/// let mut buf = [0u8; 256]; -/// let mut builder = DeviceDescriptorSetBuilder::new(&mut buf[..], windows_version::WIN8_1); -/// builder.feature(MinimumRecoveryTimeDescriptor::new(5, 10)); -/// builder.feature(ModelIdDescriptor::new(0xdeadbeef1234u128)); -/// builder.configuration(1, |conf| { -/// conf.function(cdc_interface, |func| { -/// func.winusb_device(); -/// func.feature(VendorRevisionDescriptor::new(1)); -/// }); -/// conf.function(dfu_interface, |func| { -/// func.winusb_device(); -/// func.feature(VendorRevisionDescriptor::new(1)); -/// }); -/// }); -/// ``` -pub struct DeviceDescriptorSetBuilder<'a> { - builder: DescriptorSetBuilder<'a>, - windows_version: u32, - vendor_code: u8, -} +mod sealed { + use core::mem::size_of; -impl<'a> DeviceDescriptorSetBuilder<'a> { - /// Create a device descriptor set builder. - /// - /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] - /// module. - /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. - pub fn new<'b: 'a>(buf: &'b mut [u8], windows_version: u32, vendor_code: u8) -> Self { - let mut builder = DescriptorSetBuilder { used: 0, buf }; - builder.descriptor(DescriptorSetHeader { - wLength: (size_of::() as u16).to_le(), - wDescriptorType: (DescriptorSetHeader::TYPE as u16).to_le(), - dwWindowsVersion: windows_version.to_le(), - wTotalLength: 0, - }); - Self { - builder, - windows_version, - vendor_code, + /// A trait for descriptors + pub trait Descriptor: Sized { + const TYPE: super::DescriptorType; + + /// The size of the descriptor's header. + fn size(&self) -> usize { + size_of::() } + + fn write_to(&self, buf: &mut [u8]); } - /// Add a device-level feature descriptor. - /// - /// Note that some feature descriptors may only be used at the device level in non-composite devices. - pub fn feature(&mut self, desc: T) - where - T: Descriptor + DeviceLevelDescriptor + 'a, - { - self.builder.descriptor(desc) - } - - /// Add a configuration subset. - pub fn configuration(&mut self, configuration: u8, build_conf: impl FnOnce(&mut ConfigurationSubsetBuilder<'_>)) { - let mut cb = ConfigurationSubsetBuilder::new(self.builder.remaining(), configuration); - build_conf(&mut cb); - self.builder.used += cb.finalize(); - } - - /// Finishes writing the data. - pub fn finalize(self) -> MsOsDescriptorSet<'a> { - let used = self.builder.used; - let buf = self.builder.buf; - // Update length in header with final length - let total_len = &mut buf[8..10]; - total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); - - MsOsDescriptorSet { - descriptor: &buf[..used], - windows_version: self.windows_version, - vendor_code: self.vendor_code, - } + pub trait DescriptorSet: Descriptor { + const LENGTH_OFFSET: usize; } } -pub struct ConfigurationSubsetBuilder<'a> { - builder: DescriptorSetBuilder<'a>, -} - -impl<'a> ConfigurationSubsetBuilder<'a> { - pub fn new<'b: 'a>(buf: &'b mut [u8], configuration: u8) -> Self { - let mut builder = DescriptorSetBuilder { used: 0, buf }; - builder.descriptor(ConfigurationSubsetHeader { - wLength: (size_of::() as u16).to_le(), - wDescriptorType: (ConfigurationSubsetHeader::TYPE as u16).to_le(), - bConfigurationValue: configuration, - bReserved: 0, - wTotalLength: 0, - }); - Self { builder } - } - - /// Add a function subset. - pub fn function(&mut self, interface: InterfaceNumber, build_func: impl FnOnce(&mut FunctionSubsetBuilder<'_>)) { - let mut fb = FunctionSubsetBuilder::new(self.builder.remaining(), interface); - build_func(&mut fb); - self.builder.used += fb.finalize(); - } - - /// Finishes writing the data. Returns the total number of bytes used by the descriptor set. - pub fn finalize(self) -> usize { - let used = self.builder.used; - let buf = self.builder.buf; - // Update length in header with final length - let total_len = &mut buf[6..8]; - total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); - used - } -} - -pub struct FunctionSubsetBuilder<'a> { - builder: DescriptorSetBuilder<'a>, -} - -impl<'a> FunctionSubsetBuilder<'a> { - pub fn new<'b: 'a>(buf: &'b mut [u8], interface: InterfaceNumber) -> Self { - let mut builder = DescriptorSetBuilder { used: 0, buf }; - builder.descriptor(FunctionSubsetHeader { - wLength: (size_of::() as u16).to_le(), - wDescriptorType: (FunctionSubsetHeader::TYPE as u16).to_le(), - bFirstInterface: interface.0, - bReserved: 0, - wSubsetLength: 0, - }); - Self { builder } - } - - /// Add a function-level descriptor. - /// - /// Note that many descriptors can only be used at function-level in a composite device. - pub fn feature(&mut self, desc: T) - where - T: Descriptor + FunctionLevelDescriptor + 'a, - { - self.builder.descriptor(desc) - } - - /// Adds the feature descriptors to configure this function to use the WinUSB driver. - /// - /// Adds a compatible id descriptor "WINUSB" and a registry descriptor that sets the DeviceInterfaceGUID to the - /// USB_DEVICE GUID. - pub fn winusb_device(&mut self) { - self.feature(CompatibleIdFeatureDescriptor::new_winusb()); - self.feature(RegistryPropertyFeatureDescriptor::new_usb_deviceinterfaceguid()); - } - - /// Finishes writing the data. Returns the total number of bytes used by the descriptor set. - pub fn finalize(self) -> usize { - let used = self.builder.used; - let buf = self.builder.buf; - // Update length in header with final length - let total_len = &mut buf[6..8]; - total_len.copy_from_slice((used as u16).to_le_bytes().as_slice()); - used - } -} - -/// A trait for descriptors -pub trait Descriptor: Sized { - const TYPE: DescriptorType; - - /// The size of the descriptor's header. - fn size(&self) -> usize { - size_of::() - } - - fn write_to(&self, buf: &mut [u8]); -} +use sealed::*; /// Copies the data of `t` into `buf`. /// @@ -303,7 +266,7 @@ pub trait Descriptor: Sized { /// 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()); + assert!(buf.len() >= bytes.len(), "MSOS descriptor buffer full"); (&mut buf[..bytes.len()]).copy_from_slice(bytes); } @@ -354,6 +317,17 @@ pub struct DescriptorSetHeader { wTotalLength: u16, } +impl DescriptorSetHeader { + pub fn new(windows_version: u32) -> Self { + DescriptorSetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + dwWindowsVersion: windows_version.to_le(), + wTotalLength: 0, + } + } +} + impl Descriptor for DescriptorSetHeader { const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor; fn write_to(&self, buf: &mut [u8]) { @@ -361,6 +335,10 @@ impl Descriptor for DescriptorSetHeader { } } +impl DescriptorSet for DescriptorSetHeader { + const LENGTH_OFFSET: usize = 8; +} + /// Table 11. Configuration subset header. #[allow(non_snake_case)] #[repr(C, packed(1))] @@ -372,6 +350,18 @@ pub struct ConfigurationSubsetHeader { wTotalLength: u16, } +impl ConfigurationSubsetHeader { + pub fn new(config: u8) -> Self { + ConfigurationSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bConfigurationValue: config, + bReserved: 0, + wTotalLength: 0, + } + } +} + impl Descriptor for ConfigurationSubsetHeader { const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration; fn write_to(&self, buf: &mut [u8]) { @@ -379,6 +369,10 @@ impl Descriptor for ConfigurationSubsetHeader { } } +impl DescriptorSet for ConfigurationSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + /// Table 12. Function subset header. #[allow(non_snake_case)] #[repr(C, packed(1))] @@ -390,6 +384,18 @@ pub struct FunctionSubsetHeader { wSubsetLength: u16, } +impl FunctionSubsetHeader { + pub fn new(first_interface: u8) -> Self { + FunctionSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bFirstInterface: first_interface, + bReserved: 0, + wSubsetLength: 0, + } + } +} + impl Descriptor for FunctionSubsetHeader { const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction; fn write_to(&self, buf: &mut [u8]) { @@ -397,16 +403,17 @@ impl Descriptor for FunctionSubsetHeader { } } +impl DescriptorSet for FunctionSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + // Feature Descriptors /// A marker trait for feature descriptors that are valid at the device level. -pub trait DeviceLevelDescriptor {} +pub trait DeviceLevelDescriptor: Descriptor {} /// A marker trait for feature descriptors that are valid at the function level. -pub trait FunctionLevelDescriptor { - /// `true` when the feature descriptor may only be used at the function level in composite devices. - const COMPOSITE_ONLY: bool = false; -} +pub trait FunctionLevelDescriptor: Descriptor {} /// Table 13. Microsoft OS 2.0 compatible ID descriptor. #[allow(non_snake_case)] @@ -419,9 +426,7 @@ pub struct CompatibleIdFeatureDescriptor { } impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {} -impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor { - const COMPOSITE_ONLY: bool = true; -} +impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {} impl Descriptor for CompatibleIdFeatureDescriptor { const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId; @@ -462,16 +467,12 @@ pub struct RegistryPropertyFeatureDescriptor<'a> { wLength: u16, wDescriptorType: u16, wPropertyDataType: u16, - wPropertyNameLength: u16, PropertyName: &'a [u8], - wPropertyDataLength: u16, PropertyData: &'a [u8], } impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} -impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> { - const COMPOSITE_ONLY: bool = true; -} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; @@ -479,45 +480,22 @@ impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { 10 + self.PropertyName.len() + self.PropertyData.len() } fn write_to(&self, buf: &mut [u8]) { - assert!(buf.len() >= self.size()); - assert!(self.wPropertyNameLength as usize == self.PropertyName.len()); - assert!(self.wPropertyDataLength as usize == self.PropertyData.len()); + 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.wPropertyNameLength); + 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.wPropertyDataLength); + 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> { - /// 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 { - 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(), - wPropertyNameLength: (name.len() as u16).to_le(), - PropertyName: name, - wPropertyDataLength: (data.len() as u16).to_le(), - 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 that sets the DeviceInterfaceGUIDs to the device interface class for USB devices which are - /// attached to a USB hub. - pub fn new_usb_deviceinterfaceguid() -> Self { + pub const DEVICE_INTERFACE_GUIDS_NAME: &U16CStr = { // Can't use defmt::panic in constant expressions (inside u16cstr!) macro_rules! panic { ($($x:tt)*) => { @@ -527,10 +505,25 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { }; } - Self::new_multi_string( - u16cstr!("DeviceInterfaceGUIDs"), - u16cstr!("{A5DCBF10-6530-11D2-901F-00C04FB951ED}").as_slice_with_nul(), - ) + 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. @@ -558,6 +551,10 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> { /// 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) }, diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 95d93987..cfdda076 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0" [features] default = ["nightly"] +msos-descriptor = ["embassy-usb/msos-descriptor"] nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net", "embassy-lora", "lorawan-device", "lorawan"] @@ -34,4 +35,8 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" usbd-hid = "0.6.0" -serde = { version = "1.0.136", default-features = false } \ No newline at end of file +serde = { version = "1.0.136", default-features = false } + +[[bin]] +name = "usb_serial_winusb" +required-features = ["msos-descriptor"] diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs new file mode 100644 index 00000000..443379a0 --- /dev/null +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -0,0 +1,139 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +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::{interrupt, pac}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +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() +}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + // 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)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + None, + ); + + builder.msos_descriptor(windows_version::WIN8_1, 2); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Since we want to create MS OS feature descriptors that apply to a function that has already been added to the + // builder, need to get the MsOsDescriptorWriter from the builder and manually add those descriptors. + // Inside a class constructor, you would just need to call `FunctionBuilder::msos_feature` instead. + let msos_writer = builder.msos_writer(); + 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, + )); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd, P: UsbSupply + 'd>( + class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +}