1189: USB: Add MS OS Descriptors (alternate implementation) r=Dirbaio a=alexmoon

This is an alternate API for  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  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?;
}
}