aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2023-02-07 19:31:10 +0000
committerGitHub <[email protected]>2023-02-07 19:31:10 +0000
commit366fab5b872e5d00fd84408f04cf00faf988d160 (patch)
tree9b45fb26d9a32b806931fcc6d379af00f4ffec21
parenta7fa7d0de2fa7b8fab889879b6003df8427c6841 (diff)
parentaa21aebb0b321a2085571e5be5fffcea4703584d (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.toml1
-rw-r--r--embassy-usb/src/builder.rs73
-rw-r--r--embassy-usb/src/lib.rs19
-rw-r--r--embassy-usb/src/msos.rs726
-rw-r--r--examples/nrf52840/Cargo.toml7
-rw-r--r--examples/nrf52840/src/bin/usb_serial_winusb.rs130
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]
14defmt = ["dep:defmt", "embassy-usb-driver/defmt"] 14defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
15usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] 15usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
16msos-descriptor = []
16default = ["usbd-hid"] 17default = ["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;
3use crate::control::ControlHandler; 3use crate::control::ControlHandler;
4use crate::descriptor::{BosWriter, DescriptorWriter}; 4use crate::descriptor::{BosWriter, DescriptorWriter};
5use crate::driver::{Driver, Endpoint, EndpointType}; 5use crate::driver::{Driver, Endpoint, EndpointType};
6#[cfg(feature = "msos-descriptor")]
7use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter};
6use crate::types::*; 8use crate::types::*;
7use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; 9use 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
135impl<'d, D: Driver<'d>> Builder<'d, D> { 140impl<'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> {
244pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { 278pub 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
286impl<'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
249impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { 293impl<'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;
13pub mod control; 13pub mod control;
14pub mod descriptor; 14pub mod descriptor;
15mod descriptor_reader; 15mod descriptor_reader;
16pub mod msos;
16pub mod types; 17pub mod types;
17 18
18use embassy_futures::select::{select, Either}; 19use 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
140impl<'d, D: Driver<'d>> UsbDevice<'d, D> { 143impl<'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
7use core::mem::size_of;
8
9use super::{capability_type, BosWriter};
10
11/// A serialized Microsoft OS 2.0 Descriptor set.
12///
13/// Create with [`DeviceDescriptorSetBuilder`].
14pub struct MsOsDescriptorSet<'d> {
15 descriptor: &'d [u8],
16 vendor_code: u8,
17}
18
19impl<'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.
37pub 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
46impl<'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.
218pub 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
225mod 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
245use 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)
251unsafe 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)]
260pub 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))]
284pub 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))]
294pub 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))]
306pub struct DescriptorSetHeader {
307 wLength: u16,
308 wDescriptorType: u16,
309 dwWindowsVersion: u32,
310 wTotalLength: u16,
311}
312
313impl 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
327impl 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
334impl 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))]
341pub struct ConfigurationSubsetHeader {
342 wLength: u16,
343 wDescriptorType: u16,
344 bConfigurationValue: u8,
345 bReserved: u8,
346 wTotalLength: u16,
347}
348
349impl 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
362impl 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
369impl 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))]
376pub struct FunctionSubsetHeader {
377 wLength: u16,
378 wDescriptorType: u16,
379 bFirstInterface: u8,
380 bReserved: u8,
381 wSubsetLength: u16,
382}
383
384impl 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
397impl 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
404impl 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.
411pub trait DeviceLevelDescriptor: Descriptor {}
412
413/// A marker trait for feature descriptors that are valid at the function level.
414pub trait FunctionLevelDescriptor: Descriptor {}
415
416/// Table 13. Microsoft OS 2.0 compatible ID descriptor.
417#[allow(non_snake_case)]
418#[repr(C, packed(1))]
419pub struct CompatibleIdFeatureDescriptor {
420 wLength: u16,
421 wDescriptorType: u16,
422 compatibleId: [u8; 8],
423 subCompatibleId: [u8; 8],
424}
425
426impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {}
427impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {}
428
429impl 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
436impl 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)]
461pub 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))]
469pub 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
486fn 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
492fn 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
500impl<'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)]
549pub 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
566impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
567impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
568
569impl<'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
590impl<'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))]
604pub struct MinimumRecoveryTimeDescriptor {
605 wLength: u16,
606 wDescriptorType: u16,
607 bResumeRecoveryTime: u8,
608 bResumeSignalingTime: u8,
609}
610
611impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {}
612
613impl 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
620impl 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))]
640pub struct ModelIdDescriptor {
641 wLength: u16,
642 wDescriptorType: u16,
643 modelId: [u8; 16],
644}
645
646impl DeviceLevelDescriptor for ModelIdDescriptor {}
647
648impl 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
655impl 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))]
671pub struct CcgpDeviceDescriptor {
672 wLength: u16,
673 wDescriptorType: u16,
674}
675
676impl DeviceLevelDescriptor for CcgpDeviceDescriptor {}
677
678impl 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
685impl 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))]
698pub 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
706impl DeviceLevelDescriptor for VendorRevisionDescriptor {}
707impl FunctionLevelDescriptor for VendorRevisionDescriptor {}
708
709impl 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
716impl 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]
8default = ["nightly"] 8default = ["nightly"]
9msos-descriptor = ["embassy-usb/msos-descriptor"]
9nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net", 10nightly = ["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
34rand = { version = "0.8.4", default-features = false } 35rand = { version = "0.8.4", default-features = false }
35embedded-storage = "0.3.0" 36embedded-storage = "0.3.0"
36usbd-hid = "0.6.0" 37usbd-hid = "0.6.0"
37serde = { version = "1.0.136", default-features = false } \ No newline at end of file 38serde = { version = "1.0.136", default-features = false }
39
40[[bin]]
41name = "usb_serial_winusb"
42required-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
5use core::mem;
6
7use defmt::{info, panic};
8use embassy_executor::Spawner;
9use embassy_futures::join::join;
10use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect};
11use embassy_nrf::{interrupt, pac};
12use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
13use embassy_usb::driver::EndpointError;
14use embassy_usb::msos::{self, windows_version};
15use embassy_usb::{Builder, Config};
16use {defmt_rtt as _, panic_probe as _};
17
18// This is a randomly generated GUID to allow clients on Windows to find our device
19const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"];
20
21#[embassy_executor::main]
22async 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
109struct Disconnected {}
110
111impl 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
120async 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}