Skip to content

Conversation

@cstrahan
Copy link
Contributor

@cstrahan cstrahan commented Mar 24, 2025

Hi Kevin,

For macOS, this adds support for attaching/detaching kernel drivers to/from interfaces, and checking if there is a kernel driver presently attached to a given interface. Testing shows that things seem to be working as desired (I eventually intend to use this in a cross platform, open source pen tablet driver, starting with macOS and Linux).

On macOS, the only way to detach drivers is to either reset the device by enumerating with kUSBReEnumerateCaptureDeviceMask (which libusb supports), or re-configure the device via SetConfigurationV2 with startInterfaceMatching=false (which libusb does not support). Individual interfaces can then have kernel drivers (re)attached.

I've added new interface declarations with the minimum version that adds the function pointers I need. For now, I've switched the type aliases over to these newer versions, but breaks support for older versions of macOS. How would you like to resolve that -- perhaps an enum over the two interface versions, or a new struct that cherry picks the function pointers we need?

I haven't added is_kernel_driver_attached_to_interface support for the other OSes, so the build is broken for those.

I added os_version to get the os version via sysctls, thinking that we might want to do something similar to libusb where they choose the latest available device/interface interface version, but I wasn't sure if / how you'd want to go about that, so os_version is currently unused.

I wasn't sure about naming in a number of places ("detached" vs "captured", for instance), so names may be a bit inconsistent / less than ideal.

Let me know how you'd like me to address the remaining issues (or, if it would be less of a bother for you to touch things up yourself, feel free).

@kevinmehall
Copy link
Owner

On macOS, the only way to detach drivers is to either reset the device by enumerating with kUSBReEnumerateCaptureDeviceMask (which libusb supports), or re-configure the device via SetConfigurationV2 with startInterfaceMatching=false (which libusb does not support).

Are there use cases for both?

I've added new interface declarations with the minimum version that adds the function pointers I need. For now, I've switched the type aliases over to these newer versions, but breaks support for older versions of macOS. How would you like to resolve that

The Apple docs say IOUSBInterfaceInterface700 was added in macOS 10.10+ and IOUSBDeviceInterface650 in macOS 10.9+. If that's correct, we could just use it without extra steps, because Rust libstd has a minimum of 10.12.

I wasn't sure about naming in a number of places ("detached" vs "captured", for instance), so names may be a bit inconsistent / less than ideal.

Well, the question is whether there could be a cross-platform API for this. In theory we could emulate the macOS behavior on Linux by detaching all interfaces, and that would probably be enough for most use cases. But Linux can only exclusively claim interfaces, not devices, so they have detach_and_claim_interface to avoid any race conditions there.

As it is here, it seems confusing to have detach_kernel_driver for Linux callable on macOS but doing nothing, where you need to call set_configuration_detached instead. If we're going to make the library user do different things for different platforms, these should probably all be #[cfg]'d to the supported OS. Then I think it makes sense to name them following the underlying APIs so that it matches OS-specific documentation (though there doesn't seem to be much documentation in this case).

@cstrahan
Copy link
Contributor Author

On macOS, the only way to detach drivers is to either reset the device by enumerating with kUSBReEnumerateCaptureDeviceMask (which libusb supports), or re-configure the device via SetConfigurationV2 with startInterfaceMatching=false (which libusb does not support).

Are there use cases for both?

I don't have sufficient expertise in USB to know if this is actually a concern, but I suppose it could be possible that some devices need to be interacted immediately after coming out of reset/re-enumeration, without changing the config, which would only be possible with reset_captured. Between the docs and some testing, I've found that you can detach kernel drivers via set_configuration_captured, but only if you've specified a configuration that is different than the currently active configuration, otherwise drivers remain attached. If it weren't for that, I'd say that the latter subsumes the former.

I've added new interface declarations with the minimum version that adds the function pointers I need. For now, I've switched the type aliases over to these newer versions, but breaks support for older versions of macOS. How would you like to resolve that

The Apple docs say IOUSBInterfaceInterface700 was added in macOS 10.10+ and IOUSBDeviceInterface650 in macOS 10.9+. If that's correct, we could just use it without extra steps, because Rust libstd has a minimum of 10.12.

Cool, I'll do that.

As it is here, it seems confusing to have detach_kernel_driver for Linux callable on macOS but doing nothing, where you need to call set_configuration_detached instead. If we're going to make the library user do different things for different platforms, these should probably all be #[cfg]'d to the supported OS.

I think I'll go the #[cfg] route for now, and we can "polyfill" these things later, if we want.

I'll try to clean things up, add Linux support for the kernel driver detection, and ping when I'm ready for review.

@cstrahan
Copy link
Contributor Author

@kevinmehall How does this look now?

A heads up: I haven't yet tested the new is_kernel_driver_attached_to_interface on Linux, need to go find a machine I can test on. Also, that function name is a mouthful -- open to suggestions, if you can think of a better name.

@cstrahan
Copy link
Contributor Author

Oh, and to add to what I was saying here:

I don't have sufficient expertise in USB to know if this is actually a concern, but I suppose it could be possible that some devices need to be interacted immediately after coming out of reset/re-enumeration, without changing the config, which would only be possible with reset_captured. Between the docs and some testing, I've found that you can detach kernel drivers via set_configuration_captured, but only if you've specified a configuration that is different than the currently active configuration, otherwise drivers remain attached. If it weren't for that, I'd say that the latter subsumes the former.

If you reset_captured, and then do a regular set_configuration (and assuming the configuration number you gave is different than the current configuration), the kernel drivers will be re-attached. So, at the very least, we want the set_configuration_captured, but I don't think it hurts to also have reset_captured, in case someone needs it.

@cstrahan
Copy link
Contributor Author

We may want to hold off on merging this, as I have some more work that could serve as an alternative for having a function like is_kernel_driver_attached_to_interface:

I have some changes I'm working on to add a "driver" field to the InterfaceInfo on macOS (using the UsbExclusiveOwner property) and Linux (using the USBDEVFS_GETDRIVER ioctl). On macOS, if the driver is not None (for example: Some("AppleUserUSBHostHIDDevice")), then the interface is claimed by the kernel; on Linux, if the driver is not Some("usbfs"), then the interface is claimed by the kernel. I'm not familiar with Window's APIs, but given that a DeviceInfo.driver field already exists, I'm guessing that drivers are associated with devices on Windows rather than individual interfaces, and thus it wouldn't make sense to add a device field to InterfaceInfo on WIndows.

I figure that's a lot more general purpose than a is_kernel_driver_attached_to_interface, and can be determined at enumeration rather than requiring system calls after opening a device. A bonus is that we don't have to come up with a better name for (or settle for) is_kernel_driver_attached_to_interface :)

I'll finish that work up and send a different PR for that. In the interim, if you get a chance, please do let me know if the other changes I've made so far in this PR look good.

@kevinmehall
Copy link
Owner

Yeah, I was wondering if is_kernel_driver_attached_to_interface should return Option<String>. Having it on InterfaceInfo instead or in addition would be good.

Windows does it both ways -- either the device can be bound to winusb, in which case we have userspace access to the whole device, whole device can be bound to another driver, or if the device has usbccgp ("Composite Group Parent") then the interfaces have their own device nodes with individual drivers. There is already code in nusb for getting those driver names internally.

One thing to consider is if we should abstract over the "generic driver for userspace" case into a method or enum variant so that users don't have to hard-code platform-specific driver names like "usbfs" or "winusb".

@kevinmehall
Copy link
Owner

kevinmehall commented Mar 30, 2025

And for Linux, ioctl(USBDEVFS_GETDRIVER) would be good for a Device method, but for InterfaceInfo we'd need something from sysfs -- we don't open usbfs devices in list_devices because that performs IO.

It looks like fs::read_link(interface_path.join("driver")).and_then(|driver_path| driver_path.file_name()) might be usable for that. (EDIT: this is already used in list_buses)

@cstrahan
Copy link
Contributor Author

Awesome, thanks for all the info/direction! I'll see if I can incorporate all of that in the upcoming PR.

One thing to consider is if we should abstract over the "generic driver for userspace" case into a method or enum variant so that users don't have to hard-code platform-specific driver names like "usbfs" or "winusb".

I had that thought of adding a method just like that -- dunno why I didn't suggest that in my comment earlier. Glad we're thinking along the same lines. I'll add that while I'm at it.

And, because it can't be said too many times: thanks again for all your work on nusb :)

gaykitty added a commit to gaykitty/adb_client that referenced this pull request May 8, 2025
@kevinmehall
Copy link
Owner

Extracted the IOUSBInterfaceInterface700 and IOUSBDeviceInterface650 part into #148

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants