diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1324ef7..212f97a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,7 +13,7 @@ jobs: build: strategy: matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index cef6e37..4f6b29e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,8 @@ rustix = { version = "0.38.17", features = ["fs", "event"] } [target.'cfg(target_os="windows")'.dependencies] windows-sys = { version = "0.48.0", features = ["Win32_Devices_Usb", "Win32_Devices_DeviceAndDriverInstallation", "Win32_Foundation", "Win32_Devices_Properties", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO", "Win32_System_Registry", "Win32_System_Com"] } + +[target.'cfg(target_os="macos")'.dependencies] +core-foundation = "0.9.3" +core-foundation-sys = "0.8.4" +io-kit-sys = "0.4.0" diff --git a/examples/bulk.rs b/examples/bulk.rs index f090e5a..5b06757 100644 --- a/examples/bulk.rs +++ b/examples/bulk.rs @@ -13,6 +13,10 @@ fn main() { let device = di.open().unwrap(); let interface = device.claim_interface(0).unwrap(); + block_on(interface.bulk_out(0x02, Vec::from([1, 2, 3, 4, 5]))) + .into_result() + .unwrap(); + let mut queue = interface.bulk_in_queue(0x81); loop { diff --git a/examples/control.rs b/examples/control.rs index d3a0b92..fce8d71 100644 --- a/examples/control.rs +++ b/examples/control.rs @@ -13,7 +13,7 @@ fn main() { let device = di.open().unwrap(); // Linux can make control transfers without claiming an interface - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "macos"))] { let result = block_on(device.control_out(ControlOut { control_type: ControlType::Vendor, diff --git a/src/device.rs b/src/device.rs index d614d8a..e9c8dd9 100644 --- a/src/device.rs +++ b/src/device.rs @@ -88,7 +88,7 @@ impl Device { /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "macos"))] pub fn control_in(&self, data: ControlIn) -> TransferFuture { let mut t = self.backend.make_control_transfer(); t.submit::(data); @@ -121,7 +121,7 @@ impl Device { /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "macos"))] pub fn control_out(&self, data: ControlOut) -> TransferFuture { let mut t = self.backend.make_control_transfer(); t.submit::(data); @@ -130,7 +130,7 @@ impl Device { } /// An opened interface of a USB device. -/// +/// /// Obtain an `Interface` with the [`Device::claim_interface`] method. /// /// This type is reference-counted with an [`Arc`] internally, and can be cloned cheaply for diff --git a/src/enumeration.rs b/src/enumeration.rs index 9b05e5a..e08274c 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -38,6 +38,9 @@ pub struct DeviceInfo { #[cfg(target_os = "windows")] pub(crate) interfaces: HashMap, + #[cfg(target_os = "macos")] + pub(crate) location_id: u32, + pub(crate) bus_number: u8, pub(crate) device_address: u8, @@ -87,6 +90,12 @@ impl DeviceInfo { self.driver.as_deref() } + /// *(macOS-only)* IOKit Location ID + #[cfg(target_os = "macos")] + pub fn location_id(&self) -> u32 { + self.location_id + } + /// Number identifying the bus / host controller where the device is connected. pub fn bus_number(&self) -> u8 { self.bus_number @@ -225,6 +234,7 @@ pub enum Speed { } impl Speed { + #[allow(dead_code)] // not used on all platforms pub(crate) fn from_str(s: &str) -> Option { match s { "low" | "1.5" => Some(Speed::Low), diff --git a/src/platform/macos_iokit/device.rs b/src/platform/macos_iokit/device.rs new file mode 100644 index 0000000..f35b994 --- /dev/null +++ b/src/platform/macos_iokit/device.rs @@ -0,0 +1,135 @@ +use std::{collections::BTreeMap, io::ErrorKind, sync::Arc}; + +use log::{debug, error}; + +use crate::{ + platform::macos_iokit::events::add_event_source, + transfer::{EndpointType, TransferHandle}, + DeviceInfo, Error, +}; + +use super::{ + enumeration::service_by_location_id, + events::EventRegistration, + iokit::{call_iokit_function, check_iokit_return}, + iokit_usb::{EndpointInfo, IoKitDevice, IoKitInterface}, +}; + +pub(crate) struct MacDevice { + _event_registration: EventRegistration, + pub(super) device: IoKitDevice, +} + +impl MacDevice { + pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, Error> { + let service = service_by_location_id(d.location_id)?; + let device = IoKitDevice::new(service)?; + let _event_registration = add_event_source(device.create_async_event_source()?); + + Ok(Arc::new(MacDevice { + _event_registration, + device, + })) + } + + pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> { + unsafe { + check_iokit_return(call_iokit_function!( + self.device.raw, + SetConfiguration(configuration) + )) + } + } + + pub(crate) fn reset(&self) -> Result<(), Error> { + unsafe { + check_iokit_return(call_iokit_function!( + self.device.raw, + USBDeviceReEnumerate(0) + )) + } + } + + pub(crate) fn make_control_transfer(self: &Arc) -> TransferHandle { + TransferHandle::new(super::TransferData::new_control(self.clone())) + } + + pub(crate) fn claim_interface( + self: &Arc, + interface_number: u8, + ) -> Result, Error> { + let intf_service = self + .device + .create_interface_iterator()? + .nth(interface_number as usize) + .ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?; + + let mut interface = IoKitInterface::new(intf_service)?; + let _event_registration = add_event_source(interface.create_async_event_source()?); + + interface.open()?; + + let endpoints = interface.endpoints()?; + debug!("Found endpoints: {endpoints:?}"); + + Ok(Arc::new(MacInterface { + device: self.clone(), + interface_number, + interface, + endpoints, + _event_registration, + })) + } +} + +pub(crate) struct MacInterface { + pub(crate) interface_number: u8, + _event_registration: EventRegistration, + pub(crate) interface: IoKitInterface, + pub(crate) device: Arc, + + /// Map from address to a structure that contains the `pipe_ref` used by iokit + pub(crate) endpoints: BTreeMap, +} + +impl MacInterface { + pub(crate) fn make_transfer( + self: &Arc, + endpoint: u8, + ep_type: EndpointType, + ) -> TransferHandle { + if ep_type == EndpointType::Control { + assert!(endpoint == 0); + TransferHandle::new(super::TransferData::new_control(self.device.clone())) + } else { + let endpoint = self.endpoints.get(&endpoint).expect("Endpoint not found"); + TransferHandle::new(super::TransferData::new( + self.device.clone(), + self.clone(), + endpoint, + )) + } + } + + pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> { + debug!( + "Set interface {} alt setting to {alt_setting}", + self.interface_number + ); + + unsafe { + check_iokit_return(call_iokit_function!( + self.interface.raw, + SetAlternateInterface(alt_setting) + )) + } + } +} + +impl Drop for MacInterface { + fn drop(&mut self) { + if let Err(err) = self.interface.close() { + error!("Failed to close interface: {err}") + } + } +} diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs new file mode 100644 index 0000000..8d7fd30 --- /dev/null +++ b/src/platform/macos_iokit/enumeration.rs @@ -0,0 +1,116 @@ +use std::io::ErrorKind; + +use core_foundation::{ + base::{CFType, TCFType}, + number::CFNumber, + string::CFString, + ConcreteCFType, +}; +use io_kit_sys::{ + kIOMasterPortDefault, kIORegistryIterateParents, kIORegistryIterateRecursively, + keys::kIOServicePlane, ret::kIOReturnSuccess, usb::lib::kIOUSBDeviceClassName, + IORegistryEntrySearchCFProperty, IOServiceGetMatchingServices, IOServiceMatching, +}; +use log::{error, info}; + +use crate::{DeviceInfo, Error, Speed}; + +use super::iokit::{IoService, IoServiceIterator}; + +fn usb_service_iter() -> Result { + unsafe { + let dictionary = IOServiceMatching(kIOUSBDeviceClassName); + if dictionary.is_null() { + return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed")); + } + + let mut iterator = 0; + let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator); + if r != kIOReturnSuccess { + return Err(Error::from_raw_os_error(r)); + } + + Ok(IoServiceIterator::new(iterator)) + } +} + +pub fn list_devices() -> Result, Error> { + Ok(usb_service_iter()?.filter_map(probe_device)) +} + +pub(crate) fn service_by_location_id(location_id: u32) -> Result { + usb_service_iter()? + .find(|dev| get_integer_property(dev, "locationID") == Some(location_id)) + .ok_or(Error::new(ErrorKind::NotFound, "not found by locationID")) +} + +fn probe_device(device: IoService) -> Option { + // Can run `ioreg -p IOUSB -l` to see all properties + let location_id: u32 = get_integer_property(&device, "locationID")?; + log::info!("Probing device {location_id}"); + + Some(DeviceInfo { + location_id, + bus_number: 0, // TODO: does this exist on macOS? + device_address: get_integer_property(&device, "USB Address")?, + vendor_id: get_integer_property(&device, "idVendor")?, + product_id: get_integer_property(&device, "idProduct")?, + device_version: get_integer_property(&device, "bcdDevice")?, + class: get_integer_property(&device, "bDeviceClass")?, + subclass: get_integer_property(&device, "bDeviceSubClass")?, + protocol: get_integer_property(&device, "bDeviceProtocol")?, + speed: get_integer_property(&device, "Device Speed").and_then(map_speed), + manufacturer_string: get_string_property(&device, "USB Vendor Name"), + product_string: get_string_property(&device, "USB Product Name"), + serial_number: get_string_property(&device, "USB Serial Number"), + }) +} + +fn get_property(device: &IoService, property: &'static str) -> Option { + unsafe { + let cf_property = CFString::from_static_string(property); + + let raw = IORegistryEntrySearchCFProperty( + device.get(), + kIOServicePlane as *mut i8, + cf_property.as_CFTypeRef() as *const _, + std::ptr::null(), + kIORegistryIterateRecursively | kIORegistryIterateParents, + ); + + if raw.is_null() { + info!("Device does not have property `{property}`"); + return None; + } + + let res = CFType::wrap_under_create_rule(raw).downcast_into(); + + if res.is_none() { + error!("Failed to convert device property `{property}`"); + } + + res + } +} + +fn get_string_property(device: &IoService, property: &'static str) -> Option { + get_property::(device, property).map(|s| s.to_string()) +} + +fn get_integer_property>(device: &IoService, property: &'static str) -> Option { + get_property::(device, property) + .and_then(|n| n.to_i64()) + .and_then(|n| n.try_into().ok()) +} + +fn map_speed(speed: u32) -> Option { + // https://developer.apple.com/documentation/iokit/1425357-usbdevicespeed + match speed { + 0 => Some(Speed::Low), + 1 => Some(Speed::Full), + 2 => Some(Speed::High), + 3 => Some(Speed::Super), + 4 | 5 => Some(Speed::SuperPlus), + _ => None, + } +} diff --git a/src/platform/macos_iokit/events.rs b/src/platform/macos_iokit/events.rs new file mode 100644 index 0000000..e282a35 --- /dev/null +++ b/src/platform/macos_iokit/events.rs @@ -0,0 +1,78 @@ +use std::{ + ops::Deref, + sync::{mpsc, Mutex}, + thread, +}; + +use core_foundation::runloop::{CFRunLoop, CFRunLoopSource}; +use core_foundation_sys::runloop::kCFRunLoopCommonModes; +use log::info; + +// Pending release of https://github.com/servo/core-foundation-rs/pull/610 +struct SendCFRunLoop(CFRunLoop); +unsafe impl Send for SendCFRunLoop {} +impl Deref for SendCFRunLoop { + type Target = CFRunLoop; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +struct SendCFRunLoopSource(CFRunLoopSource); +unsafe impl Send for SendCFRunLoopSource {} +unsafe impl Sync for SendCFRunLoopSource {} + +struct EventLoop { + runloop: Option, + count: usize, +} + +static EVENT_LOOP: Mutex = Mutex::new(EventLoop { + runloop: None, + count: 0, +}); + +pub(crate) fn add_event_source(source: CFRunLoopSource) -> EventRegistration { + let mut event_loop = EVENT_LOOP.lock().unwrap(); + if let Some(runloop) = event_loop.runloop.as_ref() { + if runloop.contains_source(&source, unsafe { kCFRunLoopCommonModes }) { + panic!("source already registered"); + } + runloop.add_source(&source, unsafe { kCFRunLoopCommonModes }); + event_loop.count += 1; + } else { + let (tx, rx) = mpsc::channel(); + let source = SendCFRunLoopSource(source.clone()); + info!("starting event loop thread"); + thread::spawn(move || { + let runloop = CFRunLoop::get_current(); + let source = source; + runloop.add_source(&source.0, unsafe { kCFRunLoopCommonModes }); + tx.send(SendCFRunLoop(runloop)).unwrap(); + CFRunLoop::run_current(); + info!("event loop thread exited"); + }); + event_loop.runloop = Some(rx.recv().expect("failed to start run loop thread")); + event_loop.count = 1; + } + EventRegistration(SendCFRunLoopSource(source)) +} +pub(crate) struct EventRegistration(SendCFRunLoopSource); + +impl Drop for EventRegistration { + fn drop(&mut self) { + let mut event_loop = EVENT_LOOP.lock().unwrap(); + event_loop.count -= 1; + + let runloop = event_loop + .runloop + .as_ref() + .expect("runloop should exist while events are registered"); + runloop.remove_source(&self.0 .0, unsafe { kCFRunLoopCommonModes }); + + if event_loop.count == 0 { + runloop.stop(); + } + } +} diff --git a/src/platform/macos_iokit/iokit.rs b/src/platform/macos_iokit/iokit.rs new file mode 100644 index 0000000..720f69f --- /dev/null +++ b/src/platform/macos_iokit/iokit.rs @@ -0,0 +1,134 @@ +//! Utilities for using IOKit APIs. +//! +//! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) +//! licensed under MIT OR Apache-2.0. + +use core_foundation_sys::uuid::CFUUIDBytes; +use io_kit_sys::{ + ret::{kIOReturnExclusiveAccess, kIOReturnSuccess, IOReturn}, + IOIteratorNext, IOObjectRelease, +}; +use std::io::ErrorKind; + +use crate::Error; + +use super::iokit_c::{self, CFUUIDGetUUIDBytes, IOCFPlugInInterface}; + +pub(crate) struct IoObject(u32); + +impl IoObject { + // Safety: `handle` must be an IOObject handle. Ownership is transferred. + pub unsafe fn new(handle: u32) -> IoObject { + IoObject(handle) + } + pub fn get(&self) -> u32 { + self.0 + } +} + +impl Drop for IoObject { + fn drop(&mut self) { + unsafe { + IOObjectRelease(self.0); + } + } +} + +pub(crate) struct IoService(IoObject); + +impl IoService { + // Safety: `handle` must be an IOService handle. Ownership is transferred. + pub unsafe fn new(handle: u32) -> IoService { + IoService(IoObject(handle)) + } + pub fn get(&self) -> u32 { + self.0 .0 + } +} + +pub(crate) struct IoServiceIterator(IoObject); + +impl IoServiceIterator { + // Safety: `handle` must be an IoIterator of IoService. Ownership is transferred. + pub unsafe fn new(handle: u32) -> IoServiceIterator { + IoServiceIterator(IoObject::new(handle)) + } +} + +impl Iterator for IoServiceIterator { + type Item = IoService; + + fn next(&mut self) -> Option { + unsafe { + let handle = IOIteratorNext(self.0.get()); + if handle != 0 { + Some(IoService::new(handle)) + } else { + None + } + } + } +} + +/// Helper for calling IOKit function pointers. +macro_rules! call_iokit_function { + ($ptr:expr, $function:ident($($args:expr),*)) => {{ + use std::ffi::c_void; + let func = (**$ptr).$function.expect("function pointer from IOKit was null"); + func($ptr as *mut c_void, $($args),*) + }}; +} +pub(crate) use call_iokit_function; + +/// Wrapper around a **IOCFPluginInterface that automatically releases it. +#[derive(Debug)] +pub(crate) struct PluginInterface { + interface: *mut *mut IOCFPlugInInterface, +} + +impl PluginInterface { + pub(crate) fn new(interface: *mut *mut IOCFPlugInInterface) -> Self { + Self { interface } + } + + /// Fetches the inner pointer for passing to IOKit functions. + pub(crate) fn get(&self) -> *mut *mut IOCFPlugInInterface { + self.interface + } +} + +impl Drop for PluginInterface { + fn drop(&mut self) { + unsafe { + call_iokit_function!(self.interface, Release()); + } + } +} + +/// Alias that select the "version 500" (IOKit 5.0.0) version of UsbDevice, which means +/// that we support macOS versions back to 10.7.3, which is currently every version that Rust +/// supports. Use this instead of touching the iokit_c structure; this may be bumped to +/// (compatible) newer versions of the struct as Rust's support changes. +pub(crate) type UsbDevice = iokit_c::IOUSBDeviceStruct500; +pub(crate) type UsbInterface = iokit_c::IOUSBInterfaceStruct500; + +pub(crate) fn usb_device_type_id() -> CFUUIDBytes { + unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBDeviceInterfaceID500()) } +} + +pub(crate) fn usb_interface_type_id() -> CFUUIDBytes { + unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBInterfaceInterfaceID500()) } +} + +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( + ErrorKind::Other, + "Could not be opened for exclusive access", + )), + _ => Err(Error::from_raw_os_error(r)), + } +} diff --git a/src/platform/macos_iokit/iokit_c.rs b/src/platform/macos_iokit/iokit_c.rs new file mode 100644 index 0000000..96747b0 --- /dev/null +++ b/src/platform/macos_iokit/iokit_c.rs @@ -0,0 +1,1010 @@ +//! FFI types we're including here, as they're missing from io-kit-sys. +//! You may not want to stare too closely at this; it's tweaked bindgen output. +//! +//! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) +//! licensed under MIT OR Apache-2.0. +#![allow( + non_camel_case_types, + non_snake_case, + dead_code, + non_upper_case_globals +)] + +use std::ffi::{c_int, c_void}; + +use core_foundation_sys::{ + base::{kCFAllocatorSystemDefault, mach_port_t, SInt32}, + dictionary::CFDictionaryRef, + mach_port::CFAllocatorRef, + runloop::CFRunLoopSourceRef, + uuid::{CFUUIDBytes, CFUUIDRef}, +}; +use io_kit_sys::{ + ret::IOReturn, + types::{io_iterator_t, io_service_t, IOByteCount}, + IOAsyncCallback1, +}; + +// +// Constants. +// +const SYS_IOKIT: c_int = ((0x38) & 0x3f) << 26; +const SUB_IOKIT_USB: c_int = ((1) & 0xfff) << 14; + +pub(crate) const kIOUSBUnknownPipeErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x61; // 0xe0004061 Pipe ref not recognized +pub(crate) const kIOUSBTooManyPipesErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x60; // 0xe0004060 Too many pipes +pub(crate) const kIOUSBNoAsyncPortErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x5f; // 0xe000405f no async port +pub(crate) const kIOUSBNotEnoughPipesErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x5e; // 0xe000405e not enough pipes in interface +pub(crate) const kIOUSBNotEnoughPowerErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x5d; // 0xe000405d not enough power for selected configuration +pub(crate) const kIOUSBEndpointNotFound: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x57; // 0xe0004057 Endpoint Not found +pub(crate) const kIOUSBConfigNotFound: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x56; // 0xe0004056 Configuration Not found +pub(crate) const kIOUSBPortWasSuspended: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x52; // 0xe0004052 The transaction was returned because the port was suspended +pub(crate) const kIOUSBPipeStalled: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4f; // 0xe000404f Pipe has stalled, error needs to be cleared +pub(crate) const kIOUSBInterfaceNotFound: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4e; // 0xe000404e Interface ref not recognized +pub(crate) const kIOUSBLowLatencyBufferNotPreviouslyAllocated: c_int = + SYS_IOKIT | SUB_IOKIT_USB | 0x4d; // 0xe000404d Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the data buffer) first +pub(crate) const kIOUSBLowLatencyFrameListNotPreviouslyAllocated: c_int = + SYS_IOKIT | SUB_IOKIT_USB | 0x4c; // 0xe000404c Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the frame list) first +pub(crate) const kIOUSBHighSpeedSplitError: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4b; // 0xe000404b Error to hub on high speed bus trying to do split transaction +pub(crate) const kIOUSBSyncRequestOnWLThread: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4a; // 0xe000404a A synchronous USB request was made on the workloop thread (from a callback?). Only async requests are permitted in that case +pub(crate) const kIOUSBDeviceNotHighSpeed: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x49; // 0xe0004049 Name is deprecated, see below +pub(crate) const kIOUSBDeviceTransferredToCompanion: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x49; // 0xe0004049 The device has been tranferred to another controller for enumeration +pub(crate) const kIOUSBClearPipeStallNotRecursive: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x48; // 0xe0004048 IOUSBPipe::ClearPipeStall should not be called recursively +pub(crate) const kIOUSBDevicePortWasNotSuspended: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x47; // 0xe0004047 Port was not suspended +pub(crate) const kIOUSBEndpointCountExceeded: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x46; // 0xe0004046 The endpoint was not created because the controller cannot support more endpoints +pub(crate) const kIOUSBDeviceCountExceeded: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x45; // 0xe0004045 The device cannot be enumerated because the controller cannot support more devices +pub(crate) const kIOUSBStreamsNotSupported: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x44; // 0xe0004044 The request cannot be completed because the XHCI controller does not support streams +pub(crate) const kIOUSBInvalidSSEndpoint: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x43; // 0xe0004043 An endpoint found in a SuperSpeed device is invalid (usually because there is no Endpoint Companion Descriptor) +pub(crate) const kIOUSBTooManyTransactionsPending: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x42; // 0xe0004042 The transaction cannot be submitted because it would exceed the allowed number of pending transactions +pub(crate) const kIOUSBTransactionReturned: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x50; +pub(crate) const kIOUSBTransactionTimeout: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x51; + +pub(crate) const kIOUSBFindInterfaceDontCare: UInt16 = 0xFFFF; + +// + +// +// Type aliases. +// +pub(crate) type REFIID = CFUUIDBytes; +pub(crate) type LPVOID = *mut c_void; +pub(crate) type HRESULT = SInt32; +pub(crate) type UInt8 = ::std::os::raw::c_uchar; +pub(crate) type UInt16 = ::std::os::raw::c_ushort; +pub(crate) type UInt32 = ::std::os::raw::c_uint; +pub(crate) type UInt64 = ::std::os::raw::c_ulonglong; +pub(crate) type ULONG = ::std::os::raw::c_ulong; +pub(crate) type kern_return_t = ::std::os::raw::c_int; +pub(crate) type USBDeviceAddress = UInt16; +pub(crate) type AbsoluteTime = UnsignedWide; +pub(crate) type Boolean = std::os::raw::c_uchar; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NumVersion { + pub nonRelRev: UInt8, + pub stage: UInt8, + pub minorAndBugRev: UInt8, + pub majorRev: UInt8, +} + +#[repr(C, packed(2))] +#[derive(Debug, Copy, Clone)] +pub struct UnsignedWide { + pub lo: UInt32, + pub hi: UInt32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBDevRequest { + pub bmRequestType: UInt8, + pub bRequest: UInt8, + pub wValue: UInt16, + pub wIndex: UInt16, + pub wLength: UInt16, + pub pData: *mut ::std::os::raw::c_void, + pub wLenDone: UInt32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBFindInterfaceRequest { + pub bInterfaceClass: UInt16, + pub bInterfaceSubClass: UInt16, + pub bInterfaceProtocol: UInt16, + pub bAlternateSetting: UInt16, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBDevRequestTO { + pub bmRequestType: UInt8, + pub bRequest: UInt8, + pub wValue: UInt16, + pub wIndex: UInt16, + pub wLength: UInt16, + pub pData: *mut ::std::os::raw::c_void, + pub wLenDone: UInt32, + pub noDataTimeout: UInt32, + pub completionTimeout: UInt32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBIsocFrame { + pub frStatus: IOReturn, + pub frReqCount: UInt16, + pub frActCount: UInt16, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBLowLatencyIsocFrame { + pub frStatus: IOReturn, + pub frReqCount: UInt16, + pub frActCount: UInt16, + pub frTimeStamp: AbsoluteTime, +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBDescriptorHeader { + pub bLength: u8, + pub bDescriptorType: u8, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOCFPlugInInterfaceStruct { + pub _reserved: *mut ::std::os::raw::c_void, + pub QueryInterface: ::std::option::Option< + unsafe extern "C" fn( + thisPointer: *mut ::std::os::raw::c_void, + iid: REFIID, + ppv: *mut LPVOID, + ) -> HRESULT, + >, + pub AddRef: ::std::option::Option< + unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, + >, + pub Release: ::std::option::Option< + unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, + >, + pub version: UInt16, + pub revision: UInt16, + pub Probe: ::std::option::Option< + unsafe extern "C" fn( + thisPointer: *mut ::std::os::raw::c_void, + propertyTable: CFDictionaryRef, + service: io_service_t, + order: *mut SInt32, + ) -> IOReturn, + >, + pub Start: ::std::option::Option< + unsafe extern "C" fn( + thisPointer: *mut ::std::os::raw::c_void, + propertyTable: CFDictionaryRef, + service: io_service_t, + ) -> IOReturn, + >, + pub Stop: ::std::option::Option< + unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> IOReturn, + >, +} +pub type IOCFPlugInInterface = IOCFPlugInInterfaceStruct; + +extern "C" { + pub fn CFUUIDGetUUIDBytes(uuid: CFUUIDRef) -> CFUUIDBytes; + + pub fn IOCreatePlugInInterfaceForService( + service: io_service_t, + pluginType: CFUUIDRef, + interfaceType: CFUUIDRef, + theInterface: *mut *mut *mut IOCFPlugInInterface, + theScore: *mut SInt32, + ) -> kern_return_t; + + pub fn CFUUIDGetConstantUUIDWithBytes( + alloc: CFAllocatorRef, + byte0: UInt8, + byte1: UInt8, + byte2: UInt8, + byte3: UInt8, + byte4: UInt8, + byte5: UInt8, + byte6: UInt8, + byte7: UInt8, + byte8: UInt8, + byte9: UInt8, + byte10: UInt8, + byte11: UInt8, + byte12: UInt8, + byte13: UInt8, + byte14: UInt8, + byte15: UInt8, + ) -> CFUUIDRef; + +} + +pub fn kIOUsbDeviceUserClientTypeID() -> CFUUIDRef { + unsafe { + CFUUIDGetConstantUUIDWithBytes( + std::ptr::null(), + 0x9d, + 0xc7, + 0xb7, + 0x80, + 0x9e, + 0xc0, + 0x11, + 0xD4, + 0xa5, + 0x4f, + 0x00, + 0x0a, + 0x27, + 0x05, + 0x28, + 0x61, + ) + } +} + +pub fn kIOUsbInterfaceUserClientTypeID() -> CFUUIDRef { + unsafe { + CFUUIDGetConstantUUIDWithBytes( + std::ptr::null(), + 0x2d, + 0x97, + 0x86, + 0xc6, + 0x9e, + 0xf3, + 0x11, + 0xD4, + 0xad, + 0x51, + 0x00, + 0x0a, + 0x27, + 0x05, + 0x28, + 0x61, + ) + } +} + +pub fn kIOCFPlugInInterfaceID() -> CFUUIDRef { + unsafe { + CFUUIDGetConstantUUIDWithBytes( + std::ptr::null(), + 0xC2, + 0x44, + 0xE8, + 0x58, + 0x10, + 0x9C, + 0x11, + 0xD4, + 0x91, + 0xD4, + 0x00, + 0x50, + 0xE4, + 0xC6, + 0x42, + 0x6F, + ) + } +} + +pub fn kIOUSBDeviceInterfaceID500() -> CFUUIDRef { + unsafe { + CFUUIDGetConstantUUIDWithBytes( + kCFAllocatorSystemDefault, + 0xA3, + 0x3C, + 0xF0, + 0x47, + 0x4B, + 0x5B, + 0x48, + 0xE2, + 0xB5, + 0x7D, + 0x02, + 0x07, + 0xFC, + 0xEA, + 0xE1, + 0x3B, + ) + } +} + +pub fn kIOUSBInterfaceInterfaceID500() -> CFUUIDRef { + unsafe { + CFUUIDGetConstantUUIDWithBytes( + kCFAllocatorSystemDefault, + 0x6C, + 0x0D, + 0x38, + 0xC3, + 0xB0, + 0x93, + 0x4E, + 0xA7, + 0x80, + 0x9B, + 0x09, + 0xFB, + 0x5D, + 0xDD, + 0xAC, + 0x16, + ) + } +} + +#[repr(C, packed)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBConfigurationDescriptor { + pub bLength: u8, + pub bDescriptorType: u8, + pub wTotalLength: u16, + pub bNumInterfaces: u8, + pub bConfigurationValue: u8, + pub iConfiguration: u8, + pub bmAttributes: u8, + pub MaxPower: u8, +} +pub type IOUSBConfigurationDescriptorPtr = *mut IOUSBConfigurationDescriptor; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBDeviceStruct500 { + pub _reserved: *mut ::std::os::raw::c_void, + pub QueryInterface: ::std::option::Option< + unsafe extern "C" fn( + thisPointer: *mut ::std::os::raw::c_void, + iid: REFIID, + ppv: *mut LPVOID, + ) -> HRESULT, + >, + pub AddRef: ::std::option::Option< + unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, + >, + pub Release: ::std::option::Option< + unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, + >, + pub CreateDeviceAsyncEventSource: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + source: *mut CFRunLoopSourceRef, + ) -> IOReturn, + >, + pub GetDeviceAsyncEventSource: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> CFRunLoopSourceRef, + >, + pub CreateDeviceAsyncPort: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + port: *mut mach_port_t, + ) -> IOReturn, + >, + pub GetDeviceAsyncPort: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> mach_port_t, + >, + pub USBDeviceOpen: + ::std::option::Option IOReturn>, + pub USBDeviceClose: + ::std::option::Option IOReturn>, + pub GetDeviceClass: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, devClass: *mut UInt8) -> IOReturn, + >, + pub GetDeviceSubClass: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devSubClass: *mut UInt8, + ) -> IOReturn, + >, + pub GetDeviceProtocol: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devProtocol: *mut UInt8, + ) -> IOReturn, + >, + pub GetDeviceVendor: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devVendor: *mut UInt16, + ) -> IOReturn, + >, + pub GetDeviceProduct: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devProduct: *mut UInt16, + ) -> IOReturn, + >, + pub GetDeviceReleaseNumber: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devRelNum: *mut UInt16, + ) -> IOReturn, + >, + pub GetDeviceAddress: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + addr: *mut USBDeviceAddress, + ) -> IOReturn, + >, + pub GetDeviceBusPowerAvailable: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + powerAvailable: *mut UInt32, + ) -> IOReturn, + >, + pub GetDeviceSpeed: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, devSpeed: *mut UInt8) -> IOReturn, + >, + pub GetNumberOfConfigurations: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, numConfig: *mut UInt8) -> IOReturn, + >, + pub GetLocationID: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + locationID: *mut UInt32, + ) -> IOReturn, + >, + pub GetConfigurationDescriptorPtr: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + configIndex: UInt8, + desc: *mut IOUSBConfigurationDescriptorPtr, + ) -> IOReturn, + >, + pub GetConfiguration: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, configNum: *mut UInt8) -> IOReturn, + >, + pub SetConfiguration: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, configNum: UInt8) -> IOReturn, + >, + pub GetBusFrameNumber: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + frame: *mut UInt64, + atTime: *mut AbsoluteTime, + ) -> IOReturn, + >, + pub ResetDevice: + ::std::option::Option IOReturn>, + pub DeviceRequest: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + req: *mut IOUSBDevRequest, + ) -> IOReturn, + >, + pub DeviceRequestAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + req: *mut IOUSBDevRequest, + callback: IOAsyncCallback1, + refCon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub CreateInterfaceIterator: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + req: *mut IOUSBFindInterfaceRequest, + iter: *mut io_iterator_t, + ) -> IOReturn, + >, + pub USBDeviceOpenSeize: + ::std::option::Option IOReturn>, + pub DeviceRequestTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + req: *mut IOUSBDevRequestTO, + ) -> IOReturn, + >, + pub DeviceRequestAsyncTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + req: *mut IOUSBDevRequestTO, + callback: IOAsyncCallback1, + refCon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub USBDeviceSuspend: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, suspend: Boolean) -> IOReturn, + >, + pub USBDeviceAbortPipeZero: + ::std::option::Option IOReturn>, + pub USBGetManufacturerStringIndex: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, msi: *mut UInt8) -> IOReturn, + >, + pub USBGetProductStringIndex: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, psi: *mut UInt8) -> IOReturn, + >, + pub USBGetSerialNumberStringIndex: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, snsi: *mut UInt8) -> IOReturn, + >, + pub USBDeviceReEnumerate: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, options: UInt32) -> IOReturn, + >, + pub GetBusMicroFrameNumber: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + microFrame: *mut UInt64, + atTime: *mut AbsoluteTime, + ) -> IOReturn, + >, + pub GetIOUSBLibVersion: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + ioUSBLibVersion: *mut NumVersion, + usbFamilyVersion: *mut NumVersion, + ) -> IOReturn, + >, + pub GetBusFrameNumberWithTime: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + frame: *mut UInt64, + atTime: *mut AbsoluteTime, + ) -> IOReturn, + >, + pub GetUSBDeviceInformation: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, info: *mut UInt32) -> IOReturn, + >, + pub RequestExtraPower: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + type_: UInt32, + requestedPower: UInt32, + powerAvailable: *mut UInt32, + ) -> IOReturn, + >, + pub ReturnExtraPower: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + type_: UInt32, + powerReturned: UInt32, + ) -> IOReturn, + >, + pub GetExtraPowerAllocated: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + type_: UInt32, + powerAllocated: *mut UInt32, + ) -> IOReturn, + >, + pub GetBandwidthAvailableForDevice: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + bandwidth: *mut UInt32, + ) -> IOReturn, + >, +} +pub type IOUSBDeviceInterface500 = IOUSBDeviceStruct500; + +// Tweak: these are just function pointers to thread-safe functions, +// so add send and sync to the C-type. (Calling these from multiple threads +// may cause odd behavior on the USB bus, though, so we'll still want to wrap the +// device in Mutex somewhere up from here.) +unsafe impl Send for IOUSBDeviceInterface500 {} +unsafe impl Sync for IOUSBDeviceInterface500 {} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct IOUSBInterfaceStruct500 { + pub _reserved: *mut ::std::os::raw::c_void, + pub QueryInterface: ::std::option::Option< + unsafe extern "C" fn( + thisPointer: *mut ::std::os::raw::c_void, + iid: REFIID, + ppv: *mut LPVOID, + ) -> HRESULT, + >, + pub AddRef: ::std::option::Option< + unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, + >, + pub Release: ::std::option::Option< + unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, + >, + pub CreateInterfaceAsyncEventSource: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + source: *mut CFRunLoopSourceRef, + ) -> IOReturn, + >, + pub GetInterfaceAsyncEventSource: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> CFRunLoopSourceRef, + >, + pub CreateInterfaceAsyncPort: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + port: *mut mach_port_t, + ) -> IOReturn, + >, + pub GetInterfaceAsyncPort: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> mach_port_t, + >, + pub USBInterfaceOpen: + ::std::option::Option IOReturn>, + pub USBInterfaceClose: + ::std::option::Option IOReturn>, + pub GetInterfaceClass: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, intfClass: *mut UInt8) -> IOReturn, + >, + pub GetInterfaceSubClass: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + intfSubClass: *mut UInt8, + ) -> IOReturn, + >, + pub GetInterfaceProtocol: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + intfProtocol: *mut UInt8, + ) -> IOReturn, + >, + pub GetDeviceVendor: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devVendor: *mut UInt16, + ) -> IOReturn, + >, + pub GetDeviceProduct: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devProduct: *mut UInt16, + ) -> IOReturn, + >, + pub GetDeviceReleaseNumber: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + devRelNum: *mut UInt16, + ) -> IOReturn, + >, + pub GetConfigurationValue: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, configVal: *mut UInt8) -> IOReturn, + >, + pub GetInterfaceNumber: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + intfNumber: *mut UInt8, + ) -> IOReturn, + >, + pub GetAlternateSetting: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + intfAltSetting: *mut UInt8, + ) -> IOReturn, + >, + pub GetNumEndpoints: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + intfNumEndpoints: *mut UInt8, + ) -> IOReturn, + >, + pub GetLocationID: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + locationID: *mut UInt32, + ) -> IOReturn, + >, + pub GetDevice: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + device: *mut io_service_t, + ) -> IOReturn, + >, + pub SetAlternateInterface: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + alternateSetting: UInt8, + ) -> IOReturn, + >, + pub GetBusFrameNumber: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + frame: *mut UInt64, + atTime: *mut AbsoluteTime, + ) -> IOReturn, + >, + pub ControlRequest: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + req: *mut IOUSBDevRequest, + ) -> IOReturn, + >, + pub ControlRequestAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + req: *mut IOUSBDevRequest, + callback: IOAsyncCallback1, + refCon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub GetPipeProperties: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + direction: *mut UInt8, + number: *mut UInt8, + transferType: *mut UInt8, + maxPacketSize: *mut UInt16, + interval: *mut UInt8, + ) -> IOReturn, + >, + pub GetPipeStatus: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, + >, + pub AbortPipe: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, + >, + pub ResetPipe: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, + >, + pub ClearPipeStall: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, + >, + pub ReadPipe: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: *mut UInt32, + ) -> IOReturn, + >, + pub WritePipe: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: UInt32, + ) -> IOReturn, + >, + pub ReadPipeAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: UInt32, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub WritePipeAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: UInt32, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub ReadIsochPipeAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + frameStart: UInt64, + numFrames: UInt32, + frameList: *mut IOUSBIsocFrame, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub WriteIsochPipeAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + frameStart: UInt64, + numFrames: UInt32, + frameList: *mut IOUSBIsocFrame, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub ControlRequestTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + req: *mut IOUSBDevRequestTO, + ) -> IOReturn, + >, + pub ControlRequestAsyncTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + req: *mut IOUSBDevRequestTO, + callback: IOAsyncCallback1, + refCon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub ReadPipeTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: *mut UInt32, + noDataTimeout: UInt32, + completionTimeout: UInt32, + ) -> IOReturn, + >, + pub WritePipeTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: UInt32, + noDataTimeout: UInt32, + completionTimeout: UInt32, + ) -> IOReturn, + >, + pub ReadPipeAsyncTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: UInt32, + noDataTimeout: UInt32, + completionTimeout: UInt32, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub WritePipeAsyncTO: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + size: UInt32, + noDataTimeout: UInt32, + completionTimeout: UInt32, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub USBInterfaceGetStringIndex: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, si: *mut UInt8) -> IOReturn, + >, + pub USBInterfaceOpenSeize: + ::std::option::Option IOReturn>, + pub ClearPipeStallBothEnds: ::std::option::Option< + unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, + >, + pub SetPipePolicy: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + maxPacketSize: UInt16, + maxInterval: UInt8, + ) -> IOReturn, + >, + pub GetBandwidthAvailable: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + bandwidth: *mut UInt32, + ) -> IOReturn, + >, + pub GetEndpointProperties: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + alternateSetting: UInt8, + endpointNumber: UInt8, + direction: UInt8, + transferType: *mut UInt8, + maxPacketSize: *mut UInt16, + interval: *mut UInt8, + ) -> IOReturn, + >, + pub LowLatencyReadIsochPipeAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + frameStart: UInt64, + numFrames: UInt32, + updateFrequency: UInt32, + frameList: *mut IOUSBLowLatencyIsocFrame, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub LowLatencyWriteIsochPipeAsync: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + buf: *mut ::std::os::raw::c_void, + frameStart: UInt64, + numFrames: UInt32, + updateFrequency: UInt32, + frameList: *mut IOUSBLowLatencyIsocFrame, + callback: IOAsyncCallback1, + refcon: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub LowLatencyCreateBuffer: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + buffer: *mut *mut ::std::os::raw::c_void, + size: IOByteCount, + bufferType: UInt32, + ) -> IOReturn, + >, + pub LowLatencyDestroyBuffer: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + buffer: *mut ::std::os::raw::c_void, + ) -> IOReturn, + >, + pub GetBusMicroFrameNumber: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + microFrame: *mut UInt64, + atTime: *mut AbsoluteTime, + ) -> IOReturn, + >, + pub GetFrameListTime: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + microsecondsInFrame: *mut UInt32, + ) -> IOReturn, + >, + pub GetIOUSBLibVersion: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + ioUSBLibVersion: *mut NumVersion, + usbFamilyVersion: *mut NumVersion, + ) -> IOReturn, + >, + pub FindNextAssociatedDescriptor: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + currentDescriptor: *const ::std::os::raw::c_void, + descriptorType: UInt8, + ) -> *mut IOUSBDescriptorHeader, + >, + pub FindNextAltInterface: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + current: *const ::std::os::raw::c_void, + request: *mut IOUSBFindInterfaceRequest, + ) -> *mut IOUSBDescriptorHeader, + >, + pub GetBusFrameNumberWithTime: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + frame: *mut UInt64, + atTime: *mut AbsoluteTime, + ) -> IOReturn, + >, + pub GetPipePropertiesV2: ::std::option::Option< + unsafe extern "C" fn( + self_: *mut ::std::os::raw::c_void, + pipeRef: UInt8, + direction: *mut UInt8, + number: *mut UInt8, + transferType: *mut UInt8, + maxPacketSize: *mut UInt16, + interval: *mut UInt8, + maxBurst: *mut UInt8, + mult: *mut UInt8, + bytesPerInterval: *mut UInt16, + ) -> IOReturn, + >, +} + +// Tweak: these are just function pointers to thread-safe functions, +// so add send and sync to the C-type. (Calling these from multiple threads +// may cause odd behavior on the USB bus, though, so we'll still want to wrap the +// device in Mutex somewhere up from here.) +unsafe impl Send for IOUSBInterfaceStruct500 {} +unsafe impl Sync for IOUSBInterfaceStruct500 {} diff --git a/src/platform/macos_iokit/iokit_usb.rs b/src/platform/macos_iokit/iokit_usb.rs new file mode 100644 index 0000000..6cc76cd --- /dev/null +++ b/src/platform/macos_iokit/iokit_usb.rs @@ -0,0 +1,318 @@ +//! Wrappers for IOKit USB device and interface +//! +//! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) +//! licensed under MIT OR Apache-2.0. + +use std::{collections::BTreeMap, io::ErrorKind, time::Duration}; + +use core_foundation::{base::TCFType, runloop::CFRunLoopSource}; +use core_foundation_sys::runloop::CFRunLoopSourceRef; +use io_kit_sys::{ + ret::{kIOReturnNoResources, kIOReturnSuccess}, + types::io_iterator_t, +}; +use log::error; + +use crate::{ + platform::macos_iokit::{ + iokit::usb_interface_type_id, iokit_c::kIOUsbInterfaceUserClientTypeID, + }, + Error, +}; + +use super::{ + iokit::{ + self, call_iokit_function, check_iokit_return, usb_device_type_id, IoService, + IoServiceIterator, PluginInterface, + }, + iokit_c::{ + kIOCFPlugInInterfaceID, kIOUSBFindInterfaceDontCare, kIOUsbDeviceUserClientTypeID, + IOCFPlugInInterface, IOCreatePlugInInterfaceForService, IOUSBFindInterfaceRequest, + }, +}; + +/// Wrapper around an IOKit UsbDevice +pub(crate) struct IoKitDevice { + pub(crate) raw: *mut *mut iokit::UsbDevice, +} + +impl IoKitDevice { + /// Get the raw USB device associated with the service. + pub(crate) fn new(service: IoService) -> Result { + unsafe { + // According to the libusb maintainers, this will sometimes spuriously + // return `kIOReturnNoResources` for reasons Apple won't explain, usually + // when a device is freshly plugged in. We'll allow this a few retries, + // accordingly. + // + // [This behavior actually makes sense to me -- when the device is first plugged + // in, it exists to IOKit, but hasn't been enumerated, yet. Accordingly, the device + // interface doesn't actually yet exist for us to grab, and/or doesn't yet have the + // right permissions for us to grab it. MacOS needs to see if a kernel driver binds + // to it; as its security model won't allow the userland to grab a device that the + // kernel owns.] + // + // If the kIOReturnNoResources persists, it's typically an indication that + // macOS is preventing us from touching the relevant device due to its security + // model. This happens when the device has a kernel-mode driver bound to the + // whole device -- the kernel owns it, and it's unwilling to give it to us. + for _ in 0..5 { + let mut _score: i32 = 0; + let mut raw_device_plugin: *mut *mut IOCFPlugInInterface = std::ptr::null_mut(); + + let rc = IOCreatePlugInInterfaceForService( + service.get(), + kIOUsbDeviceUserClientTypeID(), + kIOCFPlugInInterfaceID(), + &mut raw_device_plugin, + &mut _score, + ); + + if rc == kIOReturnNoResources { + std::thread::sleep(Duration::from_millis(1)); + continue; + } + + if rc != kIOReturnSuccess { + return Err(Error::from_raw_os_error(rc)); + } + + if raw_device_plugin.is_null() { + error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); + return Err(Error::new( + ErrorKind::Other, + "Could not create PlugInInterface", + )); + } + + let device_plugin = PluginInterface::new(raw_device_plugin); + + let mut raw_device: *mut *mut iokit::UsbDevice = std::ptr::null_mut(); + + call_iokit_function!( + device_plugin.get(), + QueryInterface( + usb_device_type_id(), + &mut raw_device as *mut *mut *mut _ as *mut *mut c_void + ) + ); + + // macOS claims that call will never fail, and will always produce a valid pointer. + // We don't trust it, so we're going to panic if it's lied to us. + if raw_device.is_null() { + panic!( + "query_interface returned a null pointer, which Apple says is impossible" + ); + } + + return Ok(IoKitDevice { raw: raw_device }); + } + + return Err(Error::new( + ErrorKind::NotFound, + "Couldn't create device after retries", + )); + } + } + + pub(crate) fn create_async_event_source(&self) -> Result { + unsafe { + let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); + check_iokit_return(call_iokit_function!( + self.raw, + CreateDeviceAsyncEventSource(&mut raw_source) + ))?; + Ok(CFRunLoopSource::wrap_under_create_rule(raw_source)) + } + } + + /// Returns an IOKit iterator that can be used to iterate over all interfaces on this device. + pub(crate) fn create_interface_iterator(&self) -> Result { + unsafe { + let mut iterator: io_iterator_t = 0; + + let mut dont_care = IOUSBFindInterfaceRequest { + bInterfaceClass: kIOUSBFindInterfaceDontCare, + bInterfaceSubClass: kIOUSBFindInterfaceDontCare, + bInterfaceProtocol: kIOUSBFindInterfaceDontCare, + bAlternateSetting: kIOUSBFindInterfaceDontCare, + }; + + check_iokit_return(call_iokit_function!( + self.raw, + CreateInterfaceIterator(&mut dont_care, &mut iterator) + ))?; + + Ok(IoServiceIterator::new(iterator)) + } + } +} + +impl Drop for IoKitDevice { + fn drop(&mut self) { + unsafe { + call_iokit_function!(self.raw, Release()); + } + } +} + +unsafe impl Send for IoKitDevice {} +unsafe impl Sync for IoKitDevice {} + +#[derive(Debug)] +#[allow(dead_code)] +pub(crate) struct EndpointInfo { + pub(crate) pipe_ref: u8, + pub(crate) direction: u8, + pub(crate) number: u8, + pub(crate) transfer_type: u8, + pub(crate) max_packet_size: u16, + pub(crate) interval: u8, + pub(crate) max_burst: u8, + pub(crate) mult: u8, + pub(crate) bytes_per_interval: u16, +} + +impl EndpointInfo { + pub(crate) fn address(&self) -> u8 { + if self.direction == 0 { + self.number + } else { + self.number | 0x80 + } + } +} + +/// Wrapper around an IOKit UsbInterface +pub(crate) struct IoKitInterface { + pub(crate) raw: *mut *mut iokit::UsbInterface, +} + +impl IoKitInterface { + pub(crate) fn new(service: IoService) -> Result { + unsafe { + let mut _score: i32 = 0; + let mut raw_interface_plugin: *mut *mut IOCFPlugInInterface = std::ptr::null_mut(); + + let rc = IOCreatePlugInInterfaceForService( + service.get(), + kIOUsbInterfaceUserClientTypeID(), + kIOCFPlugInInterfaceID(), + &mut raw_interface_plugin, + &mut _score, + ); + + if rc != kIOReturnSuccess { + return Err(Error::from_raw_os_error(rc)); + } + + if raw_interface_plugin.is_null() { + error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); + return Err(Error::new( + ErrorKind::Other, + "Could not create PlugInInterface", + )); + } + + let interface_plugin = PluginInterface::new(raw_interface_plugin); + + let mut raw: *mut *mut iokit::UsbInterface = std::ptr::null_mut(); + + call_iokit_function!( + interface_plugin.get(), + QueryInterface( + usb_interface_type_id(), + &mut raw as *mut *mut *mut _ as *mut *mut c_void + ) + ); + + // macOS claims that call will never fail, and will always produce a valid pointer. + // We don't trust it, so we're going to panic if it's lied to us. + if raw.is_null() { + panic!("query_interface returned a null pointer, which Apple says is impossible"); + } + + return Ok(IoKitInterface { raw }); + } + } + + pub(crate) fn open(&mut self) -> Result<(), Error> { + unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceOpen())) } + } + + pub(crate) fn close(&mut self) -> Result<(), Error> { + unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceClose())) } + } + + pub(crate) fn create_async_event_source(&self) -> Result { + unsafe { + let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); + check_iokit_return(call_iokit_function!( + self.raw, + CreateInterfaceAsyncEventSource(&mut raw_source) + ))?; + Ok(CFRunLoopSource::wrap_under_create_rule(raw_source)) + } + } + + pub(crate) fn endpoints(&mut self) -> Result, Error> { + unsafe { + let mut endpoints = BTreeMap::new(); + let mut count = 0; + check_iokit_return(call_iokit_function!(self.raw, GetNumEndpoints(&mut count)))?; + + // Pipe references are 1-indexed + for pipe_ref in 1..=count { + let mut direction: u8 = 0; + let mut number: u8 = 0; + let mut transfer_type: u8 = 0; + let mut max_packet_size: u16 = 0; + let mut interval: u8 = 0; + let mut max_burst: u8 = 0; + let mut mult: u8 = 0; + let mut bytes_per_interval: u16 = 0; + + check_iokit_return(call_iokit_function!( + self.raw, + GetPipePropertiesV2( + pipe_ref, + &mut direction, + &mut number, + &mut transfer_type, + &mut max_packet_size, + &mut interval, + &mut max_burst, + &mut mult, + &mut bytes_per_interval + ) + ))?; + + let endpoint = EndpointInfo { + pipe_ref, + direction, + number, + transfer_type, + max_packet_size, + interval, + max_burst, + mult, + bytes_per_interval, + }; + + endpoints.insert(endpoint.address(), endpoint); + } + Ok(endpoints) + } + } +} + +impl Drop for IoKitInterface { + fn drop(&mut self) { + unsafe { + call_iokit_function!(self.raw, Release()); + } + } +} + +unsafe impl Send for IoKitInterface {} +unsafe impl Sync for IoKitInterface {} diff --git a/src/platform/macos_iokit/mod.rs b/src/platform/macos_iokit/mod.rs new file mode 100644 index 0000000..4242fd4 --- /dev/null +++ b/src/platform/macos_iokit/mod.rs @@ -0,0 +1,14 @@ +mod transfer; +pub(crate) use transfer::TransferData; + +mod enumeration; +mod events; +pub use enumeration::list_devices; + +mod device; +pub(crate) use device::MacDevice as Device; +pub(crate) use device::MacInterface as Interface; + +mod iokit; +mod iokit_c; +mod iokit_usb; diff --git a/src/platform/macos_iokit/transfer.rs b/src/platform/macos_iokit/transfer.rs new file mode 100644 index 0000000..db11e1e --- /dev/null +++ b/src/platform/macos_iokit/transfer.rs @@ -0,0 +1,326 @@ +use std::{ + ffi::c_void, + mem::{self, ManuallyDrop}, + ptr::null_mut, + sync::Arc, +}; + +use io_kit_sys::ret::{ + kIOReturnAborted, kIOReturnNoDevice, kIOReturnSuccess, kIOReturnUnderrun, IOReturn, +}; +use log::{error, info}; + +use crate::{ + platform::macos_iokit::iokit_c::IOUSBDevRequest, + transfer::{ + notify_completion, Completion, ControlIn, ControlOut, PlatformSubmit, PlatformTransfer, + RequestBuffer, ResponseBuffer, TransferError, + }, +}; + +use super::{iokit::call_iokit_function, iokit_usb::EndpointInfo}; + +extern "C" fn transfer_callback(refcon: *mut c_void, result: IOReturn, len: *mut c_void) { + info!( + "Completion callback for transfer {refcon:?}, status={result:x}, len={len}", + len = len as usize + ); + + unsafe { + let callback_data = { + let inner = &mut *(refcon as *mut TransferDataInner); + inner.actual_len = len as usize; + inner.status = result; + inner.callback_data + }; + notify_completion::(callback_data) + } +} + +pub struct TransferData { + endpoint_addr: u8, + pipe_ref: u8, + buf: *mut u8, + capacity: usize, + inner: *mut TransferDataInner, + device: Arc, + interface: Option>, +} + +impl Drop for TransferData { + fn drop(&mut self) { + if !self.buf.is_null() { + unsafe { drop(Vec::from_raw_parts(self.buf, 0, self.capacity)) } + } + unsafe { drop(Box::from_raw(self.inner)) } + } +} + +/// Bring the data accessed on the transfer callback out-of-line +/// so that we can have a reference to it while the callback may +/// write to other fields concurrently. This could be included +/// in TransferData with the proposed [UnsafePinned](https://github.com/rust-lang/rfcs/pull/3467) +pub struct TransferDataInner { + actual_len: usize, + callback_data: *mut c_void, + status: IOReturn, +} + +impl TransferData { + pub(super) fn new( + device: Arc, + interface: Arc, + endpoint: &EndpointInfo, + ) -> TransferData { + TransferData { + endpoint_addr: endpoint.address(), + pipe_ref: endpoint.pipe_ref, + buf: null_mut(), + capacity: 0, + inner: Box::into_raw(Box::new(TransferDataInner { + actual_len: 0, + callback_data: null_mut(), + status: kIOReturnSuccess, + })), + device, + interface: Some(interface), + } + } + + pub(super) fn new_control(device: Arc) -> TransferData { + TransferData { + endpoint_addr: 0, + pipe_ref: 0, + buf: null_mut(), + capacity: 0, + inner: Box::into_raw(Box::new(TransferDataInner { + actual_len: 0, + callback_data: null_mut(), + status: kIOReturnSuccess, + })), + device, + interface: None, + } + } + + /// SAFETY: Requires that the transfer is not active + unsafe fn fill(&mut self, buf: Vec, callback_data: *mut c_void) { + let mut buf = ManuallyDrop::new(buf); + self.buf = buf.as_mut_ptr(); + self.capacity = buf.capacity(); + + let inner = &mut *self.inner; + inner.actual_len = 0; + inner.status = kIOReturnSuccess; + inner.callback_data = callback_data; + } + + /// SAFETY: requires that the transfer has completed and `length` bytes are initialized + unsafe fn take_buf(&mut self, length: usize) -> Vec { + assert!(!self.buf.is_null()); + let ptr = mem::replace(&mut self.buf, null_mut()); + let capacity = mem::replace(&mut self.capacity, 0); + assert!(length <= capacity); + Vec::from_raw_parts(ptr, length, capacity) + } + + /// SAFETY: requires that the transfer is not active, but is fully prepared (as it is when submitting the transfer fails) + unsafe fn check_submit_result(&mut self, res: IOReturn) { + if res != kIOReturnSuccess { + error!( + "Failed to submit transfer on endpoint {ep}: {res:x}", + ep = self.endpoint_addr + ); + let callback_data = { + let inner = &mut *self.inner; + inner.status = res; + inner.callback_data + }; + + // Complete the transfer in the place of the callback + notify_completion::(callback_data) + } + } + + /// SAFETY: requires that the transfer is in a completed state + unsafe fn take_status(&mut self) -> (Result<(), TransferError>, usize) { + let inner = unsafe { &*self.inner }; + + #[allow(non_upper_case_globals)] + #[deny(unreachable_patterns)] + let status = match inner.status { + kIOReturnSuccess | kIOReturnUnderrun => Ok(()), + kIOReturnNoDevice => Err(TransferError::Disconnected), + kIOReturnAborted => Err(TransferError::Cancelled), + _ => Err(TransferError::Unknown), + }; + + (status, inner.actual_len) + } +} + +unsafe impl Send for TransferData {} + +impl PlatformTransfer for TransferData { + fn cancel(&self) { + if let Some(intf) = self.interface.as_ref() { + let r = unsafe { call_iokit_function!(intf.interface.raw, AbortPipe(self.pipe_ref)) }; + info!( + "Cancelled all transfers on endpoint {ep:02x}. status={r:x}", + ep = self.endpoint_addr + ); + } else { + assert!(self.pipe_ref == 0); + let r = + unsafe { call_iokit_function!(self.device.device.raw, USBDeviceAbortPipeZero()) }; + info!("Cancelled all transfers on control pipe. status={r:x}"); + } + } +} + +impl PlatformSubmit> for TransferData { + unsafe fn submit(&mut self, data: Vec, callback_data: *mut std::ffi::c_void) { + assert!(self.endpoint_addr & 0x80 == 0); + let len = data.len(); + self.fill(data, callback_data); + + // SAFETY: we just properly filled the buffer and it is not already pending + let res = call_iokit_function!( + self.interface.as_ref().unwrap().interface.raw, + WritePipeAsync( + self.pipe_ref, + self.buf as *mut c_void, + u32::try_from(len).expect("request too large"), + transfer_callback, + self.inner as *mut c_void + ) + ); + info!( + "Submitted OUT transfer {inner:?} on endpoint {ep:02x}", + inner = self.inner, + ep = self.endpoint_addr + ); + self.check_submit_result(res); + } + + unsafe fn take_completed(&mut self) -> crate::transfer::Completion { + let (status, actual_len) = self.take_status(); + + // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. + let data = ResponseBuffer::from_vec(unsafe { self.take_buf(0) }, actual_len); + Completion { data, status } + } +} + +impl PlatformSubmit for TransferData { + unsafe fn submit(&mut self, data: RequestBuffer, callback_data: *mut std::ffi::c_void) { + assert!(self.endpoint_addr & 0x80 == 0x80); + + let (data, len) = data.into_vec(); + self.fill(data, callback_data); + + // SAFETY: we just properly filled the buffer and it is not already pending + let res = call_iokit_function!( + self.interface.as_ref().unwrap().interface.raw, + ReadPipeAsync( + self.pipe_ref, + self.buf as *mut c_void, + u32::try_from(len).expect("request too large"), + transfer_callback, + self.inner as *mut c_void + ) + ); + info!( + "Submitted IN transfer {inner:?} on endpoint {ep:02x}", + inner = self.inner, + ep = self.endpoint_addr + ); + + self.check_submit_result(res); + } + + unsafe fn take_completed(&mut self) -> crate::transfer::Completion> { + let (status, actual_len) = self.take_status(); + + // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. + let data = unsafe { self.take_buf(actual_len) }; + Completion { data, status } + } +} + +impl PlatformSubmit for TransferData { + unsafe fn submit(&mut self, data: ControlIn, callback_data: *mut std::ffi::c_void) { + assert!(self.pipe_ref == 0); + + let buf = Vec::with_capacity(data.length as usize); + self.fill(buf, callback_data); + + let mut req = IOUSBDevRequest { + bmRequestType: data.request_type(), + bRequest: data.request, + wValue: data.value, + wIndex: data.index, + wLength: data.length, + pData: self.buf as *mut c_void, + wLenDone: 0, + }; + + // SAFETY: we just properly filled the buffer and it is not already pending + let res = call_iokit_function!( + self.device.device.raw, + DeviceRequestAsync(&mut req, transfer_callback, self.inner as *mut c_void) + ); + info!( + "Submitted Control IN transfer {inner:?}", + inner = self.inner + ); + self.check_submit_result(res); + } + + unsafe fn take_completed(&mut self) -> crate::transfer::Completion> { + let (status, actual_len) = self.take_status(); + + // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. + let data = unsafe { self.take_buf(actual_len) }; + Completion { data, status } + } +} + +impl PlatformSubmit> for TransferData { + unsafe fn submit(&mut self, data: ControlOut<'_>, callback_data: *mut std::ffi::c_void) { + assert!(self.pipe_ref == 0); + + let buf = data.data.to_vec(); + let len = buf.len(); + self.fill(buf, callback_data); + + let mut req = IOUSBDevRequest { + bmRequestType: data.request_type(), + bRequest: data.request, + wValue: data.value, + wIndex: data.index, + wLength: u16::try_from(len).expect("request too long"), + pData: self.buf as *mut c_void, + wLenDone: 0, + }; + + // SAFETY: we just properly filled the buffer and it is not already pending + let res = call_iokit_function!( + self.device.device.raw, + DeviceRequestAsync(&mut req, transfer_callback, self.inner as *mut c_void) + ); + info!( + "Submitted Control OUT transfer {inner:?}", + inner = self.inner + ); + self.check_submit_result(res); + } + + unsafe fn take_completed(&mut self) -> crate::transfer::Completion { + let (status, actual_len) = self.take_status(); + + // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. + let data = ResponseBuffer::from_vec(unsafe { self.take_buf(0) }, actual_len); + Completion { data, status } + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 22b0789..53a2f21 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -9,3 +9,9 @@ mod windows_winusb; #[cfg(target_os = "windows")] pub use windows_winusb::*; + +#[cfg(target_os = "macos")] +mod macos_iokit; + +#[cfg(target_os = "macos")] +pub use macos_iokit::*; diff --git a/src/transfer/mod.rs b/src/transfer/mod.rs index 7c63ad7..73633ca 100644 --- a/src/transfer/mod.rs +++ b/src/transfer/mod.rs @@ -20,7 +20,7 @@ mod buffer; pub use buffer::{RequestBuffer, ResponseBuffer}; mod control; -pub(crate) use control::SETUP_PACKET_SIZE; +#[allow(unused)] pub(crate) use control::SETUP_PACKET_SIZE; pub use control::{ControlIn, ControlOut, ControlType, Direction, Recipient}; mod internal; @@ -125,7 +125,7 @@ impl TryFrom> for ResponseBuffer { } /// [`Future`] used to await the completion of a transfer. -/// +/// /// Use the methods on [`Interface`][super::Interface] to /// submit an individual transfer and obtain a `TransferFuture`. /// @@ -135,7 +135,7 @@ impl TryFrom> for ResponseBuffer { /// in `select!{}`, When racing a `TransferFuture` with a timeout /// you cannot tell whether data may have been partially transferred on timeout. /// Use the [`Queue`] interface if these matter for your application. -/// +/// /// [cancel-safe]: https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety pub struct TransferFuture { transfer: TransferHandle,