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: Fix set_configuration and reset by using USBDeviceOpen #88

Merged
merged 3 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 51 additions & 1 deletion src/platform/macos_iokit/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
ffi::c_void,
io::ErrorKind,
sync::{
atomic::{AtomicU8, Ordering},
atomic::{AtomicU8, AtomicUsize, Ordering},
Arc, Mutex,
},
time::Duration,
Expand All @@ -30,6 +30,8 @@ pub(crate) struct MacDevice {
_event_registration: EventRegistration,
pub(super) device: IoKitDevice,
active_config: AtomicU8,
is_open_exclusive: Mutex<bool>,
claimed_interfaces: AtomicUsize,
}

// `get_configuration` does IO, so avoid it in the common case that:
Expand All @@ -52,6 +54,16 @@ impl MacDevice {
let device = IoKitDevice::new(service)?;
let _event_registration = add_event_source(device.create_async_event_source()?);

let opened = match unsafe { call_iokit_function!(device.raw, USBDeviceOpen()) } {
io_kit_sys::ret::kIOReturnSuccess => true,
err => {
// Most methods don't require USBDeviceOpen() so this can be ignored
// to allow different processes to open different interfaces.
log::debug!("Could not open device for exclusive access: {err:x}");
false
}
};

let active_config = if let Some(active_config) = guess_active_config(&device) {
log::debug!("Active config from single descriptor is {}", active_config);
active_config
Expand All @@ -65,6 +77,8 @@ impl MacDevice {
_event_registration,
device,
active_config: AtomicU8::new(active_config),
is_open_exclusive: Mutex::new(opened),
claimed_interfaces: AtomicUsize::new(0),
}))
}

Expand All @@ -77,18 +91,38 @@ impl MacDevice {
(0..num_configs).flat_map(|i| self.device.get_configuration_descriptor(i).ok())
}

fn require_open_exclusive(&self) -> Result<(), Error> {
let mut state = self.is_open_exclusive.lock().unwrap();
if *state == false {
unsafe { check_iokit_return(call_iokit_function!(self.device.raw, USBDeviceOpen()))? };
*state = true;
}

if self.claimed_interfaces.load(Ordering::Relaxed) != 0 {
return Err(Error::new(
ErrorKind::Other,
"cannot perform this operation while interfaces are claimed",
));
}

Ok(())
}

pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
SetConfiguration(configuration)
))?
}
log::debug!("Set configuration {configuration}");
self.active_config.store(configuration, Ordering::SeqCst);
Ok(())
}

pub(crate) fn reset(&self) -> Result<(), Error> {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
Expand Down Expand Up @@ -180,6 +214,8 @@ impl MacDevice {
let endpoints = interface.endpoints()?;
debug!("Found endpoints: {endpoints:?}");

self.claimed_interfaces.fetch_add(1, Ordering::Acquire);

Ok(Arc::new(MacInterface {
device: self.clone(),
interface_number,
Expand All @@ -197,6 +233,17 @@ impl MacDevice {
}
}

impl Drop for MacDevice {
fn drop(&mut self) {
if *self.is_open_exclusive.get_mut().unwrap() {
match unsafe { call_iokit_function!(self.device.raw, USBDeviceClose()) } {
io_kit_sys::ret::kIOReturnSuccess => {}
err => log::debug!("Failed to close device: {err:x}"),
};
}
}
}

pub(crate) struct MacInterface {
pub(crate) interface_number: u8,
_event_registration: EventRegistration,
Expand Down Expand Up @@ -296,5 +343,8 @@ impl Drop for MacInterface {
if let Err(err) = self.interface.close() {
error!("Failed to close interface: {err}")
}
self.device
.claimed_interfaces
.fetch_sub(1, Ordering::Release);
}
}
12 changes: 5 additions & 7 deletions src/platform/macos_iokit/iokit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
//! licensed under MIT OR Apache-2.0.

use core_foundation_sys::uuid::CFUUIDBytes;
use io_kit_sys::{
ret::{kIOReturnExclusiveAccess, kIOReturnSuccess, IOReturn},
IOIteratorNext, IOObjectRelease,
};
use io_kit_sys::{ret::IOReturn, IOIteratorNext, IOObjectRelease};
use std::io::ErrorKind;

use crate::Error;
Expand Down Expand Up @@ -124,11 +121,12 @@ pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), Error> {
#[allow(non_upper_case_globals)]
#[deny(unreachable_patterns)]
match r {
kIOReturnSuccess => Ok(()),
kIOReturnExclusiveAccess => Err(Error::new(
io_kit_sys::ret::kIOReturnSuccess => Ok(()),
io_kit_sys::ret::kIOReturnExclusiveAccess => Err(Error::new(
ErrorKind::Other,
"Could not be opened for exclusive access",
"could not be opened for exclusive access",
)),
io_kit_sys::ret::kIOReturnNotFound => Err(Error::new(ErrorKind::NotFound, "not found")),
_ => Err(Error::from_raw_os_error(r)),
}
}
11 changes: 5 additions & 6 deletions src/platform/macos_iokit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
mod transfer;
use io_kit_sys::ret::{
kIOReturnAborted, kIOReturnNoDevice, kIOReturnSuccess, kIOReturnUnderrun, IOReturn,
};
use io_kit_sys::ret::IOReturn;
pub(crate) use transfer::TransferData;

mod enumeration;
Expand All @@ -28,9 +26,10 @@ fn status_to_transfer_result(status: IOReturn) -> Result<(), TransferError> {
#[allow(non_upper_case_globals)]
#[deny(unreachable_patterns)]
match status {
kIOReturnSuccess | kIOReturnUnderrun => Ok(()),
kIOReturnNoDevice => Err(TransferError::Disconnected),
kIOReturnAborted => Err(TransferError::Cancelled),
io_kit_sys::ret::kIOReturnSuccess | io_kit_sys::ret::kIOReturnUnderrun => Ok(()),
io_kit_sys::ret::kIOReturnNoDevice => Err(TransferError::Disconnected),
io_kit_sys::ret::kIOReturnAborted => Err(TransferError::Cancelled),
iokit_c::kIOUSBPipeStalled => Err(TransferError::Stall),
_ => Err(TransferError::Unknown),
}
}
Loading