| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::{ |
| DeviceDescriptor, EndpointDescriptor, EndpointType, Error, InterfaceDescriptor, Result, |
| }; |
| use std::io::Error as IOError; |
| use std::sync::{Arc, Condvar, Mutex}; |
| |
| use super::iokit_usb; |
| |
| // SAFETY: These are all stubs around constant getters to get around limitations in bindgen. There |
| // are no safety concerns with any of these. |
| #[allow(non_snake_case)] |
| pub fn GetIOUSBDeviceUserClientTypeID() -> iokit_usb::CFUUIDRef { |
| unsafe { iokit_usb::GetIOUSBDeviceUserClientTypeID() } |
| } |
| #[allow(non_snake_case)] |
| pub fn GetIOUSBInterfaceUserClientTypeID() -> iokit_usb::CFUUIDRef { |
| unsafe { iokit_usb::GetIOUSBInterfaceUserClientTypeID() } |
| } |
| #[allow(non_snake_case)] |
| fn GetIOUSBDeviceInterfaceID500() -> iokit_usb::CFUUIDRef { |
| unsafe { iokit_usb::GetIOUSBDeviceInterfaceID500() } |
| } |
| #[allow(non_snake_case)] |
| fn GetIOUSBInterfaceInterfaceID500() -> iokit_usb::CFUUIDRef { |
| unsafe { iokit_usb::GetIOUSBInterfaceInterfaceID500() } |
| } |
| #[allow(non_snake_case)] |
| fn GetIOCFPlugInInterfaceID() -> iokit_usb::CFUUIDRef { |
| unsafe { iokit_usb::GetIOCFPlugInInterfaceID() } |
| } |
| |
| /// Turns a kern_return_t into an error. |
| fn kern_return_check(error: iokit_usb::kern_return_t) -> Result<()> { |
| if error == 0 { |
| Ok(()) |
| } else { |
| Err(Error::IOError(IOError::new( |
| std::io::ErrorKind::Other, |
| format!("kern_return_t({error})"), |
| ))) |
| } |
| } |
| |
| /// Turns an IOReturn into an error. |
| pub fn ioreturn_check(error: iokit_usb::kern_return_t) -> Result<()> { |
| if error == 0 { |
| Ok(()) |
| } else { |
| Err(Error::IOError(IOError::new(std::io::ErrorKind::Other, format!("IOReturn({error})")))) |
| } |
| } |
| |
| /// Wraps a pointer to CFRunLoopSource |
| struct RunLoopSourceRef(iokit_usb::CFRunLoopSourceRef); |
| |
| impl Drop for RunLoopSourceRef { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| iokit_usb::CFRelease(self.0.cast()); |
| } |
| } |
| } |
| |
| impl Clone for RunLoopSourceRef { |
| fn clone(&self) -> RunLoopSourceRef { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| iokit_usb::CFRetain(self.0.cast()); |
| } |
| |
| RunLoopSourceRef(self.0) |
| } |
| } |
| |
| // SAFETY: The whole point of these objects is to be able to signal them from another thread. |
| unsafe impl Send for RunLoopSourceRef {} |
| unsafe impl Sync for RunLoopSourceRef {} |
| |
| /// Wraps a pointer to CFRunLoop |
| pub struct RunLoop { |
| handle: iokit_usb::CFRunLoopRef, |
| source_ref: RunLoopSourceRef, |
| } |
| |
| // SAFETY: The only thing that doesn't have this already here is handle, which is a CoreFoundation |
| // object, and thus should be thread safe. |
| unsafe impl Send for RunLoop {} |
| unsafe impl Sync for RunLoop {} |
| |
| impl RunLoop { |
| /// Constructs a new run loop. This effectively spawns a thread that we can dispatch |
| /// CoreFoundation events on. |
| pub fn new() -> RunLoop { |
| #[derive(Copy, Clone)] |
| struct Safety(iokit_usb::CFRunLoopRef); |
| |
| // SAFETY: Referring to these pointers from another thread is the only reason to have them. |
| unsafe impl Send for Safety {} |
| |
| let pair = Arc::new((Mutex::new(None), Condvar::new())); |
| let thread_side = Arc::clone(&pair); |
| |
| extern "C" fn do_nothing(_ptr: *mut libc::c_void) {} |
| |
| // If the run loop has no source it will die immediately, so we have to add one. Later, |
| // invalidating this source will release the loop. |
| // |
| // SAFETY: This operation should always be safe. We're passing it no pointers so it won't |
| // retain anything that might go out of scope. The CFRunLoopSourceContext struct is copied |
| // immediately, so a temporary pointer is fine. |
| let dummy_source = unsafe { |
| iokit_usb::CFRunLoopSourceCreate( |
| iokit_usb::kCFAllocatorDefault, |
| 0, |
| &mut iokit_usb::CFRunLoopSourceContext { |
| version: 0, |
| info: std::ptr::null_mut(), |
| retain: None, |
| release: None, |
| copyDescription: None, |
| equal: None, |
| hash: None, |
| schedule: None, |
| cancel: None, |
| perform: Some(do_nothing), |
| }, |
| ) |
| }; |
| let dummy_source = RunLoopSourceRef(dummy_source); |
| let source_ref = dummy_source.clone(); |
| |
| std::thread::spawn(move || { |
| // SAFETY: CoreFoundation manages run loop state automatically so this should always be |
| // safe. dummy_source will be refcounted by CoreFoundation so we can drop our own ref |
| // immediately. |
| unsafe { |
| let (handle_out, cvar) = &*thread_side; |
| *handle_out.lock().unwrap() = Some(Safety(iokit_usb::CFRunLoopGetCurrent())); |
| cvar.notify_one(); |
| iokit_usb::CFRunLoopAddSource( |
| iokit_usb::CFRunLoopGetCurrent(), |
| dummy_source.0, |
| iokit_usb::kCFRunLoopDefaultMode, |
| ); |
| std::mem::drop(dummy_source); |
| iokit_usb::CFRunLoopRun(); |
| } |
| }); |
| |
| let (handle_out, cvar) = &*pair; |
| let mut handle_out = handle_out.lock().unwrap(); |
| let handle = loop { |
| let Some(handle) = *handle_out else { |
| handle_out = cvar.wait(handle_out).unwrap(); |
| continue; |
| }; |
| |
| break handle.0; |
| }; |
| |
| RunLoop { handle, source_ref } |
| } |
| } |
| |
| impl Drop for RunLoop { |
| fn drop(&mut self) { |
| // SAFETY: Our source should be valid by construction and is internally refcounted by |
| // CoreFoundation, so it should be in scope. |
| unsafe { |
| iokit_usb::CFRunLoopSourceInvalidate(self.source_ref.0); |
| } |
| } |
| } |
| |
| /// Safe wrapper around a Mach port |
| pub struct MachPort(iokit_usb::mach_port_t); |
| |
| impl MachPort { |
| /// Creates a new IO master port, which is used to derive handles to other IO components. |
| pub fn new_master() -> Result<Self> { |
| let mut master_port = 0; |
| // SAFETY: We pass a constant explicitly designed for use with this call, and a pointer |
| // which points directly to this stack frame and is intended as an output value. |
| let err = unsafe { iokit_usb::IOMasterPort(iokit_usb::MACH_PORT_NULL, &mut master_port) }; |
| |
| kern_return_check(err)?; |
| Self::from_raw_port(master_port) |
| } |
| |
| /// Wrap an unadorned mach_port_t. |
| fn from_raw_port(port: iokit_usb::mach_port_t) -> Result<Self> { |
| if port != 0 { |
| Ok(MachPort(port)) |
| } else { |
| Err(Error::IOError(IOError::new( |
| std::io::ErrorKind::Other, |
| format!("Could not create port"), |
| ))) |
| } |
| } |
| |
| /// Derives a new IO Notification port from this master port. |
| pub fn new_notify(&self) -> NotifyPort { |
| // SAFETY: We know the value we're passing is valid because this object is here to ensure |
| // its lifetime. The new object we are creating does not capture the lifetime of the |
| // argument passed; documentation examples explicitly show the mach_port_t being destroyed |
| // while the NotifyPort is still in use. |
| unsafe { NotifyPort(iokit_usb::IONotificationPortCreate(self.0)) } |
| } |
| } |
| |
| impl Drop for MachPort { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| iokit_usb::mach_port_deallocate(iokit_usb::mach_task_self_, self.0); |
| } |
| } |
| } |
| |
| /// Wraps an IO notification port. |
| pub struct NotifyPort(iokit_usb::IONotificationPortRef); |
| |
| // SAFETY: These are CoreFoundation objects which are supposed to be thread-safe. |
| unsafe impl Send for NotifyPort {} |
| unsafe impl Sync for NotifyPort {} |
| |
| impl NotifyPort { |
| /// Tells the given run loop to dispatch notifications from this port. |
| pub fn add_source_to_run_loop(&self, run_loop: &RunLoop) { |
| // SAFETY: We know the port is valid by construction of this object. The documentation |
| // mentions no failure modes for this function. |
| let source = |
| RunLoopSourceRef(unsafe { iokit_usb::IONotificationPortGetRunLoopSource(self.0) }); |
| |
| // SAFETY: We are passing only constants designed specifically for this function, and the |
| // source we were just given. Run loops are automatically created and managed so it's |
| // generally safe to interact with them at any time. Run loop sources are refcounted by |
| // CoreFoundation so it's safe to drop our ref after using it here. |
| unsafe { |
| iokit_usb::CFRunLoopAddSource( |
| run_loop.handle, |
| source.0, |
| iokit_usb::kCFRunLoopDefaultMode, |
| ); |
| } |
| } |
| |
| /// Call `IOServiceAddMatchingNotification` for this port. |
| pub fn add_matching_notification( |
| &self, |
| match_type: &'static [u8], |
| matching_dict: MatchingDict, |
| callback: Option<extern "C" fn(*mut libc::c_void, iokit_usb::io_iterator_t)>, |
| data_ptr: *mut libc::c_void, |
| ) -> Result<()> { |
| let mut iter = 0; |
| let callback = callback.map(|x| x as unsafe extern "C" fn(_, _)); |
| |
| unsafe { |
| kern_return_check(iokit_usb::IOServiceAddMatchingNotification( |
| self.0, |
| match_type.as_ptr().cast_mut().cast(), |
| matching_dict.into_raw(), |
| callback, |
| data_ptr, |
| &mut iter, |
| ))?; |
| if let Some(callback) = callback { |
| callback(data_ptr, iter); |
| } |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| impl Drop for NotifyPort { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| iokit_usb::IONotificationPortDestroy(self.0); |
| } |
| } |
| } |
| |
| /// Wrapper around a matching dict, which is a structure containing device properties which we can |
| /// use to search for devices. |
| pub struct MatchingDict(std::ptr::NonNull<iokit_usb::__CFDictionary>); |
| |
| impl MatchingDict { |
| /// Create a new matching dict. These are basically balls of metadata about what sorts of |
| /// devices we want to enumerate. Cloning this struct will produce a new reference to the same |
| /// dict. |
| pub fn new_usb() -> Result<Self> { |
| // SAFETY: We're calling this function passing it a pointer that is a global constant |
| // specifically prescribed for being passed to this function. |
| let ptr = unsafe { |
| iokit_usb::IOServiceMatching(iokit_usb::kIOUSBDeviceClassName.as_ptr().cast()) |
| }; |
| Ok(MatchingDict(std::ptr::NonNull::new(ptr).ok_or_else(|| { |
| Error::IOError(IOError::new( |
| std::io::ErrorKind::Other, |
| format!("Could not create matching dict"), |
| )) |
| })?)) |
| } |
| |
| /// Get the raw pointer to the underlying matching dict. Destroys this wrapper object without |
| /// consuming the reference it represents. |
| fn into_raw(self) -> iokit_usb::CFMutableDictionaryRef { |
| let ret = self.0.as_ptr(); |
| std::mem::forget(self); |
| ret |
| } |
| } |
| |
| impl Drop for MatchingDict { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| iokit_usb::CFRelease(self.0.as_ptr().cast()); |
| } |
| } |
| } |
| |
| impl Clone for MatchingDict { |
| fn clone(&self) -> MatchingDict { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| MatchingDict(unsafe { |
| std::ptr::NonNull::new(iokit_usb::CFRetain(self.0.as_ptr().cast()).cast_mut().cast()) |
| .expect("CFRetain returned Null!") |
| }) |
| } |
| } |
| |
| /// Wrapper around version 500 of IOKit's USB Interface object |
| pub struct InterfaceInterface500(*mut *mut iokit_usb::IOUSBInterfaceInterface500); |
| |
| // SAFETY: CoreFoundation objects are generally thread safe. |
| unsafe impl Send for InterfaceInterface500 {} |
| unsafe impl Sync for InterfaceInterface500 {} |
| |
| impl InterfaceInterface500 { |
| /// SAFETY: Pointer passed to the function must be valid. |
| unsafe fn new( |
| ptr: *mut *mut iokit_usb::IOUSBInterfaceInterface500, |
| ) -> Result<InterfaceInterface500> { |
| assert!(!ptr.is_null(), "Tried to construct InterfaceInterface500 with null ptr"); |
| |
| ioreturn_check(((**ptr).USBInterfaceOpen.unwrap())(ptr.cast()))?; |
| |
| Ok(InterfaceInterface500(ptr)) |
| } |
| |
| /// Call WritePipeAsync for this interface. |
| pub fn read_pipe_async( |
| &self, |
| buf: &mut [u8], |
| idx: u8, |
| callback: Option<extern "C" fn(*mut libc::c_void, iokit_usb::IOReturn, *mut libc::c_void)>, |
| callback_data: *mut libc::c_void, |
| ) -> Result<()> { |
| let callback = callback.map(|x| x as unsafe extern "C" fn(_, _, _)); |
| |
| // SAFETY: Pointer should be valid by construction. |
| unsafe { |
| ioreturn_check(((**self.0).ReadPipeAsync.unwrap())( |
| self.0.cast(), |
| idx, |
| buf.as_mut_ptr().cast(), |
| buf.len().try_into().expect("Impossibly large USB transaction"), |
| callback, |
| callback_data, |
| )) |
| } |
| } |
| |
| /// Call WritePipeAsync for this interface. |
| pub fn write_pipe_async( |
| &self, |
| buf: &[u8], |
| idx: u8, |
| callback: Option<extern "C" fn(*mut libc::c_void, iokit_usb::IOReturn, *mut libc::c_void)>, |
| callback_data: *mut libc::c_void, |
| ) -> Result<()> { |
| let callback = callback.map(|x| x as unsafe extern "C" fn(_, _, _)); |
| |
| // SAFETY: Pointer should be valid by construction. |
| unsafe { |
| ioreturn_check(((**self.0).WritePipeAsync.unwrap())( |
| self.0.cast(), |
| idx, |
| buf.as_ptr().cast_mut().cast(), |
| buf.len().try_into().expect("Impossibly large USB transaction"), |
| callback, |
| callback_data, |
| )) |
| } |
| } |
| |
| /// Derive a crate::InterfaceDescriptor for this interface. |
| pub fn descriptor(&self) -> Result<InterfaceDescriptor> { |
| let mut ret = InterfaceDescriptor { |
| id: 0, |
| class: 0, |
| subclass: 0, |
| protocol: 0, |
| alternate: 0, |
| endpoints: Vec::new(), |
| }; |
| let mut num_endpoints = 0; |
| |
| // SAFETY: This object should guarantee the validity of the pointer. |
| unsafe { |
| ioreturn_check(((**self.0).GetInterfaceNumber.unwrap())(self.0.cast(), &mut ret.id))?; |
| ioreturn_check(((**self.0).GetInterfaceClass.unwrap())(self.0.cast(), &mut ret.class))?; |
| ioreturn_check(((**self.0).GetInterfaceSubClass.unwrap())( |
| self.0.cast(), |
| &mut ret.subclass, |
| ))?; |
| ioreturn_check(((**self.0).GetInterfaceProtocol.unwrap())( |
| self.0.cast(), |
| &mut ret.protocol, |
| ))?; |
| ioreturn_check(((**self.0).GetInterfaceProtocol.unwrap())( |
| self.0.cast(), |
| &mut ret.protocol, |
| ))?; |
| ioreturn_check(((**self.0).GetAlternateSetting.unwrap())( |
| self.0.cast(), |
| &mut ret.alternate, |
| ))?; |
| ioreturn_check(((**self.0).GetNumEndpoints.unwrap())( |
| self.0.cast(), |
| &mut num_endpoints, |
| ))?; |
| } |
| |
| ret.endpoints.reserve_exact(num_endpoints as usize); |
| for endpoint_num in 1..=num_endpoints { |
| let mut transfer_type = 0; |
| let mut address = 0; |
| let mut direction = 0; |
| |
| // SAFETY: Same as above. |
| unsafe { |
| ioreturn_check(((**self.0).GetPipeProperties.unwrap())( |
| self.0.cast(), |
| endpoint_num, |
| &mut direction, |
| &mut address, |
| &mut transfer_type, |
| &mut 0, |
| &mut 0, |
| ))?; |
| } |
| |
| let ty = match transfer_type as u32 { |
| iokit_usb::kUSBBulk => EndpointType::Bulk, |
| iokit_usb::kUSBControl => EndpointType::Control, |
| iokit_usb::kUSBIsoc => EndpointType::Isochronous, |
| iokit_usb::kUSBInterrupt => EndpointType::Interrupt, |
| _ => return Err(Error::MalformedDescriptor), |
| }; |
| |
| let address = if u32::from(direction) == iokit_usb::kUSBIn { |
| address | crate::USB_ENDPOINT_DIR_MASK |
| } else { |
| address |
| }; |
| |
| ret.endpoints.push(EndpointDescriptor { ty, address }); |
| } |
| |
| Ok(ret) |
| } |
| |
| /// Use the given event loop to listen for events from this interface. |
| pub fn register_with_event_loop(&self, run_loop: &RunLoop) -> Result<()> { |
| let mut source = std::ptr::null_mut(); |
| |
| // SAFETY: Pointer should be valid by construction. RunLoop sources are refcounted by |
| // CoreFoundation so it's safe to drop the one we create here. |
| unsafe { |
| ioreturn_check(((**self.0).CreateInterfaceAsyncEventSource.unwrap())( |
| self.0.cast(), |
| &mut source, |
| ))?; |
| let source = RunLoopSourceRef(source); |
| iokit_usb::CFRunLoopAddSource( |
| run_loop.handle, |
| source.0, |
| iokit_usb::kCFRunLoopDefaultMode, |
| ); |
| } |
| Ok(()) |
| } |
| } |
| |
| impl QueryableInterface for Result<InterfaceInterface500> { |
| fn uuid() -> iokit_usb::CFUUIDRef { |
| GetIOUSBInterfaceInterfaceID500() |
| } |
| |
| unsafe fn get(ptr: *mut libc::c_void) -> Result<InterfaceInterface500> { |
| InterfaceInterface500::new(ptr.cast()) |
| } |
| } |
| |
| impl Drop for InterfaceInterface500 { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| ((**self.0).USBInterfaceClose.unwrap())(self.0.cast()); |
| ((**self.0).Release.unwrap())(self.0.cast()); |
| } |
| } |
| } |
| |
| /// Wrapper around version 500 of IOKit's USB Device. |
| pub struct DeviceInterface500(*mut *mut iokit_usb::IOUSBDeviceInterface500); |
| |
| impl DeviceInterface500 { |
| /// Derive a crate::DeviceDescriptor from this device. |
| pub fn descriptor(&self) -> DeviceDescriptor { |
| let mut ret = |
| DeviceDescriptor { vendor: 0, product: 0, class: 0, subclass: 0, protocol: 0 }; |
| |
| // SAFETY: If our contained pointer is valid, as this object should ensure, all these should |
| // be safe uses. |
| unsafe { |
| ((**self.0).GetDeviceVendor.unwrap())(self.0.cast(), &mut ret.vendor); |
| ((**self.0).GetDeviceProduct.unwrap())(self.0.cast(), &mut ret.product); |
| ((**self.0).GetDeviceClass.unwrap())(self.0.cast(), &mut ret.class); |
| ((**self.0).GetDeviceSubClass.unwrap())(self.0.cast(), &mut ret.subclass); |
| ((**self.0).GetDeviceProtocol.unwrap())(self.0.cast(), &mut ret.protocol); |
| } |
| |
| ret |
| } |
| |
| /// Iterate the interfaces exposed by this device. |
| pub fn iter_interfaces(&self) -> Result<impl Iterator<Item = IOService>> { |
| let mut io_iter = 0; |
| let mut request = iokit_usb::IOUSBFindInterfaceRequest::default(); |
| request.bInterfaceClass = iokit_usb::kIOUSBFindInterfaceDontCare as u16; |
| request.bInterfaceSubClass = iokit_usb::kIOUSBFindInterfaceDontCare as u16; |
| request.bInterfaceProtocol = iokit_usb::kIOUSBFindInterfaceDontCare as u16; |
| request.bAlternateSetting = iokit_usb::kIOUSBFindInterfaceDontCare as u16; |
| |
| // SAFETY: This object assures the validity of self.0 and the two other pointers are outputs |
| // so local stack locations should be fine. |
| unsafe { |
| ioreturn_check(((**self.0).CreateInterfaceIterator.unwrap())( |
| self.0.cast(), |
| &mut request, |
| &mut io_iter, |
| ))?; |
| } |
| |
| // SAFETY: We just received this iterator so it should be valid. |
| Ok(std::iter::from_fn(move || { |
| Some(unsafe { iokit_usb::IOIteratorNext(io_iter) }).filter(|&x| x != 0) |
| }) |
| .map(IOService)) |
| } |
| } |
| |
| impl QueryableInterface for DeviceInterface500 { |
| fn uuid() -> iokit_usb::CFUUIDRef { |
| GetIOUSBDeviceInterfaceID500() |
| } |
| |
| unsafe fn get(ptr: *mut libc::c_void) -> DeviceInterface500 { |
| DeviceInterface500(ptr.cast()) |
| } |
| } |
| |
| impl Drop for DeviceInterface500 { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| ((**self.0).Release.unwrap())(self.0.cast()); |
| } |
| } |
| } |
| |
| /// Wrapper around IOKit's io_service_t |
| pub struct IOService(iokit_usb::io_service_t); |
| |
| impl IOService { |
| /// Construct from a raw io_service_t |
| pub fn from_raw(raw: iokit_usb::io_service_t) -> IOService { |
| IOService(raw) |
| } |
| } |
| |
| impl std::fmt::Debug for IOService { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
| let mut buf = [0u8; std::mem::size_of::<iokit_usb::io_name_t>()]; |
| |
| // SAFETY: The io_name_t type is an array that is supposed to be big enough. |
| if let Err(e) = unsafe { |
| kern_return_check(iokit_usb::IORegistryEntryGetName(self.0, buf.as_mut_ptr().cast())) |
| } { |
| write!(f, "<Name unknown ({e:?})>") |
| } else { |
| let buf = buf.split(|&x| x == 0).next().unwrap_or(&[]); |
| let string = String::from_utf8_lossy(buf); |
| f.write_str(string.as_ref()) |
| } |
| } |
| } |
| |
| impl Clone for IOService { |
| fn clone(&self) -> IOService { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| iokit_usb::IOObjectRetain(self.0); |
| } |
| |
| IOService(self.0) |
| } |
| } |
| |
| impl Drop for IOService { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| iokit_usb::IOObjectRelease(self.0); |
| } |
| } |
| } |
| |
| /// Marks an object that can be returned from a PlugIn interface. |
| pub trait QueryableInterface { |
| /// The UUID passed to the Query Interface call to get an object of this type. |
| fn uuid() -> iokit_usb::CFUUIDRef; |
| |
| /// Construct an object of this type; |
| /// SAFETY: The pointer must be valid and returned by an IOKit plugin's QueryInterface call. |
| unsafe fn get(ptr: *mut libc::c_void) -> Self; |
| } |
| |
| /// Wrapper around IOKit's PlugIn Interface |
| pub struct PlugInInterface(*mut *mut iokit_usb::IOCFPlugInInterface); |
| |
| impl PlugInInterface { |
| /// Create a new plugin interface for the given service. |
| pub fn new(obj: IOService, ty: iokit_usb::CFUUIDRef) -> Result<PlugInInterface> { |
| let mut plugin_interface = std::ptr::null_mut(); |
| |
| // SAFETY: This object should guarantee that the handle is valid. All the other arguments |
| // are constants or stack pointers known to be used only as immediate outputs. |
| unsafe { |
| kern_return_check(iokit_usb::IOCreatePlugInInterfaceForService( |
| obj.0, |
| ty, |
| GetIOCFPlugInInterfaceID(), |
| &mut plugin_interface, |
| &mut 0, |
| ))?; |
| } |
| |
| if plugin_interface.is_null() { |
| return Err(Error::IOError(IOError::new( |
| std::io::ErrorKind::Other, |
| format!("Could not create plugin interface"), |
| ))); |
| } |
| |
| Ok(PlugInInterface(plugin_interface)) |
| } |
| |
| /// Query this plugin interface for a specific interface. |
| pub fn query_interface<T: QueryableInterface>(self) -> Result<T> { |
| let mut ptr = std::ptr::null_mut(); |
| |
| // SAFETY: The passed-in object should be valid by construction of Self, and the other |
| // parameter is an output pointer which should be fine as a stack pointer. |
| let res = unsafe { |
| ((**self.0).QueryInterface.unwrap())( |
| self.0.cast(), |
| iokit_usb::CFUUIDGetUUIDBytes(T::uuid()), |
| &mut ptr, |
| ) |
| }; |
| |
| std::mem::drop(self); |
| |
| if res != 0 || ptr.is_null() { |
| Err(Error::IOError(IOError::new( |
| std::io::ErrorKind::Other, |
| format!("Could not get CoreFoundation Interface HRESULT: {res}"), |
| ))) |
| } else { |
| // SAFETY: As required, this pointer is non-null and returned from QueryInterface. |
| unsafe { Ok(T::get(ptr)) } |
| } |
| } |
| } |
| |
| impl Drop for PlugInInterface { |
| fn drop(&mut self) { |
| // SAFETY: This object's purpose is to keep this value alive and ensure its scope, so this |
| // should always be valid. |
| unsafe { |
| ((**self.0).Release.unwrap())(self.0.cast()); |
| } |
| } |
| } |