diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2023-02-07 19:31:10 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-02-07 19:31:10 +0000 |
| commit | 366fab5b872e5d00fd84408f04cf00faf988d160 (patch) | |
| tree | 9b45fb26d9a32b806931fcc6d379af00f4ffec21 | |
| parent | a7fa7d0de2fa7b8fab889879b6003df8427c6841 (diff) | |
| parent | aa21aebb0b321a2085571e5be5fffcea4703584d (diff) | |
Merge #1189
1189: USB: Add MS OS Descriptors (alternate implementation) r=Dirbaio a=alexmoon
This is an alternate API for #1152 based on the work of `@mattico.` By switching to a writer-style API instead of a builder API some compile-time guarantees are lost, but it integrates better into the usb `Builder` and makes an api that can be used by USB device class implementations.
It also adds a feature flag so there is zero cost to the MS OS descriptors for devices that don't need to use them.
I've added an example based on `usb_serial` which tells Windows to use the generic `WinUSB` driver instead of the serial port driver for the device.
Comments are welcome. It would be nice to see either this or #1152 merged as my project is going to require the MS OS Descriptors soon.
Co-authored-by: Matt Ickstadt <[email protected]>
Co-authored-by: alexmoon <[email protected]>
| -rw-r--r-- | embassy-usb/Cargo.toml | 1 | ||||
| -rw-r--r-- | embassy-usb/src/builder.rs | 73 | ||||
| -rw-r--r-- | embassy-usb/src/lib.rs | 19 | ||||
| -rw-r--r-- | embassy-usb/src/msos.rs | 726 | ||||
| -rw-r--r-- | examples/nrf52840/Cargo.toml | 7 | ||||
| -rw-r--r-- | examples/nrf52840/src/bin/usb_serial_winusb.rs | 130 |
6 files changed, 948 insertions, 8 deletions
diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 1e567bb94..eb9ba36f4 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml | |||
| @@ -13,6 +13,7 @@ target = "thumbv7em-none-eabi" | |||
| 13 | [features] | 13 | [features] |
| 14 | defmt = ["dep:defmt", "embassy-usb-driver/defmt"] | 14 | defmt = ["dep:defmt", "embassy-usb-driver/defmt"] |
| 15 | usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] | 15 | usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] |
| 16 | msos-descriptor = [] | ||
| 16 | default = ["usbd-hid"] | 17 | default = ["usbd-hid"] |
| 17 | 18 | ||
| 18 | [dependencies] | 19 | [dependencies] |
diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 41b24fecf..d1cbf674b 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs | |||
| @@ -3,6 +3,8 @@ use heapless::Vec; | |||
| 3 | use crate::control::ControlHandler; | 3 | use crate::control::ControlHandler; |
| 4 | use crate::descriptor::{BosWriter, DescriptorWriter}; | 4 | use crate::descriptor::{BosWriter, DescriptorWriter}; |
| 5 | use crate::driver::{Driver, Endpoint, EndpointType}; | 5 | use crate::driver::{Driver, Endpoint, EndpointType}; |
| 6 | #[cfg(feature = "msos-descriptor")] | ||
| 7 | use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; | ||
| 6 | use crate::types::*; | 8 | use crate::types::*; |
| 7 | use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; | 9 | use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; |
| 8 | 10 | ||
| @@ -130,6 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> { | |||
| 130 | device_descriptor: DescriptorWriter<'d>, | 132 | device_descriptor: DescriptorWriter<'d>, |
| 131 | config_descriptor: DescriptorWriter<'d>, | 133 | config_descriptor: DescriptorWriter<'d>, |
| 132 | bos_descriptor: BosWriter<'d>, | 134 | bos_descriptor: BosWriter<'d>, |
| 135 | |||
| 136 | #[cfg(feature = "msos-descriptor")] | ||
| 137 | msos_descriptor: MsOsDescriptorWriter<'d>, | ||
| 133 | } | 138 | } |
| 134 | 139 | ||
| 135 | impl<'d, D: Driver<'d>> Builder<'d, D> { | 140 | impl<'d, D: Driver<'d>> Builder<'d, D> { |
| @@ -144,6 +149,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { | |||
| 144 | device_descriptor_buf: &'d mut [u8], | 149 | device_descriptor_buf: &'d mut [u8], |
| 145 | config_descriptor_buf: &'d mut [u8], | 150 | config_descriptor_buf: &'d mut [u8], |
| 146 | bos_descriptor_buf: &'d mut [u8], | 151 | bos_descriptor_buf: &'d mut [u8], |
| 152 | #[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8], | ||
| 147 | control_buf: &'d mut [u8], | 153 | control_buf: &'d mut [u8], |
| 148 | handler: Option<&'d dyn DeviceStateHandler>, | 154 | handler: Option<&'d dyn DeviceStateHandler>, |
| 149 | ) -> Self { | 155 | ) -> Self { |
| @@ -182,11 +188,17 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { | |||
| 182 | device_descriptor, | 188 | device_descriptor, |
| 183 | config_descriptor, | 189 | config_descriptor, |
| 184 | bos_descriptor, | 190 | bos_descriptor, |
| 191 | |||
| 192 | #[cfg(feature = "msos-descriptor")] | ||
| 193 | msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf), | ||
| 185 | } | 194 | } |
| 186 | } | 195 | } |
| 187 | 196 | ||
| 188 | /// Creates the [`UsbDevice`] instance with the configuration in this builder. | 197 | /// Creates the [`UsbDevice`] instance with the configuration in this builder. |
| 189 | pub fn build(mut self) -> UsbDevice<'d, D> { | 198 | pub fn build(mut self) -> UsbDevice<'d, D> { |
| 199 | #[cfg(feature = "msos-descriptor")] | ||
| 200 | let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor); | ||
| 201 | |||
| 190 | self.config_descriptor.end_configuration(); | 202 | self.config_descriptor.end_configuration(); |
| 191 | self.bos_descriptor.end_bos(); | 203 | self.bos_descriptor.end_bos(); |
| 192 | 204 | ||
| @@ -199,6 +211,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { | |||
| 199 | self.bos_descriptor.writer.into_buf(), | 211 | self.bos_descriptor.writer.into_buf(), |
| 200 | self.interfaces, | 212 | self.interfaces, |
| 201 | self.control_buf, | 213 | self.control_buf, |
| 214 | #[cfg(feature = "msos-descriptor")] | ||
| 215 | msos_descriptor, | ||
| 202 | ) | 216 | ) |
| 203 | } | 217 | } |
| 204 | 218 | ||
| @@ -215,14 +229,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { | |||
| 215 | /// | 229 | /// |
| 216 | /// If it's not set, no IAD descriptor is added. | 230 | /// If it's not set, no IAD descriptor is added. |
| 217 | pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { | 231 | pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { |
| 232 | let first_interface = InterfaceNumber::new(self.interfaces.len() as u8); | ||
| 218 | let iface_count_index = if self.config.composite_with_iads { | 233 | let iface_count_index = if self.config.composite_with_iads { |
| 219 | self.config_descriptor.iad( | 234 | self.config_descriptor |
| 220 | InterfaceNumber::new(self.interfaces.len() as _), | 235 | .iad(first_interface, 0, class, subclass, protocol); |
| 221 | 0, | ||
| 222 | class, | ||
| 223 | subclass, | ||
| 224 | protocol, | ||
| 225 | ); | ||
| 226 | 236 | ||
| 227 | Some(self.config_descriptor.position() - 5) | 237 | Some(self.config_descriptor.position() - 5) |
| 228 | } else { | 238 | } else { |
| @@ -232,8 +242,32 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { | |||
| 232 | FunctionBuilder { | 242 | FunctionBuilder { |
| 233 | builder: self, | 243 | builder: self, |
| 234 | iface_count_index, | 244 | iface_count_index, |
| 245 | |||
| 246 | #[cfg(feature = "msos-descriptor")] | ||
| 247 | first_interface, | ||
| 235 | } | 248 | } |
| 236 | } | 249 | } |
| 250 | |||
| 251 | #[cfg(feature = "msos-descriptor")] | ||
| 252 | /// Add an MS OS 2.0 Descriptor Set. | ||
| 253 | /// | ||
| 254 | /// Panics if called more than once. | ||
| 255 | pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) { | ||
| 256 | self.msos_descriptor.header(windows_version, vendor_code); | ||
| 257 | } | ||
| 258 | |||
| 259 | #[cfg(feature = "msos-descriptor")] | ||
| 260 | /// Add an MS OS 2.0 Device Level Feature Descriptor. | ||
| 261 | pub fn msos_feature<T: DeviceLevelDescriptor>(&mut self, desc: T) { | ||
| 262 | self.msos_descriptor.device_feature(desc); | ||
| 263 | } | ||
| 264 | |||
| 265 | #[cfg(feature = "msos-descriptor")] | ||
| 266 | /// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that | ||
| 267 | /// do not add their own. | ||
| 268 | pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> { | ||
| 269 | &mut self.msos_descriptor | ||
| 270 | } | ||
| 237 | } | 271 | } |
| 238 | 272 | ||
| 239 | /// Function builder. | 273 | /// Function builder. |
| @@ -244,6 +278,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { | |||
| 244 | pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { | 278 | pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { |
| 245 | builder: &'a mut Builder<'d, D>, | 279 | builder: &'a mut Builder<'d, D>, |
| 246 | iface_count_index: Option<usize>, | 280 | iface_count_index: Option<usize>, |
| 281 | |||
| 282 | #[cfg(feature = "msos-descriptor")] | ||
| 283 | first_interface: InterfaceNumber, | ||
| 284 | } | ||
| 285 | |||
| 286 | impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> { | ||
| 287 | fn drop(&mut self) { | ||
| 288 | #[cfg(feature = "msos-descriptor")] | ||
| 289 | self.builder.msos_descriptor.end_function(); | ||
| 290 | } | ||
| 247 | } | 291 | } |
| 248 | 292 | ||
| 249 | impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { | 293 | impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { |
| @@ -273,6 +317,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { | |||
| 273 | next_alt_setting_number: 0, | 317 | next_alt_setting_number: 0, |
| 274 | } | 318 | } |
| 275 | } | 319 | } |
| 320 | |||
| 321 | #[cfg(feature = "msos-descriptor")] | ||
| 322 | /// Add an MS OS 2.0 Function Level Feature Descriptor. | ||
| 323 | pub fn msos_feature<T: FunctionLevelDescriptor>(&mut self, desc: T) { | ||
| 324 | if !self.builder.msos_descriptor.is_in_config_subset() { | ||
| 325 | self.builder.msos_descriptor.configuration(0); | ||
| 326 | } | ||
| 327 | |||
| 328 | if !self.builder.msos_descriptor.is_in_function_subset() { | ||
| 329 | self.builder.msos_descriptor.function(self.first_interface.0); | ||
| 330 | } | ||
| 331 | |||
| 332 | #[cfg(feature = "msos-descriptor")] | ||
| 333 | self.builder.msos_descriptor.function_feature(desc); | ||
| 334 | } | ||
| 276 | } | 335 | } |
| 277 | 336 | ||
| 278 | /// Interface builder. | 337 | /// Interface builder. |
diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 2656af29d..aec18524b 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs | |||
| @@ -13,6 +13,7 @@ pub mod class; | |||
| 13 | pub mod control; | 13 | pub mod control; |
| 14 | pub mod descriptor; | 14 | pub mod descriptor; |
| 15 | mod descriptor_reader; | 15 | mod descriptor_reader; |
| 16 | pub mod msos; | ||
| 16 | pub mod types; | 17 | pub mod types; |
| 17 | 18 | ||
| 18 | use embassy_futures::select::{select, Either}; | 19 | use embassy_futures::select::{select, Either}; |
| @@ -135,6 +136,8 @@ struct Inner<'d, D: Driver<'d>> { | |||
| 135 | set_address_pending: bool, | 136 | set_address_pending: bool, |
| 136 | 137 | ||
| 137 | interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>, | 138 | interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>, |
| 139 | #[cfg(feature = "msos-descriptor")] | ||
| 140 | msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, | ||
| 138 | } | 141 | } |
| 139 | 142 | ||
| 140 | impl<'d, D: Driver<'d>> UsbDevice<'d, D> { | 143 | impl<'d, D: Driver<'d>> UsbDevice<'d, D> { |
| @@ -147,6 +150,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { | |||
| 147 | bos_descriptor: &'d [u8], | 150 | bos_descriptor: &'d [u8], |
| 148 | interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>, | 151 | interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>, |
| 149 | control_buf: &'d mut [u8], | 152 | control_buf: &'d mut [u8], |
| 153 | #[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, | ||
| 150 | ) -> UsbDevice<'d, D> { | 154 | ) -> UsbDevice<'d, D> { |
| 151 | // Start the USB bus. | 155 | // Start the USB bus. |
| 152 | // This prevent further allocation by consuming the driver. | 156 | // This prevent further allocation by consuming the driver. |
| @@ -170,6 +174,8 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { | |||
| 170 | address: 0, | 174 | address: 0, |
| 171 | set_address_pending: false, | 175 | set_address_pending: false, |
| 172 | interfaces, | 176 | interfaces, |
| 177 | #[cfg(feature = "msos-descriptor")] | ||
| 178 | msos_descriptor, | ||
| 173 | }, | 179 | }, |
| 174 | } | 180 | } |
| 175 | } | 181 | } |
| @@ -603,6 +609,19 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { | |||
| 603 | None => InResponse::Rejected, | 609 | None => InResponse::Rejected, |
| 604 | } | 610 | } |
| 605 | } | 611 | } |
| 612 | #[cfg(feature = "msos-descriptor")] | ||
| 613 | (RequestType::Vendor, Recipient::Device) => { | ||
| 614 | if !self.msos_descriptor.is_empty() { | ||
| 615 | if req.request == self.msos_descriptor.vendor_code() && req.index == 7 { | ||
| 616 | // Index 7 retrieves the MS OS Descriptor Set | ||
| 617 | InResponse::Accepted(self.msos_descriptor.descriptor()) | ||
| 618 | } else { | ||
| 619 | InResponse::Rejected | ||
| 620 | } | ||
| 621 | } else { | ||
| 622 | InResponse::Rejected | ||
| 623 | } | ||
| 624 | } | ||
| 606 | _ => InResponse::Rejected, | 625 | _ => InResponse::Rejected, |
| 607 | } | 626 | } |
| 608 | } | 627 | } |
diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs new file mode 100644 index 000000000..19ed34979 --- /dev/null +++ b/embassy-usb/src/msos.rs | |||
| @@ -0,0 +1,726 @@ | |||
| 1 | #![cfg(feature = "msos-descriptor")] | ||
| 2 | |||
| 3 | //! Microsoft OS Descriptors | ||
| 4 | //! | ||
| 5 | //! <https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification> | ||
| 6 | |||
| 7 | use core::mem::size_of; | ||
| 8 | |||
| 9 | use super::{capability_type, BosWriter}; | ||
| 10 | |||
| 11 | /// A serialized Microsoft OS 2.0 Descriptor set. | ||
| 12 | /// | ||
| 13 | /// Create with [`DeviceDescriptorSetBuilder`]. | ||
| 14 | pub struct MsOsDescriptorSet<'d> { | ||
| 15 | descriptor: &'d [u8], | ||
| 16 | vendor_code: u8, | ||
| 17 | } | ||
| 18 | |||
| 19 | impl<'d> MsOsDescriptorSet<'d> { | ||
| 20 | /// Gets the raw bytes of the MS OS descriptor | ||
| 21 | pub fn descriptor(&self) -> &[u8] { | ||
| 22 | self.descriptor | ||
| 23 | } | ||
| 24 | |||
| 25 | /// Gets the vendor code used by the host to retrieve the MS OS descriptor | ||
| 26 | pub fn vendor_code(&self) -> u8 { | ||
| 27 | self.vendor_code | ||
| 28 | } | ||
| 29 | |||
| 30 | /// Returns `true` if no MS OS descriptor data is available | ||
| 31 | pub fn is_empty(&self) -> bool { | ||
| 32 | self.descriptor.is_empty() | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | /// Writes a Microsoft OS 2.0 Descriptor set into a buffer. | ||
| 37 | pub struct MsOsDescriptorWriter<'d> { | ||
| 38 | buf: &'d mut [u8], | ||
| 39 | |||
| 40 | position: usize, | ||
| 41 | config_mark: Option<usize>, | ||
| 42 | function_mark: Option<usize>, | ||
| 43 | vendor_code: u8, | ||
| 44 | } | ||
| 45 | |||
| 46 | impl<'d> MsOsDescriptorWriter<'d> { | ||
| 47 | pub(crate) fn new(buf: &'d mut [u8]) -> Self { | ||
| 48 | MsOsDescriptorWriter { | ||
| 49 | buf, | ||
| 50 | position: 0, | ||
| 51 | config_mark: None, | ||
| 52 | function_mark: None, | ||
| 53 | vendor_code: 0, | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | pub(crate) fn build(mut self, bos: &mut BosWriter) -> MsOsDescriptorSet<'d> { | ||
| 58 | self.end(); | ||
| 59 | |||
| 60 | if self.is_empty() { | ||
| 61 | MsOsDescriptorSet { | ||
| 62 | descriptor: &[], | ||
| 63 | vendor_code: 0, | ||
| 64 | } | ||
| 65 | } else { | ||
| 66 | self.write_bos(bos); | ||
| 67 | MsOsDescriptorSet { | ||
| 68 | descriptor: &self.buf[..self.position], | ||
| 69 | vendor_code: self.vendor_code, | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | /// Returns `true` if the MS OS descriptor header has not yet been written | ||
| 75 | pub fn is_empty(&self) -> bool { | ||
| 76 | self.position == 0 | ||
| 77 | } | ||
| 78 | |||
| 79 | /// Returns `true` if a configuration subset header has been started | ||
| 80 | pub fn is_in_config_subset(&self) -> bool { | ||
| 81 | self.config_mark.is_some() | ||
| 82 | } | ||
| 83 | |||
| 84 | /// Returns `true` if a function subset header has been started and not yet ended | ||
| 85 | pub fn is_in_function_subset(&self) -> bool { | ||
| 86 | self.function_mark.is_some() | ||
| 87 | } | ||
| 88 | |||
| 89 | /// Write the MS OS descriptor set header. | ||
| 90 | /// | ||
| 91 | /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] | ||
| 92 | /// module. | ||
| 93 | /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. | ||
| 94 | pub fn header(&mut self, windows_version: u32, vendor_code: u8) { | ||
| 95 | assert!(self.is_empty(), "You can only call MsOsDescriptorWriter::header once"); | ||
| 96 | self.write(DescriptorSetHeader::new(windows_version)); | ||
| 97 | self.vendor_code = vendor_code; | ||
| 98 | } | ||
| 99 | |||
| 100 | /// Add a device level feature descriptor. | ||
| 101 | /// | ||
| 102 | /// Note that some feature descriptors may only be used at the device level in non-composite devices. | ||
| 103 | /// Those features must be written before the first call to [`Self::configuration`]. | ||
| 104 | pub fn device_feature<T: DeviceLevelDescriptor>(&mut self, desc: T) { | ||
| 105 | assert!( | ||
| 106 | !self.is_empty(), | ||
| 107 | "device features may only be added after the header is written" | ||
| 108 | ); | ||
| 109 | assert!( | ||
| 110 | self.config_mark.is_none(), | ||
| 111 | "device features must be added before the first configuration subset" | ||
| 112 | ); | ||
| 113 | self.write(desc); | ||
| 114 | } | ||
| 115 | |||
| 116 | /// Add a configuration subset. | ||
| 117 | pub fn configuration(&mut self, config: u8) { | ||
| 118 | assert!( | ||
| 119 | !self.is_empty(), | ||
| 120 | "MsOsDescriptorWriter: configuration must be called after header" | ||
| 121 | ); | ||
| 122 | Self::end_subset::<ConfigurationSubsetHeader>(self.buf, self.position, &mut self.config_mark); | ||
| 123 | self.config_mark = Some(self.position); | ||
| 124 | self.write(ConfigurationSubsetHeader::new(config)); | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Add a function subset. | ||
| 128 | pub fn function(&mut self, first_interface: u8) { | ||
| 129 | assert!( | ||
| 130 | self.config_mark.is_some(), | ||
| 131 | "MsOsDescriptorWriter: function subset requires a configuration subset" | ||
| 132 | ); | ||
| 133 | self.end_function(); | ||
| 134 | self.function_mark = Some(self.position); | ||
| 135 | self.write(FunctionSubsetHeader::new(first_interface)); | ||
| 136 | } | ||
| 137 | |||
| 138 | /// Add a function level feature descriptor. | ||
| 139 | /// | ||
| 140 | /// Note that some features may only be used at the function level. Those features must be written after a call | ||
| 141 | /// to [`Self::function`]. | ||
| 142 | pub fn function_feature<T: FunctionLevelDescriptor>(&mut self, desc: T) { | ||
| 143 | assert!( | ||
| 144 | self.function_mark.is_some(), | ||
| 145 | "function features may only be added to a function subset" | ||
| 146 | ); | ||
| 147 | self.write(desc); | ||
| 148 | } | ||
| 149 | |||
| 150 | /// Ends the current function subset (if any) | ||
| 151 | pub fn end_function(&mut self) { | ||
| 152 | Self::end_subset::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark); | ||
| 153 | } | ||
| 154 | |||
| 155 | fn write<T: Descriptor>(&mut self, desc: T) { | ||
| 156 | desc.write_to(&mut self.buf[self.position..]); | ||
| 157 | self.position += desc.size(); | ||
| 158 | } | ||
| 159 | |||
| 160 | fn end_subset<T: DescriptorSet>(buf: &mut [u8], position: usize, mark: &mut Option<usize>) { | ||
| 161 | if let Some(mark) = mark.take() { | ||
| 162 | let len = position - mark; | ||
| 163 | let p = mark + T::LENGTH_OFFSET; | ||
| 164 | buf[p..(p + 2)].copy_from_slice(&(len as u16).to_le_bytes()); | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | fn end(&mut self) { | ||
| 169 | if self.position > 0 { | ||
| 170 | Self::end_subset::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark); | ||
| 171 | Self::end_subset::<ConfigurationSubsetHeader>(self.buf, self.position, &mut self.config_mark); | ||
| 172 | Self::end_subset::<DescriptorSetHeader>(self.buf, self.position, &mut Some(0)); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | fn write_bos(&mut self, bos: &mut BosWriter) { | ||
| 177 | let windows_version = &self.buf[4..8]; | ||
| 178 | let len = (self.position as u16).to_le_bytes(); | ||
| 179 | bos.capability( | ||
| 180 | capability_type::PLATFORM, | ||
| 181 | &[ | ||
| 182 | 0, // reserved | ||
| 183 | // platform capability UUID, Microsoft OS 2.0 platform compabitility | ||
| 184 | 0xdf, | ||
| 185 | 0x60, | ||
| 186 | 0xdd, | ||
| 187 | 0xd8, | ||
| 188 | 0x89, | ||
| 189 | 0x45, | ||
| 190 | 0xc7, | ||
| 191 | 0x4c, | ||
| 192 | 0x9c, | ||
| 193 | 0xd2, | ||
| 194 | 0x65, | ||
| 195 | 0x9d, | ||
| 196 | 0x9e, | ||
| 197 | 0x64, | ||
| 198 | 0x8a, | ||
| 199 | 0x9f, | ||
| 200 | // Minimum compatible Windows version | ||
| 201 | windows_version[0], | ||
| 202 | windows_version[1], | ||
| 203 | windows_version[2], | ||
| 204 | windows_version[3], | ||
| 205 | // Descriptor set length | ||
| 206 | len[0], | ||
| 207 | len[1], | ||
| 208 | self.vendor_code, | ||
| 209 | 0x0, // Device does not support alternate enumeration | ||
| 210 | ], | ||
| 211 | ); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | /// Microsoft Windows version codes | ||
| 216 | /// | ||
| 217 | /// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors. | ||
| 218 | pub mod windows_version { | ||
| 219 | /// Windows 8.1 (aka `NTDDI_WINBLUE`) | ||
| 220 | pub const WIN8_1: u32 = 0x06030000; | ||
| 221 | /// Windows 10 | ||
| 222 | pub const WIN10: u32 = 0x0A000000; | ||
| 223 | } | ||
| 224 | |||
| 225 | mod sealed { | ||
| 226 | use core::mem::size_of; | ||
| 227 | |||
| 228 | /// A trait for descriptors | ||
| 229 | pub trait Descriptor: Sized { | ||
| 230 | const TYPE: super::DescriptorType; | ||
| 231 | |||
| 232 | /// The size of the descriptor's header. | ||
| 233 | fn size(&self) -> usize { | ||
| 234 | size_of::<Self>() | ||
| 235 | } | ||
| 236 | |||
| 237 | fn write_to(&self, buf: &mut [u8]); | ||
| 238 | } | ||
| 239 | |||
| 240 | pub trait DescriptorSet: Descriptor { | ||
| 241 | const LENGTH_OFFSET: usize; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | use sealed::*; | ||
| 246 | |||
| 247 | /// Copies the data of `t` into `buf`. | ||
| 248 | /// | ||
| 249 | /// # Safety | ||
| 250 | /// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) | ||
| 251 | unsafe fn transmute_write_to<T: Sized>(t: &T, buf: &mut [u8]) { | ||
| 252 | let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::<T>()); | ||
| 253 | assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full"); | ||
| 254 | (&mut buf[..bytes.len()]).copy_from_slice(bytes); | ||
| 255 | } | ||
| 256 | |||
| 257 | /// Table 9. Microsoft OS 2.0 descriptor wDescriptorType values. | ||
| 258 | #[derive(Clone, Copy, PartialEq, Eq)] | ||
| 259 | #[repr(u16)] | ||
| 260 | pub enum DescriptorType { | ||
| 261 | /// MS OS descriptor set header | ||
| 262 | SetHeaderDescriptor = 0, | ||
| 263 | /// Configuration subset header | ||
| 264 | SubsetHeaderConfiguration = 1, | ||
| 265 | /// Function subset header | ||
| 266 | SubsetHeaderFunction = 2, | ||
| 267 | /// Compatible device ID feature descriptor | ||
| 268 | FeatureCompatibleId = 3, | ||
| 269 | /// Registry property feature descriptor | ||
| 270 | FeatureRegProperty = 4, | ||
| 271 | /// Minimum USB resume time feature descriptor | ||
| 272 | FeatureMinResumeTime = 5, | ||
| 273 | /// Vendor revision feature descriptor | ||
| 274 | FeatureModelId = 6, | ||
| 275 | /// CCGP device descriptor feature descriptor | ||
| 276 | FeatureCcgpDevice = 7, | ||
| 277 | /// Vendor revision feature descriptor | ||
| 278 | FeatureVendorRevision = 8, | ||
| 279 | } | ||
| 280 | |||
| 281 | /// Table 5. Descriptor set information structure. | ||
| 282 | #[allow(non_snake_case)] | ||
| 283 | #[repr(C, packed(1))] | ||
| 284 | pub struct DescriptorSetInformation { | ||
| 285 | dwWindowsVersion: u32, | ||
| 286 | wMSOSDescriptorSetTotalLength: u16, | ||
| 287 | bMS_VendorCode: u8, | ||
| 288 | bAltEnumCode: u8, | ||
| 289 | } | ||
| 290 | |||
| 291 | /// Table 4. Microsoft OS 2.0 platform capability descriptor header. | ||
| 292 | #[allow(non_snake_case)] | ||
| 293 | #[repr(C, packed(1))] | ||
| 294 | pub struct PlatformDescriptor { | ||
| 295 | bLength: u8, | ||
| 296 | bDescriptorType: u8, | ||
| 297 | bDevCapabilityType: u8, | ||
| 298 | bReserved: u8, | ||
| 299 | platformCapabilityUUID: [u8; 16], | ||
| 300 | descriptor_set_information: DescriptorSetInformation, | ||
| 301 | } | ||
| 302 | |||
| 303 | /// Table 10. Microsoft OS 2.0 descriptor set header. | ||
| 304 | #[allow(non_snake_case)] | ||
| 305 | #[repr(C, packed(1))] | ||
| 306 | pub struct DescriptorSetHeader { | ||
| 307 | wLength: u16, | ||
| 308 | wDescriptorType: u16, | ||
| 309 | dwWindowsVersion: u32, | ||
| 310 | wTotalLength: u16, | ||
| 311 | } | ||
| 312 | |||
| 313 | impl DescriptorSetHeader { | ||
| 314 | /// Creates a MS OS descriptor set header. | ||
| 315 | /// | ||
| 316 | /// `windows_version` is the minimum Windows version the descriptor set can apply to. | ||
| 317 | pub fn new(windows_version: u32) -> Self { | ||
| 318 | DescriptorSetHeader { | ||
| 319 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 320 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 321 | dwWindowsVersion: windows_version.to_le(), | ||
| 322 | wTotalLength: 0, | ||
| 323 | } | ||
| 324 | } | ||
| 325 | } | ||
| 326 | |||
| 327 | impl Descriptor for DescriptorSetHeader { | ||
| 328 | const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor; | ||
| 329 | fn write_to(&self, buf: &mut [u8]) { | ||
| 330 | unsafe { transmute_write_to(self, buf) } | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | impl DescriptorSet for DescriptorSetHeader { | ||
| 335 | const LENGTH_OFFSET: usize = 8; | ||
| 336 | } | ||
| 337 | |||
| 338 | /// Table 11. Configuration subset header. | ||
| 339 | #[allow(non_snake_case)] | ||
| 340 | #[repr(C, packed(1))] | ||
| 341 | pub struct ConfigurationSubsetHeader { | ||
| 342 | wLength: u16, | ||
| 343 | wDescriptorType: u16, | ||
| 344 | bConfigurationValue: u8, | ||
| 345 | bReserved: u8, | ||
| 346 | wTotalLength: u16, | ||
| 347 | } | ||
| 348 | |||
| 349 | impl ConfigurationSubsetHeader { | ||
| 350 | /// Creates a configuration subset header | ||
| 351 | pub fn new(config: u8) -> Self { | ||
| 352 | ConfigurationSubsetHeader { | ||
| 353 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 354 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 355 | bConfigurationValue: config, | ||
| 356 | bReserved: 0, | ||
| 357 | wTotalLength: 0, | ||
| 358 | } | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | impl Descriptor for ConfigurationSubsetHeader { | ||
| 363 | const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration; | ||
| 364 | fn write_to(&self, buf: &mut [u8]) { | ||
| 365 | unsafe { transmute_write_to(self, buf) } | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | impl DescriptorSet for ConfigurationSubsetHeader { | ||
| 370 | const LENGTH_OFFSET: usize = 6; | ||
| 371 | } | ||
| 372 | |||
| 373 | /// Table 12. Function subset header. | ||
| 374 | #[allow(non_snake_case)] | ||
| 375 | #[repr(C, packed(1))] | ||
| 376 | pub struct FunctionSubsetHeader { | ||
| 377 | wLength: u16, | ||
| 378 | wDescriptorType: u16, | ||
| 379 | bFirstInterface: u8, | ||
| 380 | bReserved: u8, | ||
| 381 | wSubsetLength: u16, | ||
| 382 | } | ||
| 383 | |||
| 384 | impl FunctionSubsetHeader { | ||
| 385 | /// Creates a function subset header | ||
| 386 | pub fn new(first_interface: u8) -> Self { | ||
| 387 | FunctionSubsetHeader { | ||
| 388 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 389 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 390 | bFirstInterface: first_interface, | ||
| 391 | bReserved: 0, | ||
| 392 | wSubsetLength: 0, | ||
| 393 | } | ||
| 394 | } | ||
| 395 | } | ||
| 396 | |||
| 397 | impl Descriptor for FunctionSubsetHeader { | ||
| 398 | const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction; | ||
| 399 | fn write_to(&self, buf: &mut [u8]) { | ||
| 400 | unsafe { transmute_write_to(self, buf) } | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | impl DescriptorSet for FunctionSubsetHeader { | ||
| 405 | const LENGTH_OFFSET: usize = 6; | ||
| 406 | } | ||
| 407 | |||
| 408 | // Feature Descriptors | ||
| 409 | |||
| 410 | /// A marker trait for feature descriptors that are valid at the device level. | ||
| 411 | pub trait DeviceLevelDescriptor: Descriptor {} | ||
| 412 | |||
| 413 | /// A marker trait for feature descriptors that are valid at the function level. | ||
| 414 | pub trait FunctionLevelDescriptor: Descriptor {} | ||
| 415 | |||
| 416 | /// Table 13. Microsoft OS 2.0 compatible ID descriptor. | ||
| 417 | #[allow(non_snake_case)] | ||
| 418 | #[repr(C, packed(1))] | ||
| 419 | pub struct CompatibleIdFeatureDescriptor { | ||
| 420 | wLength: u16, | ||
| 421 | wDescriptorType: u16, | ||
| 422 | compatibleId: [u8; 8], | ||
| 423 | subCompatibleId: [u8; 8], | ||
| 424 | } | ||
| 425 | |||
| 426 | impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {} | ||
| 427 | impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {} | ||
| 428 | |||
| 429 | impl Descriptor for CompatibleIdFeatureDescriptor { | ||
| 430 | const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId; | ||
| 431 | fn write_to(&self, buf: &mut [u8]) { | ||
| 432 | unsafe { transmute_write_to(self, buf) } | ||
| 433 | } | ||
| 434 | } | ||
| 435 | |||
| 436 | impl CompatibleIdFeatureDescriptor { | ||
| 437 | /// Creates a compatible ID feature descriptor | ||
| 438 | /// | ||
| 439 | /// The ids must be 8 ASCII bytes or fewer. | ||
| 440 | pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self { | ||
| 441 | assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8); | ||
| 442 | let mut cid = [0u8; 8]; | ||
| 443 | (&mut cid[..compatible_id.len()]).copy_from_slice(compatible_id.as_bytes()); | ||
| 444 | let mut scid = [0u8; 8]; | ||
| 445 | (&mut scid[..sub_compatible_id.len()]).copy_from_slice(sub_compatible_id.as_bytes()); | ||
| 446 | Self::new_raw(cid, scid) | ||
| 447 | } | ||
| 448 | |||
| 449 | fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { | ||
| 450 | Self { | ||
| 451 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 452 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 453 | compatibleId: compatible_id, | ||
| 454 | subCompatibleId: sub_compatible_id, | ||
| 455 | } | ||
| 456 | } | ||
| 457 | } | ||
| 458 | |||
| 459 | /// Table 14. Microsoft OS 2.0 registry property descriptor | ||
| 460 | #[allow(non_snake_case)] | ||
| 461 | pub struct RegistryPropertyFeatureDescriptor<'a> { | ||
| 462 | name: &'a str, | ||
| 463 | data: PropertyData<'a>, | ||
| 464 | } | ||
| 465 | |||
| 466 | /// Data values that can be encoded into a registry property descriptor | ||
| 467 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 468 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 469 | pub enum PropertyData<'a> { | ||
| 470 | /// A registry property containing a string. | ||
| 471 | Sz(&'a str), | ||
| 472 | /// A registry property containing a string that expands environment variables. | ||
| 473 | ExpandSz(&'a str), | ||
| 474 | /// A registry property containing binary data. | ||
| 475 | Binary(&'a [u8]), | ||
| 476 | /// A registry property containing a little-endian 32-bit integer. | ||
| 477 | DwordLittleEndian(u32), | ||
| 478 | /// A registry property containing a big-endian 32-bit integer. | ||
| 479 | DwordBigEndian(u32), | ||
| 480 | /// A registry property containing a string that contains a symbolic link. | ||
| 481 | Link(&'a str), | ||
| 482 | /// A registry property containing multiple strings. | ||
| 483 | RegMultiSz(&'a [&'a str]), | ||
| 484 | } | ||
| 485 | |||
| 486 | fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize { | ||
| 487 | assert!(buf.len() >= val.len()); | ||
| 488 | buf[..val.len()].copy_from_slice(val); | ||
| 489 | val.len() | ||
| 490 | } | ||
| 491 | |||
| 492 | fn write_utf16(val: &str, buf: &mut [u8]) -> usize { | ||
| 493 | let mut pos = 0; | ||
| 494 | for c in val.encode_utf16() { | ||
| 495 | pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]); | ||
| 496 | } | ||
| 497 | pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) | ||
| 498 | } | ||
| 499 | |||
| 500 | impl<'a> PropertyData<'a> { | ||
| 501 | /// Gets the `PropertyDataType` for this property value | ||
| 502 | pub fn kind(&self) -> PropertyDataType { | ||
| 503 | match self { | ||
| 504 | PropertyData::Sz(_) => PropertyDataType::Sz, | ||
| 505 | PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz, | ||
| 506 | PropertyData::Binary(_) => PropertyDataType::Binary, | ||
| 507 | PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian, | ||
| 508 | PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian, | ||
| 509 | PropertyData::Link(_) => PropertyDataType::Link, | ||
| 510 | PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz, | ||
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | /// Gets the size (in bytes) of this property value when encoded. | ||
| 515 | pub fn size(&self) -> usize { | ||
| 516 | match self { | ||
| 517 | PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => { | ||
| 518 | core::mem::size_of::<u16>() * (val.encode_utf16().count() + 1) | ||
| 519 | } | ||
| 520 | PropertyData::Binary(val) => val.len(), | ||
| 521 | PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val), | ||
| 522 | PropertyData::RegMultiSz(val) => { | ||
| 523 | core::mem::size_of::<u16>() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::<usize>() + 1 | ||
| 524 | } | ||
| 525 | } | ||
| 526 | } | ||
| 527 | |||
| 528 | /// Encodes the data for this property value and writes it to `buf`. | ||
| 529 | pub fn write(&self, buf: &mut [u8]) -> usize { | ||
| 530 | match self { | ||
| 531 | PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf), | ||
| 532 | PropertyData::Binary(val) => write_bytes(val, buf), | ||
| 533 | PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf), | ||
| 534 | PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf), | ||
| 535 | PropertyData::RegMultiSz(val) => { | ||
| 536 | let mut pos = 0; | ||
| 537 | for s in *val { | ||
| 538 | pos += write_utf16(s, &mut buf[pos..]); | ||
| 539 | } | ||
| 540 | pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) | ||
| 541 | } | ||
| 542 | } | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | /// Table 15. wPropertyDataType values for the Microsoft OS 2.0 registry property descriptor. | ||
| 547 | #[derive(Clone, Copy, PartialEq, Eq)] | ||
| 548 | #[repr(u16)] | ||
| 549 | pub enum PropertyDataType { | ||
| 550 | /// A registry property containing a string. | ||
| 551 | Sz = 1, | ||
| 552 | /// A registry property containing a string that expands environment variables. | ||
| 553 | ExpandSz = 2, | ||
| 554 | /// A registry property containing binary data. | ||
| 555 | Binary = 3, | ||
| 556 | /// A registry property containing a little-endian 32-bit integer. | ||
| 557 | DwordLittleEndian = 4, | ||
| 558 | /// A registry property containing a big-endian 32-bit integer. | ||
| 559 | DwordBigEndian = 5, | ||
| 560 | /// A registry property containing a string that contains a symbolic link. | ||
| 561 | Link = 6, | ||
| 562 | /// A registry property containing multiple strings. | ||
| 563 | RegMultiSz = 7, | ||
| 564 | } | ||
| 565 | |||
| 566 | impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} | ||
| 567 | impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} | ||
| 568 | |||
| 569 | impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { | ||
| 570 | const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; | ||
| 571 | |||
| 572 | fn size(&self) -> usize { | ||
| 573 | 10 + self.name_size() + self.data.size() | ||
| 574 | } | ||
| 575 | |||
| 576 | fn write_to(&self, buf: &mut [u8]) { | ||
| 577 | assert!(buf.len() >= self.size(), "MS OS descriptor buffer full"); | ||
| 578 | |||
| 579 | let mut pos = 0; | ||
| 580 | pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]); | ||
| 581 | pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]); | ||
| 582 | pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]); | ||
| 583 | pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]); | ||
| 584 | pos += write_utf16(self.name, &mut buf[pos..]); | ||
| 585 | pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]); | ||
| 586 | self.data.write(&mut buf[pos..]); | ||
| 587 | } | ||
| 588 | } | ||
| 589 | |||
| 590 | impl<'a> RegistryPropertyFeatureDescriptor<'a> { | ||
| 591 | /// A registry property. | ||
| 592 | pub fn new(name: &'a str, data: PropertyData<'a>) -> Self { | ||
| 593 | Self { name, data } | ||
| 594 | } | ||
| 595 | |||
| 596 | fn name_size(&self) -> usize { | ||
| 597 | core::mem::size_of::<u16>() * (self.name.encode_utf16().count() + 1) | ||
| 598 | } | ||
| 599 | } | ||
| 600 | |||
| 601 | /// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor. | ||
| 602 | #[allow(non_snake_case)] | ||
| 603 | #[repr(C, packed(1))] | ||
| 604 | pub struct MinimumRecoveryTimeDescriptor { | ||
| 605 | wLength: u16, | ||
| 606 | wDescriptorType: u16, | ||
| 607 | bResumeRecoveryTime: u8, | ||
| 608 | bResumeSignalingTime: u8, | ||
| 609 | } | ||
| 610 | |||
| 611 | impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {} | ||
| 612 | |||
| 613 | impl Descriptor for MinimumRecoveryTimeDescriptor { | ||
| 614 | const TYPE: DescriptorType = DescriptorType::FeatureMinResumeTime; | ||
| 615 | fn write_to(&self, buf: &mut [u8]) { | ||
| 616 | unsafe { transmute_write_to(self, buf) } | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | impl MinimumRecoveryTimeDescriptor { | ||
| 621 | /// Times are in milliseconds. | ||
| 622 | /// | ||
| 623 | /// `resume_recovery_time` must be >= 0 and <= 10. | ||
| 624 | /// `resume_signaling_time` must be >= 1 and <= 20. | ||
| 625 | pub fn new(resume_recovery_time: u8, resume_signaling_time: u8) -> Self { | ||
| 626 | assert!(resume_recovery_time <= 10); | ||
| 627 | assert!(resume_signaling_time >= 1 && resume_signaling_time <= 20); | ||
| 628 | Self { | ||
| 629 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 630 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 631 | bResumeRecoveryTime: resume_recovery_time, | ||
| 632 | bResumeSignalingTime: resume_signaling_time, | ||
| 633 | } | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | /// Table 17. Microsoft OS 2.0 model ID descriptor. | ||
| 638 | #[allow(non_snake_case)] | ||
| 639 | #[repr(C, packed(1))] | ||
| 640 | pub struct ModelIdDescriptor { | ||
| 641 | wLength: u16, | ||
| 642 | wDescriptorType: u16, | ||
| 643 | modelId: [u8; 16], | ||
| 644 | } | ||
| 645 | |||
| 646 | impl DeviceLevelDescriptor for ModelIdDescriptor {} | ||
| 647 | |||
| 648 | impl Descriptor for ModelIdDescriptor { | ||
| 649 | const TYPE: DescriptorType = DescriptorType::FeatureModelId; | ||
| 650 | fn write_to(&self, buf: &mut [u8]) { | ||
| 651 | unsafe { transmute_write_to(self, buf) } | ||
| 652 | } | ||
| 653 | } | ||
| 654 | |||
| 655 | impl ModelIdDescriptor { | ||
| 656 | /// Creates a new model ID descriptor | ||
| 657 | /// | ||
| 658 | /// `model_id` should be a uuid that uniquely identifies a physical device. | ||
| 659 | pub fn new(model_id: u128) -> Self { | ||
| 660 | Self { | ||
| 661 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 662 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 663 | modelId: model_id.to_le_bytes(), | ||
| 664 | } | ||
| 665 | } | ||
| 666 | } | ||
| 667 | |||
| 668 | /// Table 18. Microsoft OS 2.0 CCGP device descriptor. | ||
| 669 | #[allow(non_snake_case)] | ||
| 670 | #[repr(C, packed(1))] | ||
| 671 | pub struct CcgpDeviceDescriptor { | ||
| 672 | wLength: u16, | ||
| 673 | wDescriptorType: u16, | ||
| 674 | } | ||
| 675 | |||
| 676 | impl DeviceLevelDescriptor for CcgpDeviceDescriptor {} | ||
| 677 | |||
| 678 | impl Descriptor for CcgpDeviceDescriptor { | ||
| 679 | const TYPE: DescriptorType = DescriptorType::FeatureCcgpDevice; | ||
| 680 | fn write_to(&self, buf: &mut [u8]) { | ||
| 681 | unsafe { transmute_write_to(self, buf) } | ||
| 682 | } | ||
| 683 | } | ||
| 684 | |||
| 685 | impl CcgpDeviceDescriptor { | ||
| 686 | /// Creates a new CCGP device descriptor | ||
| 687 | pub fn new() -> Self { | ||
| 688 | Self { | ||
| 689 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 690 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 691 | } | ||
| 692 | } | ||
| 693 | } | ||
| 694 | |||
| 695 | /// Table 19. Microsoft OS 2.0 vendor revision descriptor. | ||
| 696 | #[allow(non_snake_case)] | ||
| 697 | #[repr(C, packed(1))] | ||
| 698 | pub struct VendorRevisionDescriptor { | ||
| 699 | wLength: u16, | ||
| 700 | wDescriptorType: u16, | ||
| 701 | /// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or | ||
| 702 | /// other MS OS descriptor. Shell set to greater than or equal to 1. | ||
| 703 | VendorRevision: u16, | ||
| 704 | } | ||
| 705 | |||
| 706 | impl DeviceLevelDescriptor for VendorRevisionDescriptor {} | ||
| 707 | impl FunctionLevelDescriptor for VendorRevisionDescriptor {} | ||
| 708 | |||
| 709 | impl Descriptor for VendorRevisionDescriptor { | ||
| 710 | const TYPE: DescriptorType = DescriptorType::FeatureVendorRevision; | ||
| 711 | fn write_to(&self, buf: &mut [u8]) { | ||
| 712 | unsafe { transmute_write_to(self, buf) } | ||
| 713 | } | ||
| 714 | } | ||
| 715 | |||
| 716 | impl VendorRevisionDescriptor { | ||
| 717 | /// Creates a new vendor revision descriptor | ||
| 718 | pub fn new(revision: u16) -> Self { | ||
| 719 | assert!(revision >= 1); | ||
| 720 | Self { | ||
| 721 | wLength: (size_of::<Self>() as u16).to_le(), | ||
| 722 | wDescriptorType: (Self::TYPE as u16).to_le(), | ||
| 723 | VendorRevision: revision.to_le(), | ||
| 724 | } | ||
| 725 | } | ||
| 726 | } | ||
diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 95d939873..cfdda076e 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml | |||
| @@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0" | |||
| 6 | 6 | ||
| 7 | [features] | 7 | [features] |
| 8 | default = ["nightly"] | 8 | default = ["nightly"] |
| 9 | msos-descriptor = ["embassy-usb/msos-descriptor"] | ||
| 9 | nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net", | 10 | nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net", |
| 10 | "embassy-lora", "lorawan-device", "lorawan"] | 11 | "embassy-lora", "lorawan-device", "lorawan"] |
| 11 | 12 | ||
| @@ -34,4 +35,8 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa | |||
| 34 | rand = { version = "0.8.4", default-features = false } | 35 | rand = { version = "0.8.4", default-features = false } |
| 35 | embedded-storage = "0.3.0" | 36 | embedded-storage = "0.3.0" |
| 36 | usbd-hid = "0.6.0" | 37 | usbd-hid = "0.6.0" |
| 37 | serde = { version = "1.0.136", default-features = false } \ No newline at end of file | 38 | serde = { version = "1.0.136", default-features = false } |
| 39 | |||
| 40 | [[bin]] | ||
| 41 | name = "usb_serial_winusb" | ||
| 42 | 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 000000000..f4b828de6 --- /dev/null +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use core::mem; | ||
| 6 | |||
| 7 | use defmt::{info, panic}; | ||
| 8 | use embassy_executor::Spawner; | ||
| 9 | use embassy_futures::join::join; | ||
| 10 | use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect}; | ||
| 11 | use embassy_nrf::{interrupt, pac}; | ||
| 12 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 13 | use embassy_usb::driver::EndpointError; | ||
| 14 | use embassy_usb::msos::{self, windows_version}; | ||
| 15 | use embassy_usb::{Builder, Config}; | ||
| 16 | use {defmt_rtt as _, panic_probe as _}; | ||
| 17 | |||
| 18 | // This is a randomly generated GUID to allow clients on Windows to find our device | ||
| 19 | const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"]; | ||
| 20 | |||
| 21 | #[embassy_executor::main] | ||
| 22 | async fn main(_spawner: Spawner) { | ||
| 23 | let p = embassy_nrf::init(Default::default()); | ||
| 24 | let clock: pac::CLOCK = unsafe { mem::transmute(()) }; | ||
| 25 | |||
| 26 | info!("Enabling ext hfosc..."); | ||
| 27 | clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); | ||
| 28 | while clock.events_hfclkstarted.read().bits() != 1 {} | ||
| 29 | |||
| 30 | // Create the driver, from the HAL. | ||
| 31 | let irq = interrupt::take!(USBD); | ||
| 32 | let power_irq = interrupt::take!(POWER_CLOCK); | ||
| 33 | let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq)); | ||
| 34 | |||
| 35 | // Create embassy-usb Config | ||
| 36 | let mut config = Config::new(0xc0de, 0xcafe); | ||
| 37 | config.manufacturer = Some("Embassy"); | ||
| 38 | config.product = Some("USB-serial example"); | ||
| 39 | config.serial_number = Some("12345678"); | ||
| 40 | config.max_power = 100; | ||
| 41 | config.max_packet_size_0 = 64; | ||
| 42 | |||
| 43 | // Required for windows compatiblity. | ||
| 44 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 45 | config.device_class = 0xEF; | ||
| 46 | config.device_sub_class = 0x02; | ||
| 47 | config.device_protocol = 0x01; | ||
| 48 | config.composite_with_iads = true; | ||
| 49 | |||
| 50 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 51 | // It needs some buffers for building the descriptors. | ||
| 52 | let mut device_descriptor = [0; 256]; | ||
| 53 | let mut config_descriptor = [0; 256]; | ||
| 54 | let mut bos_descriptor = [0; 256]; | ||
| 55 | let mut msos_descriptor = [0; 256]; | ||
| 56 | let mut control_buf = [0; 64]; | ||
| 57 | |||
| 58 | let mut state = State::new(); | ||
| 59 | |||
| 60 | let mut builder = Builder::new( | ||
| 61 | driver, | ||
| 62 | config, | ||
| 63 | &mut device_descriptor, | ||
| 64 | &mut config_descriptor, | ||
| 65 | &mut bos_descriptor, | ||
| 66 | &mut msos_descriptor, | ||
| 67 | &mut control_buf, | ||
| 68 | None, | ||
| 69 | ); | ||
| 70 | |||
| 71 | builder.msos_descriptor(windows_version::WIN8_1, 2); | ||
| 72 | |||
| 73 | // Create classes on the builder. | ||
| 74 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||
| 75 | |||
| 76 | // Since we want to create MS OS feature descriptors that apply to a function that has already been added to the | ||
| 77 | // builder, need to get the MsOsDescriptorWriter from the builder and manually add those descriptors. | ||
| 78 | // Inside a class constructor, you would just need to call `FunctionBuilder::msos_feature` instead. | ||
| 79 | let msos_writer = builder.msos_writer(); | ||
| 80 | msos_writer.configuration(0); | ||
| 81 | msos_writer.function(0); | ||
| 82 | msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); | ||
| 83 | msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new( | ||
| 84 | "DeviceInterfaceGUIDs", | ||
| 85 | msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), | ||
| 86 | )); | ||
| 87 | |||
| 88 | // Build the builder. | ||
| 89 | let mut usb = builder.build(); | ||
| 90 | |||
| 91 | // Run the USB device. | ||
| 92 | let usb_fut = usb.run(); | ||
| 93 | |||
| 94 | // Do stuff with the class! | ||
| 95 | let echo_fut = async { | ||
| 96 | loop { | ||
| 97 | class.wait_connection().await; | ||
| 98 | info!("Connected"); | ||
| 99 | let _ = echo(&mut class).await; | ||
| 100 | info!("Disconnected"); | ||
| 101 | } | ||
| 102 | }; | ||
| 103 | |||
| 104 | // Run everything concurrently. | ||
| 105 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 106 | join(usb_fut, echo_fut).await; | ||
| 107 | } | ||
| 108 | |||
| 109 | struct Disconnected {} | ||
| 110 | |||
| 111 | impl From<EndpointError> for Disconnected { | ||
| 112 | fn from(val: EndpointError) -> Self { | ||
| 113 | match val { | ||
| 114 | EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||
| 115 | EndpointError::Disabled => Disconnected {}, | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( | ||
| 121 | class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, | ||
| 122 | ) -> Result<(), Disconnected> { | ||
| 123 | let mut buf = [0; 64]; | ||
| 124 | loop { | ||
| 125 | let n = class.read_packet(&mut buf).await?; | ||
| 126 | let data = &buf[..n]; | ||
| 127 | info!("data: {:x}", data); | ||
| 128 | class.write_packet(data).await?; | ||
| 129 | } | ||
| 130 | } | ||
