use std::ffi::{OsStr, OsString};
use log::debug;
use windows_sys::Win32::Devices::{
Properties::{
DEVPKEY_Device_Address, DEVPKEY_Device_BusReportedDeviceDesc, DEVPKEY_Device_DeviceDesc,
DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, DEVPKEY_Device_LocationPaths,
DEVPKEY_Device_Parent, DEVPKEY_Device_Service,
},
Usb::{GUID_DEVINTERFACE_USB_DEVICE, GUID_DEVINTERFACE_USB_HUB},
};
use crate::{
descriptors::{
decode_string_descriptor, language_id::US_ENGLISH, ConfigurationDescriptor,
DESCRIPTOR_TYPE_CONFIGURATION, DESCRIPTOR_TYPE_STRING,
},
maybe_future::{blocking::Blocking, MaybeFuture},
BusInfo, DeviceInfo, Error, ErrorKind, InterfaceInfo, UsbControllerType,
};
use super::{
cfgmgr32::{self, get_device_interface_property, DevInst},
hub::HubPort,
util::WCString,
};
pub fn list_devices() -> impl MaybeFuture<Output = Result<impl Iterator<Item = DeviceInfo>, Error>>
{
Blocking::new(|| {
let devs: Vec<DeviceInfo> = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_DEVICE, None)
.iter()
.chain(cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None).iter())
.flat_map(|i| get_device_interface_property::<WCString>(i, DEVPKEY_Device_InstanceId))
.flat_map(|d| DevInst::from_instance_id(&d))
.flat_map(probe_device)
.collect();
Ok(devs.into_iter())
})
}
pub fn list_buses() -> impl MaybeFuture<Output = Result<impl Iterator<Item = BusInfo>, Error>> {
Blocking::new(|| {
let devs: Vec<BusInfo> = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None)
.iter()
.flat_map(|i| get_device_interface_property::<WCString>(i, DEVPKEY_Device_InstanceId))
.flat_map(|d| DevInst::from_instance_id(&d))
.flat_map(probe_bus)
.collect();
Ok(devs.into_iter())
})
}
pub fn probe_device(devinst: DevInst) -> Option<DeviceInfo> {
let instance_id = devinst.get_property::<OsString>(DEVPKEY_Device_InstanceId)?;
if instance_id.to_string_lossy().starts_with("USB\\ROOT_HUB") {
return None;
}
debug!("Probing device {instance_id:?}");
let parent_instance_id = devinst.get_property::<OsString>(DEVPKEY_Device_Parent)?;
let port_number = devinst.get_property::<u32>(DEVPKEY_Device_Address)?;
let hub_port = HubPort::by_child_devinst(devinst).ok()?;
let info = hub_port.get_info().ok()?;
let product_string = devinst
.get_property::<OsString>(DEVPKEY_Device_BusReportedDeviceDesc)
.and_then(|s| s.into_string().ok());
let serial_number = if info.device_desc.iSerialNumber != 0 {
hub_port
.get_descriptor(
DESCRIPTOR_TYPE_STRING,
info.device_desc.iSerialNumber,
US_ENGLISH,
)
.ok()
.and_then(|data| decode_string_descriptor(&data).ok())
} else {
None
};
let driver = get_driver_name(devinst);
let mut interfaces =
list_interfaces_from_desc(&hub_port, info.active_config).unwrap_or_default();
if driver.eq_ignore_ascii_case("usbccgp") {
devinst
.children()
.flat_map(|intf| {
let interface_number = get_interface_number(intf)?;
let interface_string =
intf.get_property::<OsString>(DEVPKEY_Device_BusReportedDeviceDesc)?;
Some((interface_number, interface_string))
})
.for_each(|(intf_num, interface_string)| {
if let Some(interface_info) = interfaces
.iter_mut()
.find(|i| i.interface_number == intf_num)
{
interface_info.interface_string = interface_string.into_string().ok();
}
});
}
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_id,
device_address: info.address,
vendor_id: info.device_desc.idVendor,
product_id: info.device_desc.idProduct,
device_version: info.device_desc.bcdDevice,
usb_version: info.device_desc.bcdUSB,
class: info.device_desc.bDeviceClass,
subclass: info.device_desc.bDeviceSubClass,
protocol: info.device_desc.bDeviceProtocol,
speed: info.speed,
manufacturer_string: None,
product_string,
serial_number,
interfaces,
})
}
pub fn probe_bus(devinst: DevInst) -> Option<BusInfo> {
let instance_id = devinst.get_property::<OsString>(DEVPKEY_Device_InstanceId)?;
if !instance_id.to_string_lossy().starts_with("USB\\ROOT_HUB") {
return None;
}
debug!("Probing bus {instance_id:?}");
let parent_instance_id = devinst.get_property::<WCString>(DEVPKEY_Device_Parent)?;
let parent_devinst = DevInst::from_instance_id(&parent_instance_id)?;
let controller_type = parent_devinst
.get_property::<OsString>(DEVPKEY_Device_Service)
.and_then(|s| UsbControllerType::from_str(&s.to_string_lossy()));
let root_hub_description = devinst
.get_property::<OsString>(DEVPKEY_Device_DeviceDesc)?
.to_string_lossy()
.to_string();
let driver = get_driver_name(devinst);
let location_paths = devinst
.get_property::<Vec<OsString>>(DEVPKEY_Device_LocationPaths)
.unwrap_or_default();
let (bus_id, _) = location_paths
.iter()
.find_map(|p| parse_location_path(p))
.unwrap_or_default();
Some(BusInfo {
instance_id,
parent_instance_id: parent_instance_id.into(),
location_paths,
devinst,
driver: Some(driver).filter(|s| !s.is_empty()),
bus_id,
controller_type,
root_hub_description,
})
}
fn list_interfaces_from_desc(hub_port: &HubPort, active_config: u8) -> Option<Vec<InterfaceInfo>> {
let buf = hub_port
.get_descriptor(
DESCRIPTOR_TYPE_CONFIGURATION,
active_config.saturating_sub(1),
0,
)
.ok()?;
let desc = ConfigurationDescriptor::new(&buf[..])?;
if desc.configuration_value() != active_config {
return None;
}
Some(
desc.interfaces()
.map(|i| {
let i_desc = i.first_alt_setting();
InterfaceInfo {
interface_number: i.interface_number(),
class: i_desc.class(),
subclass: i_desc.subclass(),
protocol: i_desc.protocol(),
interface_string: None,
}
})
.collect(),
)
}
pub(crate) fn get_driver_name(dev: DevInst) -> String {
dev.get_property::<OsString>(DEVPKEY_Device_Service)
.and_then(|s| s.into_string().ok())
.unwrap_or_default()
}
pub(crate) fn get_winusb_device_path(dev: DevInst) -> Result<WCString, Error> {
let paths = dev.interfaces(GUID_DEVINTERFACE_USB_DEVICE);
let Some(path) = paths.iter().next() else {
return Err(Error::new(
ErrorKind::Other,
"failed to find device path for WinUSB device",
));
};
Ok(path.to_owned())
}
pub(crate) fn find_usbccgp_child(dev: DevInst, interface: u8) -> Option<(u8, DevInst)> {
dev.children()
.filter_map(|child| Some((get_interface_number(child)?, child)))
.filter(|(interface_number, _)| *interface_number <= interface)
.max_by_key(|(interface_number, _)| *interface_number)
}
pub(crate) fn get_usbccgp_winusb_device_path(child: DevInst) -> Result<WCString, Error> {
let Some(driver) = child.get_property::<OsString>(DEVPKEY_Device_Service) else {
return Err(Error::new(
ErrorKind::Unsupported,
"could not determine driver for interface",
));
};
if !driver.eq_ignore_ascii_case("winusb") {
debug!("Incompatible driver {driver:?} for interface, not WinUSB");
return Err(Error::new(
ErrorKind::Unsupported,
"incompatible driver is installed for this interface",
));
}
let reg_key = child.registry_key().unwrap();
let guid = match reg_key.query_value_guid("DeviceInterfaceGUIDs") {
Ok(s) => s,
Err(e) => match reg_key.query_value_guid("DeviceInterfaceGUID") {
Ok(s) => s,
Err(f) => {
if e.kind() == f.kind() {
debug!("Failed to get DeviceInterfaceGUID or DeviceInterfaceGUIDs from registry: {e}");
} else {
debug!("Failed to get DeviceInterfaceGUID or DeviceInterfaceGUIDs from registry: {e}, {f}");
}
return Err(Error::new(
ErrorKind::Unsupported,
"Could not find DeviceInterfaceGUIDs in registry. WinUSB driver may not be correctly installed for this interface."
));
}
},
};
let paths = child.interfaces(guid);
let Some(path) = paths.iter().next() else {
return Err(Error::new(
ErrorKind::Unsupported,
"failed to find device path for WinUSB interface",
)
.log_debug());
};
Ok(path.to_owned())
}
fn get_interface_number(intf_dev: DevInst) -> Option<u8> {
let hw_ids = intf_dev.get_property::<Vec<OsString>>(DEVPKEY_Device_HardwareIds);
hw_ids
.as_deref()
.unwrap_or_default()
.iter()
.find_map(|id| parse_hardware_id(id))
.or_else(|| {
debug!("Failed to parse interface number in hardware IDs: {hw_ids:?}");
None
})
}
fn parse_hardware_id(s: &OsStr) -> Option<u8> {
let s = s.to_str()?;
let s = s.rsplit_once("&MI_")?.1;
u8::from_str_radix(s.get(0..2)?, 16).ok()
}
#[test]
fn test_parse_hardware_id() {
assert_eq!(parse_hardware_id(OsStr::new("")), None);
assert_eq!(
parse_hardware_id(OsStr::new("USB\\VID_1234&PID_5678&MI_0A")),
Some(10)
);
assert_eq!(
parse_hardware_id(OsStr::new("USB\\VID_9999&PID_AAAA&REV_0101&MI_01")),
Some(1)
);
}
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
);
}