From 4a80b782f95e4c58c19b2b846b694058d38b1624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sun, 28 Jan 2024 08:57:52 +0100 Subject: [PATCH 1/7] Implement detach_and_claim_interface --- src/device.rs | 10 +++++++++ src/platform/linux_usbfs/device.rs | 26 ++++++++++++++++++++++ src/platform/linux_usbfs/usbfs.rs | 32 +++++++++++++++++++++++++++ src/platform/macos_iokit/device.rs | 7 ++++++ src/platform/windows_winusb/device.rs | 7 ++++++ 5 files changed, 82 insertions(+) diff --git a/src/device.rs b/src/device.rs index 80b1cfa..2e0bb0c 100644 --- a/src/device.rs +++ b/src/device.rs @@ -52,6 +52,16 @@ impl Device { Ok(Interface { backend }) } + /// Detach kernel drivers and open an interface of the device and claim it for exclusive use. + /// + /// ### Platform notes + /// This function can only detach kernel drivers on Linux. Calling on other platforms has + /// the same effect as [`claim_interface`][`Device::claim_interface`]. + pub fn detach_and_claim_interface(&self, interface: u8) -> Result { + let backend = self.backend.detach_and_claim_interface(interface)?; + Ok(Interface { backend }) + } + /// Get information about the active configuration. /// /// This returns cached data and does not perform IO. However, it can fail if the diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index 590a49d..36a33b1 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -230,6 +230,23 @@ impl LinuxDevice { Ok(Arc::new(LinuxInterface { device: self.clone(), interface, + reattach: false, + })) + } + + pub(crate) fn detach_and_claim_interface( + self: &Arc, + interface: u8, + ) -> Result, Error> { + usbfs::detach_and_claim_interface(&self.fd, interface)?; + debug!( + "Claimed interface {interface} on device id {dev}", + dev = self.events_id + ); + Ok(Arc::new(LinuxInterface { + device: self.clone(), + interface, + reattach: true, })) } @@ -272,6 +289,7 @@ impl Drop for LinuxDevice { pub(crate) struct LinuxInterface { pub(crate) interface: u8, pub(crate) device: Arc, + pub(crate) reattach: bool, } impl LinuxInterface { @@ -326,5 +344,13 @@ impl Drop for LinuxInterface { "Released interface {} on device {}: {res:?}", self.interface, self.device.events_id ); + + if res.is_ok() && self.reattach { + let res = usbfs::attach_kernel_driver(&self.device.fd, self.interface); + debug!( + "Reattached kernel drivers for interface {} on device {}: {res:?}", + self.interface, self.device.events_id + ); + } } } diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index 8588095..eb7b515 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -31,6 +31,31 @@ pub fn claim_interface(fd: Fd, interface: u8) -> io::Result<()> { } } +#[repr(C)] +struct DetachAndClaimIface { + interface: c_uint, + flags: c_uint, + + // Note: USBDEVFS_MAXDRIVERNAME is 255, but we only use 5 bytes for the driver name. Using 5 + // here simplifies the implementation. + driver: [c_uchar; 5 + 1], +} + +pub fn detach_and_claim_interface(fd: Fd, interface: u8) -> io::Result<()> { + const USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER: c_uint = 0x02; + unsafe { + let ctl = ioctl::Setter::< + ioctl::ReadOpcode, + DetachAndClaimIface, + >::new(DetachAndClaimIface { + interface: interface.into(), + flags: USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER, + driver: *b"usbfs\0", + }); + ioctl::ioctl(fd, ctl) + } +} + pub fn release_interface(fd: Fd, interface: u8) -> io::Result<()> { unsafe { let ctl = @@ -39,6 +64,13 @@ pub fn release_interface(fd: Fd, interface: u8) -> io::Result<()> { } } +pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { + unsafe { + let ctl = ioctl::NoArg::>::new(); + ioctl::ioctl(fd, ctl) + } +} + #[repr(C)] struct SetAltSetting { interface: c_int, diff --git a/src/platform/macos_iokit/device.rs b/src/platform/macos_iokit/device.rs index 8f849c2..a306a7e 100644 --- a/src/platform/macos_iokit/device.rs +++ b/src/platform/macos_iokit/device.rs @@ -188,6 +188,13 @@ impl MacDevice { _event_registration, })) } + + pub(crate) fn detach_and_claim_interface( + self: &Arc, + interface: u8, + ) -> Result, Error> { + self.claim_interface(interface) + } } pub(crate) struct MacInterface { diff --git a/src/platform/windows_winusb/device.rs b/src/platform/windows_winusb/device.rs index 61360a5..6d3a0a6 100644 --- a/src/platform/windows_winusb/device.rs +++ b/src/platform/windows_winusb/device.rs @@ -130,6 +130,13 @@ impl WindowsDevice { winusb_handle, })) } + + pub(crate) fn detach_and_claim_interface( + self: &Arc, + interface: u8, + ) -> Result, Error> { + self.claim_interface(interface) + } } pub(crate) struct WindowsInterface { From a94b39a53382527e5c593f46075b1c0f0e629245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sun, 28 Jan 2024 09:44:35 +0100 Subject: [PATCH 2/7] Try to detach drivers separately --- src/platform/linux_usbfs/usbfs.rs | 90 +++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index eb7b515..2a4dd48 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -31,42 +31,102 @@ pub fn claim_interface(fd: Fd, interface: u8) -> io::Result<()> { } } +pub fn release_interface(fd: Fd, interface: u8) -> io::Result<()> { + unsafe { + let ctl = + ioctl::Setter::, c_uint>::new(interface.into()); + ioctl::ioctl(fd, ctl) + } +} + #[repr(C)] -struct DetachAndClaimIface { +struct DetachAndClaim { interface: c_uint, flags: c_uint, - - // Note: USBDEVFS_MAXDRIVERNAME is 255, but we only use 5 bytes for the driver name. Using 5 - // here simplifies the implementation. - driver: [c_uchar; 5 + 1], + driver: [c_uchar; 255 + 1], } pub fn detach_and_claim_interface(fd: Fd, interface: u8) -> io::Result<()> { const USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER: c_uint = 0x02; unsafe { - let ctl = ioctl::Setter::< - ioctl::ReadOpcode, - DetachAndClaimIface, - >::new(DetachAndClaimIface { + let mut dc = DetachAndClaim { interface: interface.into(), flags: USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER, - driver: *b"usbfs\0", - }); - ioctl::ioctl(fd, ctl) + driver: [0; 256], + }; + + dc.driver[0..6].copy_from_slice(b"usbfs\0"); + + let ctl = + ioctl::Setter::, DetachAndClaim>::new(dc); + match ioctl::ioctl(&fd, ctl) { + Err(e) if e == io::Errno::NOTTY => {} + other => return other, + } + + // disconnect-and-claim failed, fall back to the racy detach-and-claim + let detach_result = detach_kernel_driver(&fd, interface); + + if let Err(e) = claim_interface(fd, interface) { + if let Err(detach_err) = detach_result { + return Err(detach_err); + } + + return Err(e); + } + + Ok(()) } } -pub fn release_interface(fd: Fd, interface: u8) -> io::Result<()> { +#[repr(C)] +struct UsbFsIoctl { + interface: c_uint, + ioctl_code: c_uint, + data: *mut c_void, +} + +#[repr(C)] +struct GetDriver { + interface: c_int, + driver: [u8; 256], +} + +pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { unsafe { + // Only detach usbfs + let mut driver = GetDriver { + interface: interface.into(), + driver: [0; 256], + }; let ctl = - ioctl::Setter::, c_uint>::new(interface.into()); + ioctl::Updater::, GetDriver>::new(&mut driver); + ioctl::ioctl(&fd, ctl)?; + + if driver.driver[0..6] != *b"usbfs\0" { + return Err(io::Errno::NOENT); + } + + let command = UsbFsIoctl { + interface: interface.into(), + ioctl_code: ioctl::NoneOpcode::::OPCODE.raw(), // IOCTL_USBFS_DISCONNECT + data: std::ptr::null_mut(), + }; + let ctl = + ioctl::Setter::, UsbFsIoctl>::new(command); ioctl::ioctl(fd, ctl) } } pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { unsafe { - let ctl = ioctl::NoArg::>::new(); + let command = UsbFsIoctl { + interface: interface.into(), + ioctl_code: ioctl::NoneOpcode::::OPCODE.raw(), // IOCTL_USBFS_CONNECT + data: std::ptr::null_mut(), + }; + let ctl = + ioctl::Setter::, UsbFsIoctl>::new(command); ioctl::ioctl(fd, ctl) } } From 9387649340c2497684a3335c91b934c25a22365d Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 28 Jan 2024 16:10:13 -0700 Subject: [PATCH 3/7] Keep usbfs module just ioctl wrappers, implement policy elsewhere --- src/platform/linux_usbfs/device.rs | 20 +++++++++-- src/platform/linux_usbfs/usbfs.rs | 53 +++++++++++------------------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index 36a33b1..fdce8a0 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -238,9 +238,25 @@ impl LinuxDevice { self: &Arc, interface: u8, ) -> Result, Error> { - usbfs::detach_and_claim_interface(&self.fd, interface)?; + match usbfs::detach_and_claim_interface(&self.fd, interface) { + Ok(()) => {} + Err(e) if e == Errno::NOTTY => { + debug!("USBDEVFS_DISCONNECT_CLAIM ioctl failed, falling back to detach-then-claim"); + + let driver = usbfs::get_driver(&self.fd, interface)?; + + // avoid detaching another instance of nusb or libusb + if !driver.starts_with(b"usbfs\0") { + usbfs::detach_kernel_driver(&self.fd, interface)?; + } + + usbfs::claim_interface(&self.fd, interface)?; + } + Err(e) => return Err(e.into()), + } + debug!( - "Claimed interface {interface} on device id {dev}", + "Detached and claimed interface {interface} on device id {dev}", dev = self.events_id ); Ok(Arc::new(LinuxInterface { diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index 2a4dd48..fbc6c64 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -11,7 +11,7 @@ use std::{ use rustix::{ fd::AsFd, - io, + io::{self, Errno}, ioctl::{self, CompileTimeOpcode, Ioctl, IoctlOutput}, }; @@ -59,54 +59,41 @@ pub fn detach_and_claim_interface(fd: Fd, interface: u8) -> io::Result let ctl = ioctl::Setter::, DetachAndClaim>::new(dc); - match ioctl::ioctl(&fd, ctl) { - Err(e) if e == io::Errno::NOTTY => {} - other => return other, - } - - // disconnect-and-claim failed, fall back to the racy detach-and-claim - let detach_result = detach_kernel_driver(&fd, interface); - - if let Err(e) = claim_interface(fd, interface) { - if let Err(detach_err) = detach_result { - return Err(detach_err); - } - - return Err(e); - } - Ok(()) + ioctl::ioctl(&fd, ctl) } } -#[repr(C)] -struct UsbFsIoctl { - interface: c_uint, - ioctl_code: c_uint, - data: *mut c_void, -} - #[repr(C)] struct GetDriver { interface: c_int, driver: [u8; 256], } -pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { +pub fn get_driver(fd: Fd, interface: u8) -> io::Result<[u8; 256]> { + let mut driver = GetDriver { + interface: interface.into(), + driver: [0; 256], + }; + unsafe { - // Only detach usbfs - let mut driver = GetDriver { - interface: interface.into(), - driver: [0; 256], - }; let ctl = ioctl::Updater::, GetDriver>::new(&mut driver); ioctl::ioctl(&fd, ctl)?; + } - if driver.driver[0..6] != *b"usbfs\0" { - return Err(io::Errno::NOENT); - } + Ok(driver.driver) +} +#[repr(C)] +struct UsbFsIoctl { + interface: c_uint, + ioctl_code: c_uint, + data: *mut c_void, +} + +pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { + unsafe { let command = UsbFsIoctl { interface: interface.into(), ioctl_code: ioctl::NoneOpcode::::OPCODE.raw(), // IOCTL_USBFS_DISCONNECT From abd9363c335ee1857a729328a6f3d4da061fd3ee Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 28 Jan 2024 16:50:43 -0700 Subject: [PATCH 4/7] Fix ioctl opcodes --- src/platform/linux_usbfs/usbfs.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index fbc6c64..40ffb74 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -78,7 +78,7 @@ pub fn get_driver(fd: Fd, interface: u8) -> io::Result<[u8; 256]> { unsafe { let ctl = - ioctl::Updater::, GetDriver>::new(&mut driver); + ioctl::Updater::, GetDriver>::new(&mut driver); ioctl::ioctl(&fd, ctl)?; } @@ -100,7 +100,7 @@ pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { data: std::ptr::null_mut(), }; let ctl = - ioctl::Setter::, UsbFsIoctl>::new(command); + ioctl::Setter::, UsbFsIoctl>::new(command); ioctl::ioctl(fd, ctl) } } @@ -113,7 +113,7 @@ pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { data: std::ptr::null_mut(), }; let ctl = - ioctl::Setter::, UsbFsIoctl>::new(command); + ioctl::Setter::, UsbFsIoctl>::new(command); ioctl::ioctl(fd, ctl) } } From 63a696f27ce3993e0a37b473a587dd9aa07942c0 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 28 Jan 2024 16:53:22 -0700 Subject: [PATCH 5/7] get_driver fails with ENODATA when there is no driver --- src/platform/linux_usbfs/device.rs | 2 +- src/platform/linux_usbfs/usbfs.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index fdce8a0..f59dc43 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -246,7 +246,7 @@ impl LinuxDevice { let driver = usbfs::get_driver(&self.fd, interface)?; // avoid detaching another instance of nusb or libusb - if !driver.starts_with(b"usbfs\0") { + if driver.is_some_and(|d| !d.starts_with(b"usbfs\0")) { usbfs::detach_kernel_driver(&self.fd, interface)?; } diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index 40ffb74..8f61a2f 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -70,19 +70,23 @@ struct GetDriver { driver: [u8; 256], } -pub fn get_driver(fd: Fd, interface: u8) -> io::Result<[u8; 256]> { +pub fn get_driver(fd: Fd, interface: u8) -> io::Result> { let mut driver = GetDriver { interface: interface.into(), driver: [0; 256], }; - unsafe { + let r = unsafe { let ctl = ioctl::Updater::, GetDriver>::new(&mut driver); - ioctl::ioctl(&fd, ctl)?; - } + ioctl::ioctl(&fd, ctl) + }; - Ok(driver.driver) + match r { + Ok(()) => Ok(Some(driver.driver)), + Err(Errno::NODATA) => Ok(None), + Err(e) => Err(e), + } } #[repr(C)] From 6ded9244d0e39af9b2cb70aba9c330edcbbc7c7d Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Mon, 29 Jan 2024 20:42:02 -0700 Subject: [PATCH 6/7] remove detach-then-claim fallback not needed for any modern kernel --- src/platform/linux_usbfs/device.rs | 18 +------------- src/platform/linux_usbfs/usbfs.rs | 40 +----------------------------- 2 files changed, 2 insertions(+), 56 deletions(-) diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index f59dc43..c5f78f0 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -238,23 +238,7 @@ impl LinuxDevice { self: &Arc, interface: u8, ) -> Result, Error> { - match usbfs::detach_and_claim_interface(&self.fd, interface) { - Ok(()) => {} - Err(e) if e == Errno::NOTTY => { - debug!("USBDEVFS_DISCONNECT_CLAIM ioctl failed, falling back to detach-then-claim"); - - let driver = usbfs::get_driver(&self.fd, interface)?; - - // avoid detaching another instance of nusb or libusb - if driver.is_some_and(|d| !d.starts_with(b"usbfs\0")) { - usbfs::detach_kernel_driver(&self.fd, interface)?; - } - - usbfs::claim_interface(&self.fd, interface)?; - } - Err(e) => return Err(e.into()), - } - + usbfs::detach_and_claim_interface(&self.fd, interface)?; debug!( "Detached and claimed interface {interface} on device id {dev}", dev = self.events_id diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index 8f61a2f..8eca168 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -11,7 +11,7 @@ use std::{ use rustix::{ fd::AsFd, - io::{self, Errno}, + io, ioctl::{self, CompileTimeOpcode, Ioctl, IoctlOutput}, }; @@ -64,31 +64,6 @@ pub fn detach_and_claim_interface(fd: Fd, interface: u8) -> io::Result } } -#[repr(C)] -struct GetDriver { - interface: c_int, - driver: [u8; 256], -} - -pub fn get_driver(fd: Fd, interface: u8) -> io::Result> { - let mut driver = GetDriver { - interface: interface.into(), - driver: [0; 256], - }; - - let r = unsafe { - let ctl = - ioctl::Updater::, GetDriver>::new(&mut driver); - ioctl::ioctl(&fd, ctl) - }; - - match r { - Ok(()) => Ok(Some(driver.driver)), - Err(Errno::NODATA) => Ok(None), - Err(e) => Err(e), - } -} - #[repr(C)] struct UsbFsIoctl { interface: c_uint, @@ -96,19 +71,6 @@ struct UsbFsIoctl { data: *mut c_void, } -pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { - unsafe { - let command = UsbFsIoctl { - interface: interface.into(), - ioctl_code: ioctl::NoneOpcode::::OPCODE.raw(), // IOCTL_USBFS_DISCONNECT - data: std::ptr::null_mut(), - }; - let ctl = - ioctl::Setter::, UsbFsIoctl>::new(command); - ioctl::ioctl(fd, ctl) - } -} - pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { unsafe { let command = UsbFsIoctl { From 5aeca69d765ce832906f5e42e358c6849c294de1 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Tue, 30 Jan 2024 09:02:58 -0700 Subject: [PATCH 7/7] Add detach_claim example for testing --- examples/detach_claim.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/detach_claim.rs diff --git a/examples/detach_claim.rs b/examples/detach_claim.rs new file mode 100644 index 0000000..cb3fa3d --- /dev/null +++ b/examples/detach_claim.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() == 0x6010) + .expect("device should be connected"); + + let device = di.open().unwrap(); + let interface = device.detach_and_claim_interface(0).unwrap(); + sleep(Duration::from_secs(1)); + drop(interface); +}