Merge pull request #71 from kevinmehall/port-chain
Add DeviceInfo::port_chain
This commit is contained in:
commit
3ec3508324
8 changed files with 154 additions and 18 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")?;
|
||||
|
||||
|
|
|
|||
|
|
@ -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")?,
|
||||
|
|
|
|||
|
|
@ -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![]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue