Convert MS OS descriptor builder to a writer API
This brings it inline with the other embassy-usb descriptor APIs and allows it to integrate well with the Builder to allow class constructors to add MS OS descriptors. Also adds a `usb_serial_winusb` example to demonstrate how to use the API.
This commit is contained in:
parent
b9ecdb72bb
commit
9f9230ae7a
6 changed files with 461 additions and 273 deletions
|
@ -13,6 +13,7 @@ target = "thumbv7em-none-eabi"
|
||||||
[features]
|
[features]
|
||||||
defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
|
defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
|
||||||
usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
|
usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
|
||||||
|
msos-descriptor = ["dep:widestring"]
|
||||||
default = ["usbd-hid"]
|
default = ["usbd-hid"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -24,7 +25,7 @@ embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "0.3", optional = true }
|
||||||
log = { version = "0.4.14", optional = true }
|
log = { version = "0.4.14", optional = true }
|
||||||
heapless = "0.7.10"
|
heapless = "0.7.10"
|
||||||
widestring = { version = "1.0.2", default-features = false }
|
widestring = { version = "1.0.2", default-features = false, optional = true }
|
||||||
|
|
||||||
# for HID
|
# for HID
|
||||||
usbd-hid = { version = "0.6.0", optional = true }
|
usbd-hid = { version = "0.6.0", optional = true }
|
||||||
|
|
|
@ -3,6 +3,8 @@ use heapless::Vec;
|
||||||
use crate::control::ControlHandler;
|
use crate::control::ControlHandler;
|
||||||
use crate::descriptor::{BosWriter, DescriptorWriter};
|
use crate::descriptor::{BosWriter, DescriptorWriter};
|
||||||
use crate::driver::{Driver, Endpoint, EndpointType};
|
use crate::driver::{Driver, Endpoint, EndpointType};
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
|
use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter};
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START};
|
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>,
|
device_descriptor: DescriptorWriter<'d>,
|
||||||
config_descriptor: DescriptorWriter<'d>,
|
config_descriptor: DescriptorWriter<'d>,
|
||||||
bos_descriptor: BosWriter<'d>,
|
bos_descriptor: BosWriter<'d>,
|
||||||
msos_descriptor: Option<crate::msos::MsOsDescriptorSet<'d>>,
|
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
|
msos_descriptor: MsOsDescriptorWriter<'d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, D: Driver<'d>> Builder<'d, 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],
|
device_descriptor_buf: &'d mut [u8],
|
||||||
config_descriptor_buf: &'d mut [u8],
|
config_descriptor_buf: &'d mut [u8],
|
||||||
bos_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],
|
control_buf: &'d mut [u8],
|
||||||
handler: Option<&'d dyn DeviceStateHandler>,
|
handler: Option<&'d dyn DeviceStateHandler>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -183,12 +188,17 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||||
device_descriptor,
|
device_descriptor,
|
||||||
config_descriptor,
|
config_descriptor,
|
||||||
bos_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.
|
/// Creates the [`UsbDevice`] instance with the configuration in this builder.
|
||||||
pub fn build(mut self) -> UsbDevice<'d, D> {
|
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.config_descriptor.end_configuration();
|
||||||
self.bos_descriptor.end_bos();
|
self.bos_descriptor.end_bos();
|
||||||
|
|
||||||
|
@ -201,7 +211,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||||
self.bos_descriptor.writer.into_buf(),
|
self.bos_descriptor.writer.into_buf(),
|
||||||
self.interfaces,
|
self.interfaces,
|
||||||
self.control_buf,
|
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.
|
/// If it's not set, no IAD descriptor is added.
|
||||||
pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> {
|
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 {
|
let iface_count_index = if self.config.composite_with_iads {
|
||||||
self.config_descriptor.iad(
|
self.config_descriptor
|
||||||
InterfaceNumber::new(self.interfaces.len() as _),
|
.iad(first_interface, 0, class, subclass, protocol);
|
||||||
0,
|
|
||||||
class,
|
|
||||||
subclass,
|
|
||||||
protocol,
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(self.config_descriptor.position() - 5)
|
Some(self.config_descriptor.position() - 5)
|
||||||
} else {
|
} else {
|
||||||
|
@ -235,19 +242,31 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
|
||||||
FunctionBuilder {
|
FunctionBuilder {
|
||||||
builder: self,
|
builder: self,
|
||||||
iface_count_index,
|
iface_count_index,
|
||||||
|
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
|
first_interface,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
/// Add an MS OS 2.0 Descriptor Set.
|
/// Add an MS OS 2.0 Descriptor Set.
|
||||||
///
|
///
|
||||||
/// Panics if called more than once.
|
/// Panics if called more than once.
|
||||||
pub fn msos_descriptor(&mut self, msos_descriptor: crate::msos::MsOsDescriptorSet<'d>) {
|
pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) {
|
||||||
if self.msos_descriptor.is_some() {
|
self.msos_descriptor.header(windows_version, vendor_code);
|
||||||
panic!("msos_descriptor already set");
|
}
|
||||||
}
|
|
||||||
self.msos_descriptor
|
#[cfg(feature = "msos-descriptor")]
|
||||||
.insert(msos_descriptor)
|
/// Add an MS OS 2.0 Device Level Feature Descriptor.
|
||||||
.write_bos_capability(&mut self.bos_descriptor);
|
pub fn msos_feature<T: DeviceLevelDescriptor>(&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>> {
|
pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> {
|
||||||
builder: &'a mut Builder<'d, D>,
|
builder: &'a mut Builder<'d, D>,
|
||||||
iface_count_index: Option<usize>,
|
iface_count_index: Option<usize>,
|
||||||
|
|
||||||
|
#[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> {
|
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,
|
next_alt_setting_number: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
|
/// Add an MS OS 2.0 Function Level Feature Descriptor.
|
||||||
|
pub fn msos_feature<T: FunctionLevelDescriptor>(&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.
|
/// Interface builder.
|
||||||
|
|
|
@ -136,8 +136,8 @@ struct Inner<'d, D: Driver<'d>> {
|
||||||
set_address_pending: bool,
|
set_address_pending: bool,
|
||||||
|
|
||||||
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
|
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
msos_descriptor: Option<crate::msos::MsOsDescriptorSet<'d>>,
|
msos_descriptor: crate::msos::MsOsDescriptorSet<'d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, D: Driver<'d>> UsbDevice<'d, 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],
|
bos_descriptor: &'d [u8],
|
||||||
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
|
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
|
||||||
control_buf: &'d mut [u8],
|
control_buf: &'d mut [u8],
|
||||||
msos_descriptor: Option<crate::msos::MsOsDescriptorSet<'d>>,
|
#[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>,
|
||||||
) -> UsbDevice<'d, D> {
|
) -> UsbDevice<'d, D> {
|
||||||
// Start the USB bus.
|
// Start the USB bus.
|
||||||
// This prevent further allocation by consuming the driver.
|
// This prevent further allocation by consuming the driver.
|
||||||
|
@ -174,6 +174,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
|
||||||
address: 0,
|
address: 0,
|
||||||
set_address_pending: false,
|
set_address_pending: false,
|
||||||
interfaces,
|
interfaces,
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
msos_descriptor,
|
msos_descriptor,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -608,11 +609,12 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||||
None => InResponse::Rejected,
|
None => InResponse::Rejected,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "msos-descriptor")]
|
||||||
(RequestType::Vendor, Recipient::Device) => {
|
(RequestType::Vendor, Recipient::Device) => {
|
||||||
if let Some(msos) = &self.msos_descriptor {
|
if !self.msos_descriptor.is_empty() {
|
||||||
if req.request == msos.vendor_code() && req.index == 7 {
|
if req.request == self.msos_descriptor.vendor_code() && req.index == 7 {
|
||||||
// Index 7 retrieves the MS OS Descriptor Set
|
// Index 7 retrieves the MS OS Descriptor Set
|
||||||
InResponse::Accepted(msos.descriptor())
|
InResponse::Accepted(self.msos_descriptor.descriptor())
|
||||||
} else {
|
} else {
|
||||||
InResponse::Rejected
|
InResponse::Rejected
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![cfg(feature = "msos-descriptor")]
|
||||||
|
|
||||||
//! Microsoft OS Descriptors
|
//! Microsoft OS Descriptors
|
||||||
//!
|
//!
|
||||||
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification>
|
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification>
|
||||||
|
@ -5,10 +7,9 @@
|
||||||
use core::mem::size_of;
|
use core::mem::size_of;
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
|
|
||||||
pub use widestring::{u16cstr, U16CStr};
|
pub use widestring::{u16cstr, u16str, U16CStr, U16Str};
|
||||||
|
|
||||||
use crate::descriptor::{capability_type, BosWriter};
|
use super::{capability_type, BosWriter};
|
||||||
use crate::types::InterfaceNumber;
|
|
||||||
|
|
||||||
fn write_u16<T: Into<u16>>(buf: &mut [u8], range: Range<usize>, data: T) {
|
fn write_u16<T: Into<u16>>(buf: &mut [u8], range: Range<usize>, data: T) {
|
||||||
(&mut buf[range]).copy_from_slice(data.into().to_le_bytes().as_slice())
|
(&mut buf[range]).copy_from_slice(data.into().to_le_bytes().as_slice())
|
||||||
|
@ -17,13 +18,12 @@ fn write_u16<T: Into<u16>>(buf: &mut [u8], range: Range<usize>, data: T) {
|
||||||
/// A serialized Microsoft OS 2.0 Descriptor set.
|
/// A serialized Microsoft OS 2.0 Descriptor set.
|
||||||
///
|
///
|
||||||
/// Create with [`DeviceDescriptorSetBuilder`].
|
/// Create with [`DeviceDescriptorSetBuilder`].
|
||||||
pub struct MsOsDescriptorSet<'a> {
|
pub struct MsOsDescriptorSet<'d> {
|
||||||
descriptor: &'a [u8],
|
descriptor: &'d [u8],
|
||||||
windows_version: u32,
|
|
||||||
vendor_code: u8,
|
vendor_code: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MsOsDescriptorSet<'a> {
|
impl<'d> MsOsDescriptorSet<'d> {
|
||||||
pub fn descriptor(&self) -> &[u8] {
|
pub fn descriptor(&self) -> &[u8] {
|
||||||
self.descriptor
|
self.descriptor
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,150 @@ impl<'a> MsOsDescriptorSet<'a> {
|
||||||
self.vendor_code
|
self.vendor_code
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_bos_capability(&self, bos: &mut BosWriter) {
|
pub fn is_empty(&self) -> bool {
|
||||||
let windows_version = self.windows_version.to_le_bytes();
|
self.descriptor.is_empty()
|
||||||
let len = self.descriptor.len().to_le_bytes();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<usize>,
|
||||||
|
function_mark: Option<usize>,
|
||||||
|
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<T: DeviceLevelDescriptor>(&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::<ConfigurationSubsetHeader>(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<T: FunctionLevelDescriptor>(&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::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<T: Descriptor>(&mut self, desc: T) {
|
||||||
|
desc.write_to(&mut self.buf[self.position..]);
|
||||||
|
self.position += desc.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_subset<T: DescriptorSet>(buf: &mut [u8], position: usize, mark: &mut Option<usize>) {
|
||||||
|
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::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark);
|
||||||
|
Self::end_subset::<ConfigurationSubsetHeader>(self.buf, self.position, &mut self.config_mark);
|
||||||
|
Self::end_subset::<DescriptorSetHeader>(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(
|
bos.capability(
|
||||||
capability_type::PLATFORM,
|
capability_type::PLATFORM,
|
||||||
&[
|
&[
|
||||||
|
@ -67,30 +208,7 @@ impl<'a> MsOsDescriptorSet<'a> {
|
||||||
self.vendor_code,
|
self.vendor_code,
|
||||||
0x0, // Device does not support alternate enumeration
|
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<T>(&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;
|
pub const WIN10: u32 = 0x0A000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helps build a Microsoft OS 2.0 Descriptor set.
|
mod sealed {
|
||||||
///
|
use core::mem::size_of;
|
||||||
/// # Example
|
|
||||||
/// ```rust
|
|
||||||
/// # use embassy_usb::types::InterfaceNumber;
|
|
||||||
/// # use embassy_usb::msos::*;
|
|
||||||
/// # let cdc_interface = unsafe { core::mem::transmute::<u8, InterfaceNumber>(0) };
|
|
||||||
/// # let dfu_interface = unsafe { core::mem::transmute::<u8, InterfaceNumber>(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,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DeviceDescriptorSetBuilder<'a> {
|
/// A trait for descriptors
|
||||||
/// Create a device descriptor set builder.
|
pub trait Descriptor: Sized {
|
||||||
///
|
const TYPE: super::DescriptorType;
|
||||||
/// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`]
|
|
||||||
/// module.
|
/// The size of the descriptor's header.
|
||||||
/// - `vendor_code` is the vendor request code used to read the MS OS descriptor set.
|
fn size(&self) -> usize {
|
||||||
pub fn new<'b: 'a>(buf: &'b mut [u8], windows_version: u32, vendor_code: u8) -> Self {
|
size_of::<Self>()
|
||||||
let mut builder = DescriptorSetBuilder { used: 0, buf };
|
|
||||||
builder.descriptor(DescriptorSetHeader {
|
|
||||||
wLength: (size_of::<DescriptorSetHeader>() as u16).to_le(),
|
|
||||||
wDescriptorType: (DescriptorSetHeader::TYPE as u16).to_le(),
|
|
||||||
dwWindowsVersion: windows_version.to_le(),
|
|
||||||
wTotalLength: 0,
|
|
||||||
});
|
|
||||||
Self {
|
|
||||||
builder,
|
|
||||||
windows_version,
|
|
||||||
vendor_code,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_to(&self, buf: &mut [u8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a device-level feature descriptor.
|
pub trait DescriptorSet: Descriptor {
|
||||||
///
|
const LENGTH_OFFSET: usize;
|
||||||
/// Note that some feature descriptors may only be used at the device level in non-composite devices.
|
|
||||||
pub fn feature<T>(&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 struct ConfigurationSubsetBuilder<'a> {
|
use sealed::*;
|
||||||
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::<ConfigurationSubsetHeader>() 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::<FunctionSubsetHeader>() 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<T>(&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::<Self>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_to(&self, buf: &mut [u8]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies the data of `t` into `buf`.
|
/// 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)
|
/// 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: Sized>(t: &T, buf: &mut [u8]) {
|
unsafe fn transmute_write_to<T: Sized>(t: &T, buf: &mut [u8]) {
|
||||||
let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::<T>());
|
let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::<T>());
|
||||||
assert!(buf.len() >= bytes.len());
|
assert!(buf.len() >= bytes.len(), "MSOS descriptor buffer full");
|
||||||
(&mut buf[..bytes.len()]).copy_from_slice(bytes);
|
(&mut buf[..bytes.len()]).copy_from_slice(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,6 +317,17 @@ pub struct DescriptorSetHeader {
|
||||||
wTotalLength: u16,
|
wTotalLength: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DescriptorSetHeader {
|
||||||
|
pub fn new(windows_version: u32) -> Self {
|
||||||
|
DescriptorSetHeader {
|
||||||
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
|
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||||
|
dwWindowsVersion: windows_version.to_le(),
|
||||||
|
wTotalLength: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Descriptor for DescriptorSetHeader {
|
impl Descriptor for DescriptorSetHeader {
|
||||||
const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor;
|
const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor;
|
||||||
fn write_to(&self, buf: &mut [u8]) {
|
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.
|
/// Table 11. Configuration subset header.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[repr(C, packed(1))]
|
#[repr(C, packed(1))]
|
||||||
|
@ -372,6 +350,18 @@ pub struct ConfigurationSubsetHeader {
|
||||||
wTotalLength: u16,
|
wTotalLength: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ConfigurationSubsetHeader {
|
||||||
|
pub fn new(config: u8) -> Self {
|
||||||
|
ConfigurationSubsetHeader {
|
||||||
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
|
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||||
|
bConfigurationValue: config,
|
||||||
|
bReserved: 0,
|
||||||
|
wTotalLength: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Descriptor for ConfigurationSubsetHeader {
|
impl Descriptor for ConfigurationSubsetHeader {
|
||||||
const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration;
|
const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration;
|
||||||
fn write_to(&self, buf: &mut [u8]) {
|
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.
|
/// Table 12. Function subset header.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[repr(C, packed(1))]
|
#[repr(C, packed(1))]
|
||||||
|
@ -390,6 +384,18 @@ pub struct FunctionSubsetHeader {
|
||||||
wSubsetLength: u16,
|
wSubsetLength: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FunctionSubsetHeader {
|
||||||
|
pub fn new(first_interface: u8) -> Self {
|
||||||
|
FunctionSubsetHeader {
|
||||||
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
|
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||||
|
bFirstInterface: first_interface,
|
||||||
|
bReserved: 0,
|
||||||
|
wSubsetLength: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Descriptor for FunctionSubsetHeader {
|
impl Descriptor for FunctionSubsetHeader {
|
||||||
const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction;
|
const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction;
|
||||||
fn write_to(&self, buf: &mut [u8]) {
|
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
|
// Feature Descriptors
|
||||||
|
|
||||||
/// A marker trait for feature descriptors that are valid at the device level.
|
/// 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.
|
/// A marker trait for feature descriptors that are valid at the function level.
|
||||||
pub trait FunctionLevelDescriptor {
|
pub trait FunctionLevelDescriptor: Descriptor {}
|
||||||
/// `true` when the feature descriptor may only be used at the function level in composite devices.
|
|
||||||
const COMPOSITE_ONLY: bool = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Table 13. Microsoft OS 2.0 compatible ID descriptor.
|
/// Table 13. Microsoft OS 2.0 compatible ID descriptor.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -419,9 +426,7 @@ pub struct CompatibleIdFeatureDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {}
|
impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {}
|
||||||
impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {
|
impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {}
|
||||||
const COMPOSITE_ONLY: bool = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Descriptor for CompatibleIdFeatureDescriptor {
|
impl Descriptor for CompatibleIdFeatureDescriptor {
|
||||||
const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId;
|
const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId;
|
||||||
|
@ -462,16 +467,12 @@ pub struct RegistryPropertyFeatureDescriptor<'a> {
|
||||||
wLength: u16,
|
wLength: u16,
|
||||||
wDescriptorType: u16,
|
wDescriptorType: u16,
|
||||||
wPropertyDataType: u16,
|
wPropertyDataType: u16,
|
||||||
wPropertyNameLength: u16,
|
|
||||||
PropertyName: &'a [u8],
|
PropertyName: &'a [u8],
|
||||||
wPropertyDataLength: u16,
|
|
||||||
PropertyData: &'a [u8],
|
PropertyData: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
|
impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
|
||||||
impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {
|
impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
|
||||||
const COMPOSITE_ONLY: bool = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> {
|
impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> {
|
||||||
const TYPE: DescriptorType = DescriptorType::FeatureRegProperty;
|
const TYPE: DescriptorType = DescriptorType::FeatureRegProperty;
|
||||||
|
@ -479,45 +480,22 @@ impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> {
|
||||||
10 + self.PropertyName.len() + self.PropertyData.len()
|
10 + self.PropertyName.len() + self.PropertyData.len()
|
||||||
}
|
}
|
||||||
fn write_to(&self, buf: &mut [u8]) {
|
fn write_to(&self, buf: &mut [u8]) {
|
||||||
assert!(buf.len() >= self.size());
|
assert!(buf.len() >= self.size(), "MSOS descriptor buffer full");
|
||||||
assert!(self.wPropertyNameLength as usize == self.PropertyName.len());
|
|
||||||
assert!(self.wPropertyDataLength as usize == self.PropertyData.len());
|
|
||||||
write_u16(buf, 0..2, self.wLength);
|
write_u16(buf, 0..2, self.wLength);
|
||||||
write_u16(buf, 2..4, self.wDescriptorType);
|
write_u16(buf, 2..4, self.wDescriptorType);
|
||||||
write_u16(buf, 4..6, self.wPropertyDataType);
|
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();
|
let pne = 8 + self.PropertyName.len();
|
||||||
(&mut buf[8..pne]).copy_from_slice(self.PropertyName);
|
(&mut buf[8..pne]).copy_from_slice(self.PropertyName);
|
||||||
let pds = pne + 2;
|
let pds = pne + 2;
|
||||||
let pde = pds + self.PropertyData.len();
|
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);
|
(&mut buf[pds..pde]).copy_from_slice(self.PropertyData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RegistryPropertyFeatureDescriptor<'a> {
|
impl<'a> RegistryPropertyFeatureDescriptor<'a> {
|
||||||
/// A registry property.
|
pub const DEVICE_INTERFACE_GUIDS_NAME: &U16CStr = {
|
||||||
///
|
|
||||||
/// `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 {
|
|
||||||
// Can't use defmt::panic in constant expressions (inside u16cstr!)
|
// Can't use defmt::panic in constant expressions (inside u16cstr!)
|
||||||
macro_rules! panic {
|
macro_rules! panic {
|
||||||
($($x:tt)*) => {
|
($($x:tt)*) => {
|
||||||
|
@ -527,10 +505,25 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::new_multi_string(
|
u16cstr!("DeviceInterfaceGUIDs")
|
||||||
u16cstr!("DeviceInterfaceGUIDs"),
|
};
|
||||||
u16cstr!("{A5DCBF10-6530-11D2-901F-00C04FB951ED}").as_slice_with_nul(),
|
|
||||||
)
|
/// 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.
|
/// 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.
|
/// 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 {
|
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::new_raw(
|
||||||
Self::u16str_bytes(name),
|
Self::u16str_bytes(name),
|
||||||
unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) },
|
unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) },
|
||||||
|
|
|
@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["nightly"]
|
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",
|
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"]
|
"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 }
|
rand = { version = "0.8.4", default-features = false }
|
||||||
embedded-storage = "0.3.0"
|
embedded-storage = "0.3.0"
|
||||||
usbd-hid = "0.6.0"
|
usbd-hid = "0.6.0"
|
||||||
serde = { version = "1.0.136", default-features = false }
|
serde = { version = "1.0.136", default-features = false }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "usb_serial_winusb"
|
||||||
|
required-features = ["msos-descriptor"]
|
||||||
|
|
139
examples/nrf52840/src/bin/usb_serial_winusb.rs
Normal file
139
examples/nrf52840/src/bin/usb_serial_winusb.rs
Normal file
|
@ -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<EndpointError> 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?;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue