Skip to content

Commit df9d145

Browse files
committed
Add iocontrol feature
This feature only affects the Windows systems. Enabling this feature causes the `manufacturer` and `product` fields of the UsbPortInfo structure to provide the same values provided on Unix systems.
1 parent 819b640 commit df9d145

File tree

3 files changed

+347
-12
lines changed

3 files changed

+347
-12
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ default = ["libudev"]
5151
# TODO: Make the feature unconditionally available with the next major release
5252
# (5.0) and remove this feature gate.
5353
usbportinfo-interface = []
54+
iocontrol = ["winapi/ioapiset", "winapi/usbioctl"]

src/windows/enumerate.rs

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ use winapi::um::winreg::*;
1414

1515
use crate::{Error, ErrorKind, Result, SerialPortInfo, SerialPortType, UsbPortInfo};
1616

17+
#[cfg(feature = "iocontrol")]
18+
mod iocontrol;
19+
20+
#[cfg(feature = "iocontrol")]
21+
use iocontrol::{IoControl, IoDescriptor};
22+
1723
// According to the MSDN docs, we should use SetupDiGetClassDevs, SetupDiEnumDeviceInfo
1824
// and SetupDiGetDeviceInstanceId in order to enumerate devices.
1925
// https://msdn.microsoft.com/en-us/windows/hardware/drivers/install/enumerating-installed-devices
@@ -94,7 +100,11 @@ fn get_ports_guids() -> Result<Vec<GUID>> {
94100
/// - BlackMagic GDB Server: USB\VID_1D50&PID_6018&MI_00\6&A694CA9&0&0000
95101
/// - BlackMagic UART port: USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0002
96102
/// - FTDI Serial Adapter: FTDIBUS\VID_0403+PID_6001+A702TB52A\0000
97-
fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> Option<UsbPortInfo> {
103+
fn parse_usb_port_info(
104+
hardware_id: &str,
105+
parent_hardware_id: Option<&str>,
106+
#[cfg(feature = "iocontrol")] device_location_info: Option<&str>,
107+
) -> Option<UsbPortInfo> {
98108
let re = Regex::new(concat!(
99109
r"VID_(?P<vid>[[:xdigit:]]{4})",
100110
r"[&+]PID_(?P<pid>[[:xdigit:]]{4})",
@@ -109,12 +119,31 @@ fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> O
109119
.name("iid")
110120
.and_then(|m| u8::from_str_radix(m.as_str(), 16).ok());
111121

112-
if let Some(_) = interface {
122+
if interface.is_some() {
113123
// If this is a composite device, we need to parse the parent's HWID to get the correct information.
114124
caps = re.captures(parent_hardware_id?)?;
115125
}
116126

117-
Some(UsbPortInfo {
127+
#[cfg(not(feature = "iocontrol"))]
128+
let usb_port_info = UsbPortInfo {
129+
vid: u16::from_str_radix(&caps[1], 16).ok()?,
130+
pid: u16::from_str_radix(&caps[2], 16).ok()?,
131+
serial_number: caps.name("serial").map(|m| {
132+
let m = m.as_str();
133+
if m.contains('&') {
134+
m.split('&').nth(1).unwrap().to_string()
135+
} else {
136+
m.to_string()
137+
}
138+
}),
139+
manufacturer: None,
140+
product: None,
141+
#[cfg(feature = "usbportinfo-interface")]
142+
interface,
143+
};
144+
145+
#[cfg(feature = "iocontrol")]
146+
let mut usb_port_info = UsbPortInfo {
118147
vid: u16::from_str_radix(&caps[1], 16).ok()?,
119148
pid: u16::from_str_radix(&caps[2], 16).ok()?,
120149
serial_number: caps.name("serial").map(|m| {
@@ -128,8 +157,35 @@ fn parse_usb_port_info(hardware_id: &str, parent_hardware_id: Option<&str>) -> O
128157
manufacturer: None,
129158
product: None,
130159
#[cfg(feature = "usbportinfo-interface")]
131-
interface: interface,
132-
})
160+
interface,
161+
};
162+
163+
#[cfg(feature = "iocontrol")]
164+
if parent_hardware_id.is_some() && device_location_info.is_some() {
165+
let re = Regex::new(concat!(r"Port_#(?P<hub_device_location>[[:xdigit:]]{4})",)).unwrap();
166+
167+
caps = re.captures(device_location_info?)?;
168+
let port_number = u8::from_str_radix(&caps[1], 8).ok()?;
169+
170+
let hub_name = format!(
171+
"{}#{{f18a0e88-c30c-11d0-8815-00a0c906bed8}}",
172+
parent_hardware_id?.replace('\\', "#"),
173+
);
174+
175+
let hdevice = IoControl::get_handle(&mut hub_name.clone()).ok()?;
176+
177+
let imanufacturer =
178+
IoControl::get_string_descriptor(&hdevice, port_number, &IoDescriptor::Manufacturer)
179+
.ok()?;
180+
181+
let iproduct =
182+
IoControl::get_string_descriptor(&hdevice, port_number, &IoDescriptor::Product).ok()?;
183+
184+
usb_port_info.manufacturer = Some(imanufacturer);
185+
usb_port_info.product = Some(iproduct);
186+
}
187+
188+
Some(usb_port_info)
133189
}
134190

135191
struct PortDevices {
@@ -320,11 +376,32 @@ impl PortDevice {
320376
// Determines the port_type for this device, and if it's a USB port populate the various fields.
321377
pub fn port_type(&mut self) -> SerialPortType {
322378
self.instance_id()
323-
.map(|s| (s, self.parent_instance_id())) // Get parent instance id if it exists.
324-
.and_then(|(d, p)| parse_usb_port_info(&d, p.as_deref()))
379+
.map(|s| {
380+
(
381+
s,
382+
self.parent_instance_id(),
383+
#[cfg(feature = "iocontrol")]
384+
self.property(SPDRP_LOCATION_INFORMATION),
385+
)
386+
}) // Get parent instance id if it exists.
387+
.and_then(
388+
|#[cfg(not(feature = "iocontrol"))] (d, p),
389+
#[cfg(feature = "iocontrol")] (d, p, l)| {
390+
parse_usb_port_info(
391+
&d,
392+
p.as_deref(),
393+
#[cfg(feature = "iocontrol")]
394+
l.as_deref(),
395+
)
396+
},
397+
)
325398
.map(|mut info: UsbPortInfo| {
326-
info.manufacturer = self.property(SPDRP_MFG);
327-
info.product = self.property(SPDRP_FRIENDLYNAME);
399+
if info.manufacturer.is_none() {
400+
info.manufacturer = self.property(SPDRP_MFG)
401+
};
402+
if info.product.is_none() {
403+
info.product = self.property(SPDRP_FRIENDLYNAME)
404+
};
328405
SerialPortType::UsbPort(info)
329406
})
330407
.unwrap_or(SerialPortType::Unknown)
@@ -399,7 +476,13 @@ pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
399476
fn test_parsing_usb_port_information() {
400477
let bm_uart_hwid = r"USB\VID_1D50&PID_6018&MI_02\6&A694CA9&0&0000";
401478
let bm_parent_hwid = r"USB\VID_1D50&PID_6018\85A12F01";
402-
let info = parse_usb_port_info(bm_uart_hwid, Some(bm_parent_hwid)).unwrap();
479+
let info = parse_usb_port_info(
480+
bm_uart_hwid,
481+
Some(bm_parent_hwid),
482+
#[cfg(feature = "iocontrol")]
483+
None,
484+
)
485+
.unwrap();
403486

404487
assert_eq!(info.vid, 0x1D50);
405488
assert_eq!(info.pid, 0x6018);
@@ -408,7 +491,13 @@ fn test_parsing_usb_port_information() {
408491
assert_eq!(info.interface, Some(2));
409492

410493
let ftdi_serial_hwid = r"FTDIBUS\VID_0403+PID_6001+A702TB52A\0000";
411-
let info = parse_usb_port_info(ftdi_serial_hwid, None).unwrap();
494+
let info = parse_usb_port_info(
495+
ftdi_serial_hwid,
496+
None,
497+
#[cfg(feature = "iocontrol")]
498+
None,
499+
)
500+
.unwrap();
412501

413502
assert_eq!(info.vid, 0x0403);
414503
assert_eq!(info.pid, 0x6001);
@@ -417,7 +506,13 @@ fn test_parsing_usb_port_information() {
417506
assert_eq!(info.interface, None);
418507

419508
let pyboard_hwid = r"USB\VID_F055&PID_9802\385435603432";
420-
let info = parse_usb_port_info(pyboard_hwid, None).unwrap();
509+
let info = parse_usb_port_info(
510+
pyboard_hwid,
511+
None,
512+
#[cfg(feature = "iocontrol")]
513+
None,
514+
)
515+
.unwrap();
421516

422517
assert_eq!(info.vid, 0xF055);
423518
assert_eq!(info.pid, 0x9802);

0 commit comments

Comments
 (0)