Skip to content

Commit

Permalink
specific BusInfo type for 'root hubs'
Browse files Browse the repository at this point in the history
  • Loading branch information
tuna-f1sh committed Aug 30, 2024
1 parent b2784c8 commit 9503cf3
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 84 deletions.
2 changes: 1 addition & 1 deletion examples/root_hubs.rs → examples/buses.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
fn main() {
env_logger::init();
for dev in nusb::list_root_hubs().unwrap() {
for dev in nusb::list_buses().unwrap() {
println!("{:#?}", dev);
}
}
272 changes: 272 additions & 0 deletions src/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,275 @@ impl std::fmt::Debug for InterfaceInfo {
.finish()
}
}

/// PCI device information for host controllers
#[derive(Clone)]
pub struct PciInfo {
#[cfg(target_os = "linux")]
pub(crate) path: SysfsPath,

#[cfg(target_os = "windows")]
pub(crate) instance_id: OsString,
/// PCI vendor ID
pub(crate) vendor_id: u16,
/// PCI device ID
pub(crate) device_id: u16,
/// PCI hardware revision
pub(crate) revision: Option<u16>,
/// PCI subsystem vendor ID
pub(crate) subsystem_vendor_id: Option<u16>,
/// PCI subsystem device ID
pub(crate) subsystem_device_id: Option<u16>,
}

impl PciInfo {
/// PCI vendor ID
pub fn vendor_id(&self) -> u16 {
self.vendor_id
}

/// PCI device ID
pub fn device_id(&self) -> u16 {
self.device_id
}

/// PCI hardware revision
pub fn revision(&self) -> Option<u16> {
self.revision
}

/// PCI subsystem vendor ID
pub fn subsystem_vendor_id(&self) -> Option<u16> {
self.subsystem_vendor_id
}

/// PCI subsystem device ID
pub fn subsystem_device_id(&self) -> Option<u16> {
self.subsystem_device_id
}
}

impl std::fmt::Debug for PciInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("PciInfo");

s.field("vendor_id", &format_args!("0x{:04X}", self.vendor_id))
.field("device_id", &format_args!("0x{:04X}", self.device_id))
.field("revision", &self.revision)
.field("subsystem_vendor_id", &self.subsystem_vendor_id)
.field("subsystem_device_id", &self.subsystem_device_id);

#[cfg(target_os = "linux")]
{
s.field("sysfs_path", &self.path);
}

s.finish()
}
}

/// USB host controller type
#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
#[non_exhaustive]
pub enum UsbController {
/// xHCI controller (USB 3.0+)
XHCI,
/// EHCI controller (USB 2.0)
EHCI,
/// OHCI controller (USB 1.1)
OHCI,
/// VHCI controller (virtual internal USB)
VHCI,
}

/// Information about a system USB bus
///
/// Aims to be a more useful and portable version of the Linux root hub device.
pub struct BusInfo {
#[cfg(target_os = "linux")]
pub(crate) path: SysfsPath,

/// The phony root hub device
#[cfg(target_os = "linux")]
pub(crate) root_hub: DeviceInfo,

#[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,

#[cfg(target_os = "windows")]
pub(crate) port_number: u32,

#[cfg(target_os = "windows")]
pub(crate) devinst: crate::platform::DevInst,

#[cfg(any(target_os = "windows", target_os = "macos"))]
pub(crate) driver: Option<String>,

#[cfg(target_os = "macos")]
pub(crate) registry_id: u64,

#[cfg(target_os = "macos")]
pub(crate) location_id: u32,

#[cfg(target_os = "macos")]
pub(crate) name: Option<String>,

/// System ID for the bus
pub(crate) bus_id: String,

/// Optional PCI information if the bus is connected to a PCI Host Controller
pub(crate) pci_info: Option<PciInfo>,

/// Detected USB controller type
pub(crate) controller: Option<UsbController>,

/// System provider class name for the bus
pub(crate) provider_class: Option<String>,
/// System class name for the bus
pub(crate) class_name: Option<String>,
}

impl BusInfo {
/// *(Linux-only)* Sysfs path for the bus.
#[doc(hidden)]
#[deprecated = "use `sysfs_path()` instead"]
#[cfg(target_os = "linux")]
pub fn path(&self) -> &SysfsPath {
&self.path
}

/// *(Linux-only)* Sysfs path for the bus.
#[cfg(target_os = "linux")]
pub fn sysfs_path(&self) -> &std::path::Path {
&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 {
&self.parent_instance_id
}

/// *(Windows-only)* Port number
#[cfg(target_os = "windows")]
pub fn port_number(&self) -> u32 {
self.port_number
}

/// *(Windows-only)* Driver associated with the device as a whole
#[cfg(any(target_os = "windows", target_os = "macos"))]
pub fn driver(&self) -> Option<&str> {
self.driver.as_deref()
}

/// *(macOS-only)* IOKit Location ID
#[cfg(target_os = "macos")]
pub fn location_id(&self) -> u32 {
self.location_id
}

/// *(macOS-only)* IOKit [Registry Entry ID](https://developer.apple.com/documentation/iokit/1514719-ioregistryentrygetregistryentryi?language=objc)
#[cfg(target_os = "macos")]
pub fn registry_entry_id(&self) -> u64 {
self.registry_id
}

/// Name of the bus
#[cfg(target_os = "macos")]
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}

/// Identifier for the bus
pub fn bus_id(&self) -> &str {
&self.bus_id
}

/// Optional PCI information if the bus is connected to a PCI Host Controller
pub fn pci_info(&self) -> Option<&PciInfo> {
self.pci_info.as_ref()
}

/// Detected USB controller type
pub fn controller(&self) -> Option<UsbController> {
self.controller
}

/// System provider class name for the bus
pub fn provider_class(&self) -> Option<&str> {
self.provider_class.as_deref()
}

/// System class name for the bus
pub fn class_name(&self) -> Option<&str> {
self.class_name.as_deref()
}
}

impl std::fmt::Debug for BusInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("BusInfo");

#[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);
}

#[cfg(target_os = "macos")]
{
s.field("location_id", &format_args!("0x{:08X}", self.location_id));
s.field(
"registry_entry_id",
&format_args!("0x{:08X}", self.registry_id),
);
s.field("name", &self.name);
}

s.field("bus_id", &self.bus_id)
.field("pci_info", &self.pci_info)
.field("controller", &self.controller)
.field("provider_class", &self.provider_class)
.field("class_name", &self.class_name);

s.finish()
}
}
40 changes: 8 additions & 32 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ mod platform;

pub mod descriptors;
mod enumeration;
pub use enumeration::{DeviceId, DeviceInfo, InterfaceInfo, Speed};
pub use enumeration::{DeviceId, DeviceInfo, InterfaceInfo, Speed, BusInfo, PciInfo, UsbController};

mod device;
pub use device::{Device, Interface};
Expand Down Expand Up @@ -149,45 +149,21 @@ pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
platform::list_devices()
}

/// Get an iterator listing the root hubs; psuedo devices that can be
/// considered buses on a Host Controller.
/// Get an iterator listing the system USB buses.
///
/// ### Example
///
/// ```no_run
/// use nusb;
/// let hub = nusb::list_root_hubs().unwrap()
/// .find(|dev| dev.vendor_id() == 0x1d6b)
/// .expect("Linux Foundation root hub not found");
/// let hub = nusb::list_buses().unwrap()
/// .find(|bus| bus.bus_id().parse() == Ok(1))
/// .expect("bus #1 not found");
/// ```
///
/// ### Platform-specific notes
///
/// Root hubs are not actual devices, but an abstration to the physical Host
/// Controller they are attached to:
///
/// * On Linux, the data is obtained from the sysfs filesystem root hub 'device':
/// - vendor_id -> constant Linux root hub ID (0x1d6b)
/// - product_id -> compresponds to the hub speed (0x001 for USB1.1, 0x0002 for USB2, 0x0003 for USB3 etc.)
/// - manufacturer_string -> kernel generated string, normally the kernel build and HCI driver
/// - product_string -> kernel generated string, normally the type of Host Controller
/// - device_version -> normally the kernel version
/// - serial_number -> normally sysfs kernel name of the PCI Host Controller
/// - class -> 0x09 (Hub)
/// - subclass -> 0x00 (Unused)
/// - protocol -> based on hub speed
/// * On non-Linux platforms, the data is a combination of Host Controller and Root Hub information (where available):
/// - vendor_id -> Host Controller Vendor ID, 0x0000 if not available
/// - product_id -> Host Controller Product ID, 0x0000 if not available
/// - device_version -> Windows: USB version parsed from instance ID, macOS: USB version based on Host Controller Interface
/// - manufacturer_string -> Windows: Root Hub Manufacturer, macOS: IOProviderClass
/// - product_string -> Windows: Root Hub Product, macOS: IOClass
/// - serial_number -> Windows: parsed from end of instance ID, macOS: missing
/// - class -> 0x09 (Hub)
/// - subclass -> 0x00 (Unused)
/// - protocol -> resolved from parsed USB BCD but maybe inaccurate
pub fn list_root_hubs() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
platform::list_root_hubs()
/// * On Linux, the abstraction of the "bus" is a phony device known as the root hub. This device is available at bus.root_hub()
pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
platform::list_buses()
}

/// Get a [`Stream`][`futures_core::Stream`] that yields an
Expand Down
Loading

0 comments on commit 9503cf3

Please sign in to comment.