Merge pull request #71 from kevinmehall/port-chain

Add DeviceInfo::port_chain
This commit is contained in:
Kevin Mehall 2024-08-11 23:15:07 -06:00 committed by GitHub
commit 3ec3508324
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 154 additions and 18 deletions

View file

@ -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(),

View file

@ -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(),

View file

@ -25,9 +25,15 @@ 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,
#[cfg(target_os = "windows")]
pub(crate) location_paths: Vec<OsString>,
#[cfg(target_os = "windows")]
pub(crate) parent_instance_id: OsString,
@ -46,8 +52,9 @@ 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: Vec<u8>,
pub(crate) vendor_id: u16,
pub(crate) product_id: u16,
@ -79,7 +86,7 @@ impl DeviceInfo {
#[cfg(target_os = "linux")]
{
DeviceId(crate::platform::DeviceId {
bus: self.bus_number,
bus: self.busnum,
addr: self.device_address,
})
}
@ -104,12 +111,26 @@ 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 {
&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 {
@ -122,6 +143,17 @@ impl DeviceInfo {
self.port_number
}
/// Path of port numbers identifying the port where the device is connected.
///
/// 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.
pub fn port_chain(&self) -> &[u8] {
&self.port_chain
}
/// *(Windows-only)* Driver associated with the device as a whole
#[cfg(target_os = "windows")]
pub fn driver(&self) -> Option<&str> {
@ -140,9 +172,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.
@ -261,8 +293,9 @@ 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))
.field("product_id", &format_args!("0x{:04X}", self.product_id))
.field(
@ -281,12 +314,14 @@ 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")]
{
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);
}

View file

@ -46,7 +46,7 @@ pub(crate) struct LinuxDevice {
impl LinuxDevice {
pub(crate) fn from_device_info(d: &DeviceInfo) -> Result<Arc<LinuxDevice>, Error> {
let busnum = d.bus_number();
let busnum = d.busnum();
let devnum = d.device_address();
let active_config = d.path.read_attr("bConfigurationValue")?;

View file

@ -124,9 +124,25 @@ pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
pub fn probe_device(path: SysfsPath) -> Result<DeviceInfo, SysfsError> {
debug!("Probing device {:?}", path.0);
let busnum = path.read_attr("busnum")?;
let device_address = path.read_attr("devnum")?;
let port_chain = path
.read_attr::<String>("devpath")
.ok()
.and_then(|p| {
p.split('.')
.map(|v| v.parse::<u8>().ok())
.collect::<Option<Vec<u8>>>()
})
.unwrap_or_default();
Ok(DeviceInfo {
bus_number: path.read_attr("busnum")?,
device_address: path.read_attr("devnum")?,
busnum,
bus_id: format!("{busnum:03}"),
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")?,

View file

@ -49,12 +49,15 @@ pub(crate) fn probe_device(device: IoService) -> Option<DeviceInfo> {
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_id: format!("{:02x}", (location_id >> 24) as u8),
device_address: get_integer_property(&device, "USB Address")? as u8,
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,
@ -161,3 +164,25 @@ fn map_speed(speed: i64) -> Option<Speed> {
_ => None,
}
}
fn parse_location_id(id: u32) -> Vec<u8> {
let mut chain = vec![];
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![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![]);
}

View file

@ -6,8 +6,8 @@ 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_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<DeviceInfo> {
debug!("Probing device {instance_id:?}");
let parent_instance_id = devinst.get_property::<OsString>(DEVPKEY_Device_Parent)?;
let bus_number = devinst.get_property::<u32>(DEVPKEY_Device_BusNumber)?;
let port_number = devinst.get_property::<u32>(DEVPKEY_Device_Address)?;
let hub_port = HubPort::by_child_devinst(devinst).ok()?;
@ -98,13 +97,24 @@ pub fn probe_device(devinst: DevInst) -> Option<DeviceInfo> {
interfaces.sort_unstable_by_key(|i| i.interface_number);
let location_paths = devinst
.get_property::<Vec<OsString>>(DEVPKEY_Device_LocationPaths)
.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,
location_paths,
parent_instance_id,
devinst,
port_number,
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,
@ -287,3 +297,47 @@ fn test_parse_compatible_id() {
Some((3, 17, 34))
);
}
fn parse_location_path(s: &OsStr) -> Option<(String, Vec<u8>)> {
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![];
while let Some((_, next)) = s.split_once("#USB(") {
let (port_num, next) = next.split_once(")")?;
path.push(port_num.parse().ok()?);
s = next;
}
Some((bus.to_owned(), path))
}
#[test]
fn test_parse_location_path() {
assert_eq!(
parse_location_path(OsStr::new(
"PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(0)#USB(23)#USB(2)#USB(1)#USB(3)#USB(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(1)#USB(16)"
)),
Some(("PCIROOT(0)#PCI(0201)#PCI(0000)#USBROOT(1)".into(), vec![16]))
);
assert_eq!(
parse_location_path(OsStr::new(
"ACPI(_SB_)#ACPI(PCI0)#ACPI(S11_)#ACPI(S00_)#ACPI(RHUB)#ACPI(HS04)"
)),
None
);
}

View file

@ -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
}