From 91fe523d76ce0d40ac77029d2a34c1fb27965dea Mon Sep 17 00:00:00 2001 From: kirisauce Date: Sun, 29 Dec 2024 17:53:45 +0800 Subject: [PATCH 01/11] Add DeviceDescriptor type for parsing device descriptors --- src/descriptors.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/src/descriptors.rs b/src/descriptors.rs index 6d8a62f..4e9be3a 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -17,6 +17,7 @@ use crate::{ Error, }; +pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01; pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18; pub(crate) const DESCRIPTOR_TYPE_CONFIGURATION: u8 = 0x02; @@ -180,6 +181,104 @@ macro_rules! descriptor_fields { } } +/// Information about a USB device. +#[derive(Clone)] +pub struct DeviceDescriptor<'a>(&'a [u8]); + +impl<'a> DeviceDescriptor<'a> { + /// Create a `DeviceDescriptor` from a buffer containing a series of descriptors. + /// + /// You normally obtain a `DeviceDescriptor` from a [`Device`][crate::Device], but this allows creating + /// one from your own descriptor bytes for tests. + /// + /// ### Panics + /// * when the buffer is too short for a device descriptor + /// * when the first descriptor is not a device descriptor + pub fn new(buf: &'a [u8]) -> Self { + assert!(buf.len() >= DESCRIPTOR_LEN_DEVICE as usize); + assert!(buf[0] as usize >= DESCRIPTOR_LEN_DEVICE as usize); + assert!(buf[1] == DESCRIPTOR_TYPE_DEVICE); + Self(buf) + } +} + +descriptor_fields! { + impl<'a> DeviceDescriptor<'a> { + /// `bcdUSB` descriptor field: USB Specification Number. + #[doc(alias = "bcdUSB")] + pub fn usb_version at 2 -> u16; + + /// `bDeviceClass` descriptor field: Class code, assigned by USB-IF. + #[doc(alias = "bDeviceClass")] + pub fn class at 4 -> u8; + + /// `bDeviceSubClass` descriptor field: Subclass code, assigned by USB-IF. + #[doc(alias = "bDeviceSubClass")] + pub fn subclass at 5 -> u8; + + /// `bDeviceProtocol` descriptor field: Protocol code, assigned by USB-IF. + #[doc(alias = "bDeviceProtocol")] + pub fn protocol at 6 -> u8; + + /// `bMaxPacketSize0` descriptor field: Maximum packet size for 0 Endpoint. + #[doc(alias = "bMaxPacketSize0")] + pub fn max_packet_size_0 at 7 -> u8; + + /// `idVendor` descriptor field: Vendor ID, assigned by USB-IF. + #[doc(alias = "idVendor")] + pub fn vendor_id at 8 -> u16; + + /// `idProduct` descriptor field: Product ID, assigned by the manufacturer. + #[doc(alias = "idProduct")] + pub fn product_id at 10 -> u16; + + /// `bcdDevice` descriptor field: Device release number. + #[doc(alias = "bcdDevice")] + pub fn device_version at 12 -> u16; + + /// `iManufacturer` descriptor field: Index for manufacturer description string. + pub fn manufacturer_string_index at 14 -> u8; + + /// `iProduct` descriptor field: Index for product description string. + pub fn product_string_index at 15 -> u8; + + /// `iSerialNumber` descriptor field: Index for serial number string. + pub fn serial_number_string_index at 16 -> u8; + + /// `bNumConfigurations` descriptor field: Number of configurations + #[doc(alias = "bNumConfigurations")] + pub fn num_configurations at 17 -> u8; + } +} + +impl<'a> Debug for DeviceDescriptor<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DeviceDescriptor") + .field("usb_version", &format_args!("0x{:04X}", self.usb_version())) + .field("class", &format_args!("0x{:02X}", self.class())) + .field("subclass", &format_args!("0x{:02X}", self.subclass())) + .field("protocol", &format_args!("0x{:02X}", self.protocol())) + .field("max_packet_size_0", &self.max_packet_size_0()) + .field("vendor_id", &format_args!("0x{:04X}", self.vendor_id())) + .field("product_id", &format_args!("0x{:04X}", self.product_id())) + .field( + "device_version", + &format_args!("0x{:04X}", self.device_version()), + ) + .field( + "manufacturer_string_index", + &self.manufacturer_string_index(), + ) + .field("product_string_index", &self.product_string_index()) + .field( + "serial_number_string_index", + &self.serial_number_string_index(), + ) + .field("num_configurations", &self.num_configurations()) + .finish() + } +} + pub(crate) fn validate_config_descriptor(buf: &[u8]) -> Option { if buf.len() < DESCRIPTOR_LEN_CONFIGURATION as usize { if buf.len() != 0 { @@ -659,6 +758,23 @@ fn test_malformed() { #[test] #[rustfmt::skip] fn test_linux_root_hub() { + let dev = DeviceDescriptor::new(&[ + 0x12, 0x01, 0x00, 0x02, 0x09, 0x00, 0x01, 0x40, 0x6b, + 0x1d, 0x02, 0x00, 0x10, 0x05, 0x03, 0x02, 0x01, 0x01 + ]); + assert_eq!(dev.usb_version(), 0x0200); + assert_eq!(dev.class(), 0x09); + assert_eq!(dev.subclass(), 0x00); + assert_eq!(dev.protocol(), 0x01); + assert_eq!(dev.max_packet_size_0(), 64); + assert_eq!(dev.vendor_id(), 0x1d6b); + assert_eq!(dev.product_id(), 0x0002); + assert_eq!(dev.device_version(), 0x0510); + assert_eq!(dev.manufacturer_string_index(), 3); + assert_eq!(dev.product_string_index(), 2); + assert_eq!(dev.serial_number_string_index(), 1); + assert_eq!(dev.num_configurations(), 1); + let c = Configuration(&[ 0x09, 0x02, 0x19, 0x00, 0x01, 0x01, 0x00, 0xe0, 0x00, 0x09, 0x04, 0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, From 2c6a6d37580941d301078bc816b74cea9bba5d32 Mon Sep 17 00:00:00 2001 From: kirisauce Date: Sun, 29 Dec 2024 18:04:43 +0800 Subject: [PATCH 02/11] Linux: Support getting DeviceDescriptor and DeviceInfo from a opened device --- src/descriptors.rs | 30 ++++++++++++++++++++++++++++++ src/device.rs | 21 +++++++++++++++++++-- src/platform/linux_usbfs/device.rs | 4 ++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index 4e9be3a..eb238e3 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -181,6 +181,36 @@ macro_rules! descriptor_fields { } } +/// Check whether the buffer contains a valid device descriptor. +/// On success, it will return length of the descriptor, or returns `None`. +pub(crate) fn validate_device_descriptor(buf: &[u8]) -> Option { + if buf.len() < DESCRIPTOR_LEN_DEVICE as usize { + if buf.len() != 0 { + warn!( + "device descriptor buffer is {} bytes, need {}", + buf.len(), + DESCRIPTOR_LEN_DEVICE + ); + } + return None; + } + + if buf[0] < DESCRIPTOR_LEN_DEVICE { + warn!("invalid device descriptor bLength"); + return None; + } + + if buf[1] != DESCRIPTOR_TYPE_DEVICE { + warn!( + "device bDescriptorType is {}, not a device descriptor", + buf[1] + ); + return None; + } + + return Some(buf[0] as usize); +} + /// Information about a USB device. #[derive(Clone)] pub struct DeviceDescriptor<'a>(&'a [u8]); diff --git a/src/device.rs b/src/device.rs index b3d6227..5c706f8 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,7 +1,8 @@ use crate::{ descriptors::{ - decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError, - Configuration, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING, + decode_string_descriptor, validate_device_descriptor, validate_string_descriptor, + ActiveConfigurationError, Configuration, DeviceDescriptor, InterfaceAltSetting, + DESCRIPTOR_TYPE_STRING, }, platform, transfer::{ @@ -341,6 +342,22 @@ impl Device { t.submit::(data); TransferFuture::new(t) } + + /// Get the device descriptor. + /// + /// The only situation when it returns `None` is + /// that the cached descriptors contain no valid device descriptor. + /// + /// ### Platform-specific notes + /// + /// * This is only supported on Linux at present. + /// * On Linux, this method uses descriptors cached in memory, instead + /// of sending a request to the device for a descriptor. + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn device_descriptor(&self) -> Option { + let buf = self.backend.descriptors(); + validate_device_descriptor(&buf).map(|len| DeviceDescriptor::new(&buf[0..len])) + } } /// An opened interface of a USB device. diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index 0db566f..a0c9cfd 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -405,6 +405,10 @@ impl LinuxDevice { ); return Err(ErrorKind::Other.into()); } + + pub(crate) fn descriptors(&self) -> &[u8] { + &self.descriptors + } } impl Drop for LinuxDevice { From 5a7a4be0cf6772518c4d804923782789105eb152 Mon Sep 17 00:00:00 2001 From: kirisauce Date: Mon, 13 Jan 2025 22:58:12 +0800 Subject: [PATCH 03/11] Linux: Support getting device speed from the kernel This is done through an ioctl `USBDEVFS_GET_SPEED`. Judging from the kernel souce code, it just returns a cached value, and should not make any request to the device. --- src/device.rs | 15 ++++++++++++++- src/platform/linux_usbfs/device.rs | 16 +++++++++++++++- src/platform/linux_usbfs/usbfs.rs | 7 +++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/device.rs b/src/device.rs index 5c706f8..63f86c6 100644 --- a/src/device.rs +++ b/src/device.rs @@ -9,7 +9,7 @@ use crate::{ Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError, TransferFuture, }, - DeviceInfo, Error, + DeviceInfo, Error, Speed, }; use log::error; use std::{io::ErrorKind, sync::Arc, time::Duration}; @@ -358,6 +358,19 @@ impl Device { let buf = self.backend.descriptors(); validate_device_descriptor(&buf).map(|len| DeviceDescriptor::new(&buf[0..len])) } + + /// Get device speed. + /// + /// On most platforms, the speed is stored by the OS, and calling this method should not + /// make any request to the device. + /// + /// ### Platform-specific notes + /// + /// * This is only supported on Linux at present. + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn get_speed(&self) -> Result, Error> { + self.backend.get_speed() + } } /// An opened interface of a USB device. diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index a0c9cfd..6adeeb5 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -33,7 +33,7 @@ use crate::{ transfer::{ notify_completion, Control, Direction, EndpointType, TransferError, TransferHandle, }, - DeviceInfo, Error, + DeviceInfo, Error, Speed, }; pub(crate) struct LinuxDevice { @@ -409,6 +409,20 @@ impl LinuxDevice { pub(crate) fn descriptors(&self) -> &[u8] { &self.descriptors } + + pub(crate) fn get_speed(&self) -> Result, Error> { + usbfs::get_speed(&self.fd) + .map_err(|errno| errno.into()) + .map(|raw_speed| match raw_speed { + 1 => Some(Speed::Low), + 2 => Some(Speed::Full), + 3 => Some(Speed::High), + // 4 is wireless USB, but we don't support it + 5 => Some(Speed::Super), + 6 => Some(Speed::SuperPlus), + _ => None, + }) + } } impl Drop for LinuxDevice { diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index 22725db..20485d5 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -286,3 +286,10 @@ pub fn clear_halt(fd: Fd, endpoint: u8) -> io::Result<()> { ioctl::ioctl(fd, ctl) } } + +pub fn get_speed(fd: Fd) -> io::Result { + unsafe { + let ctl = Transfer::, ()>::new(()); + ioctl::ioctl(fd, ctl) + } +} From c6f2812671649d66d7c2101a8084ea579c90a8b7 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 19:01:15 -0700 Subject: [PATCH 04/11] Make DeviceDescriptor contain a fixed size array Unlike the Configuration descriptor, it does not contain nested descriptors. It's also made infallible, because the kernel wouldn't enumerate the device without it. --- examples/descriptors.rs | 2 ++ src/descriptors.rs | 18 ++++++++-------- src/device.rs | 34 ++++++++++++------------------ src/platform/linux_usbfs/device.rs | 17 ++++++++++----- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/examples/descriptors.rs b/examples/descriptors.rs index fbc2d03..b5ebddc 100644 --- a/examples/descriptors.rs +++ b/examples/descriptors.rs @@ -25,6 +25,8 @@ fn inspect_device(dev: DeviceInfo) { } }; + println!("{:#?}", dev.device_descriptor()); + match dev.active_configuration() { Ok(config) => println!("Active configuration is {}", config.configuration_value()), Err(e) => println!("Unknown active configuration: {e}"), diff --git a/src/descriptors.rs b/src/descriptors.rs index eb238e3..efcc21c 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -165,13 +165,13 @@ impl<'a> Iterator for Descriptors<'a> { } macro_rules! descriptor_fields { - (impl<'a> $tname:ident<'a> { + (impl $(<$( $i_lt:lifetime ),+>)? $tname:ident $(<$( $t_lt:lifetime ),+>)? { $( $(#[$attr:meta])* $vis:vis fn $name:ident at $pos:literal -> $ty:ty; )* }) => { - impl<'a> $tname<'a> { + impl $(<$( $i_lt ),+>)? $tname $(<$( $t_lt ),+>)? { $( $(#[$attr])* #[inline] @@ -213,10 +213,10 @@ pub(crate) fn validate_device_descriptor(buf: &[u8]) -> Option { /// Information about a USB device. #[derive(Clone)] -pub struct DeviceDescriptor<'a>(&'a [u8]); +pub struct DeviceDescriptor([u8; DESCRIPTOR_LEN_DEVICE as usize]); -impl<'a> DeviceDescriptor<'a> { - /// Create a `DeviceDescriptor` from a buffer containing a series of descriptors. +impl DeviceDescriptor { + /// Create a `DeviceDescriptor` from a buffer beginning with a device descriptor. /// /// You normally obtain a `DeviceDescriptor` from a [`Device`][crate::Device], but this allows creating /// one from your own descriptor bytes for tests. @@ -224,16 +224,16 @@ impl<'a> DeviceDescriptor<'a> { /// ### Panics /// * when the buffer is too short for a device descriptor /// * when the first descriptor is not a device descriptor - pub fn new(buf: &'a [u8]) -> Self { + pub fn new(buf: &[u8]) -> Self { assert!(buf.len() >= DESCRIPTOR_LEN_DEVICE as usize); assert!(buf[0] as usize >= DESCRIPTOR_LEN_DEVICE as usize); assert!(buf[1] == DESCRIPTOR_TYPE_DEVICE); - Self(buf) + Self(buf[0..DESCRIPTOR_LEN_DEVICE as usize].try_into().unwrap()) } } descriptor_fields! { - impl<'a> DeviceDescriptor<'a> { + impl DeviceDescriptor { /// `bcdUSB` descriptor field: USB Specification Number. #[doc(alias = "bcdUSB")] pub fn usb_version at 2 -> u16; @@ -281,7 +281,7 @@ descriptor_fields! { } } -impl<'a> Debug for DeviceDescriptor<'a> { +impl Debug for DeviceDescriptor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DeviceDescriptor") .field("usb_version", &format_args!("0x{:04X}", self.usb_version())) diff --git a/src/device.rs b/src/device.rs index 63f86c6..6d8bf92 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,8 +1,7 @@ use crate::{ descriptors::{ - decode_string_descriptor, validate_device_descriptor, validate_string_descriptor, - ActiveConfigurationError, Configuration, DeviceDescriptor, InterfaceAltSetting, - DESCRIPTOR_TYPE_STRING, + decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError, + Configuration, DeviceDescriptor, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING, }, platform, transfer::{ @@ -95,6 +94,18 @@ impl Device { Ok(()) } + /// Get the device descriptor. + /// + /// This returns cached data and does not perform IO. + /// + /// ### Platform-specific notes + /// + /// * This is only supported on Linux at present. + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn device_descriptor(&self) -> DeviceDescriptor { + self.backend.device_descriptor() + } + /// Get information about the active configuration. /// /// This returns cached data and does not perform IO. However, it can fail if the @@ -342,23 +353,6 @@ impl Device { t.submit::(data); TransferFuture::new(t) } - - /// Get the device descriptor. - /// - /// The only situation when it returns `None` is - /// that the cached descriptors contain no valid device descriptor. - /// - /// ### Platform-specific notes - /// - /// * This is only supported on Linux at present. - /// * On Linux, this method uses descriptors cached in memory, instead - /// of sending a request to the device for a descriptor. - #[cfg(any(target_os = "linux", target_os = "android"))] - pub fn device_descriptor(&self) -> Option { - let buf = self.backend.descriptors(); - validate_device_descriptor(&buf).map(|len| DeviceDescriptor::new(&buf[0..len])) - } - /// Get device speed. /// /// On most platforms, the speed is stored by the OS, and calling this method should not diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index 6adeeb5..aac6719 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -25,7 +25,7 @@ use super::{ usbfs::{self, Urb}, SysfsPath, }; -use crate::descriptors::Configuration; +use crate::descriptors::{validate_device_descriptor, Configuration, DeviceDescriptor}; use crate::platform::linux_usbfs::events::Watch; use crate::transfer::{ControlType, Recipient}; use crate::{ @@ -84,6 +84,13 @@ impl LinuxDevice { buf }; + let Some(_) = validate_device_descriptor(&descriptors) else { + return Err(Error::new( + ErrorKind::InvalidData, + "invalid device descriptor", + )); + }; + let active_config = if let Some(active_config) = active_config { active_config } else { @@ -152,6 +159,10 @@ impl LinuxDevice { } } + pub(crate) fn device_descriptor(&self) -> DeviceDescriptor { + DeviceDescriptor::new(&self.descriptors) + } + pub(crate) fn configuration_descriptors(&self) -> impl Iterator { parse_concatenated_config_descriptors(&self.descriptors[DESCRIPTOR_LEN_DEVICE as usize..]) } @@ -406,10 +417,6 @@ impl LinuxDevice { return Err(ErrorKind::Other.into()); } - pub(crate) fn descriptors(&self) -> &[u8] { - &self.descriptors - } - pub(crate) fn get_speed(&self) -> Result, Error> { usbfs::get_speed(&self.fd) .map_err(|errno| errno.into()) From de5771030c343e99d60a0071befc7c4df1dc2665 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 19:02:44 -0700 Subject: [PATCH 05/11] Simplify `Device::speed` API We'll log if the ioctl failed, but there's probably not a use case to determine why it failed to justify Result>. It'll be infallible (besides unknown values) on other platforms. --- examples/descriptors.rs | 2 ++ src/device.rs | 22 ++++++++++------------ src/platform/linux_usbfs/device.rs | 7 ++++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/examples/descriptors.rs b/examples/descriptors.rs index b5ebddc..98f46ff 100644 --- a/examples/descriptors.rs +++ b/examples/descriptors.rs @@ -27,6 +27,8 @@ fn inspect_device(dev: DeviceInfo) { println!("{:#?}", dev.device_descriptor()); + println!("Speed: {:?}", dev.speed()); + match dev.active_configuration() { Ok(config) => println!("Active configuration is {}", config.configuration_value()), Err(e) => println!("Unknown active configuration: {e}"), diff --git a/src/device.rs b/src/device.rs index 6d8bf92..6022ed0 100644 --- a/src/device.rs +++ b/src/device.rs @@ -106,6 +106,16 @@ impl Device { self.backend.device_descriptor() } + /// Get device speed. + /// + /// ### Platform-specific notes + /// + /// * This is only supported on Linux at present. + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn speed(&self) -> Option { + self.backend.speed() + } + /// Get information about the active configuration. /// /// This returns cached data and does not perform IO. However, it can fail if the @@ -353,18 +363,6 @@ impl Device { t.submit::(data); TransferFuture::new(t) } - /// Get device speed. - /// - /// On most platforms, the speed is stored by the OS, and calling this method should not - /// make any request to the device. - /// - /// ### Platform-specific notes - /// - /// * This is only supported on Linux at present. - #[cfg(any(target_os = "linux", target_os = "android"))] - pub fn get_speed(&self) -> Result, Error> { - self.backend.get_speed() - } } /// An opened interface of a USB device. diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index aac6719..a078e04 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -417,10 +417,11 @@ impl LinuxDevice { return Err(ErrorKind::Other.into()); } - pub(crate) fn get_speed(&self) -> Result, Error> { + pub(crate) fn speed(&self) -> Option { usbfs::get_speed(&self.fd) - .map_err(|errno| errno.into()) - .map(|raw_speed| match raw_speed { + .inspect_err(|e| log::error!("USBDEVFS_GET_SPEED failed: {e}")) + .ok() + .and_then(|raw_speed| match raw_speed { 1 => Some(Speed::Low), 2 => Some(Speed::Full), 3 => Some(Speed::High), From 55072d40e7dbd65ac0ccaee12843e6cbed065ae8 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 19:32:25 -0700 Subject: [PATCH 06/11] Implement Device::{device_descriptor, speed} on Windows --- src/device.rs | 8 +++---- src/platform/windows_winusb/device.rs | 30 +++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/device.rs b/src/device.rs index 6022ed0..2d6345d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -100,8 +100,8 @@ impl Device { /// /// ### Platform-specific notes /// - /// * This is only supported on Linux at present. - #[cfg(any(target_os = "linux", target_os = "android"))] + /// * This is only supported on Linux and Windows at present. + #[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))] pub fn device_descriptor(&self) -> DeviceDescriptor { self.backend.device_descriptor() } @@ -110,8 +110,8 @@ impl Device { /// /// ### Platform-specific notes /// - /// * This is only supported on Linux at present. - #[cfg(any(target_os = "linux", target_os = "android"))] + /// * This is only supported on Linux and Windows at present. + #[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))] pub fn speed(&self) -> Option { self.backend.speed() } diff --git a/src/platform/windows_winusb/device.rs b/src/platform/windows_winusb/device.rs index 12273e4..e0a3b2f 100644 --- a/src/platform/windows_winusb/device.rs +++ b/src/platform/windows_winusb/device.rs @@ -2,7 +2,7 @@ use std::{ collections::{btree_map::Entry, BTreeMap}, ffi::c_void, io::{self, ErrorKind}, - mem::size_of_val, + mem::{size_of_val, transmute}, os::windows::{ io::{AsRawHandle, RawHandle}, prelude::OwnedHandle, @@ -23,9 +23,12 @@ use windows_sys::Win32::{ }; use crate::{ - descriptors::{validate_config_descriptor, DESCRIPTOR_TYPE_CONFIGURATION}, + descriptors::{ + validate_config_descriptor, DeviceDescriptor, DESCRIPTOR_LEN_DEVICE, + DESCRIPTOR_TYPE_CONFIGURATION, + }, transfer::{Control, Direction, EndpointType, Recipient, TransferError, TransferHandle}, - DeviceInfo, Error, + DeviceInfo, Error, Speed, }; use super::{ @@ -38,8 +41,10 @@ use super::{ }; pub(crate) struct WindowsDevice { + device_descriptor: DeviceDescriptor, config_descriptors: Vec>, active_config: u8, + speed: Option, devinst: DevInst, handles: Mutex>, } @@ -54,8 +59,15 @@ impl WindowsDevice { // instead. let hub_port = HubPort::by_child_devinst(d.devinst)?; let connection_info = hub_port.get_info()?; - let num_configurations = connection_info.device_desc.bNumConfigurations; + // Safety: Windows API struct is repr(C), packed, and we're assuming Windows is little-endian + let device_descriptor = unsafe { + DeviceDescriptor::new(&transmute::<_, [u8; DESCRIPTOR_LEN_DEVICE as usize]>( + connection_info.device_desc, + )) + }; + + let num_configurations = connection_info.device_desc.bNumConfigurations; let config_descriptors = (0..num_configurations) .flat_map(|i| { let res = hub_port.get_descriptor(DESCRIPTOR_TYPE_CONFIGURATION, i, 0); @@ -70,13 +82,23 @@ impl WindowsDevice { .collect(); Ok(Arc::new(WindowsDevice { + device_descriptor, config_descriptors, + speed: connection_info.speed, active_config: connection_info.active_config, devinst: d.devinst, handles: Mutex::new(BTreeMap::new()), })) } + pub(crate) fn device_descriptor(&self) -> DeviceDescriptor { + self.device_descriptor.clone() + } + + pub(crate) fn speed(&self) -> Option { + self.speed + } + pub(crate) fn active_configuration_value(&self) -> u8 { self.active_config } From 9c98f2bd32b24be86ac6c4b1b3c6117d04fa07c8 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 20:47:37 -0800 Subject: [PATCH 07/11] Implement Device::{device_descriptor, speed} on macOS --- src/descriptors.rs | 36 +++++++++++++++++++++++++ src/device.rs | 10 ------- src/platform/macos_iokit/device.rs | 26 +++++++++++++++--- src/platform/macos_iokit/enumeration.rs | 24 ++++++++++++++++- src/platform/macos_iokit/iokit_usb.rs | 2 +- 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index efcc21c..449bc1f 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -230,6 +230,42 @@ impl DeviceDescriptor { assert!(buf[1] == DESCRIPTOR_TYPE_DEVICE); Self(buf[0..DESCRIPTOR_LEN_DEVICE as usize].try_into().unwrap()) } + + pub(crate) fn from_fields( + usb_version: u16, + class: u8, + subclass: u8, + protocol: u8, + max_packet_size_0: u8, + vendor_id: u16, + product_id: u16, + device_version: u16, + manufacturer_string_index: u8, + product_string_index: u8, + serial_number_string_index: u8, + num_configurations: u8, + ) -> DeviceDescriptor { + DeviceDescriptor([ + DESCRIPTOR_LEN_DEVICE, + DESCRIPTOR_TYPE_DEVICE, + usb_version.to_le_bytes()[0], + usb_version.to_le_bytes()[1], + class, + subclass, + protocol, + max_packet_size_0, + vendor_id.to_le_bytes()[0], + vendor_id.to_le_bytes()[1], + product_id.to_le_bytes()[0], + product_id.to_le_bytes()[1], + device_version.to_le_bytes()[0], + device_version.to_le_bytes()[1], + manufacturer_string_index, + product_string_index, + serial_number_string_index, + num_configurations, + ]) + } } descriptor_fields! { diff --git a/src/device.rs b/src/device.rs index 2d6345d..a54d0ec 100644 --- a/src/device.rs +++ b/src/device.rs @@ -97,21 +97,11 @@ impl Device { /// Get the device descriptor. /// /// This returns cached data and does not perform IO. - /// - /// ### Platform-specific notes - /// - /// * This is only supported on Linux and Windows at present. - #[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))] pub fn device_descriptor(&self) -> DeviceDescriptor { self.backend.device_descriptor() } /// Get device speed. - /// - /// ### Platform-specific notes - /// - /// * This is only supported on Linux and Windows at present. - #[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))] pub fn speed(&self) -> Option { self.backend.speed() } diff --git a/src/platform/macos_iokit/device.rs b/src/platform/macos_iokit/device.rs index 0f00297..65eb3c9 100644 --- a/src/platform/macos_iokit/device.rs +++ b/src/platform/macos_iokit/device.rs @@ -12,9 +12,10 @@ use std::{ use log::{debug, error}; use crate::{ - platform::macos_iokit::events::add_event_source, + descriptors::DeviceDescriptor, + platform::macos_iokit::{enumeration::device_descriptor_from_fields, events::add_event_source}, transfer::{Control, Direction, EndpointType, TransferError, TransferHandle}, - DeviceInfo, Error, + DeviceInfo, Error, Speed, }; use super::{ @@ -29,6 +30,8 @@ use super::{ pub(crate) struct MacDevice { _event_registration: EventRegistration, pub(super) device: IoKitDevice, + device_descriptor: DeviceDescriptor, + speed: Option, active_config: AtomicU8, is_open_exclusive: Mutex, claimed_interfaces: AtomicUsize, @@ -51,7 +54,7 @@ impl MacDevice { pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, Error> { log::info!("Opening device from registry id {}", d.registry_id); let service = service_by_registry_id(d.registry_id)?; - let device = IoKitDevice::new(service)?; + let device = IoKitDevice::new(&service)?; let _event_registration = add_event_source(device.create_async_event_source()?); let opened = match unsafe { call_iokit_function!(device.raw, USBDeviceOpen()) } { @@ -64,6 +67,13 @@ impl MacDevice { } }; + let device_descriptor = device_descriptor_from_fields(&service).ok_or_else(|| { + Error::new( + ErrorKind::Other, + "could not read properties for device descriptor", + ) + })?; + let active_config = if let Some(active_config) = guess_active_config(&device) { log::debug!("Active config from single descriptor is {}", active_config); active_config @@ -76,12 +86,22 @@ impl MacDevice { Ok(Arc::new(MacDevice { _event_registration, device, + device_descriptor, + speed: d.speed, active_config: AtomicU8::new(active_config), is_open_exclusive: Mutex::new(opened), claimed_interfaces: AtomicUsize::new(0), })) } + pub(crate) fn device_descriptor(&self) -> DeviceDescriptor { + self.device_descriptor.clone() + } + + pub(crate) fn speed(&self) -> Option { + self.speed + } + pub(crate) fn active_configuration_value(&self) -> u8 { self.active_config.load(Ordering::SeqCst) } diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index 1f9f850..0db3fd7 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -15,7 +15,10 @@ use io_kit_sys::{ }; use log::debug; -use crate::{BusInfo, DeviceInfo, Error, InterfaceInfo, Speed, UsbControllerType}; +use crate::{ + descriptors::DeviceDescriptor, BusInfo, DeviceInfo, Error, InterfaceInfo, Speed, + UsbControllerType, +}; use super::iokit::{IoService, IoServiceIterator}; /// IOKit class name for PCI USB XHCI high-speed controllers (USB 3.0+) @@ -268,6 +271,25 @@ fn parse_location_id(id: u32) -> Vec { chain } +/// There is no API in iokit to get the cached device descriptor as bytes, but +/// we have all the fields to rebuild it exactly. +pub(crate) fn device_descriptor_from_fields(device: &IoService) -> Option { + Some(DeviceDescriptor::from_fields( + get_integer_property(&device, "bcdUSB")? as u16, + get_integer_property(&device, "bDeviceClass")? as u8, + get_integer_property(&device, "bDeviceSubClass")? as u8, + get_integer_property(&device, "bDeviceProtocol")? as u8, + get_integer_property(&device, "bMaxPacketSize0")? as u8, + get_integer_property(&device, "idVendor")? as u16, + get_integer_property(&device, "idProduct")? as u16, + get_integer_property(&device, "bcdDevice")? as u16, + get_integer_property(&device, "iManufacturer")? as u8, + get_integer_property(&device, "iProduct")? as u8, + get_integer_property(&device, "iSerialNumber")? as u8, + get_integer_property(&device, "bNumConfigurations")? as u8, + )) +} + #[test] fn test_parse_location_id() { assert_eq!(parse_location_id(0x01234567), vec![2, 3, 4, 5, 6, 7]); diff --git a/src/platform/macos_iokit/iokit_usb.rs b/src/platform/macos_iokit/iokit_usb.rs index 30de38f..68758dd 100644 --- a/src/platform/macos_iokit/iokit_usb.rs +++ b/src/platform/macos_iokit/iokit_usb.rs @@ -39,7 +39,7 @@ pub(crate) struct IoKitDevice { impl IoKitDevice { /// Get the raw USB device associated with the service. - pub(crate) fn new(service: IoService) -> Result { + pub(crate) fn new(service: &IoService) -> Result { unsafe { // According to the libusb maintainers, this will sometimes spuriously // return `kIOReturnNoResources` for reasons Apple won't explain, usually From 9a93ca796d63c44f356d6dfb51a97a05eddc0d72 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 20:59:05 -0800 Subject: [PATCH 08/11] Wrap DeviceDescriptor string index fields in Option --- src/descriptors.rs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/descriptors.rs b/src/descriptors.rs index 449bc1f..7b0522b 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -302,14 +302,9 @@ descriptor_fields! { #[doc(alias = "bcdDevice")] pub fn device_version at 12 -> u16; - /// `iManufacturer` descriptor field: Index for manufacturer description string. - pub fn manufacturer_string_index at 14 -> u8; - - /// `iProduct` descriptor field: Index for product description string. - pub fn product_string_index at 15 -> u8; - - /// `iSerialNumber` descriptor field: Index for serial number string. - pub fn serial_number_string_index at 16 -> u8; + fn manufacturer_string_index_raw at 14 -> u8; + fn product_string_index_raw at 15 -> u8; + fn serial_number_string_index_raw at 16 -> u8; /// `bNumConfigurations` descriptor field: Number of configurations #[doc(alias = "bNumConfigurations")] @@ -317,6 +312,22 @@ descriptor_fields! { } } +impl DeviceDescriptor { + /// `iManufacturer` descriptor field: Index for manufacturer description string. + pub fn manufacturer_string_index(&self) -> Option { + Some(self.manufacturer_string_index_raw()).filter(|&i| i != 0) + } + + /// `iProduct` descriptor field: Index for product description string. + pub fn product_string_index(&self) -> Option { + Some(self.product_string_index_raw()).filter(|&i| i != 0) + } + + /// `iSerialNumber` descriptor field: Index for serial number string. + pub fn serial_number_string_index(&self) -> Option { + Some(self.serial_number_string_index_raw()).filter(|&i| i != 0) + } +} impl Debug for DeviceDescriptor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DeviceDescriptor") @@ -836,9 +847,9 @@ fn test_linux_root_hub() { assert_eq!(dev.vendor_id(), 0x1d6b); assert_eq!(dev.product_id(), 0x0002); assert_eq!(dev.device_version(), 0x0510); - assert_eq!(dev.manufacturer_string_index(), 3); - assert_eq!(dev.product_string_index(), 2); - assert_eq!(dev.serial_number_string_index(), 1); + assert_eq!(dev.manufacturer_string_index(), Some(3)); + assert_eq!(dev.product_string_index(), Some(2)); + assert_eq!(dev.serial_number_string_index(), Some(1)); assert_eq!(dev.num_configurations(), 1); let c = Configuration(&[ From 8e4caaa5b222fb9f08e24f9d7c43b34293f1e433 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 21:00:03 -0800 Subject: [PATCH 09/11] Use Device::device_descriptor in string_descriptors example --- examples/string_descriptors.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/examples/string_descriptors.rs b/examples/string_descriptors.rs index 8d81cb0..a2fdcc2 100644 --- a/examples/string_descriptors.rs +++ b/examples/string_descriptors.rs @@ -29,14 +29,7 @@ fn inspect_device(dev: DeviceInfo) { let timeout = Duration::from_millis(100); - let dev_descriptor = dev.get_descriptor(0x01, 0, 0, timeout).unwrap(); - if dev_descriptor.len() < 18 - || dev_descriptor[0] as usize > dev_descriptor.len() - || dev_descriptor[1] != 0x01 - { - println!(" Invalid device descriptor: {dev_descriptor:?}"); - return; - } + let dev_descriptor = dev.device_descriptor(); let languages: Vec = dev .get_string_descriptor_supported_languages(timeout) @@ -46,20 +39,17 @@ fn inspect_device(dev: DeviceInfo) { let language = languages.first().copied().unwrap_or(US_ENGLISH); - let i_manufacturer = dev_descriptor[14]; - if i_manufacturer != 0 { + if let Some(i_manufacturer) = dev_descriptor.manufacturer_string_index() { let s = dev.get_string_descriptor(i_manufacturer, language, timeout); println!(" Manufacturer({i_manufacturer}): {s:?}"); } - let i_product = dev_descriptor[15]; - if i_product != 0 { + if let Some(i_product) = dev_descriptor.product_string_index() { let s = dev.get_string_descriptor(i_product, language, timeout); println!(" Product({i_product}): {s:?}"); } - let i_serial = dev_descriptor[16]; - if i_serial != 0 { + if let Some(i_serial) = dev_descriptor.serial_number_string_index() { let s = dev.get_string_descriptor(i_serial, language, timeout); println!(" Serial({i_serial}): {s:?}"); } From 0e8c9f59f435268ec72704863bf950452ec4da77 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 21:04:16 -0800 Subject: [PATCH 10/11] Add DeviceDescriptor::as_bytes --- src/descriptors.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/descriptors.rs b/src/descriptors.rs index 7b0522b..e3bb4be 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -231,6 +231,11 @@ impl DeviceDescriptor { Self(buf[0..DESCRIPTOR_LEN_DEVICE as usize].try_into().unwrap()) } + /// Get the bytes of the descriptor. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + pub(crate) fn from_fields( usb_version: u16, class: u8, From 4871f227a0adbbe42470140988ef9e7e67c5e2a2 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 19 Jan 2025 21:04:40 -0800 Subject: [PATCH 11/11] Fix existing warnings --- src/descriptors.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/descriptors.rs b/src/descriptors.rs index e3bb4be..2f64e16 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -183,6 +183,7 @@ macro_rules! descriptor_fields { /// Check whether the buffer contains a valid device descriptor. /// On success, it will return length of the descriptor, or returns `None`. +#[allow(unused)] pub(crate) fn validate_device_descriptor(buf: &[u8]) -> Option { if buf.len() < DESCRIPTOR_LEN_DEVICE as usize { if buf.len() != 0 { @@ -236,6 +237,7 @@ impl DeviceDescriptor { &self.0 } + #[allow(unused)] pub(crate) fn from_fields( usb_version: u16, class: u8, @@ -361,6 +363,7 @@ impl Debug for DeviceDescriptor { } } +#[allow(unused)] pub(crate) fn validate_config_descriptor(buf: &[u8]) -> Option { if buf.len() < DESCRIPTOR_LEN_CONFIGURATION as usize { if buf.len() != 0 { @@ -730,6 +733,7 @@ impl From for Error { } /// Split a chain of concatenated configuration descriptors by `wTotalLength` +#[allow(unused)] pub(crate) fn parse_concatenated_config_descriptors(mut buf: &[u8]) -> impl Iterator { iter::from_fn(move || { let total_len = validate_config_descriptor(buf)?;