Merge pull request #102 from kirisauce/dev

Add support for getting device descriptor and speed from opened device
This commit is contained in:
Kevin Mehall 2025-01-19 23:10:31 -07:00 committed by GitHub
commit a34c4497b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 334 additions and 29 deletions

View file

@ -25,6 +25,10 @@ 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}"),

View file

@ -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<u16> = 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:?}");
}

View file

@ -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;
@ -164,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]
@ -180,6 +181,189 @@ 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<usize> {
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([u8; DESCRIPTOR_LEN_DEVICE as usize]);
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.
///
/// ### Panics
/// * when the buffer is too short for a device descriptor
/// * when the first descriptor is not a device descriptor
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[0..DESCRIPTOR_LEN_DEVICE as usize].try_into().unwrap())
}
/// Get the bytes of the descriptor.
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[allow(unused)]
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! {
impl DeviceDescriptor {
/// `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;
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")]
pub fn num_configurations at 17 -> u8;
}
}
impl DeviceDescriptor {
/// `iManufacturer` descriptor field: Index for manufacturer description string.
pub fn manufacturer_string_index(&self) -> Option<u8> {
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<u8> {
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<u8> {
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")
.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()
}
}
#[allow(unused)]
pub(crate) fn validate_config_descriptor(buf: &[u8]) -> Option<usize> {
if buf.len() < DESCRIPTOR_LEN_CONFIGURATION as usize {
if buf.len() != 0 {
@ -549,6 +733,7 @@ impl From<ActiveConfigurationError> 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<Item = &[u8]> {
iter::from_fn(move || {
let total_len = validate_config_descriptor(buf)?;
@ -659,6 +844,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(), 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(&[
0x09, 0x02, 0x19, 0x00, 0x01, 0x01, 0x00, 0xe0, 0x00,
0x09, 0x04, 0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00,

View file

@ -1,14 +1,14 @@
use crate::{
descriptors::{
decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError,
Configuration, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING,
Configuration, DeviceDescriptor, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING,
},
platform,
transfer::{
Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError,
TransferFuture,
},
DeviceInfo, Error,
DeviceInfo, Error, Speed,
};
use log::error;
use std::{io::ErrorKind, sync::Arc, time::Duration};
@ -94,6 +94,18 @@ impl Device {
Ok(())
}
/// Get the device descriptor.
///
/// This returns cached data and does not perform IO.
pub fn device_descriptor(&self) -> DeviceDescriptor {
self.backend.device_descriptor()
}
/// Get device speed.
pub fn speed(&self) -> Option<Speed> {
self.backend.speed()
}
/// Get information about the active configuration.
///
/// This returns cached data and does not perform IO. However, it can fail if the

View file

@ -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::{
@ -33,7 +33,7 @@ use crate::{
transfer::{
notify_completion, Control, Direction, EndpointType, TransferError, TransferHandle,
},
DeviceInfo, Error,
DeviceInfo, Error, Speed,
};
pub(crate) struct LinuxDevice {
@ -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<Item = &[u8]> {
parse_concatenated_config_descriptors(&self.descriptors[DESCRIPTOR_LEN_DEVICE as usize..])
}
@ -405,6 +416,21 @@ impl LinuxDevice {
);
return Err(ErrorKind::Other.into());
}
pub(crate) fn speed(&self) -> Option<Speed> {
usbfs::get_speed(&self.fd)
.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),
// 4 is wireless USB, but we don't support it
5 => Some(Speed::Super),
6 => Some(Speed::SuperPlus),
_ => None,
})
}
}
impl Drop for LinuxDevice {

View file

@ -286,3 +286,10 @@ pub fn clear_halt<Fd: AsFd>(fd: Fd, endpoint: u8) -> io::Result<()> {
ioctl::ioctl(fd, ctl)
}
}
pub fn get_speed<Fd: AsFd>(fd: Fd) -> io::Result<usize> {
unsafe {
let ctl = Transfer::<ioctl::NoneOpcode<b'U', 31, ()>, ()>::new(());
ioctl::ioctl(fd, ctl)
}
}

View file

@ -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<Speed>,
active_config: AtomicU8,
is_open_exclusive: Mutex<bool>,
claimed_interfaces: AtomicUsize,
@ -51,7 +54,7 @@ impl MacDevice {
pub(crate) fn from_device_info(d: &DeviceInfo) -> Result<Arc<MacDevice>, 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<Speed> {
self.speed
}
pub(crate) fn active_configuration_value(&self) -> u8 {
self.active_config.load(Ordering::SeqCst)
}

View file

@ -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<u8> {
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<DeviceDescriptor> {
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]);

View file

@ -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<IoKitDevice, Error> {
pub(crate) fn new(service: &IoService) -> Result<IoKitDevice, Error> {
unsafe {
// According to the libusb maintainers, this will sometimes spuriously
// return `kIOReturnNoResources` for reasons Apple won't explain, usually

View file

@ -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<Vec<u8>>,
active_config: u8,
speed: Option<Speed>,
devinst: DevInst,
handles: Mutex<BTreeMap<u8, WinusbFileHandle>>,
}
@ -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<Speed> {
self.speed
}
pub(crate) fn active_configuration_value(&self) -> u8 {
self.active_config
}