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

macOS implementation lacks USBDeviceOpenSeize #87

Closed
carlossless opened this issue Oct 25, 2024 · 7 comments · Fixed by #88
Closed

macOS implementation lacks USBDeviceOpenSeize #87

carlossless opened this issue Oct 25, 2024 · 7 comments · Fixed by #88

Comments

@carlossless
Copy link

carlossless commented Oct 25, 2024

Hey,

Thanks for creating a pure rust replacement for libusb! I was eager to replace rusb with nusb in my project, but I found an issue in the macos implementation - Device::open does not (always?) open devices.

Details

let device = info.open().unwrap();
device.set_configuration(1).unwrap();

results in

INFO  [nusb::platform::macos_iokit::device] Opening device from registry id 4294978273
INFO  [nusb::platform::macos_iokit::events] starting event loop thread
DEBUG [nusb::platform::macos_iokit::device] Active config from request is Ok(0)
called `Result::unwrap()` on an `Err` value: Os { code: -536870195, kind: Uncategorized, message: "Unknown error: -536870195" }
mach_error_string(-536870195) # (iokit/common) device not open

Side-note: would be great if nusb returned strings for those errors!

Solution

I looked into what libusb was doing and found that it was always calling USBDeviceOpenSeize.

I tested this out by adding the following to IoKitDevice::new:

let result = call_iokit_function!(raw_device, USBDeviceOpenSeize());
if result != kIOReturnSuccess {
    return Err(Error::from_raw_os_error(result));
}

And it worked!

I curiously didn't find any direct call to USBDeviceOpen or USBDeviceOpenSeize in nusb, I guess that this should have happened implicitly somewhere, but I am not that familiar with iokit to tell exactly where.

@kevinmehall
Copy link
Owner

Does claim_interface work and only set_configuration is the problem? I'm assuming the OS sets configuration 1 by default and there's a good chance you don't need to explicitly set that.

If that's the case, it might make sense to make set_configuration open, set configuration, and close the device. set_configuration inherently requires exclusive access -- On Linux it will fail if any other process or driver has an interface claimed. I wouldn't want to default to exclusive access because it should allow other processes to claim different interfaces of the same device.

@carlossless
Copy link
Author

carlossless commented Oct 26, 2024

Does claim_interface work and only set_configuration is the problem? I'm assuming the OS sets configuration 1 by default and there's a good chance you don't need to explicitly set that.

I checked this, but the interface that I need to claim only exists on configuration 1 and it doesn't get set automatically. Claiming the interface without setting a configuration results in Err(Custom { kind: NotFound, error: "interface not found" }).

If that's the case, it might make sense to make set_configuration open, set configuration, and close the device. set_configuration inherently requires exclusive access -- On Linux it will fail if any other process or driver has an interface claimed. I wouldn't want to default to exclusive access because it should allow other processes to claim different interfaces of the same device.

I tested this out and it does seem to work with my device. Although, if exclusive access doesn't persist throughout the whole time the device is open, is there anything that's stopping another process from changing the active configuration while I have one or several interfaces from that configuration claimed? A claimed interface should prevent the active configuration from being changed.

@carlossless
Copy link
Author

Additionally Device::reset() also suffers from the same issue.

@kevinmehall
Copy link
Owner

Opened #88

Turns out that having an interface claimed does not cause set_configuration and reset to fail on macOS like it does on Linux. After doing some testing, I ended up doing what libusb does and attempting to USBDeviceOpen when opening a device to try to get exclusive access, but ignoring failures, and trying again and requiring success only before set_configuration and reset.

I checked this, but the interface that I need to claim only exists on configuration 1 and it doesn't get set automatically.

Old Apple documentation seems to indicate that a device with bDeviceClass != 0 (composite / class defined at interface level) is not guaranteed to have a configuration set by default, and I remember this being the case with bDeviceClass = 0xff (vendor) at one point. However, I'm testing now with a vendor-class device and I do get configuration 1 by default, not sure why that is.

@carlossless
Copy link
Author

carlossless commented Oct 28, 2024

I overviewed #88, gave it a test (it works), but also came up with a question.

However, I'm testing now with a vendor-class device and I do get configuration 1 by default, not sure why that is.

I don't know if this would help, but I could provide you with packet captures from wireshark with the OS enumerating my device. Maybe that could give us some clues?

@kevinmehall
Copy link
Owner

Sure, I'll take a look. I could maybe clone the descriptors with Facedancer and see if I can reproduce that behavior.

@carlossless
Copy link
Author

Sure, I'll take a look. I could maybe clone the descriptors with Facedancer and see if I can reproduce that behavior.

In that case, I'll do you one better. I checked, and it results in the same behavior as with the real device.

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 a pull request may close this issue.

2 participants