Skip to content

Commit

Permalink
Merge pull request #88 from kevinmehall/macos-fix
Browse files Browse the repository at this point in the history
macOS: Fix set_configuration and reset by using USBDeviceOpen
  • Loading branch information
kevinmehall authored Oct 30, 2024
2 parents b3097f5 + ce3a29e commit f8f23b2
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 14 deletions.
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),
}
}

0 comments on commit f8f23b2

Please sign in to comment.