-
Notifications
You must be signed in to change notification settings - Fork 35
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
Conversation
I can see this being quite useful on the application side too, for helping to identify where on the system a device is connected, or in bug reporting for identifying issues/behaviour that seem to be specific to certain hardware. Hacking this information into I reckon this would be better as |
I agree with @martinling, it should return a dedicated
|
Thanks for the feedback. A specific type was something I debated over too. One I considered was a How about something like this:
That way on Linux the root hub device can still be obtained if desired. In answer to the above:
One issue I see with the above is that on Linux two root hubs can share the same host controller - different USB versions for example. Would we return two of the same |
Actually I have a idea for the multiple root hubs issue: rather than include the |
I would suggest putting all the PCI-specific info in a |
80251ea
to
3efdb54
Compare
I'm happier with this and so ready for review again. I opted for a |
@martinling @kevinmehall not sure if you get notified by the change of status for this - sorry if this is a double notification! Would be good to get some feedback and potentially get this merged if you are happy. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty good but I think it could still do with some API tweaks.
src/enumeration.rs
Outdated
/// 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>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although these two fields are populated on all systems, the names and their descriptions only make sense in the context of macOS. What's being put in these fields on Windows and Linux doesn't match the description here. I think we should just make these two fields #[cfg(target_os = "macos")]
only.
Another option would be to name these two fields something more generic. At the moment though, the text that gets put here is so inconsistent in nature that I just don't think it's useful to expose it through a common API. Here's what I see for an XHCI controller on each system (provider_class
, class_name
):
macOS: AppleARMIODevice
, AppleT1803USBXHCI
Linux: Linux 6.1.0-22-amd64 xhci-hcd
, xHCI Host Controller
Windows: (Standard USB HUBs)
, USB Root Hub (USB 3.0)
The only actual information to be found in these strings is:
- The fact it's an XHCI / USB3 controller, which we already have an enum for.
- The class names in use on macOS - but those should be in macOS-specific fields.
- The driver name in use on Linux - but that should go in a Linux-specific field.
- The Linux kernel version - but programs have better ways to get that info than through nusb.
The USB Root Hub (USB 3.0)
string is somewhat useful because it corresponds to what the user will see in Device Manager, but again, the place for that would be a Windows-specific field.
Also note that by making these fields OS-specific, we can avoid the need to make them Option
.
My suggestion here would be:
/// System provider class name for the bus' host controller.
#[cfg(target_os = "macos")]
pub(crate) provider_class_name: String,
/// System class name for the bus' host controller.
#[cfg(target_os = "macos")]
pub(crate) class_name: String,
/// Kernel driver name for the bus' host controller.
#[cfg(target_os = "linux")]
pub(crate) driver_name: String,
#[cfg(target_os = "windows")]
/// Description of the root hub. This is how the bus will appear in Device Manager.
pub(crate) root_hub_description: String,
And then similarly for the public accessor methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, but on the other hand, I'm somewhat skeptical of having the majority of fields on this type be specific to one OS. Is any nusb user going to actually write OS-specific code against these? Or would it be better to consolidate them down to one name
or description
field that is a human-readable display name, not intended to map to any specific property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be wary of getting into the business of generating human-readable text, because downstream application authors are never going to all agree on exactly what that text should look like, and that's before you even get into the can of worms that is localization.
We're already in the situation of having to consider parsing some of these details out of strings "helpfully" generated by the OS; it'd be good not to add another layer of that on top.
I think it's better to just stick to just reporting the known facts: this is bus N
on an XHCI
controller handled by driver Y
, etc, and then everyone's free to generate text in any language to suit their preferences from the facts available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had this conflict myself too, all good points. I went for the middle ground of:
- Separating
class_name
,provider_class_name
to macOS with accessor methods. - Adding the field for
root_hub_description
on Windows. - Made
driver
universal since it can be read from the pci driver symblic link filename. This is actually better than relying on the sysfs manufacturer field I think. - Created a
system_name()
(maybe better as justname()
?) accessor method for all platforms, which returnsname
on macOS,root_hub_description
on Windows and the root hub product string on Linux.
devinst, | ||
driver: Some(driver).filter(|s| !s.is_empty()), | ||
bus_id, | ||
controller: None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if we could figure out a way to fill in the controller type on Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered parsing the device name on Windows; whether it has >= 3.0 (XHCI), 2.0 (EHCI) else OHCI. Should be accurate but it feels a bit dirty. I guess I could do this with a platform caveat covering this on Windows in the docs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This maybe should be looking at GUID_DEVINTERFACE_USB_HOST_CONTROLLER
instead of GUID_DEVINTERFACE_USB_HUB
and filtering for root hubs -- In a quick look at Device Manager, the host controller properties seem a lot more relevant than the root hub's, including several that say XHCI (don't know which of those would be safest to try to parse into the enum, though).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good point, the Host Controller contains a Service that seems to contain the controller type. I've updated to use this 366b2ab
Iterating over GUID_DEVINTERFACE_USB_HUB
then getting the parent host controller like the Linux root hub exploration (rather than iterating over GUID_DEVINTERFACE_USB_HUB
) still makes because a host controller can have more than one 'bus'. Iterating over the root hubs is the best way to build a BusInfo
type I believe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I haven't had a chance to test this yet, but here are some initial comments (and thanks @martinling for jumping in).
To be honest, I'm wondering if your system profiler use case would be better served by grabbing some of the sysfs / cfgmgr32 / iokit bits and writing a new library that is explicitly an abstraction over the device hierarchy beyond USB. Specifically, I'm leaning towards saying no to the PCI parts here, because it's quite a bit of code and API surface that, well, isn't USB. Along those lines, people commonly ask for ways to find mass storage device drive letters / mount points, or serial port names corresponding to USB devices. Since libusb/nusb are focused on userspace device drivers for vendor class devices, those OS drivers and devices above and below USB seem out of scope, but there's a valid use case there that I don't think is served in a cross-platform way.
There's definitely some kind of BusInfo
that makes sense in nusb though, and this is a useful exploration of what might belong on it.
Ok(usb_service_iter(kAppleUSBXHCI)? | ||
.filter_map(|h| probe_bus(h, UsbController::XHCI)) | ||
.chain( | ||
usb_service_iter(kAppleUSBEHCI)? | ||
.filter_map(|h| probe_bus(h, UsbController::EHCI)) | ||
.chain( | ||
usb_service_iter(kAppleUSBOHCI)? | ||
.filter_map(|h| probe_bus(h, UsbController::OHCI)) | ||
.chain( | ||
usb_service_iter(kAppleUSBVHCI)? | ||
.filter_map(|h| probe_bus(h, UsbController::VHCI)), | ||
), | ||
), | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[...].into_iter().flatten()
would be a better way to write this than the nested .chain()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean like?
Ok([
usb_service_iter(kAppleUSBXHCI)?
.filter_map(|h| probe_bus(h, UsbControllerType::XHCI))
.collect::<Vec<_>>(),
usb_service_iter(kAppleUSBEHCI)?
.filter_map(|h| probe_bus(h, UsbControllerType::EHCI))
.collect::<Vec<_>>(),
usb_service_iter(kAppleUSBOHCI)?
.filter_map(|h| probe_bus(h, UsbControllerType::OHCI))
.collect::<Vec<_>>(),
usb_service_iter(kAppleUSBVHCI)?
.filter_map(|h| probe_bus(h, UsbControllerType::VHCI))
.collect::<Vec<_>>(),
]
.into_iter()
.flatten())
I was under the impression the chain is better because it keeps the iterator lazy? Or maybe I misunderstood and you're not suggesting collecting each step.
devinst, | ||
driver: Some(driver).filter(|s| !s.is_empty()), | ||
bus_id, | ||
controller: None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This maybe should be looking at GUID_DEVINTERFACE_USB_HOST_CONTROLLER
instead of GUID_DEVINTERFACE_USB_HUB
and filtering for root hubs -- In a quick look at Device Manager, the host controller properties seem a lot more relevant than the root hub's, including several that say XHCI (don't know which of those would be safest to try to parse into the enum, though).
src/enumeration.rs
Outdated
/// 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>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, but on the other hand, I'm somewhat skeptical of having the majority of fields on this type be specific to one OS. Is any nusb user going to actually write OS-specific code against these? Or would it be better to consolidate them down to one name
or description
field that is a human-readable display name, not intended to map to any specific property?
f20302a
to
1f67bf5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks better; the XHCI controller type is detected on Windows for me now, and the fields make a lot more sense.
I like the system_name
field; I think that's a good compromise for including something that's human-readable and not OS-specific, but which matches descriptions visible elsewhere, rather than being something nusb has to invent.
However, the system_name
values on macOS are a bit odd. The names I see on a Macbook M1 running 12.6.7 are usb-drd0
and usb-drd1
. In System Information, these buses are both listed as USB 3.1 Bus
. If we could get that latter string somehow that would be much more appropriate.
Thanks. Re the I searched for where System Information gets the name and can't get a good answer. I'm lead to believe it's a generated human readable name based on the bus information. It's not listed with |
I've made the controller type on Windows much more robust and put a note regarding where the I understand your thoughts @kevinmehall regarding the |
A compromise on the On Linux for instance, this could just be the value of That would give programs a way to "connect" the USB information from nusb to the rest of the system's device hierarchy, without needing to complicate the nusb API with non-USB details. |
Yeah I think that's the way to go, and |
Great. I've removed the Happy to rebase too, unless you're going to squash merge. |
Co-authored-by: Martin Ling <[email protected]>
I've also rebased with main to include the new Android support. Something I'm not fully sure of though: The path is still included in the |
Yeah, all of the functionality around listing devices and now buses aren't expected to work on (un-rooted) Android because it doesn't allow access to sysfs. Instead, you request device permissions and get a file descriptor to the device via Java APIs, and can use that with the new |
How do you want to move forwards on this @kevinmehall? The suggestion makes sense to me. I've done this for the |
Thanks! |
Considered this as a discussion but thought a PR is easier to show implementation. I understand the removal of root hubs from the
list_devices
function (since they are not devices) but I wonder if you would be open to a separate function, specifically to get the root hubs?I realise the ambitions of this crate is for low level implementation of user space drivers but I feel a lot of people are going to use it as a replacement of libusb too. Whilst root hubs are an phoney device and an abstraction of the Host Controller, I think they can be helpful:
On Linux it's easy to add this feature since I believe that's where the whole root hub concept came from and they are in sysfs. On the other platforms, I've parsed what data exists in a similar manor to how the Linux 'devices' return their descriptor. The Windows API does actually have mention of root hubs in a couple of places. I've tried to be explicit in the docs on where the data comes from.
To provide some context, I've been working on replacing libusb with nusb in my lsusb compatiable system USB profiler cyme. So for my use case it is specifically profiling the system USB. With the addition of this
list_root_hubs
function, the output is actually more verbose than libusb because that only returns root hub information on Linux (develop PR).I understand if this is outside the scope of what you want to or too woolly do but hope since it's not impacting the other code and should be quite maintainable it's of interest. The existing code you've written in this crate (not exposed) make it easy to add but not outside the crate context. Open to changes if you like the idea but think it could be done differently - not everything I'm totally happy with.