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 <matt@beckenterprises.com>
Co-authored-by: alexmoon <alex@moonspot.org>
This commit is contained in:
bors[bot] 2023-02-07 19:31:10 +00:00 committed by GitHub
commit 366fab5b87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 948 additions and 8 deletions

View File

@ -13,6 +13,7 @@ target = "thumbv7em-none-eabi"
[features]
defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
msos-descriptor = []
default = ["usbd-hid"]
[dependencies]

View File

@ -3,6 +3,8 @@ use heapless::Vec;
use crate::control::ControlHandler;
use crate::descriptor::{BosWriter, DescriptorWriter};
use crate::driver::{Driver, Endpoint, EndpointType};
#[cfg(feature = "msos-descriptor")]
use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter};
use crate::types::*;
use crate::{DeviceStateHandler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START};
@ -130,6 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> {
device_descriptor: DescriptorWriter<'d>,
config_descriptor: DescriptorWriter<'d>,
bos_descriptor: BosWriter<'d>,
#[cfg(feature = "msos-descriptor")]
msos_descriptor: MsOsDescriptorWriter<'d>,
}
impl<'d, D: Driver<'d>> Builder<'d, D> {
@ -144,6 +149,7 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
device_descriptor_buf: &'d mut [u8],
config_descriptor_buf: &'d mut [u8],
bos_descriptor_buf: &'d mut [u8],
#[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8],
control_buf: &'d mut [u8],
handler: Option<&'d dyn DeviceStateHandler>,
) -> Self {
@ -182,11 +188,17 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
device_descriptor,
config_descriptor,
bos_descriptor,
#[cfg(feature = "msos-descriptor")]
msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf),
}
}
/// Creates the [`UsbDevice`] instance with the configuration in this builder.
pub fn build(mut self) -> UsbDevice<'d, D> {
#[cfg(feature = "msos-descriptor")]
let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor);
self.config_descriptor.end_configuration();
self.bos_descriptor.end_bos();
@ -199,6 +211,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
self.bos_descriptor.writer.into_buf(),
self.interfaces,
self.control_buf,
#[cfg(feature = "msos-descriptor")]
msos_descriptor,
)
}
@ -215,14 +229,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
///
/// If it's not set, no IAD descriptor is added.
pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> {
let first_interface = InterfaceNumber::new(self.interfaces.len() as u8);
let iface_count_index = if self.config.composite_with_iads {
self.config_descriptor.iad(
InterfaceNumber::new(self.interfaces.len() as _),
0,
class,
subclass,
protocol,
);
self.config_descriptor
.iad(first_interface, 0, class, subclass, protocol);
Some(self.config_descriptor.position() - 5)
} else {
@ -232,8 +242,32 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
FunctionBuilder {
builder: self,
iface_count_index,
#[cfg(feature = "msos-descriptor")]
first_interface,
}
}
#[cfg(feature = "msos-descriptor")]
/// Add an MS OS 2.0 Descriptor Set.
///
/// Panics if called more than once.
pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) {
self.msos_descriptor.header(windows_version, vendor_code);
}
#[cfg(feature = "msos-descriptor")]
/// Add an MS OS 2.0 Device Level Feature Descriptor.
pub fn msos_feature<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
}
}
/// Function builder.
@ -244,6 +278,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> {
pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> {
builder: &'a mut Builder<'d, D>,
iface_count_index: Option<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> {
@ -273,6 +317,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> {
next_alt_setting_number: 0,
}
}
#[cfg(feature = "msos-descriptor")]
/// Add an MS OS 2.0 Function Level Feature Descriptor.
pub fn msos_feature<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.

View File

@ -13,6 +13,7 @@ pub mod class;
pub mod control;
pub mod descriptor;
mod descriptor_reader;
pub mod msos;
pub mod types;
use embassy_futures::select::{select, Either};
@ -135,6 +136,8 @@ struct Inner<'d, D: Driver<'d>> {
set_address_pending: bool,
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
#[cfg(feature = "msos-descriptor")]
msos_descriptor: crate::msos::MsOsDescriptorSet<'d>,
}
impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
@ -147,6 +150,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
bos_descriptor: &'d [u8],
interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>,
control_buf: &'d mut [u8],
#[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>,
) -> UsbDevice<'d, D> {
// Start the USB bus.
// This prevent further allocation by consuming the driver.
@ -170,6 +174,8 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
address: 0,
set_address_pending: false,
interfaces,
#[cfg(feature = "msos-descriptor")]
msos_descriptor,
},
}
}
@ -603,6 +609,19 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
None => InResponse::Rejected,
}
}
#[cfg(feature = "msos-descriptor")]
(RequestType::Vendor, Recipient::Device) => {
if !self.msos_descriptor.is_empty() {
if req.request == self.msos_descriptor.vendor_code() && req.index == 7 {
// Index 7 retrieves the MS OS Descriptor Set
InResponse::Accepted(self.msos_descriptor.descriptor())
} else {
InResponse::Rejected
}
} else {
InResponse::Rejected
}
}
_ => InResponse::Rejected,
}
}

726
embassy-usb/src/msos.rs Normal file
View File

@ -0,0 +1,726 @@
#![cfg(feature = "msos-descriptor")]
//! Microsoft OS Descriptors
//!
//! <https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification>
use core::mem::size_of;
use super::{capability_type, BosWriter};
/// A serialized Microsoft OS 2.0 Descriptor set.
///
/// Create with [`DeviceDescriptorSetBuilder`].
pub struct MsOsDescriptorSet<'d> {
descriptor: &'d [u8],
vendor_code: u8,
}
impl<'d> MsOsDescriptorSet<'d> {
/// Gets the raw bytes of the MS OS descriptor
pub fn descriptor(&self) -> &[u8] {
self.descriptor
}
/// Gets the vendor code used by the host to retrieve the MS OS descriptor
pub fn vendor_code(&self) -> u8 {
self.vendor_code
}
/// Returns `true` if no MS OS descriptor data is available
pub fn is_empty(&self) -> bool {
self.descriptor.is_empty()
}
}
/// Writes a Microsoft OS 2.0 Descriptor set into a buffer.
pub struct MsOsDescriptorWriter<'d> {
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,
}
}
}
/// Returns `true` if the MS OS descriptor header has not yet been written
pub fn is_empty(&self) -> bool {
self.position == 0
}
/// Returns `true` if a configuration subset header has been started
pub fn is_in_config_subset(&self) -> bool {
self.config_mark.is_some()
}
/// Returns `true` if a function subset header has been started and not yet ended
pub fn is_in_function_subset(&self) -> bool {
self.function_mark.is_some()
}
/// 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);
}
/// Ends the current function subset (if any)
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(
capability_type::PLATFORM,
&[
0, // reserved
// platform capability UUID, Microsoft OS 2.0 platform compabitility
0xdf,
0x60,
0xdd,
0xd8,
0x89,
0x45,
0xc7,
0x4c,
0x9c,
0xd2,
0x65,
0x9d,
0x9e,
0x64,
0x8a,
0x9f,
// Minimum compatible Windows version
windows_version[0],
windows_version[1],
windows_version[2],
windows_version[3],
// Descriptor set length
len[0],
len[1],
self.vendor_code,
0x0, // Device does not support alternate enumeration
],
);
}
}
/// Microsoft Windows version codes
///
/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors.
pub mod windows_version {
/// Windows 8.1 (aka `NTDDI_WINBLUE`)
pub const WIN8_1: u32 = 0x06030000;
/// Windows 10
pub const WIN10: u32 = 0x0A000000;
}
mod sealed {
use core::mem::size_of;
/// A trait for descriptors
pub trait Descriptor: Sized {
const TYPE: super::DescriptorType;
/// The size of the descriptor's header.
fn size(&self) -> usize {
size_of::<Self>()
}
fn write_to(&self, buf: &mut [u8]);
}
pub trait DescriptorSet: Descriptor {
const LENGTH_OFFSET: usize;
}
}
use sealed::*;
/// Copies the data of `t` into `buf`.
///
/// # Safety
/// 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]) {
let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::<T>());
assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full");
(&mut buf[..bytes.len()]).copy_from_slice(bytes);
}
/// Table 9. Microsoft OS 2.0 descriptor wDescriptorType values.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum DescriptorType {
/// MS OS descriptor set header
SetHeaderDescriptor = 0,
/// Configuration subset header
SubsetHeaderConfiguration = 1,
/// Function subset header
SubsetHeaderFunction = 2,
/// Compatible device ID feature descriptor
FeatureCompatibleId = 3,
/// Registry property feature descriptor
FeatureRegProperty = 4,
/// Minimum USB resume time feature descriptor
FeatureMinResumeTime = 5,
/// Vendor revision feature descriptor
FeatureModelId = 6,
/// CCGP device descriptor feature descriptor
FeatureCcgpDevice = 7,
/// Vendor revision feature descriptor
FeatureVendorRevision = 8,
}
/// Table 5. Descriptor set information structure.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct DescriptorSetInformation {
dwWindowsVersion: u32,
wMSOSDescriptorSetTotalLength: u16,
bMS_VendorCode: u8,
bAltEnumCode: u8,
}
/// Table 4. Microsoft OS 2.0 platform capability descriptor header.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct PlatformDescriptor {
bLength: u8,
bDescriptorType: u8,
bDevCapabilityType: u8,
bReserved: u8,
platformCapabilityUUID: [u8; 16],
descriptor_set_information: DescriptorSetInformation,
}
/// Table 10. Microsoft OS 2.0 descriptor set header.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct DescriptorSetHeader {
wLength: u16,
wDescriptorType: u16,
dwWindowsVersion: u32,
wTotalLength: u16,
}
impl DescriptorSetHeader {
/// Creates a MS OS descriptor set header.
///
/// `windows_version` is the minimum Windows version the descriptor set can apply to.
pub fn new(windows_version: u32) -> Self {
DescriptorSetHeader {
wLength: (size_of::<Self>() as u16).to_le(),
wDescriptorType: (Self::TYPE as u16).to_le(),
dwWindowsVersion: windows_version.to_le(),
wTotalLength: 0,
}
}
}
impl Descriptor for DescriptorSetHeader {
const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl DescriptorSet for DescriptorSetHeader {
const LENGTH_OFFSET: usize = 8;
}
/// Table 11. Configuration subset header.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct ConfigurationSubsetHeader {
wLength: u16,
wDescriptorType: u16,
bConfigurationValue: u8,
bReserved: u8,
wTotalLength: u16,
}
impl ConfigurationSubsetHeader {
/// Creates a configuration subset header
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 {
const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl DescriptorSet for ConfigurationSubsetHeader {
const LENGTH_OFFSET: usize = 6;
}
/// Table 12. Function subset header.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct FunctionSubsetHeader {
wLength: u16,
wDescriptorType: u16,
bFirstInterface: u8,
bReserved: u8,
wSubsetLength: u16,
}
impl FunctionSubsetHeader {
/// Creates a function subset header
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 {
const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl DescriptorSet for FunctionSubsetHeader {
const LENGTH_OFFSET: usize = 6;
}
// Feature Descriptors
/// A marker trait for feature descriptors that are valid at the device level.
pub trait DeviceLevelDescriptor: Descriptor {}
/// A marker trait for feature descriptors that are valid at the function level.
pub trait FunctionLevelDescriptor: Descriptor {}
/// Table 13. Microsoft OS 2.0 compatible ID descriptor.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct CompatibleIdFeatureDescriptor {
wLength: u16,
wDescriptorType: u16,
compatibleId: [u8; 8],
subCompatibleId: [u8; 8],
}
impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {}
impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {}
impl Descriptor for CompatibleIdFeatureDescriptor {
const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl CompatibleIdFeatureDescriptor {
/// Creates a compatible ID feature descriptor
///
/// The ids must be 8 ASCII bytes or fewer.
pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self {
assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8);
let mut cid = [0u8; 8];
(&mut cid[..compatible_id.len()]).copy_from_slice(compatible_id.as_bytes());
let mut scid = [0u8; 8];
(&mut scid[..sub_compatible_id.len()]).copy_from_slice(sub_compatible_id.as_bytes());
Self::new_raw(cid, scid)
}
fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self {
Self {
wLength: (size_of::<Self>() as u16).to_le(),
wDescriptorType: (Self::TYPE as u16).to_le(),
compatibleId: compatible_id,
subCompatibleId: sub_compatible_id,
}
}
}
/// Table 14. Microsoft OS 2.0 registry property descriptor
#[allow(non_snake_case)]
pub struct RegistryPropertyFeatureDescriptor<'a> {
name: &'a str,
data: PropertyData<'a>,
}
/// Data values that can be encoded into a registry property descriptor
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PropertyData<'a> {
/// A registry property containing a string.
Sz(&'a str),
/// A registry property containing a string that expands environment variables.
ExpandSz(&'a str),
/// A registry property containing binary data.
Binary(&'a [u8]),
/// A registry property containing a little-endian 32-bit integer.
DwordLittleEndian(u32),
/// A registry property containing a big-endian 32-bit integer.
DwordBigEndian(u32),
/// A registry property containing a string that contains a symbolic link.
Link(&'a str),
/// A registry property containing multiple strings.
RegMultiSz(&'a [&'a str]),
}
fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize {
assert!(buf.len() >= val.len());
buf[..val.len()].copy_from_slice(val);
val.len()
}
fn write_utf16(val: &str, buf: &mut [u8]) -> usize {
let mut pos = 0;
for c in val.encode_utf16() {
pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]);
}
pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..])
}
impl<'a> PropertyData<'a> {
/// Gets the `PropertyDataType` for this property value
pub fn kind(&self) -> PropertyDataType {
match self {
PropertyData::Sz(_) => PropertyDataType::Sz,
PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz,
PropertyData::Binary(_) => PropertyDataType::Binary,
PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian,
PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian,
PropertyData::Link(_) => PropertyDataType::Link,
PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz,
}
}
/// Gets the size (in bytes) of this property value when encoded.
pub fn size(&self) -> usize {
match self {
PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => {
core::mem::size_of::<u16>() * (val.encode_utf16().count() + 1)
}
PropertyData::Binary(val) => val.len(),
PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val),
PropertyData::RegMultiSz(val) => {
core::mem::size_of::<u16>() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::<usize>() + 1
}
}
}
/// Encodes the data for this property value and writes it to `buf`.
pub fn write(&self, buf: &mut [u8]) -> usize {
match self {
PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf),
PropertyData::Binary(val) => write_bytes(val, buf),
PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf),
PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf),
PropertyData::RegMultiSz(val) => {
let mut pos = 0;
for s in *val {
pos += write_utf16(s, &mut buf[pos..]);
}
pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..])
}
}
}
}
/// Table 15. wPropertyDataType values for the Microsoft OS 2.0 registry property descriptor.
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum PropertyDataType {
/// A registry property containing a string.
Sz = 1,
/// A registry property containing a string that expands environment variables.
ExpandSz = 2,
/// A registry property containing binary data.
Binary = 3,
/// A registry property containing a little-endian 32-bit integer.
DwordLittleEndian = 4,
/// A registry property containing a big-endian 32-bit integer.
DwordBigEndian = 5,
/// A registry property containing a string that contains a symbolic link.
Link = 6,
/// A registry property containing multiple strings.
RegMultiSz = 7,
}
impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> {
const TYPE: DescriptorType = DescriptorType::FeatureRegProperty;
fn size(&self) -> usize {
10 + self.name_size() + self.data.size()
}
fn write_to(&self, buf: &mut [u8]) {
assert!(buf.len() >= self.size(), "MS OS descriptor buffer full");
let mut pos = 0;
pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]);
pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]);
pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]);
pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]);
pos += write_utf16(self.name, &mut buf[pos..]);
pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]);
self.data.write(&mut buf[pos..]);
}
}
impl<'a> RegistryPropertyFeatureDescriptor<'a> {
/// A registry property.
pub fn new(name: &'a str, data: PropertyData<'a>) -> Self {
Self { name, data }
}
fn name_size(&self) -> usize {
core::mem::size_of::<u16>() * (self.name.encode_utf16().count() + 1)
}
}
/// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct MinimumRecoveryTimeDescriptor {
wLength: u16,
wDescriptorType: u16,
bResumeRecoveryTime: u8,
bResumeSignalingTime: u8,
}
impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {}
impl Descriptor for MinimumRecoveryTimeDescriptor {
const TYPE: DescriptorType = DescriptorType::FeatureMinResumeTime;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl MinimumRecoveryTimeDescriptor {
/// Times are in milliseconds.
///
/// `resume_recovery_time` must be >= 0 and <= 10.
/// `resume_signaling_time` must be >= 1 and <= 20.
pub fn new(resume_recovery_time: u8, resume_signaling_time: u8) -> Self {
assert!(resume_recovery_time <= 10);
assert!(resume_signaling_time >= 1 && resume_signaling_time <= 20);
Self {
wLength: (size_of::<Self>() as u16).to_le(),
wDescriptorType: (Self::TYPE as u16).to_le(),
bResumeRecoveryTime: resume_recovery_time,
bResumeSignalingTime: resume_signaling_time,
}
}
}
/// Table 17. Microsoft OS 2.0 model ID descriptor.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct ModelIdDescriptor {
wLength: u16,
wDescriptorType: u16,
modelId: [u8; 16],
}
impl DeviceLevelDescriptor for ModelIdDescriptor {}
impl Descriptor for ModelIdDescriptor {
const TYPE: DescriptorType = DescriptorType::FeatureModelId;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl ModelIdDescriptor {
/// Creates a new model ID descriptor
///
/// `model_id` should be a uuid that uniquely identifies a physical device.
pub fn new(model_id: u128) -> Self {
Self {
wLength: (size_of::<Self>() as u16).to_le(),
wDescriptorType: (Self::TYPE as u16).to_le(),
modelId: model_id.to_le_bytes(),
}
}
}
/// Table 18. Microsoft OS 2.0 CCGP device descriptor.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct CcgpDeviceDescriptor {
wLength: u16,
wDescriptorType: u16,
}
impl DeviceLevelDescriptor for CcgpDeviceDescriptor {}
impl Descriptor for CcgpDeviceDescriptor {
const TYPE: DescriptorType = DescriptorType::FeatureCcgpDevice;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl CcgpDeviceDescriptor {
/// Creates a new CCGP device descriptor
pub fn new() -> Self {
Self {
wLength: (size_of::<Self>() as u16).to_le(),
wDescriptorType: (Self::TYPE as u16).to_le(),
}
}
}
/// Table 19. Microsoft OS 2.0 vendor revision descriptor.
#[allow(non_snake_case)]
#[repr(C, packed(1))]
pub struct VendorRevisionDescriptor {
wLength: u16,
wDescriptorType: u16,
/// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or
/// other MS OS descriptor. Shell set to greater than or equal to 1.
VendorRevision: u16,
}
impl DeviceLevelDescriptor for VendorRevisionDescriptor {}
impl FunctionLevelDescriptor for VendorRevisionDescriptor {}
impl Descriptor for VendorRevisionDescriptor {
const TYPE: DescriptorType = DescriptorType::FeatureVendorRevision;
fn write_to(&self, buf: &mut [u8]) {
unsafe { transmute_write_to(self, buf) }
}
}
impl VendorRevisionDescriptor {
/// Creates a new vendor revision descriptor
pub fn new(revision: u16) -> Self {
assert!(revision >= 1);
Self {
wLength: (size_of::<Self>() as u16).to_le(),
wDescriptorType: (Self::TYPE as u16).to_le(),
VendorRevision: revision.to_le(),
}
}
}

View File

@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0"
[features]
default = ["nightly"]
msos-descriptor = ["embassy-usb/msos-descriptor"]
nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net",
"embassy-lora", "lorawan-device", "lorawan"]
@ -34,4 +35,8 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
rand = { version = "0.8.4", default-features = false }
embedded-storage = "0.3.0"
usbd-hid = "0.6.0"
serde = { version = "1.0.136", default-features = false }
serde = { version = "1.0.136", default-features = false }
[[bin]]
name = "usb_serial_winusb"
required-features = ["msos-descriptor"]

View File

@ -0,0 +1,130 @@
#![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, HardwareVbusDetect, Instance, VbusDetect};
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 _};
// This is a randomly generated GUID to allow clients on Windows to find our device
const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"];
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
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, HardwareVbusDetect::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(
"DeviceInterfaceGUIDs",
msos::PropertyData::RegMultiSz(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: VbusDetect + '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?;
}
}