diff --git a/src/enumeration.rs b/src/enumeration.rs index 6f2f1ffc..01174bbf 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -18,7 +18,7 @@ pub struct DeviceId(pub(crate) crate::platform::DeviceId); /// /// * Some fields are platform-specific /// * Linux: `sysfs_path` -/// * Windows: `instance_id`, `parent_instance_id`, `port_number`, `driver` +/// * Windows: `instance_id`, `parent_instance_id`, `driver` /// * macOS: `registry_id`, `location_id` #[derive(Clone)] pub struct DeviceInfo { @@ -31,9 +31,6 @@ pub struct DeviceInfo { #[cfg(target_os = "windows")] pub(crate) parent_instance_id: OsString, - #[cfg(target_os = "windows")] - pub(crate) port_number: u32, - #[cfg(target_os = "windows")] pub(crate) devinst: crate::platform::DevInst, @@ -47,6 +44,8 @@ pub struct DeviceInfo { pub(crate) location_id: u32, pub(crate) bus_number: u8, + pub(crate) port_number: u32, + pub(crate) port_chain: Vec, pub(crate) device_address: u8, pub(crate) vendor_id: u16, @@ -114,12 +113,6 @@ impl DeviceInfo { &self.parent_instance_id } - /// *(Windows-only)* Port number - #[cfg(target_os = "windows")] - pub fn port_number(&self) -> u32 { - self.port_number - } - /// *(Windows-only)* Driver associated with the device as a whole #[cfg(target_os = "windows")] pub fn driver(&self) -> Option<&str> { @@ -143,6 +136,16 @@ impl DeviceInfo { self.bus_number } + /// Port number + pub fn port_number(&self) -> u32 { + self.port_number + } + + /// Port chain + pub fn port_chain(&self) -> impl Iterator { + self.port_chain.iter() + } + /// Number identifying the device within the bus. pub fn device_address(&self) -> u8 { self.device_address @@ -254,6 +257,8 @@ impl std::fmt::Debug for DeviceInfo { let mut s = f.debug_struct("DeviceInfo"); s.field("bus_number", &self.bus_number) + .field("port_number", &self.port_number) + .field("port_chain", &self.port_chain) .field("device_address", &self.device_address) .field("vendor_id", &format_args!("0x{:04X}", self.vendor_id)) .field("product_id", &format_args!("0x{:04X}", self.product_id)) @@ -278,7 +283,6 @@ impl std::fmt::Debug for DeviceInfo { { s.field("instance_id", &self.instance_id); s.field("parent_instance_id", &self.parent_instance_id); - s.field("port_number", &self.port_number); s.field("driver", &self.driver); } diff --git a/src/platform/linux_usbfs/enumeration.rs b/src/platform/linux_usbfs/enumeration.rs index e046f361..fa1c7d48 100644 --- a/src/platform/linux_usbfs/enumeration.rs +++ b/src/platform/linux_usbfs/enumeration.rs @@ -77,8 +77,15 @@ pub fn list_devices() -> Result, Error> { pub fn probe_device(path: SysfsPath) -> Result { debug!("probe device {path:?}"); + let port_chain: Vec = path + .read_attr::("devpath")? + .split('.') + .flat_map(|v| v.parse::()) + .collect(); Ok(DeviceInfo { bus_number: path.read_attr("busnum")?, + port_number: *port_chain.last().unwrap_or(&0), + port_chain, device_address: path.read_attr("devnum")?, vendor_id: path.read_attr_hex("idVendor")?, product_id: path.read_attr_hex("idProduct")?, diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index 6a2717e1..b10a7033 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -1,7 +1,8 @@ -use std::io::ErrorKind; +use std::{collections::VecDeque, io::ErrorKind}; use core_foundation::{ base::{CFType, TCFType}, + data::CFData, number::CFNumber, string::CFString, ConcreteCFType, @@ -9,8 +10,9 @@ use core_foundation::{ use io_kit_sys::{ kIOMasterPortDefault, kIORegistryIterateParents, kIORegistryIterateRecursively, keys::kIOServicePlane, ret::kIOReturnSuccess, usb::lib::kIOUSBDeviceClassName, - IORegistryEntryGetChildIterator, IORegistryEntryGetRegistryEntryID, - IORegistryEntrySearchCFProperty, IOServiceGetMatchingServices, IOServiceMatching, + IORegistryEntryGetChildIterator, IORegistryEntryGetParentEntry, + IORegistryEntryGetRegistryEntryID, IORegistryEntrySearchCFProperty, + IOServiceGetMatchingServices, IOServiceMatching, }; use log::debug; @@ -50,10 +52,14 @@ pub(crate) fn probe_device(device: IoService) -> Option { log::debug!("Probing device {registry_id:08x}"); // Can run `ioreg -p IOUSB -l` to see all properties + let location_id = get_integer_property(&device, "locationID")? as u32; + let port_chain: Vec = get_port_chain(&device).collect(); 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, + port_number: *port_chain.last().unwrap_or(&0), + port_chain, device_address: get_integer_property(&device, "USB Address")? as u8, vendor_id: get_integer_property(&device, "idVendor")? as u16, product_id: get_integer_property(&device, "idProduct")? as u16, @@ -127,6 +133,10 @@ fn get_string_property(device: &IoService, property: &'static str) -> Option(device, property).map(|s| s.to_string()) } +fn get_data_property(device: &IoService, property: &'static str) -> Option> { + get_property::(device, property).map(|d| d.to_vec()) +} + fn get_integer_property(device: &IoService, property: &'static str) -> Option { let n = get_property::(device, property)?; n.to_i64().or_else(|| { @@ -149,6 +159,76 @@ fn get_children(device: &IoService) -> Result { } } +fn get_parent(device: &IoService) -> Result { + unsafe { + let mut handle = 0; + let r = IORegistryEntryGetParentEntry(device.get(), kIOServicePlane as *mut _, &mut handle); + if r != kIOReturnSuccess { + debug!("IORegistryEntryGetParentEntry failed: {r}"); + return Err(Error::from_raw_os_error(r)); + } + + Ok(IoService::new(handle)) + } +} + +fn get_port_number(device: &IoService) -> Option { + get_integer_property(device, "PortNum").map_or_else( + || { + if let Ok(parent) = get_parent(device) { + return get_data_property(&parent, "port") + .map(|d| u32::from_ne_bytes(d[0..4].try_into().unwrap())); + } + None + }, + |v| Some(v as u32), + ) +} + +fn get_port_chain(device: &IoService) -> impl Iterator { + let mut port_chain = VecDeque::new(); + + if let Some(port_number) = get_port_number(device) { + port_chain.push_back(port_number); + } + + if let Ok(mut hub) = get_parent(device) { + loop { + let port_number = match get_port_number(&hub) { + Some(p) => p, + None => break, + }; + if port_number == 0 { + break; + } + port_chain.push_front(port_number); + + let session_id = match get_integer_property(&hub, "sessionID") { + Some(session_id) => session_id, + None => break, + }; + + hub = match get_parent(&hub) { + Ok(hub) => hub, + Err(_) => break, + }; + + // Ignore the same sessionID + if session_id + == match get_integer_property(&hub, "sessionID") { + Some(session_id) => session_id, + None => break, + } + { + port_chain.pop_front(); + continue; + } + } + } + + port_chain.into_iter() +} + fn map_speed(speed: i64) -> Option { // https://developer.apple.com/documentation/iokit/1425357-usbdevicespeed match speed { diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index f7f3d66b..897e0eca 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -1,4 +1,5 @@ use std::{ + collections::{HashMap, VecDeque}, ffi::{OsStr, OsString}, io::ErrorKind, }; @@ -6,11 +7,12 @@ 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_Parent, DEVPKEY_Device_Service, + DEVPKEY_Device_Address, DEVPKEY_Device_BusReportedDeviceDesc, DEVPKEY_Device_CompatibleIds, + DEVPKEY_Device_EnumeratorName, DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, + DEVPKEY_Device_LocationInfo, DEVPKEY_Device_LocationPaths, DEVPKEY_Device_Parent, + DEVPKEY_Device_Service, }, - Usb::GUID_DEVINTERFACE_USB_DEVICE, + Usb::{GUID_DEVINTERFACE_USB_DEVICE, GUID_DEVINTERFACE_USB_HOST_CONTROLLER}, }; use crate::{ @@ -42,8 +44,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)?; - let port_number = devinst.get_property::(DEVPKEY_Device_Address)?; let hub_port = HubPort::by_child_devinst(devinst).ok()?; let info = hub_port.get_info().ok()?; @@ -98,13 +98,27 @@ pub fn probe_device(devinst: DevInst) -> Option { interfaces.sort_unstable_by_key(|i| i.interface_number); + let bus_devs = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HOST_CONTROLLER, None) + .iter() + .flat_map(|i| get_device_interface_property::(i, DEVPKEY_Device_InstanceId)) + .flat_map(|d| DevInst::from_instance_id(&d)) + .flat_map(|d| d.children()) + .map(|d| d.instance_id().to_string()) + .enumerate() + .map(|v| (v.1, (v.0 + 1) as u8)) + .collect::>(); + + let (bus_number, port_chain) = get_port_chain(devinst, &bus_devs); + let port_chain = port_chain.collect::>(); + Some(DeviceInfo { instance_id, parent_instance_id, devinst, - port_number, driver: Some(driver).filter(|s| !s.is_empty()), - bus_number: bus_number as u8, + bus_number, + port_number: *port_chain.last().unwrap_or(&0), + port_chain, device_address: info.address, vendor_id: info.device_desc.idVendor, product_id: info.device_desc.idProduct, @@ -232,6 +246,75 @@ pub(crate) fn get_usbccgp_winusb_device_path(child: DevInst) -> Result) -> (u8, impl Iterator) { + let mut bus_number = 0; + let mut port_chain = VecDeque::new(); + + if let Some(port_number) = get_port_number(dev) { + port_chain.push_back(port_number); + } + + if let Some(mut parent) = dev.parent() { + loop { + if parent + .get_property::(DEVPKEY_Device_EnumeratorName) + .unwrap_or_default() + .eq_ignore_ascii_case("USB") + { + if let Some(port_number) = get_port_number(parent) { + if port_number != 0 { + port_chain.push_front(port_number); + if let Some(d) = parent.parent() { + parent = d; + continue; + } + } else { + if let Some(bus_dev) = + parent.get_property::(DEVPKEY_Device_InstanceId) + { + bus_number = *bus_devs.get(&bus_dev.to_string()).unwrap_or(&0); + } + } + } + } + break; + } + } + + (bus_number, port_chain.into_iter()) +} + +fn get_port_number(devinst: DevInst) -> Option { + // Find Port_#xxxx + // Port_#0002.Hub_#000D + if let Some(location_info) = devinst.get_property::(DEVPKEY_Device_LocationInfo) { + let s = location_info.to_string_lossy(); + if &s[0..6] == "Port_#" { + if let Ok(n) = s[6..10].parse::() { + return Some(n); + } + } + } + // Find last #USB(x) + // PCIROOT(B2)#PCI(0300)#PCI(0000)#USBROOT(0)#USB(1)#USB(2)#USBMI(3) + if let Some(location_paths) = + devinst.get_property::>(DEVPKEY_Device_LocationPaths) + { + for location_path in location_paths { + let s = location_path.to_string_lossy(); + for b in s.split('#').rev() { + if b.contains("USB(") { + if let Ok(n) = b[5..b.len()].parse::() { + return Some(n); + } + break; + } + } + } + } + devinst.get_property::(DEVPKEY_Device_Address) +} + fn get_interface_number(intf_dev: DevInst) -> Option { let hw_ids = intf_dev.get_property::>(DEVPKEY_Device_HardwareIds); hw_ids diff --git a/src/platform/windows_winusb/util.rs b/src/platform/windows_winusb/util.rs index 5661d390..8ac32897 100644 --- a/src/platform/windows_winusb/util.rs +++ b/src/platform/windows_winusb/util.rs @@ -166,7 +166,11 @@ impl<'a> Iterator for NulSepListIter<'a> { fn next(&mut self) -> Option { 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; + self.0 = if next.is_empty() || next.first() == Some(&0) { + &[] + } else { + next + }; Some(unsafe { WCStr::from_slice_unchecked(i) }) } else { None