Lazily encode UTF16 values and add docs
This commit is contained in:
parent
9f9230ae7a
commit
aa21aebb0b
3 changed files with 166 additions and 180 deletions
|
@ -13,7 +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"]
|
msos-descriptor = []
|
||||||
default = ["usbd-hid"]
|
default = ["usbd-hid"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -25,7 +25,6 @@ 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, optional = true }
|
|
||||||
|
|
||||||
# for HID
|
# for HID
|
||||||
usbd-hid = { version = "0.6.0", optional = true }
|
usbd-hid = { version = "0.6.0", optional = true }
|
||||||
|
|
|
@ -5,16 +5,9 @@
|
||||||
//! <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>
|
||||||
|
|
||||||
use core::mem::size_of;
|
use core::mem::size_of;
|
||||||
use core::ops::Range;
|
|
||||||
|
|
||||||
pub use widestring::{u16cstr, u16str, U16CStr, U16Str};
|
|
||||||
|
|
||||||
use super::{capability_type, BosWriter};
|
use super::{capability_type, BosWriter};
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A serialized Microsoft OS 2.0 Descriptor set.
|
/// A serialized Microsoft OS 2.0 Descriptor set.
|
||||||
///
|
///
|
||||||
/// Create with [`DeviceDescriptorSetBuilder`].
|
/// Create with [`DeviceDescriptorSetBuilder`].
|
||||||
|
@ -24,14 +17,17 @@ pub struct MsOsDescriptorSet<'d> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d> MsOsDescriptorSet<'d> {
|
impl<'d> MsOsDescriptorSet<'d> {
|
||||||
|
/// Gets the raw bytes of the MS OS descriptor
|
||||||
pub fn descriptor(&self) -> &[u8] {
|
pub fn descriptor(&self) -> &[u8] {
|
||||||
self.descriptor
|
self.descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the vendor code used by the host to retrieve the MS OS descriptor
|
||||||
pub fn vendor_code(&self) -> u8 {
|
pub fn vendor_code(&self) -> u8 {
|
||||||
self.vendor_code
|
self.vendor_code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if no MS OS descriptor data is available
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.descriptor.is_empty()
|
self.descriptor.is_empty()
|
||||||
}
|
}
|
||||||
|
@ -39,7 +35,7 @@ impl<'d> MsOsDescriptorSet<'d> {
|
||||||
|
|
||||||
/// Writes a Microsoft OS 2.0 Descriptor set into a buffer.
|
/// Writes a Microsoft OS 2.0 Descriptor set into a buffer.
|
||||||
pub struct MsOsDescriptorWriter<'d> {
|
pub struct MsOsDescriptorWriter<'d> {
|
||||||
pub buf: &'d mut [u8],
|
buf: &'d mut [u8],
|
||||||
|
|
||||||
position: usize,
|
position: usize,
|
||||||
config_mark: Option<usize>,
|
config_mark: Option<usize>,
|
||||||
|
@ -75,14 +71,17 @@ impl<'d> MsOsDescriptorWriter<'d> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the MS OS descriptor header has not yet been written
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.position == 0
|
self.position == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if a configuration subset header has been started
|
||||||
pub fn is_in_config_subset(&self) -> bool {
|
pub fn is_in_config_subset(&self) -> bool {
|
||||||
self.config_mark.is_some()
|
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 {
|
pub fn is_in_function_subset(&self) -> bool {
|
||||||
self.function_mark.is_some()
|
self.function_mark.is_some()
|
||||||
}
|
}
|
||||||
|
@ -148,6 +147,7 @@ impl<'d> MsOsDescriptorWriter<'d> {
|
||||||
self.write(desc);
|
self.write(desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ends the current function subset (if any)
|
||||||
pub fn end_function(&mut self) {
|
pub fn end_function(&mut self) {
|
||||||
Self::end_subset::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark);
|
Self::end_subset::<FunctionSubsetHeader>(self.buf, self.position, &mut self.function_mark);
|
||||||
}
|
}
|
||||||
|
@ -212,29 +212,13 @@ impl<'d> MsOsDescriptorWriter<'d> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Microsoft Windows version codes
|
||||||
|
///
|
||||||
|
/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors.
|
||||||
pub mod windows_version {
|
pub mod windows_version {
|
||||||
pub const WIN2K: u32 = 0x05000000;
|
/// Windows 8.1 (aka `NTDDI_WINBLUE`)
|
||||||
pub const WIN2KSP1: u32 = 0x05000100;
|
|
||||||
pub const WIN2KSP2: u32 = 0x05000200;
|
|
||||||
pub const WIN2KSP3: u32 = 0x05000300;
|
|
||||||
pub const WIN2KSP4: u32 = 0x05000400;
|
|
||||||
|
|
||||||
pub const WINXP: u32 = 0x05010000;
|
|
||||||
pub const WINXPSP1: u32 = 0x05010100;
|
|
||||||
pub const WINXPSP2: u32 = 0x05010200;
|
|
||||||
pub const WINXPSP3: u32 = 0x05010300;
|
|
||||||
pub const WINXPSP4: u32 = 0x05010400;
|
|
||||||
|
|
||||||
pub const VISTA: u32 = 0x06000000;
|
|
||||||
pub const VISTASP1: u32 = 0x06000100;
|
|
||||||
pub const VISTASP2: u32 = 0x06000200;
|
|
||||||
pub const VISTASP3: u32 = 0x06000300;
|
|
||||||
pub const VISTASP4: u32 = 0x06000400;
|
|
||||||
|
|
||||||
pub const WIN7: u32 = 0x06010000;
|
|
||||||
pub const WIN8: u32 = 0x06020000;
|
|
||||||
/// AKA `NTDDI_WINBLUE`
|
|
||||||
pub const WIN8_1: u32 = 0x06030000;
|
pub const WIN8_1: u32 = 0x06030000;
|
||||||
|
/// Windows 10
|
||||||
pub const WIN10: u32 = 0x0A000000;
|
pub const WIN10: u32 = 0x0A000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +250,7 @@ use sealed::*;
|
||||||
/// 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(), "MSOS descriptor buffer full");
|
assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full");
|
||||||
(&mut buf[..bytes.len()]).copy_from_slice(bytes);
|
(&mut buf[..bytes.len()]).copy_from_slice(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,14 +258,23 @@ unsafe fn transmute_write_to<T: Sized>(t: &T, buf: &mut [u8]) {
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum DescriptorType {
|
pub enum DescriptorType {
|
||||||
|
/// MS OS descriptor set header
|
||||||
SetHeaderDescriptor = 0,
|
SetHeaderDescriptor = 0,
|
||||||
|
/// Configuration subset header
|
||||||
SubsetHeaderConfiguration = 1,
|
SubsetHeaderConfiguration = 1,
|
||||||
|
/// Function subset header
|
||||||
SubsetHeaderFunction = 2,
|
SubsetHeaderFunction = 2,
|
||||||
|
/// Compatible device ID feature descriptor
|
||||||
FeatureCompatibleId = 3,
|
FeatureCompatibleId = 3,
|
||||||
|
/// Registry property feature descriptor
|
||||||
FeatureRegProperty = 4,
|
FeatureRegProperty = 4,
|
||||||
|
/// Minimum USB resume time feature descriptor
|
||||||
FeatureMinResumeTime = 5,
|
FeatureMinResumeTime = 5,
|
||||||
|
/// Vendor revision feature descriptor
|
||||||
FeatureModelId = 6,
|
FeatureModelId = 6,
|
||||||
|
/// CCGP device descriptor feature descriptor
|
||||||
FeatureCcgpDevice = 7,
|
FeatureCcgpDevice = 7,
|
||||||
|
/// Vendor revision feature descriptor
|
||||||
FeatureVendorRevision = 8,
|
FeatureVendorRevision = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,6 +311,9 @@ pub struct DescriptorSetHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DescriptorSetHeader {
|
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 {
|
pub fn new(windows_version: u32) -> Self {
|
||||||
DescriptorSetHeader {
|
DescriptorSetHeader {
|
||||||
wLength: (size_of::<Self>() as u16).to_le(),
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
|
@ -351,6 +347,7 @@ pub struct ConfigurationSubsetHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurationSubsetHeader {
|
impl ConfigurationSubsetHeader {
|
||||||
|
/// Creates a configuration subset header
|
||||||
pub fn new(config: u8) -> Self {
|
pub fn new(config: u8) -> Self {
|
||||||
ConfigurationSubsetHeader {
|
ConfigurationSubsetHeader {
|
||||||
wLength: (size_of::<Self>() as u16).to_le(),
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
|
@ -385,6 +382,7 @@ pub struct FunctionSubsetHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FunctionSubsetHeader {
|
impl FunctionSubsetHeader {
|
||||||
|
/// Creates a function subset header
|
||||||
pub fn new(first_interface: u8) -> Self {
|
pub fn new(first_interface: u8) -> Self {
|
||||||
FunctionSubsetHeader {
|
FunctionSubsetHeader {
|
||||||
wLength: (size_of::<Self>() as u16).to_le(),
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
|
@ -436,11 +434,8 @@ impl Descriptor for CompatibleIdFeatureDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompatibleIdFeatureDescriptor {
|
impl CompatibleIdFeatureDescriptor {
|
||||||
/// Creates a compatible ID descriptor that signals WINUSB driver compatiblilty.
|
/// Creates a compatible ID feature descriptor
|
||||||
pub fn new_winusb() -> Self {
|
///
|
||||||
Self::new_raw([b'W', b'I', b'N', b'U', b'S', b'B', 0, 0], [0u8; 8])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The ids must be 8 ASCII bytes or fewer.
|
/// The ids must be 8 ASCII bytes or fewer.
|
||||||
pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self {
|
pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self {
|
||||||
assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8);
|
assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8);
|
||||||
|
@ -451,7 +446,7 @@ impl CompatibleIdFeatureDescriptor {
|
||||||
Self::new_raw(cid, scid)
|
Self::new_raw(cid, scid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self {
|
fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
wLength: (size_of::<Self>() as u16).to_le(),
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||||
|
@ -464,129 +459,87 @@ impl CompatibleIdFeatureDescriptor {
|
||||||
/// Table 14. Microsoft OS 2.0 registry property descriptor
|
/// Table 14. Microsoft OS 2.0 registry property descriptor
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub struct RegistryPropertyFeatureDescriptor<'a> {
|
pub struct RegistryPropertyFeatureDescriptor<'a> {
|
||||||
wLength: u16,
|
name: &'a str,
|
||||||
wDescriptorType: u16,
|
data: PropertyData<'a>,
|
||||||
wPropertyDataType: u16,
|
|
||||||
PropertyName: &'a [u8],
|
|
||||||
PropertyData: &'a [u8],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
|
/// Data values that can be encoded into a registry property descriptor
|
||||||
impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {}
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> {
|
pub enum PropertyData<'a> {
|
||||||
const TYPE: DescriptorType = DescriptorType::FeatureRegProperty;
|
/// A registry property containing a string.
|
||||||
fn size(&self) -> usize {
|
Sz(&'a str),
|
||||||
10 + self.PropertyName.len() + self.PropertyData.len()
|
/// A registry property containing a string that expands environment variables.
|
||||||
}
|
ExpandSz(&'a str),
|
||||||
fn write_to(&self, buf: &mut [u8]) {
|
|
||||||
assert!(buf.len() >= self.size(), "MSOS descriptor buffer full");
|
|
||||||
write_u16(buf, 0..2, self.wLength);
|
|
||||||
write_u16(buf, 2..4, self.wDescriptorType);
|
|
||||||
write_u16(buf, 4..6, self.wPropertyDataType);
|
|
||||||
write_u16(buf, 6..8, (self.PropertyName.len() as u16).to_le());
|
|
||||||
let pne = 8 + self.PropertyName.len();
|
|
||||||
(&mut buf[8..pne]).copy_from_slice(self.PropertyName);
|
|
||||||
let pds = pne + 2;
|
|
||||||
let pde = pds + self.PropertyData.len();
|
|
||||||
write_u16(buf, pne..pds, (self.PropertyData.len() as u16).to_le());
|
|
||||||
(&mut buf[pds..pde]).copy_from_slice(self.PropertyData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RegistryPropertyFeatureDescriptor<'a> {
|
|
||||||
pub const DEVICE_INTERFACE_GUIDS_NAME: &U16CStr = {
|
|
||||||
// Can't use defmt::panic in constant expressions (inside u16cstr!)
|
|
||||||
macro_rules! panic {
|
|
||||||
($($x:tt)*) => {
|
|
||||||
{
|
|
||||||
::core::panic!($($x)*);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
u16cstr!("DeviceInterfaceGUIDs")
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
pub fn new_string<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self {
|
|
||||||
Self::new_raw(Self::u16str_bytes(name), Self::u16str_bytes(data), PropertyDataType::Sz)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A registry property containing a NUL-terminated 16-bit Unicode string that expands environment variables.
|
|
||||||
pub fn new_string_expand<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self {
|
|
||||||
Self::new_raw(
|
|
||||||
Self::u16str_bytes(name),
|
|
||||||
Self::u16str_bytes(data),
|
|
||||||
PropertyDataType::ExpandSz,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A registry property containing a NUL-terminated 16-bit Unicode string that contains a symbolic link.
|
|
||||||
pub fn new_link<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d U16CStr) -> Self {
|
|
||||||
Self::new_raw(
|
|
||||||
Self::u16str_bytes(name),
|
|
||||||
Self::u16str_bytes(data),
|
|
||||||
PropertyDataType::Link,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
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::u16str_bytes(name),
|
|
||||||
unsafe { core::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) },
|
|
||||||
PropertyDataType::RegMultiSz,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A registry property containing binary data.
|
/// A registry property containing binary data.
|
||||||
pub fn new_binary<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d [u8]) -> Self {
|
Binary(&'a [u8]),
|
||||||
Self::new_raw(Self::u16str_bytes(name), data, PropertyDataType::Binary)
|
/// A registry property containing a little-endian 32-bit integer.
|
||||||
}
|
DwordLittleEndian(u32),
|
||||||
|
|
||||||
/// A registry property containing a Little-Endian 32-bit integer.
|
|
||||||
///
|
|
||||||
/// The function assumes that `data` is already little-endian, it does not convert it.
|
|
||||||
pub fn new_dword_le<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self {
|
|
||||||
Self::new_raw(
|
|
||||||
Self::u16str_bytes(name),
|
|
||||||
unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::<i32>()) },
|
|
||||||
PropertyDataType::DwordLittleEndian,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A registry property containing a big-endian 32-bit integer.
|
/// A registry property containing a big-endian 32-bit integer.
|
||||||
///
|
DwordBigEndian(u32),
|
||||||
/// The function assumes that `data` is already big-endian, it does not convert it.
|
/// A registry property containing a string that contains a symbolic link.
|
||||||
pub fn new_dword_be<'n: 'a, 'd: 'a>(name: &'n U16CStr, data: &'d i32) -> Self {
|
Link(&'a str),
|
||||||
Self::new_raw(
|
/// A registry property containing multiple strings.
|
||||||
Self::u16str_bytes(name),
|
RegMultiSz(&'a [&'a str]),
|
||||||
unsafe { core::slice::from_raw_parts(data as *const i32 as *const u8, size_of::<i32>()) },
|
}
|
||||||
PropertyDataType::DwordBigEndian,
|
|
||||||
)
|
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..])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,15 +547,57 @@ impl<'a> RegistryPropertyFeatureDescriptor<'a> {
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum PropertyDataType {
|
pub enum PropertyDataType {
|
||||||
|
/// A registry property containing a string.
|
||||||
Sz = 1,
|
Sz = 1,
|
||||||
|
/// A registry property containing a string that expands environment variables.
|
||||||
ExpandSz = 2,
|
ExpandSz = 2,
|
||||||
|
/// A registry property containing binary data.
|
||||||
Binary = 3,
|
Binary = 3,
|
||||||
|
/// A registry property containing a little-endian 32-bit integer.
|
||||||
DwordLittleEndian = 4,
|
DwordLittleEndian = 4,
|
||||||
|
/// A registry property containing a big-endian 32-bit integer.
|
||||||
DwordBigEndian = 5,
|
DwordBigEndian = 5,
|
||||||
|
/// A registry property containing a string that contains a symbolic link.
|
||||||
Link = 6,
|
Link = 6,
|
||||||
|
/// A registry property containing multiple strings.
|
||||||
RegMultiSz = 7,
|
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.
|
/// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[repr(C, packed(1))]
|
#[repr(C, packed(1))]
|
||||||
|
@ -658,15 +653,14 @@ impl Descriptor for ModelIdDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelIdDescriptor {
|
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 {
|
pub fn new(model_id: u128) -> Self {
|
||||||
Self::new_bytes(model_id.to_le_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_bytes(model_id: [u8; 16]) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
wLength: (size_of::<Self>() as u16).to_le(),
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
wDescriptorType: (Self::TYPE as u16).to_le(),
|
wDescriptorType: (Self::TYPE as u16).to_le(),
|
||||||
modelId: model_id,
|
modelId: model_id.to_le_bytes(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -689,6 +683,7 @@ impl Descriptor for CcgpDeviceDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CcgpDeviceDescriptor {
|
impl CcgpDeviceDescriptor {
|
||||||
|
/// Creates a new CCGP device descriptor
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
wLength: (size_of::<Self>() as u16).to_le(),
|
wLength: (size_of::<Self>() as u16).to_le(),
|
||||||
|
@ -704,7 +699,7 @@ pub struct VendorRevisionDescriptor {
|
||||||
wLength: u16,
|
wLength: u16,
|
||||||
wDescriptorType: u16,
|
wDescriptorType: u16,
|
||||||
/// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or
|
/// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or
|
||||||
/// other MSOS descriptor. Shell set to greater than or equal to 1.
|
/// other MS OS descriptor. Shell set to greater than or equal to 1.
|
||||||
VendorRevision: u16,
|
VendorRevision: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,6 +714,7 @@ impl Descriptor for VendorRevisionDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VendorRevisionDescriptor {
|
impl VendorRevisionDescriptor {
|
||||||
|
/// Creates a new vendor revision descriptor
|
||||||
pub fn new(revision: u16) -> Self {
|
pub fn new(revision: u16) -> Self {
|
||||||
assert!(revision >= 1);
|
assert!(revision >= 1);
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use core::mem;
|
||||||
use defmt::{info, panic};
|
use defmt::{info, panic};
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_futures::join::join;
|
use embassy_futures::join::join;
|
||||||
use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply};
|
use embassy_nrf::usb::{Driver, HardwareVbusDetect, Instance, VbusDetect};
|
||||||
use embassy_nrf::{interrupt, pac};
|
use embassy_nrf::{interrupt, pac};
|
||||||
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
|
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
|
||||||
use embassy_usb::driver::EndpointError;
|
use embassy_usb::driver::EndpointError;
|
||||||
|
@ -15,17 +15,8 @@ use embassy_usb::msos::{self, windows_version};
|
||||||
use embassy_usb::{Builder, Config};
|
use embassy_usb::{Builder, Config};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
const DEVICE_INTERFACE_GUIDS: &[u16] = {
|
// This is a randomly generated GUID to allow clients on Windows to find our device
|
||||||
// Can't use defmt::panic in constant expressions (inside u16str!)
|
const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"];
|
||||||
macro_rules! panic {
|
|
||||||
($($x:tt)*) => {
|
|
||||||
{
|
|
||||||
::core::panic!($($x)*);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
msos::u16str!("{EAA9A5DC-30BA-44BC-9232-606CDC875321}\0\0").as_slice()
|
|
||||||
};
|
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
|
@ -39,7 +30,7 @@ async fn main(_spawner: Spawner) {
|
||||||
// Create the driver, from the HAL.
|
// Create the driver, from the HAL.
|
||||||
let irq = interrupt::take!(USBD);
|
let irq = interrupt::take!(USBD);
|
||||||
let power_irq = interrupt::take!(POWER_CLOCK);
|
let power_irq = interrupt::take!(POWER_CLOCK);
|
||||||
let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq));
|
let driver = Driver::new(p.USBD, irq, HardwareVbusDetect::new(power_irq));
|
||||||
|
|
||||||
// Create embassy-usb Config
|
// Create embassy-usb Config
|
||||||
let mut config = Config::new(0xc0de, 0xcafe);
|
let mut config = Config::new(0xc0de, 0xcafe);
|
||||||
|
@ -89,9 +80,9 @@ async fn main(_spawner: Spawner) {
|
||||||
msos_writer.configuration(0);
|
msos_writer.configuration(0);
|
||||||
msos_writer.function(0);
|
msos_writer.function(0);
|
||||||
msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));
|
msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));
|
||||||
msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new_multi_string(
|
msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new(
|
||||||
msos::RegistryPropertyFeatureDescriptor::DEVICE_INTERFACE_GUIDS_NAME,
|
"DeviceInterfaceGUIDs",
|
||||||
DEVICE_INTERFACE_GUIDS,
|
msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Build the builder.
|
// Build the builder.
|
||||||
|
@ -126,7 +117,7 @@ impl From<EndpointError> for Disconnected {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn echo<'d, T: Instance + 'd, P: UsbSupply + 'd>(
|
async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>(
|
||||||
class: &mut CdcAcmClass<'d, Driver<'d, T, P>>,
|
class: &mut CdcAcmClass<'d, Driver<'d, T, P>>,
|
||||||
) -> Result<(), Disconnected> {
|
) -> Result<(), Disconnected> {
|
||||||
let mut buf = [0; 64];
|
let mut buf = [0; 64];
|
||||||
|
|
Loading…
Reference in a new issue