implement equivalent to libusb_detach_kernel_driver

This commit is contained in:
Ryan Butler 2024-07-23 16:24:19 -04:00
parent bcab2875f0
commit 984c3b1bc7
No known key found for this signature in database
GPG key ID: 0576003CB4286138
8 changed files with 112 additions and 11 deletions

14
examples/detach.rs Normal file
View file

@ -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);
}

View file

@ -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();

View file

@ -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<DetachedInterface, Error> {
#[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<platform::DetachedInterface>,
}
#[test]
fn assert_send_sync() {
fn require_send_sync<T: Send + Sync>() {}

View file

@ -250,6 +250,17 @@ impl LinuxDevice {
}))
}
pub(crate) fn detach_kernel_driver(
self: &Arc<Self>,
interface_number: u8,
) -> Result<Arc<LinuxDetachedInterface>, 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<LinuxDevice>,
}
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
);
}
}

View file

@ -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;

View file

@ -57,8 +57,7 @@ pub fn detach_and_claim_interface<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result
dc.driver[0..6].copy_from_slice(b"usbfs\0");
let ctl =
ioctl::Setter::<ioctl::ReadOpcode<b'U', 27, DetachAndClaim>, DetachAndClaim>::new(dc);
let ctl = ioctl::Setter::<opcodes::USBDEVFS_DISCONNECT_CLAIM, DetachAndClaim>::new(dc);
ioctl::ioctl(&fd, ctl)
}
@ -71,15 +70,43 @@ struct UsbFsIoctl {
data: *mut c_void,
}
pub fn attach_kernel_driver<Fd: AsFd>(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<b'U', 18, UsbFsIoctl>;
pub type USBDEVFS_DISCONNECT = ioctl::NoneOpcode<b'U', 22, ()>;
pub type USBDEVFS_CONNECT = ioctl::NoneOpcode<b'U', 23, ()>;
pub type USBDEVFS_DISCONNECT_CLAIM = ioctl::ReadOpcode<b'U', 27, DetachAndClaim>;
}
pub fn detach_kernel_driver<Fd: AsFd>(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::<b'U', 23, ()>::OPCODE.raw() as c_uint, // IOCTL_USBFS_CONNECT
data: std::ptr::null_mut(),
};
let ctl =
ioctl::Setter::<ioctl::ReadWriteOpcode<b'U', 18, UsbFsIoctl>, UsbFsIoctl>::new(command);
let ctl = ioctl::Setter::<opcodes::USBDEVFS_IOCTL, UsbFsIoctl>::new(command);
ioctl::ioctl(fd, ctl)
}
}
pub fn attach_kernel_driver<Fd: AsFd>(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::<opcodes::USBDEVFS_IOCTL, UsbFsIoctl>::new(command);
ioctl::ioctl(fd, ctl)
}
}

View file

@ -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;

View file

@ -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;