From 984c3b1bc7d144691f8cbad9daaa3a151da8f218 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Tue, 23 Jul 2024 16:24:19 -0400 Subject: [PATCH] implement equivalent to libusb_detach_kernel_driver --- examples/detach.rs | 14 +++++++++ examples/detach_claim.rs | 3 +- src/device.rs | 30 +++++++++++++++++++ src/platform/linux_usbfs/device.rs | 26 +++++++++++++++++ src/platform/linux_usbfs/mod.rs | 1 + src/platform/linux_usbfs/usbfs.rs | 47 +++++++++++++++++++++++------- src/platform/macos_iokit/mod.rs | 1 + src/platform/windows_winusb/mod.rs | 1 + 8 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 examples/detach.rs diff --git a/examples/detach.rs b/examples/detach.rs new file mode 100644 index 0000000..b1d42a7 --- /dev/null +++ b/examples/detach.rs @@ -0,0 +1,14 @@ +//! Detach the kernel driver for an FTDI device and then reattach it. +use std::{thread::sleep, time::Duration}; +fn main() { + env_logger::init(); + let di = nusb::list_devices() + .unwrap() + .find(|d| d.vendor_id() == 0x0403 && d.product_id() == 0x6001) + .expect("device should be connected"); + + let device = di.open().unwrap(); + let interface = device.detach_kernel_driver(0).unwrap(); + sleep(Duration::from_secs(1)); + drop(interface); +} diff --git a/examples/detach_claim.rs b/examples/detach_claim.rs index cb3fa3d..908a053 100644 --- a/examples/detach_claim.rs +++ b/examples/detach_claim.rs @@ -1,4 +1,5 @@ -//! Detach the kernel driver for an FTDI device and then reattach it. +//! Detach the kernel driver for an FTDI device, claim the USB interface, and +//! then reattach it. use std::{thread::sleep, time::Duration}; fn main() { env_logger::init(); diff --git a/src/device.rs b/src/device.rs index 5f0d419..5ab6c23 100644 --- a/src/device.rs +++ b/src/device.rs @@ -62,6 +62,24 @@ impl Device { Ok(Interface { backend }) } + /// Detach kernel drivers for the specified interface. The drivers will be + /// reattached on Drop. + /// + /// ### Platform notes + /// This function can only detach kernel drivers on Linux. Calling on other platforms has + /// no effect. + pub fn detach_kernel_driver(&self, interface: u8) -> Result { + #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] + let interface = interface; + + #[cfg(target_os = "linux")] + let backend = self.backend.detach_kernel_driver(interface)?; + #[cfg(not(target_os = "linux"))] + let backend = Arc::new(()); + + Ok(DetachedInterface { _backend: backend }) + } + /// Get information about the active configuration. /// /// This returns cached data and does not perform IO. However, it can fail if the @@ -561,6 +579,18 @@ impl Interface { } } +/// A detached interface of a USB device. +/// +/// Obtain a `DetachedInterface` with the [`Device::detach_kernel_driver`] method. +/// +/// This type is reference-counted with an [`Arc`] internally, and can be cloned cheaply for +/// use in multiple places in your program. The interface is released when all clones +/// are dropped. +#[derive(Clone)] +pub struct DetachedInterface { + _backend: Arc, +} + #[test] fn assert_send_sync() { fn require_send_sync() {} diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index f26e192..eb125c8 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -250,6 +250,17 @@ impl LinuxDevice { })) } + pub(crate) fn detach_kernel_driver( + self: &Arc, + interface_number: u8, + ) -> Result, Error> { + usbfs::detach_kernel_driver(&self.fd, interface_number)?; + Ok(Arc::new(LinuxDetachedInterface { + device: self.clone(), + interface_number, + })) + } + pub(crate) unsafe fn submit_urb(&self, urb: *mut Urb) { let ep = unsafe { (*urb).endpoint }; if let Err(e) = usbfs::submit_urb(&self.fd, urb) { @@ -359,3 +370,18 @@ impl Drop for LinuxInterface { } } } + +pub(crate) struct LinuxDetachedInterface { + pub(crate) interface_number: u8, + pub(crate) device: Arc, +} + +impl Drop for LinuxDetachedInterface { + fn drop(&mut self) { + let res = usbfs::attach_kernel_driver(&self.device.fd, self.interface_number); + debug!( + "Reattached kernel drivers for interface {} on device {}: {res:?}", + self.interface_number, self.device.events_id + ); + } +} diff --git a/src/platform/linux_usbfs/mod.rs b/src/platform/linux_usbfs/mod.rs index 9671aa0..85ec2a8 100644 --- a/src/platform/linux_usbfs/mod.rs +++ b/src/platform/linux_usbfs/mod.rs @@ -8,6 +8,7 @@ mod events; pub use enumeration::{list_devices, SysfsPath}; mod device; +pub(crate) use device::LinuxDetachedInterface as DetachedInterface; pub(crate) use device::LinuxDevice as Device; pub(crate) use device::LinuxInterface as Interface; diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index 949b0a6..59a0444 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -57,8 +57,7 @@ pub fn detach_and_claim_interface(fd: Fd, interface: u8) -> io::Result dc.driver[0..6].copy_from_slice(b"usbfs\0"); - let ctl = - ioctl::Setter::, DetachAndClaim>::new(dc); + let ctl = ioctl::Setter::::new(dc); ioctl::ioctl(&fd, ctl) } @@ -71,15 +70,43 @@ struct UsbFsIoctl { data: *mut c_void, } -pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { +/// Opcodes used in ioctl with the usb device fs. +/// +/// Taken from https://github.com/torvalds/linux/blob/e9680017b2dc8686a908ea1b51941a91b6da9f1d/include/uapi/linux/usbdevice_fs.h#L187 +// TODO: Move the rest of the opcodes into here? +#[allow(non_camel_case_types)] +mod opcodes { + use super::*; + + // We repeat the USBDEVFS_ prefix to help keep the same names as what linux uses. + // This makes the code more searchable. + + pub type USBDEVFS_IOCTL = ioctl::ReadWriteOpcode; + pub type USBDEVFS_DISCONNECT = ioctl::NoneOpcode; + pub type USBDEVFS_CONNECT = ioctl::NoneOpcode; + pub type USBDEVFS_DISCONNECT_CLAIM = ioctl::ReadOpcode; +} + +pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { + let command = UsbFsIoctl { + interface: interface.into(), + ioctl_code: opcodes::USBDEVFS_DISCONNECT::OPCODE.raw(), + data: std::ptr::null_mut(), + }; unsafe { - let command = UsbFsIoctl { - interface: interface.into(), - ioctl_code: ioctl::NoneOpcode::::OPCODE.raw() as c_uint, // IOCTL_USBFS_CONNECT - data: std::ptr::null_mut(), - }; - let ctl = - ioctl::Setter::, UsbFsIoctl>::new(command); + let ctl = ioctl::Setter::::new(command); + ioctl::ioctl(fd, ctl) + } +} + +pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { + let command = UsbFsIoctl { + interface: interface.into(), + ioctl_code: opcodes::USBDEVFS_CONNECT::OPCODE.raw(), + data: std::ptr::null_mut(), + }; + unsafe { + let ctl = ioctl::Setter::::new(command); ioctl::ioctl(fd, ctl) } } diff --git a/src/platform/macos_iokit/mod.rs b/src/platform/macos_iokit/mod.rs index e29a099..f8157e6 100644 --- a/src/platform/macos_iokit/mod.rs +++ b/src/platform/macos_iokit/mod.rs @@ -11,6 +11,7 @@ pub use enumeration::list_devices; mod device; pub(crate) use device::MacDevice as Device; pub(crate) use device::MacInterface as Interface; +pub(crate) type DetachedInterface = (); use crate::transfer::TransferError; diff --git a/src/platform/windows_winusb/mod.rs b/src/platform/windows_winusb/mod.rs index bb03e17..caf8b0d 100644 --- a/src/platform/windows_winusb/mod.rs +++ b/src/platform/windows_winusb/mod.rs @@ -6,6 +6,7 @@ mod events; mod device; pub(crate) use device::WindowsDevice as Device; pub(crate) use device::WindowsInterface as Interface; +pub(crate) type DetachedInterface = (); mod transfer; pub(crate) use transfer::TransferData;