Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ windows-sys = { version = "0.59.0", features = ["Win32_Devices_Usb", "Win32_Devi
core-foundation = "0.9.3"
core-foundation-sys = "0.8.4"
io-kit-sys = "0.4.0"
libc = "0.2"

[target.'cfg(any(target_os="linux", target_os="android", target_os="windows", target_os="macos"))'.dependencies]
blocking ="1.6.1"
Expand Down
10 changes: 10 additions & 0 deletions examples/detach.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
//! Detach the kernel driver for an FTDI device and then reattach it.

#[cfg(not(target_os = "linux"))]
fn main() {
println!("This example is only supported on Linux.");
}

#[cfg(target_os = "linux")]
use std::{thread::sleep, time::Duration};

#[cfg(target_os = "linux")]
use nusb::MaybeFuture;

#[cfg(target_os = "linux")]
fn main() {
env_logger::init();
let di = nusb::list_devices()
Expand Down
58 changes: 54 additions & 4 deletions src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ impl Device {
/// Detach kernel drivers for the specified interface.
///
/// ### Platform notes
/// This function can only detach kernel drivers on Linux. Calling on other platforms has
/// no effect.
/// This function can only detach kernel drivers on Linux.
#[cfg(target_os = "linux")]
pub fn detach_kernel_driver(&self, interface: u8) -> Result<(), Error> {
#[cfg(target_os = "linux")]
self.backend.detach_kernel_driver(interface)?;
Expand All @@ -97,16 +97,34 @@ impl Device {
/// Attach kernel drivers for the specified interface.
///
/// ### Platform notes
/// This function can only attach kernel drivers on Linux. Calling on other platforms has
/// This function can only attach kernel drivers on Linux and macOS. Calling on other platforms has
/// no effect.
/// * macOS: requires either root or com.apple.vm.device-access entitlement
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn attach_kernel_driver(&self, interface: u8) -> Result<(), Error> {
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "macos"))]
self.backend.attach_kernel_driver(interface)?;
let _ = interface;

Ok(())
}

/// Check if a kernel driver is attached to the specified interface.
///
/// ### Platform-specific details
///
/// * Only supported on Linux and macOS
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn is_kernel_driver_attached_to_interface(
&self,
interface_number: u8,
) -> Result<bool, Error> {
#[cfg(not(target_os = "windows"))]
self.backend
.clone()
.is_kernel_driver_attached_to_interface(interface_number)
}

/// Get the device descriptor.
///
/// This returns cached data and does not perform IO.
Expand Down Expand Up @@ -158,6 +176,25 @@ impl Device {
self.backend.clone().set_configuration(configuration)
}

/// Set the device configuration, with kernel drivers detached.
///
/// The argument is the desired configuration's `bConfigurationValue`
/// descriptor field from [`Configuration::configuration_value`] or `0` to
/// unconfigure the device.
///
/// ### Platform-specific notes
/// * Only supported on macOS
/// * macOS: requires either root or com.apple.vm.device-access entitlement
#[cfg(target_os = "macos")]
pub fn set_configuration_captured(
&self,
configuration: u8,
) -> impl MaybeFuture<Output = Result<(), Error>> {
self.backend
.clone()
.set_configuration_captured(configuration)
}

/// Request a descriptor from the device.
///
/// The `language_id` should be `0` unless you are requesting a string descriptor.
Expand Down Expand Up @@ -267,6 +304,19 @@ impl Device {
self.backend.clone().reset()
}

/// Reset the device, forcing it to re-enumerate, with kernel drivers detached.
///
/// This `Device` will no longer be usable, and you should drop it and call
/// [`super::list_devices`] to find and re-open it again.
///
/// ### Platform-specific notes
/// * Only supported on macOS
/// * macOS: requires either root or com.apple.vm.device-access entitlement
#[cfg(target_os = "macos")]
pub fn reset_captured(&self) -> impl MaybeFuture<Output = Result<(), Error>> {
self.backend.clone().reset_captured()
}

/// Synchronously perform a single **IN (device-to-host)** transfer on the default **control** endpoint.
///
/// ### Platform-specific notes
Expand Down
9 changes: 9 additions & 0 deletions src/platform/linux_usbfs/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,15 @@ impl LinuxDevice {
usbfs::attach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into())
}

#[cfg(target_os = "linux")]
pub(crate) fn is_kernel_driver_attached_to_interface(
self: &Arc<Self>,
interface_number: u8,
) -> Result<bool, Error> {
usbfs::is_kernel_driver_attached_to_interface(&self.fd, interface_number)
.map_err(|e| e.into())
}

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) {
Expand Down
74 changes: 70 additions & 4 deletions src/platform/linux_usbfs/usbfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ use std::ffi::{c_int, c_uchar, c_uint, c_void};

use linux_raw_sys::ioctl::{
USBDEVFS_CLAIMINTERFACE, USBDEVFS_CLEAR_HALT, USBDEVFS_CONNECT, USBDEVFS_CONTROL,
USBDEVFS_DISCARDURB, USBDEVFS_DISCONNECT, USBDEVFS_DISCONNECT_CLAIM, USBDEVFS_GET_SPEED,
USBDEVFS_IOCTL, USBDEVFS_REAPURBNDELAY, USBDEVFS_RELEASEINTERFACE, USBDEVFS_RESET,
USBDEVFS_SETCONFIGURATION, USBDEVFS_SETINTERFACE, USBDEVFS_SUBMITURB,
USBDEVFS_DISCARDURB, USBDEVFS_DISCONNECT, USBDEVFS_DISCONNECT_CLAIM, USBDEVFS_GETDRIVER,
USBDEVFS_GET_SPEED, USBDEVFS_IOCTL, USBDEVFS_REAPURBNDELAY, USBDEVFS_RELEASEINTERFACE,
USBDEVFS_RESET, USBDEVFS_SETCONFIGURATION, USBDEVFS_SETINTERFACE, USBDEVFS_SUBMITURB,
};
use rustix::{
fd::AsFd,
io,
io::{self, Errno},
ioctl::{self, Ioctl, IoctlOutput, Opcode},
};

Expand Down Expand Up @@ -65,6 +65,35 @@ pub fn detach_and_claim_interface<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result
}
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
struct DriverName([u8; Self::MAX_LEN + 1]);

impl DriverName {
const MAX_LEN: usize = 255;

fn new() -> Self {
Self([0; Self::MAX_LEN + 1])
}

fn from_string(&mut self, driver: &str) {
let bytes = driver.as_bytes();
let len = std::cmp::min(bytes.len(), Self::MAX_LEN);

self.0[..len].copy_from_slice(&bytes[..len]);
self.0[len..].copy_from_slice(&[0u8; Self::MAX_LEN + 1][len..]);
}

fn as_str(&self) -> &str {
let len = self.driver_len();
std::str::from_utf8(self.0[..len].as_ref()).unwrap_or("")
}

fn driver_len(&self) -> usize {
self.0.iter().position(|&b| b == 0).unwrap_or(0)
}
}

#[repr(C)]
struct UsbFsIoctl {
interface: c_uint,
Expand Down Expand Up @@ -97,6 +126,43 @@ pub fn attach_kernel_driver<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<()> {
}
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
struct GetDriver {
interface: u32,
driver: DriverName,
}

impl GetDriver {
fn new() -> Self {
Self {
interface: 0,
driver: DriverName::new(),
}
}

fn driver(&self) -> &str {
self.driver.as_str()
}
}

pub fn is_kernel_driver_attached_to_interface<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<bool> {
let mut getdrv = GetDriver::new();
getdrv.interface = interface as u32;

unsafe {
let ctl = ioctl::Updater::<{ USBDEVFS_GETDRIVER as _ }, GetDriver>::new(&mut getdrv);

match ioctl::ioctl(fd, ctl) {
Err(e) if e == Errno::NODATA => {
return Ok(false);
}
Err(e) => return Err(e),
Ok(_) => Ok(getdrv.driver() != "usbfs"),
}
}
}

#[repr(C)]
struct SetAltSetting {
interface: c_int,
Expand Down
Loading
Loading