Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List root hubs function #77

Merged
merged 27 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9a30fe8
experimental list_root_hubs
tuna-f1sh Aug 19, 2024
651d97a
notes on list_root_hubs usage
tuna-f1sh Aug 22, 2024
e709abe
attempt to get other Apple HCI
tuna-f1sh Aug 23, 2024
ae98f5c
tidy and remove unused get_hub_info
tuna-f1sh Aug 23, 2024
d4e1220
specific BusInfo type for 'root hubs'
tuna-f1sh Aug 30, 2024
c767591
a more applied doc list_buses example
tuna-f1sh Sep 2, 2024
602cfb0
macos: add CFData parsing of revision and name
tuna-f1sh Sep 3, 2024
a0ed0ce
rename UsbController; parse from instance_id on Win
tuna-f1sh Sep 9, 2024
5a4ee26
pr feedback
tuna-f1sh Sep 10, 2024
8694962
add system_name for all platforms
tuna-f1sh Sep 10, 2024
60076e6
tidy linux readlink and parent_path
tuna-f1sh Sep 10, 2024
9bdf074
win: probe parent service for concrete controller_type
tuna-f1sh Sep 12, 2024
d0b37bb
mac: and_then not flatten, note on IONameMatch
tuna-f1sh Sep 12, 2024
b4959da
remove pci_info and struct
tuna-f1sh Sep 18, 2024
b607a9b
add grabbed parent path on Linux and Windows
tuna-f1sh Sep 19, 2024
6685115
windows: remove now unused host controller parsing
tuna-f1sh Sep 19, 2024
c9822be
Replace .map() + .flatten() with .and_then()
martinling Sep 20, 2024
b3917aa
Avoid nested use of Iterator::chain().
martinling Sep 20, 2024
dfa821b
Update src/platform/linux_usbfs/enumeration.rs
tuna-f1sh Sep 23, 2024
a4d4892
windows: just check starts with root_hub
tuna-f1sh Sep 23, 2024
f86952c
remove bus_id example filter
tuna-f1sh Sep 23, 2024
d8b1f10
prevent unsafe function by passing UsbControllerType
tuna-f1sh Sep 23, 2024
c7d28f8
untested android support
tuna-f1sh Sep 23, 2024
1ffba0c
macos: return usb_service_iter to main
tuna-f1sh Sep 24, 2024
f4f3719
macos: tidy list_buses now passed hci_type
tuna-f1sh Sep 27, 2024
060f284
add getters for android; note that list_buses requires root
tuna-f1sh Sep 27, 2024
4774c75
Remove dead code
kevinmehall Sep 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/buses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
env_logger::init();
for dev in nusb::list_buses().unwrap() {
println!("{:#?}", dev);
}
}
265 changes: 265 additions & 0 deletions src/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,268 @@ impl std::fmt::Debug for InterfaceInfo {
.finish()
}
}

/// USB host controller type
#[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)]
#[non_exhaustive]
pub enum UsbControllerType {
/// xHCI controller (USB 3.0+)
XHCI,

/// EHCI controller (USB 2.0)
EHCI,

/// OHCI controller (USB 1.1)
OHCI,

/// UHCI controller (USB 1.x) (proprietary interface created by Intel)
UHCI,

/// VHCI controller (virtual internal USB)
VHCI,
}

impl UsbControllerType {
#[allow(dead_code)] // not used on all platforms
pub(crate) fn from_str(s: &str) -> Option<Self> {
let lower_s = s.to_owned().to_ascii_lowercase();
match lower_s
.find("hci")
.filter(|i| *i > 0)
.and_then(|i| lower_s.bytes().nth(i - 1))
{
Some(b'x') => Some(UsbControllerType::XHCI),
Some(b'e') => Some(UsbControllerType::EHCI),
Some(b'o') => Some(UsbControllerType::OHCI),
Some(b'v') => Some(UsbControllerType::VHCI),
Some(b'u') => Some(UsbControllerType::UHCI),
_ => None,
}
}
}

/// Information about a system USB bus.
///
/// Platform-specific fields:
/// * Linux: `path`, `parent_path`, `busnum`, `root_hub`
/// * Windows: `instance_id`, `parent_instance_id`, `location_paths`, `devinst`, `root_hub_description`
/// * macOS: `registry_id`, `location_id`, `name`, `provider_class_name`, `class_name`
pub struct BusInfo {
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) path: SysfsPath,

#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) parent_path: SysfsPath,

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

#[cfg(any(target_os = "linux", target_os = "android"))]
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) devinst: crate::platform::DevInst,

#[cfg(target_os = "windows")]
pub(crate) root_hub_description: String,

#[cfg(target_os = "windows")]
pub(crate) parent_instance_id: OsString,

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

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

#[cfg(target_os = "macos")]
pub(crate) provider_class_name: String,

#[cfg(target_os = "macos")]
pub(crate) class_name: String,

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

pub(crate) driver: Option<String>,

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

/// Detected USB controller type
pub(crate) controller_type: Option<UsbControllerType>,
}

impl BusInfo {
/// *(Linux-only)* Sysfs path for the bus.
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn sysfs_path(&self) -> &std::path::Path {
&self.path.0
}

/// *(Linux-only)* Sysfs path for the parent controller
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn parent_sysfs_path(&self) -> &std::path::Path {
&self.parent_path.0
}

/// *(Linux-only)* Bus number.
///
/// On Linux, the `bus_id` is an integer and this provides the value as `u8`.
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn busnum(&self) -> u8 {
self.busnum
}

/// *(Linux-only)* The root hub [`DeviceInfo`] representing the bus.
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn root_hub(&self) -> &DeviceInfo {
&self.root_hub
}

/// *(Windows-only)* Instance ID path of this device
#[cfg(target_os = "windows")]
pub fn instance_id(&self) -> &OsStr {
&self.instance_id
}

/// *(Windows-only)* Instance ID path of the parent device
#[cfg(target_os = "windows")]
pub fn parent_instance_id(&self) -> &OsStr {
&self.parent_instance_id
}

/// *(Windows-only)* Location paths property
#[cfg(target_os = "windows")]
pub fn location_paths(&self) -> &[OsString] {
&self.location_paths
}

/// *(Windows-only)* Device Instance ID
#[cfg(target_os = "windows")]
pub fn devinst(&self) -> crate::platform::DevInst {
self.devinst
}

/// *(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
}

/// *(macOS-only)* IOKit provider class name
#[cfg(target_os = "macos")]
pub fn provider_class_name(&self) -> &str {
&self.provider_class_name
}

/// *(macOS-only)* IOKit class name
#[cfg(target_os = "macos")]
pub fn class_name(&self) -> &str {
&self.class_name
}

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

/// Driver associated with the bus
pub fn driver(&self) -> Option<&str> {
self.driver.as_deref()
}

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

/// Detected USB controller type
///
/// None means the controller type could not be determined.
///
/// ### Platform-specific notes
///
/// * Linux: Parsed from driver in use.
/// * macOS: The IOService entry matched.
/// * Windows: Parsed from the numbers following ROOT_HUB in the instance_id.
pub fn controller_type(&self) -> Option<UsbControllerType> {
self.controller_type
}

/// System name of the bus
///
/// ### Platform-specific notes
///
/// * Linux: The root hub product string.
/// * macOS: The [IONameMatched](https://developer.apple.com/documentation/bundleresources/information_property_list/ionamematch) key of the IOService entry.
/// * Windows: Description field of the root hub device. How the bus will appear in Device Manager.
pub fn system_name(&self) -> Option<&str> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
self.root_hub.product_string()
}

#[cfg(target_os = "windows")]
{
Some(&self.root_hub_description)
}

#[cfg(target_os = "macos")]
{
self.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(any(target_os = "linux", target_os = "android"))]
{
s.field("sysfs_path", &self.path);
s.field("parent_sysfs_path", &self.parent_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);
}

#[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("class_name", &self.class_name);
s.field("provider_class_name", &self.provider_class_name);
}

s.field("bus_id", &self.bus_id)
.field("system_name", &self.system_name())
.field("controller_type", &self.controller_type)
.field("driver", &self.driver);

s.finish()
}
}
27 changes: 26 additions & 1 deletion 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::{BusInfo, DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType};

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

/// Get an iterator listing the system USB buses.
///
/// ### Example
///
/// Group devices by bus:
///
/// ```no_run
/// use std::collections::HashMap;
///
/// let devices = nusb::list_devices().unwrap().collect::<Vec<_>>();
/// let buses: HashMap<String, (nusb::BusInfo, Vec::<nusb::DeviceInfo>)> = nusb::list_buses().unwrap()
/// .map(|bus| {
/// let bus_id = bus.bus_id().to_owned();
/// (bus.bus_id().to_owned(), (bus, devices.clone().into_iter().filter(|dev| dev.bus_id() == bus_id).collect()))
/// })
/// .collect();
/// ```
///
/// ### Platform-specific notes
/// * On Linux, the abstraction of the "bus" is a phony device known as the root hub. This device is available at bus.root_hub()
/// * On Android, this will only work on rooted devices due to sysfs path usage
pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
platform::list_buses()
}

/// Get a [`Stream`][`futures_core::Stream`] that yields an
/// [event][`hotplug::HotplugEvent`] when a USB device is connected or
/// disconnected from the system.
Expand Down
Loading
Loading