From 2c9c699a286b6b23317a9f026ec06279b2bb8dac Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 28 Jul 2024 23:59:20 -0600 Subject: [PATCH 1/5] Add port_chain --- src/enumeration.rs | 15 +++++++ src/platform/linux_usbfs/enumeration.rs | 16 ++++++- src/platform/macos_iokit/enumeration.rs | 30 ++++++++++++- src/platform/windows_winusb/enumeration.rs | 50 +++++++++++++++++++++- src/platform/windows_winusb/util.rs | 8 +++- 5 files changed, 112 insertions(+), 7 deletions(-) diff --git a/src/enumeration.rs b/src/enumeration.rs index 4718ded..8c9d146 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -48,6 +48,7 @@ pub struct DeviceInfo { pub(crate) bus_number: u8, pub(crate) device_address: u8, + pub(crate) port_chain: Option>, pub(crate) vendor_id: u16, pub(crate) product_id: u16, @@ -122,6 +123,19 @@ impl DeviceInfo { self.port_number } + /// Path of port numbers identifying the physical port where the device is + /// connected. + /// + /// The first value is the bus number, and subsequent values represent the + /// port used on each hub on the path to this device. The path is expected + /// to remain stable across device insertions or reboots. + /// + /// Since USB SuperSpeed is a separate topology from USB 2.0 speeds, a + /// physical port may be identified differently depending on speed. + pub fn port_chain(&self) -> Option<&[u8]> { + self.port_chain.as_deref() + } + /// *(Windows-only)* Driver associated with the device as a whole #[cfg(target_os = "windows")] pub fn driver(&self) -> Option<&str> { @@ -263,6 +277,7 @@ impl std::fmt::Debug for DeviceInfo { s.field("bus_number", &self.bus_number) .field("device_address", &self.device_address) + .field("port_chain", &format_args!("{:?}", self.port_chain)) .field("vendor_id", &format_args!("0x{:04X}", self.vendor_id)) .field("product_id", &format_args!("0x{:04X}", self.product_id)) .field( diff --git a/src/platform/linux_usbfs/enumeration.rs b/src/platform/linux_usbfs/enumeration.rs index d72792b..4d63817 100644 --- a/src/platform/linux_usbfs/enumeration.rs +++ b/src/platform/linux_usbfs/enumeration.rs @@ -1,5 +1,6 @@ use std::fs; use std::io; +use std::iter; use std::num::ParseIntError; use std::path::PathBuf; use std::str::FromStr; @@ -124,9 +125,20 @@ pub fn list_devices() -> Result, Error> { pub fn probe_device(path: SysfsPath) -> Result { debug!("Probing device {:?}", path.0); + + let bus_number = path.read_attr("busnum")?; + let device_address = path.read_attr("devnum")?; + + let port_chain = path.read_attr::("devpath").ok().and_then(|p| { + iter::once(Some(bus_number)) + .chain(p.split('.').map(|v| v.parse::().ok())) + .collect::>>() + }); + Ok(DeviceInfo { - bus_number: path.read_attr("busnum")?, - device_address: path.read_attr("devnum")?, + bus_number, + device_address, + port_chain, vendor_id: path.read_attr_hex("idVendor")?, product_id: path.read_attr_hex("idProduct")?, device_version: path.read_attr_hex("bcdDevice")?, diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index 7432b29..ea8addc 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -49,12 +49,15 @@ pub(crate) fn probe_device(device: IoService) -> Option { let registry_id = get_registry_id(&device)?; log::debug!("Probing device {registry_id:08x}"); + let location_id = get_integer_property(&device, "locationID")? as u32; + // Can run `ioreg -p IOUSB -l` to see all properties Some(DeviceInfo { registry_id, - location_id: get_integer_property(&device, "locationID")? as u32, - bus_number: 0, // TODO: does this exist on macOS? + location_id, + bus_number: (location_id >> 24) as u8, device_address: get_integer_property(&device, "USB Address")? as u8, + port_chain: Some(parse_location_id(location_id)), vendor_id: get_integer_property(&device, "idVendor")? as u16, product_id: get_integer_property(&device, "idProduct")? as u16, device_version: get_integer_property(&device, "bcdDevice")? as u16, @@ -161,3 +164,26 @@ fn map_speed(speed: i64) -> Option { _ => None, } } + +fn parse_location_id(id: u32) -> Vec { + let bus_num = id >> 24; + let mut chain = vec![bus_num as u8]; + let mut shift = id << 8; + + while shift != 0 { + let port = shift >> 28; + chain.push(port as u8); + shift = shift << 4; + } + + chain +} + +#[test] +fn test_parse_location_id() { + assert_eq!(parse_location_id(0x01234567), vec![1, 2, 3, 4, 5, 6, 7]); + assert_eq!(parse_location_id(0xff875000), vec![255, 8, 7, 5]); + assert_eq!(parse_location_id(0x08400000), vec![8, 4]); + assert_eq!(parse_location_id(0x02040100), vec![2, 0, 4, 0, 1]); + assert_eq!(parse_location_id(0), vec![0]); +} diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index 07976e8..0f63176 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -8,7 +8,7 @@ use windows_sys::Win32::Devices::{ Properties::{ DEVPKEY_Device_Address, DEVPKEY_Device_BusNumber, DEVPKEY_Device_BusReportedDeviceDesc, DEVPKEY_Device_CompatibleIds, DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, - DEVPKEY_Device_Parent, DEVPKEY_Device_Service, + DEVPKEY_Device_LocationPaths, DEVPKEY_Device_Parent, DEVPKEY_Device_Service, }, Usb::GUID_DEVINTERFACE_USB_DEVICE, }; @@ -42,7 +42,7 @@ pub fn probe_device(devinst: DevInst) -> Option { debug!("Probing device {instance_id:?}"); let parent_instance_id = devinst.get_property::(DEVPKEY_Device_Parent)?; - let bus_number = devinst.get_property::(DEVPKEY_Device_BusNumber)?; + let bus_number = devinst.get_property::(DEVPKEY_Device_BusNumber)? as u8; let port_number = devinst.get_property::(DEVPKEY_Device_Address)?; let hub_port = HubPort::by_child_devinst(devinst).ok()?; @@ -98,11 +98,16 @@ pub fn probe_device(devinst: DevInst) -> Option { interfaces.sort_unstable_by_key(|i| i.interface_number); + let port_chain = devinst + .get_property::>(DEVPKEY_Device_LocationPaths) + .and_then(|s| s.iter().find_map(|p| parse_location_path(bus_number, p))); + Some(DeviceInfo { instance_id, parent_instance_id, devinst, port_number, + port_chain, driver: Some(driver).filter(|s| !s.is_empty()), bus_number: bus_number as u8, device_address: info.address, @@ -287,3 +292,44 @@ fn test_parse_compatible_id() { Some((3, 17, 34)) ); } + +fn parse_location_path(bus_num: u8, s: &OsStr) -> Option> { + let (_, mut s) = s.to_str()?.split_once("#USBROOT(")?; + + let mut path = vec![bus_num]; + + while let Some((_, next)) = s.split_once("#USB(") { + let (port_num, next) = next.split_once(")")?; + path.push(port_num.parse().ok()?); + s = next; + } + + Some(path) +} + +#[test] +fn test_parse_location_path() { + assert_eq!( + parse_location_path( + 0, + OsStr::new( + "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(23)#USB(2)#USB(1)#USB(3)#USB(4)" + ) + ), + Some(vec![0, 23, 2, 1, 3, 4]) + ); + assert_eq!( + parse_location_path( + 0, + OsStr::new("PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(16)") + ), + Some(vec![0, 16]) + ); + assert_eq!( + parse_location_path( + 0, + OsStr::new("ACPI(_SB_)#ACPI(PCI0)#ACPI(S11_)#ACPI(S00_)#ACPI(RHUB)#ACPI(HS04)") + ), + None + ); +} diff --git a/src/platform/windows_winusb/util.rs b/src/platform/windows_winusb/util.rs index 5661d39..f4b62d4 100644 --- a/src/platform/windows_winusb/util.rs +++ b/src/platform/windows_winusb/util.rs @@ -167,7 +167,13 @@ impl<'a> Iterator for NulSepListIter<'a> { if let Some(next_nul) = self.0.iter().copied().position(|x| x == 0) { let (i, next) = self.0.split_at(next_nul + 1); self.0 = next; - Some(unsafe { WCStr::from_slice_unchecked(i) }) + + if i.len() <= 1 { + // Empty element (double `\0`) terminates the list + None + } else { + Some(unsafe { WCStr::from_slice_unchecked(i) }) + } } else { None } From b8a57a994f6565b6b1dd25b702cfa98b8d1710f0 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sat, 10 Aug 2024 13:36:18 -0600 Subject: [PATCH 2/5] Remove bus number from port_chain --- src/enumeration.rs | 8 ++---- src/platform/linux_usbfs/enumeration.rs | 6 ++-- src/platform/macos_iokit/enumeration.rs | 13 ++++----- src/platform/windows_winusb/enumeration.rs | 33 +++++++++------------- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/enumeration.rs b/src/enumeration.rs index 8c9d146..31d48ce 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -123,12 +123,10 @@ impl DeviceInfo { self.port_number } - /// Path of port numbers identifying the physical port where the device is - /// connected. + /// Path of port numbers identifying the port where the device is connected. /// - /// The first value is the bus number, and subsequent values represent the - /// port used on each hub on the path to this device. The path is expected - /// to remain stable across device insertions or reboots. + /// Together with the bus ID, it identifies a physical port. The path is + /// expected to remain stable across device insertions or reboots. /// /// Since USB SuperSpeed is a separate topology from USB 2.0 speeds, a /// physical port may be identified differently depending on speed. diff --git a/src/platform/linux_usbfs/enumeration.rs b/src/platform/linux_usbfs/enumeration.rs index 4d63817..de793c6 100644 --- a/src/platform/linux_usbfs/enumeration.rs +++ b/src/platform/linux_usbfs/enumeration.rs @@ -1,6 +1,5 @@ use std::fs; use std::io; -use std::iter; use std::num::ParseIntError; use std::path::PathBuf; use std::str::FromStr; @@ -130,11 +129,10 @@ pub fn probe_device(path: SysfsPath) -> Result { let device_address = path.read_attr("devnum")?; let port_chain = path.read_attr::("devpath").ok().and_then(|p| { - iter::once(Some(bus_number)) - .chain(p.split('.').map(|v| v.parse::().ok())) + p.split('.') + .map(|v| v.parse::().ok()) .collect::>>() }); - Ok(DeviceInfo { bus_number, device_address, diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index ea8addc..81c478d 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -166,8 +166,7 @@ fn map_speed(speed: i64) -> Option { } fn parse_location_id(id: u32) -> Vec { - let bus_num = id >> 24; - let mut chain = vec![bus_num as u8]; + let mut chain = vec![]; let mut shift = id << 8; while shift != 0 { @@ -181,9 +180,9 @@ fn parse_location_id(id: u32) -> Vec { #[test] fn test_parse_location_id() { - assert_eq!(parse_location_id(0x01234567), vec![1, 2, 3, 4, 5, 6, 7]); - assert_eq!(parse_location_id(0xff875000), vec![255, 8, 7, 5]); - assert_eq!(parse_location_id(0x08400000), vec![8, 4]); - assert_eq!(parse_location_id(0x02040100), vec![2, 0, 4, 0, 1]); - assert_eq!(parse_location_id(0), vec![0]); + assert_eq!(parse_location_id(0x01234567), vec![2, 3, 4, 5, 6, 7]); + assert_eq!(parse_location_id(0xff875000), vec![8, 7, 5]); + assert_eq!(parse_location_id(0x08400000), vec![4]); + assert_eq!(parse_location_id(0x02040100), vec![0, 4, 0, 1]); + assert_eq!(parse_location_id(0), vec![]); } diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index 0f63176..3ec5b48 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -100,7 +100,7 @@ pub fn probe_device(devinst: DevInst) -> Option { let port_chain = devinst .get_property::>(DEVPKEY_Device_LocationPaths) - .and_then(|s| s.iter().find_map(|p| parse_location_path(bus_number, p))); + .and_then(|s| s.iter().find_map(|p| parse_location_path(p))); Some(DeviceInfo { instance_id, @@ -293,10 +293,10 @@ fn test_parse_compatible_id() { ); } -fn parse_location_path(bus_num: u8, s: &OsStr) -> Option> { +fn parse_location_path(s: &OsStr) -> Option> { let (_, mut s) = s.to_str()?.split_once("#USBROOT(")?; - let mut path = vec![bus_num]; + let mut path = vec![]; while let Some((_, next)) = s.split_once("#USB(") { let (port_num, next) = next.split_once(")")?; @@ -310,26 +310,21 @@ fn parse_location_path(bus_num: u8, s: &OsStr) -> Option> { #[test] fn test_parse_location_path() { assert_eq!( - parse_location_path( - 0, - OsStr::new( - "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(23)#USB(2)#USB(1)#USB(3)#USB(4)" - ) - ), - Some(vec![0, 23, 2, 1, 3, 4]) + parse_location_path(OsStr::new( + "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(23)#USB(2)#USB(1)#USB(3)#USB(4)" + )), + Some(vec![23, 2, 1, 3, 4]) ); assert_eq!( - parse_location_path( - 0, - OsStr::new("PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(16)") - ), - Some(vec![0, 16]) + parse_location_path(OsStr::new( + "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(16)" + )), + Some(vec![16]) ); assert_eq!( - parse_location_path( - 0, - OsStr::new("ACPI(_SB_)#ACPI(PCI0)#ACPI(S11_)#ACPI(S00_)#ACPI(RHUB)#ACPI(HS04)") - ), + parse_location_path(OsStr::new( + "ACPI(_SB_)#ACPI(PCI0)#ACPI(S11_)#ACPI(S00_)#ACPI(RHUB)#ACPI(HS04)" + )), None ); } From 2ad365e99b33814182d0482ecd1bfa9ffeacd3d1 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sat, 10 Aug 2024 13:58:35 -0600 Subject: [PATCH 3/5] Replace DeviceInfo::bus_number with bus_id --- examples/descriptors.rs | 2 +- examples/string_descriptors.rs | 2 +- src/enumeration.rs | 24 +++++++++---- src/platform/linux_usbfs/device.rs | 2 +- src/platform/linux_usbfs/enumeration.rs | 5 +-- src/platform/macos_iokit/enumeration.rs | 2 +- src/platform/windows_winusb/enumeration.rs | 40 ++++++++++++++-------- 7 files changed, 51 insertions(+), 26 deletions(-) diff --git a/examples/descriptors.rs b/examples/descriptors.rs index b8f59b1..fbc2d03 100644 --- a/examples/descriptors.rs +++ b/examples/descriptors.rs @@ -10,7 +10,7 @@ fn main() { fn inspect_device(dev: DeviceInfo) { println!( "Device {:03}.{:03} ({:04x}:{:04x}) {} {}", - dev.bus_number(), + dev.bus_id(), dev.device_address(), dev.vendor_id(), dev.product_id(), diff --git a/examples/string_descriptors.rs b/examples/string_descriptors.rs index f21752f..8d81cb0 100644 --- a/examples/string_descriptors.rs +++ b/examples/string_descriptors.rs @@ -12,7 +12,7 @@ fn main() { fn inspect_device(dev: DeviceInfo) { println!( "Device {:03}.{:03} ({:04x}:{:04x}) {} {}", - dev.bus_number(), + dev.bus_id(), dev.device_address(), dev.vendor_id(), dev.product_id(), diff --git a/src/enumeration.rs b/src/enumeration.rs index 31d48ce..7f4a825 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -25,6 +25,9 @@ pub struct DeviceInfo { #[cfg(target_os = "linux")] pub(crate) path: SysfsPath, + #[cfg(target_os = "linux")] + pub(crate) busnum: u8, + #[cfg(target_os = "windows")] pub(crate) instance_id: OsString, @@ -46,7 +49,7 @@ pub struct DeviceInfo { #[cfg(target_os = "macos")] pub(crate) location_id: u32, - pub(crate) bus_number: u8, + pub(crate) bus_id: String, pub(crate) device_address: u8, pub(crate) port_chain: Option>, @@ -80,7 +83,7 @@ impl DeviceInfo { #[cfg(target_os = "linux")] { DeviceId(crate::platform::DeviceId { - bus: self.bus_number, + bus: self.busnum, addr: self.device_address, }) } @@ -105,6 +108,14 @@ impl DeviceInfo { &self.path.0 } + /// *(Linux-only)* Bus number. + /// + /// On Linux, the `bus_id` is an integer and this provides the value as `u8`. + #[cfg(target_os = "linux")] + pub fn busnum(&self) -> u8 { + self.busnum + } + /// *(Windows-only)* Instance ID path of this device #[cfg(target_os = "windows")] pub fn instance_id(&self) -> &OsStr { @@ -152,9 +163,9 @@ impl DeviceInfo { self.registry_id } - /// Number identifying the bus / host controller where the device is connected. - pub fn bus_number(&self) -> u8 { - self.bus_number + /// Identifier for the bus / host controller where the device is connected. + pub fn bus_id(&self) -> &str { + &self.bus_id } /// Number identifying the device within the bus. @@ -273,7 +284,7 @@ impl std::fmt::Debug for DeviceInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("DeviceInfo"); - s.field("bus_number", &self.bus_number) + s.field("bus_id", &self.bus_id) .field("device_address", &self.device_address) .field("port_chain", &format_args!("{:?}", self.port_chain)) .field("vendor_id", &format_args!("0x{:04X}", self.vendor_id)) @@ -294,6 +305,7 @@ impl std::fmt::Debug for DeviceInfo { #[cfg(target_os = "linux")] { s.field("sysfs_path", &self.path); + s.field("busnum", &self.busnum); } #[cfg(target_os = "windows")] diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index 8d1d733..dc130b9 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -46,7 +46,7 @@ pub(crate) struct LinuxDevice { impl LinuxDevice { pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, Error> { - let busnum = d.bus_number(); + let busnum = d.busnum(); let devnum = d.device_address(); let active_config = d.path.read_attr("bConfigurationValue")?; diff --git a/src/platform/linux_usbfs/enumeration.rs b/src/platform/linux_usbfs/enumeration.rs index de793c6..effe192 100644 --- a/src/platform/linux_usbfs/enumeration.rs +++ b/src/platform/linux_usbfs/enumeration.rs @@ -125,7 +125,7 @@ pub fn list_devices() -> Result, Error> { pub fn probe_device(path: SysfsPath) -> Result { debug!("Probing device {:?}", path.0); - let bus_number = path.read_attr("busnum")?; + let busnum = path.read_attr("busnum")?; let device_address = path.read_attr("devnum")?; let port_chain = path.read_attr::("devpath").ok().and_then(|p| { @@ -134,7 +134,8 @@ pub fn probe_device(path: SysfsPath) -> Result { .collect::>>() }); Ok(DeviceInfo { - bus_number, + busnum, + bus_id: format!("{busnum:03}"), device_address, port_chain, vendor_id: path.read_attr_hex("idVendor")?, diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index 81c478d..e37073b 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -55,7 +55,7 @@ pub(crate) fn probe_device(device: IoService) -> Option { Some(DeviceInfo { registry_id, location_id, - bus_number: (location_id >> 24) as u8, + bus_id: format!("{:02x}", (location_id >> 24) as u8), device_address: get_integer_property(&device, "USB Address")? as u8, port_chain: Some(parse_location_id(location_id)), vendor_id: get_integer_property(&device, "idVendor")? as u16, diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index 3ec5b48..da9f96a 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -6,9 +6,9 @@ use std::{ use log::debug; use windows_sys::Win32::Devices::{ Properties::{ - DEVPKEY_Device_Address, DEVPKEY_Device_BusNumber, DEVPKEY_Device_BusReportedDeviceDesc, - DEVPKEY_Device_CompatibleIds, DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, - DEVPKEY_Device_LocationPaths, DEVPKEY_Device_Parent, DEVPKEY_Device_Service, + DEVPKEY_Device_Address, DEVPKEY_Device_BusReportedDeviceDesc, DEVPKEY_Device_CompatibleIds, + DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, DEVPKEY_Device_LocationPaths, + DEVPKEY_Device_Parent, DEVPKEY_Device_Service, }, Usb::GUID_DEVINTERFACE_USB_DEVICE, }; @@ -42,7 +42,6 @@ pub fn probe_device(devinst: DevInst) -> Option { debug!("Probing device {instance_id:?}"); let parent_instance_id = devinst.get_property::(DEVPKEY_Device_Parent)?; - let bus_number = devinst.get_property::(DEVPKEY_Device_BusNumber)? as u8; let port_number = devinst.get_property::(DEVPKEY_Device_Address)?; let hub_port = HubPort::by_child_devinst(devinst).ok()?; @@ -98,18 +97,23 @@ pub fn probe_device(devinst: DevInst) -> Option { interfaces.sort_unstable_by_key(|i| i.interface_number); - let port_chain = devinst + let location_paths = devinst .get_property::>(DEVPKEY_Device_LocationPaths) - .and_then(|s| s.iter().find_map(|p| parse_location_path(p))); + .unwrap_or_default(); + + let (bus_id, port_chain) = location_paths + .iter() + .find_map(|p| parse_location_path(p)) + .unwrap_or_default(); Some(DeviceInfo { instance_id, parent_instance_id, devinst, port_number, - port_chain, + port_chain: Some(port_chain), driver: Some(driver).filter(|s| !s.is_empty()), - bus_number: bus_number as u8, + bus_id, device_address: info.address, vendor_id: info.device_desc.idVendor, product_id: info.device_desc.idProduct, @@ -293,8 +297,13 @@ fn test_parse_compatible_id() { ); } -fn parse_location_path(s: &OsStr) -> Option> { - let (_, mut s) = s.to_str()?.split_once("#USBROOT(")?; +fn parse_location_path(s: &OsStr) -> Option<(String, Vec)> { + let s = s.to_str()?; + + let usbroot = "#USBROOT("; + let start_i = s.find(usbroot)?; + let close_i = s[start_i + usbroot.len()..].find(')')?; + let (bus, mut s) = s.split_at(start_i + usbroot.len() + close_i + 1); let mut path = vec![]; @@ -304,7 +313,7 @@ fn parse_location_path(s: &OsStr) -> Option> { s = next; } - Some(path) + Some((bus.to_owned(), path)) } #[test] @@ -313,13 +322,16 @@ fn test_parse_location_path() { parse_location_path(OsStr::new( "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(23)#USB(2)#USB(1)#USB(3)#USB(4)" )), - Some(vec![23, 2, 1, 3, 4]) + Some(( + "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)".into(), + vec![23, 2, 1, 3, 4] + )) ); assert_eq!( parse_location_path(OsStr::new( - "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(16)" + "PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(1)#USB(16)" )), - Some(vec![16]) + Some(("PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(1)".into(), vec![16])) ); assert_eq!( parse_location_path(OsStr::new( From e733fd1b95a1fc8fb062fd93da84d3e44216b7c4 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sat, 10 Aug 2024 17:33:32 -0600 Subject: [PATCH 4/5] Make port_chain return Vec not Option --- src/enumeration.rs | 6 +++--- src/platform/linux_usbfs/enumeration.rs | 15 ++++++++++----- src/platform/macos_iokit/enumeration.rs | 2 +- src/platform/windows_winusb/enumeration.rs | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/enumeration.rs b/src/enumeration.rs index 7f4a825..0173c7c 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -51,7 +51,7 @@ pub struct DeviceInfo { pub(crate) bus_id: String, pub(crate) device_address: u8, - pub(crate) port_chain: Option>, + pub(crate) port_chain: Vec, pub(crate) vendor_id: u16, pub(crate) product_id: u16, @@ -141,8 +141,8 @@ impl DeviceInfo { /// /// Since USB SuperSpeed is a separate topology from USB 2.0 speeds, a /// physical port may be identified differently depending on speed. - pub fn port_chain(&self) -> Option<&[u8]> { - self.port_chain.as_deref() + pub fn port_chain(&self) -> &[u8] { + &self.port_chain } /// *(Windows-only)* Driver associated with the device as a whole diff --git a/src/platform/linux_usbfs/enumeration.rs b/src/platform/linux_usbfs/enumeration.rs index effe192..12af27e 100644 --- a/src/platform/linux_usbfs/enumeration.rs +++ b/src/platform/linux_usbfs/enumeration.rs @@ -128,11 +128,16 @@ pub fn probe_device(path: SysfsPath) -> Result { let busnum = path.read_attr("busnum")?; let device_address = path.read_attr("devnum")?; - let port_chain = path.read_attr::("devpath").ok().and_then(|p| { - p.split('.') - .map(|v| v.parse::().ok()) - .collect::>>() - }); + let port_chain = path + .read_attr::("devpath") + .ok() + .and_then(|p| { + p.split('.') + .map(|v| v.parse::().ok()) + .collect::>>() + }) + .unwrap_or_default(); + Ok(DeviceInfo { busnum, bus_id: format!("{busnum:03}"), diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index e37073b..e6063dd 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -57,7 +57,7 @@ pub(crate) fn probe_device(device: IoService) -> Option { location_id, bus_id: format!("{:02x}", (location_id >> 24) as u8), device_address: get_integer_property(&device, "USB Address")? as u8, - port_chain: Some(parse_location_id(location_id)), + port_chain: parse_location_id(location_id), vendor_id: get_integer_property(&device, "idVendor")? as u16, product_id: get_integer_property(&device, "idProduct")? as u16, device_version: get_integer_property(&device, "bcdDevice")? as u16, diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index da9f96a..46cd896 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -111,7 +111,7 @@ pub fn probe_device(devinst: DevInst) -> Option { parent_instance_id, devinst, port_number, - port_chain: Some(port_chain), + port_chain, driver: Some(driver).filter(|s| !s.is_empty()), bus_id, device_address: info.address, From 24cc1832bfa5beb673d92127718cbcea382fd657 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sat, 10 Aug 2024 17:34:03 -0600 Subject: [PATCH 5/5] windows: Expose location_paths --- src/enumeration.rs | 10 ++++++++++ src/platform/windows_winusb/enumeration.rs | 1 + 2 files changed, 11 insertions(+) diff --git a/src/enumeration.rs b/src/enumeration.rs index 0173c7c..996094f 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -31,6 +31,9 @@ pub struct DeviceInfo { #[cfg(target_os = "windows")] pub(crate) instance_id: OsString, + #[cfg(target_os = "windows")] + pub(crate) location_paths: Vec, + #[cfg(target_os = "windows")] pub(crate) parent_instance_id: OsString, @@ -122,6 +125,12 @@ impl DeviceInfo { &self.instance_id } + /// *(Windows-only)* Location paths property + #[cfg(target_os = "windows")] + pub fn location_paths(&self) -> &[OsString] { + &self.location_paths + } + /// *(Windows-only)* Instance ID path of the parent hub #[cfg(target_os = "windows")] pub fn parent_instance_id(&self) -> &OsStr { @@ -312,6 +321,7 @@ impl std::fmt::Debug for DeviceInfo { { s.field("instance_id", &self.instance_id); s.field("parent_instance_id", &self.parent_instance_id); + s.field("location_paths", &self.location_paths); s.field("port_number", &self.port_number); s.field("driver", &self.driver); } diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index 46cd896..275d3c7 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -108,6 +108,7 @@ pub fn probe_device(devinst: DevInst) -> Option { Some(DeviceInfo { instance_id, + location_paths, parent_instance_id, devinst, port_number,