Skip to content

Commit a0d5066

Browse files
committed
specific BusInfo type for 'root hubs'
root hubs are a Linuxism - an abstract USB device capturing information about a system USB bus; connected to a Host Controller. Bus data is useful for system profiling so a BusInfo type is added to crate a more portable 'root hub' type. Considering whether to use a HostControllerInfo highlighted the place for root hubs. There can be multiple system USB buses on a Host Controller Interface (HCI), so it makes sense to have a separate type. Multiple buses can share the same PciInfo for example because they sit on the same HCI.
1 parent b2784c8 commit a0d5066

File tree

9 files changed

+444
-130
lines changed

9 files changed

+444
-130
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
fn main() {
22
env_logger::init();
3-
for dev in nusb::list_root_hubs().unwrap() {
3+
for dev in nusb::list_buses().unwrap() {
44
println!("{:#?}", dev);
55
}
66
}

src/enumeration.rs

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,320 @@ impl std::fmt::Debug for InterfaceInfo {
424424
.finish()
425425
}
426426
}
427+
428+
/// PCI device information for host controllers
429+
#[derive(Clone)]
430+
pub struct PciInfo {
431+
#[cfg(target_os = "linux")]
432+
pub(crate) path: SysfsPath,
433+
434+
#[cfg(target_os = "windows")]
435+
pub(crate) instance_id: OsString,
436+
/// PCI vendor ID
437+
pub(crate) vendor_id: u16,
438+
/// PCI device ID
439+
pub(crate) device_id: u16,
440+
/// PCI hardware revision
441+
pub(crate) revision: Option<u16>,
442+
/// PCI subsystem vendor ID
443+
pub(crate) subsystem_vendor_id: Option<u16>,
444+
/// PCI subsystem device ID
445+
pub(crate) subsystem_device_id: Option<u16>,
446+
}
447+
448+
impl PciInfo {
449+
/// *(Linux-only)* Sysfs path for the PCI device.
450+
#[cfg(target_os = "linux")]
451+
pub fn sysfs_path(&self) -> &std::path::Path {
452+
&self.path.0
453+
}
454+
455+
/// *(Windows-only)* Instance ID path of this device
456+
#[cfg(target_os = "windows")]
457+
pub fn instance_id(&self) -> &OsStr {
458+
&self.instance_id
459+
}
460+
461+
/// PCI vendor ID
462+
pub fn vendor_id(&self) -> u16 {
463+
self.vendor_id
464+
}
465+
466+
/// PCI device ID
467+
pub fn device_id(&self) -> u16 {
468+
self.device_id
469+
}
470+
471+
/// PCI hardware revision
472+
pub fn revision(&self) -> Option<u16> {
473+
self.revision
474+
}
475+
476+
/// PCI subsystem vendor ID
477+
pub fn subsystem_vendor_id(&self) -> Option<u16> {
478+
self.subsystem_vendor_id
479+
}
480+
481+
/// PCI subsystem device ID
482+
pub fn subsystem_device_id(&self) -> Option<u16> {
483+
self.subsystem_device_id
484+
}
485+
}
486+
487+
impl std::fmt::Debug for PciInfo {
488+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489+
let mut s = f.debug_struct("PciInfo");
490+
491+
s.field("vendor_id", &format_args!("0x{:04X}", self.vendor_id))
492+
.field("device_id", &format_args!("0x{:04X}", self.device_id))
493+
.field("revision", &self.revision)
494+
.field("subsystem_vendor_id", &self.subsystem_vendor_id)
495+
.field("subsystem_device_id", &self.subsystem_device_id);
496+
497+
#[cfg(target_os = "linux")]
498+
{
499+
s.field("sysfs_path", &self.path);
500+
}
501+
502+
#[cfg(target_os = "windows")]
503+
{
504+
s.field("instance_id", &self.instance_id);
505+
}
506+
507+
s.finish()
508+
}
509+
}
510+
511+
/// USB host controller type
512+
#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
513+
#[non_exhaustive]
514+
pub enum UsbController {
515+
/// xHCI controller (USB 3.0+)
516+
XHCI,
517+
518+
/// EHCI controller (USB 2.0)
519+
EHCI,
520+
521+
/// OHCI controller (USB 1.1)
522+
OHCI,
523+
524+
/// VHCI controller (virtual internal USB)
525+
VHCI,
526+
}
527+
528+
impl UsbController {
529+
#[allow(dead_code)] // not used on all platforms
530+
pub(crate) fn from_str(s: &str) -> Option<Self> {
531+
match s
532+
.find("HCI")
533+
.filter(|i| *i > 0)
534+
.and_then(|i| s.bytes().nth(i - 1))
535+
{
536+
Some(b'x') | Some(b'X') => Some(UsbController::XHCI),
537+
Some(b'e') | Some(b'E') => Some(UsbController::EHCI),
538+
Some(b'o') | Some(b'O') => Some(UsbController::OHCI),
539+
Some(b'v') | Some(b'V') => Some(UsbController::VHCI),
540+
_ => None,
541+
}
542+
}
543+
}
544+
545+
/// Information about a system USB bus. Aims to be a more useful and portable version of the Linux root hub device.
546+
///
547+
/// Platform-specific fields:
548+
/// * Linux: `path`, `busnum`, `root_hub`
549+
/// * Windows: `instance_id`, `location_paths`, `devinst`, `driver`
550+
/// * macOS: `registry_id`, `location_id`, `name`, `driver`
551+
pub struct BusInfo {
552+
#[cfg(target_os = "linux")]
553+
pub(crate) path: SysfsPath,
554+
555+
/// The phony root hub device
556+
#[cfg(target_os = "linux")]
557+
pub(crate) root_hub: DeviceInfo,
558+
559+
#[cfg(target_os = "linux")]
560+
pub(crate) busnum: u8,
561+
562+
#[cfg(target_os = "windows")]
563+
pub(crate) instance_id: OsString,
564+
565+
#[cfg(target_os = "windows")]
566+
pub(crate) location_paths: Vec<OsString>,
567+
568+
#[cfg(target_os = "windows")]
569+
pub(crate) devinst: crate::platform::DevInst,
570+
571+
#[cfg(any(target_os = "windows", target_os = "macos"))]
572+
pub(crate) driver: Option<String>,
573+
574+
#[cfg(target_os = "macos")]
575+
pub(crate) registry_id: u64,
576+
577+
#[cfg(target_os = "macos")]
578+
pub(crate) location_id: u32,
579+
580+
#[cfg(target_os = "macos")]
581+
pub(crate) name: Option<String>,
582+
583+
/// System ID for the bus
584+
pub(crate) bus_id: String,
585+
586+
/// Optional PCI information if the bus is connected to a PCI Host Controller
587+
pub(crate) pci_info: Option<PciInfo>,
588+
589+
/// Detected USB controller type
590+
pub(crate) controller: Option<UsbController>,
591+
592+
/// System provider class name for the bus
593+
pub(crate) provider_class: Option<String>,
594+
/// System class name for the bus
595+
pub(crate) class_name: Option<String>,
596+
}
597+
598+
impl BusInfo {
599+
/// Opaque identifier for the device.
600+
pub fn id(&self) -> DeviceId {
601+
#[cfg(target_os = "windows")]
602+
{
603+
DeviceId(self.devinst)
604+
}
605+
606+
#[cfg(target_os = "linux")]
607+
{
608+
self.root_hub.id()
609+
}
610+
611+
#[cfg(target_os = "macos")]
612+
{
613+
DeviceId(self.registry_id)
614+
}
615+
}
616+
617+
/// *(Linux-only)* Sysfs path for the bus.
618+
#[doc(hidden)]
619+
#[deprecated = "use `sysfs_path()` instead"]
620+
#[cfg(target_os = "linux")]
621+
pub fn path(&self) -> &SysfsPath {
622+
&self.path
623+
}
624+
625+
/// *(Linux-only)* Sysfs path for the bus.
626+
#[cfg(target_os = "linux")]
627+
pub fn sysfs_path(&self) -> &std::path::Path {
628+
&self.path.0
629+
}
630+
631+
/// *(Linux-only)* Bus number.
632+
///
633+
/// On Linux, the `bus_id` is an integer and this provides the value as `u8`.
634+
#[cfg(target_os = "linux")]
635+
pub fn busnum(&self) -> u8 {
636+
self.busnum
637+
}
638+
639+
/// *(Linux-only)* The root hub [`DeviceInfo`] representing the bus.
640+
#[cfg(target_os = "linux")]
641+
pub fn root_hub(&self) -> &DeviceInfo {
642+
&self.root_hub
643+
}
644+
645+
/// *(Windows-only)* Instance ID path of this device
646+
#[cfg(target_os = "windows")]
647+
pub fn instance_id(&self) -> &OsStr {
648+
&self.instance_id
649+
}
650+
651+
/// *(Windows-only)* Location paths property
652+
#[cfg(target_os = "windows")]
653+
pub fn location_paths(&self) -> &[OsString] {
654+
&self.location_paths
655+
}
656+
657+
/// *(Windows/macOS-only)* Driver associated with the device as a whole
658+
#[cfg(any(target_os = "windows", target_os = "macos"))]
659+
pub fn driver(&self) -> Option<&str> {
660+
self.driver.as_deref()
661+
}
662+
663+
/// *(macOS-only)* IOKit Location ID
664+
#[cfg(target_os = "macos")]
665+
pub fn location_id(&self) -> u32 {
666+
self.location_id
667+
}
668+
669+
/// *(macOS-only)* IOKit [Registry Entry ID](https://developer.apple.com/documentation/iokit/1514719-ioregistryentrygetregistryentryi?language=objc)
670+
#[cfg(target_os = "macos")]
671+
pub fn registry_entry_id(&self) -> u64 {
672+
self.registry_id
673+
}
674+
675+
/// Name of the bus
676+
#[cfg(target_os = "macos")]
677+
pub fn name(&self) -> Option<&str> {
678+
self.name.as_deref()
679+
}
680+
681+
/// Identifier for the bus
682+
pub fn bus_id(&self) -> &str {
683+
&self.bus_id
684+
}
685+
686+
/// Optional PCI information if the bus is connected to a PCI Host Controller
687+
pub fn pci_info(&self) -> Option<&PciInfo> {
688+
self.pci_info.as_ref()
689+
}
690+
691+
/// Detected USB controller type
692+
pub fn controller(&self) -> Option<UsbController> {
693+
self.controller
694+
}
695+
696+
/// System provider class name for the bus
697+
pub fn provider_class(&self) -> Option<&str> {
698+
self.provider_class.as_deref()
699+
}
700+
701+
/// System class name for the bus
702+
pub fn class_name(&self) -> Option<&str> {
703+
self.class_name.as_deref()
704+
}
705+
}
706+
707+
impl std::fmt::Debug for BusInfo {
708+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
709+
let mut s = f.debug_struct("BusInfo");
710+
711+
#[cfg(target_os = "linux")]
712+
{
713+
s.field("sysfs_path", &self.path);
714+
s.field("busnum", &self.busnum);
715+
}
716+
717+
#[cfg(target_os = "windows")]
718+
{
719+
s.field("instance_id", &self.instance_id);
720+
s.field("location_paths", &self.location_paths);
721+
s.field("driver", &self.driver);
722+
}
723+
724+
#[cfg(target_os = "macos")]
725+
{
726+
s.field("location_id", &format_args!("0x{:08X}", self.location_id));
727+
s.field(
728+
"registry_entry_id",
729+
&format_args!("0x{:08X}", self.registry_id),
730+
);
731+
s.field("name", &self.name);
732+
s.field("driver", &self.driver);
733+
}
734+
735+
s.field("bus_id", &self.bus_id)
736+
.field("pci_info", &self.pci_info)
737+
.field("controller", &self.controller)
738+
.field("provider_class", &self.provider_class)
739+
.field("class_name", &self.class_name);
740+
741+
s.finish()
742+
}
743+
}

src/lib.rs

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ mod platform;
120120

121121
pub mod descriptors;
122122
mod enumeration;
123-
pub use enumeration::{DeviceId, DeviceInfo, InterfaceInfo, Speed};
123+
pub use enumeration::{
124+
BusInfo, DeviceId, DeviceInfo, InterfaceInfo, PciInfo, Speed, UsbController,
125+
};
124126

125127
mod device;
126128
pub use device::{Device, Interface};
@@ -149,45 +151,21 @@ pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
149151
platform::list_devices()
150152
}
151153

152-
/// Get an iterator listing the root hubs; psuedo devices that can be
153-
/// considered buses on a Host Controller.
154+
/// Get an iterator listing the system USB buses.
154155
///
155156
/// ### Example
156157
///
157158
/// ```no_run
158159
/// use nusb;
159-
/// let hub = nusb::list_root_hubs().unwrap()
160-
/// .find(|dev| dev.vendor_id() == 0x1d6b)
161-
/// .expect("Linux Foundation root hub not found");
160+
/// let hub = nusb::list_buses().unwrap()
161+
/// .find(|bus| bus.bus_id().parse() == Ok(1))
162+
/// .expect("bus #1 not found");
162163
/// ```
163164
///
164165
/// ### Platform-specific notes
165-
///
166-
/// Root hubs are not actual devices, but an abstration to the physical Host
167-
/// Controller they are attached to:
168-
///
169-
/// * On Linux, the data is obtained from the sysfs filesystem root hub 'device':
170-
/// - vendor_id -> constant Linux root hub ID (0x1d6b)
171-
/// - product_id -> compresponds to the hub speed (0x001 for USB1.1, 0x0002 for USB2, 0x0003 for USB3 etc.)
172-
/// - manufacturer_string -> kernel generated string, normally the kernel build and HCI driver
173-
/// - product_string -> kernel generated string, normally the type of Host Controller
174-
/// - device_version -> normally the kernel version
175-
/// - serial_number -> normally sysfs kernel name of the PCI Host Controller
176-
/// - class -> 0x09 (Hub)
177-
/// - subclass -> 0x00 (Unused)
178-
/// - protocol -> based on hub speed
179-
/// * On non-Linux platforms, the data is a combination of Host Controller and Root Hub information (where available):
180-
/// - vendor_id -> Host Controller Vendor ID, 0x0000 if not available
181-
/// - product_id -> Host Controller Product ID, 0x0000 if not available
182-
/// - device_version -> Windows: USB version parsed from instance ID, macOS: USB version based on Host Controller Interface
183-
/// - manufacturer_string -> Windows: Root Hub Manufacturer, macOS: IOProviderClass
184-
/// - product_string -> Windows: Root Hub Product, macOS: IOClass
185-
/// - serial_number -> Windows: parsed from end of instance ID, macOS: missing
186-
/// - class -> 0x09 (Hub)
187-
/// - subclass -> 0x00 (Unused)
188-
/// - protocol -> resolved from parsed USB BCD but maybe inaccurate
189-
pub fn list_root_hubs() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
190-
platform::list_root_hubs()
166+
/// * On Linux, the abstraction of the "bus" is a phony device known as the root hub. This device is available at bus.root_hub()
167+
pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
168+
platform::list_buses()
191169
}
192170

193171
/// Get a [`Stream`][`futures_core::Stream`] that yields an

0 commit comments

Comments
 (0)