Skip to content

Commit

Permalink
Make blocking APIs optionally async
Browse files Browse the repository at this point in the history
`DeviceInfo::open`, `Device::from_fd`, `Device::set_configuration`,
`Device::reset`, `Interface::set_alt_setting`, `Interface::clear_halt`
all perform IO but are currently blocking because the underlying OS APIs
are blocking.

`list_devices`,`list_buses`, `Device::claim_interface`
`Device::detach_and_claim_interface` theoretically don't perform IO, but
are also included here because they need to be async on WebUSB.

The `IoAction` trait allows defering these actions to the thread pool
from the `blocking` crate when used asynchronously with `.await` /
`IntoFuture`, or directly runs the blocking syscall synchronously with a
`.wait()` method.
  • Loading branch information
kevinmehall committed Dec 30, 2024
1 parent b167c7e commit d0a6f08
Show file tree
Hide file tree
Showing 17 changed files with 311 additions and 178 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ core-foundation = "0.9.3"
core-foundation-sys = "0.8.4"
io-kit-sys = "0.4.0"

[target.'cfg(any(target_os="linux", target_os="android", target_os="windows", target_os="macos"))'.dependencies]
blocking ="1.6.1"

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
10 changes: 7 additions & 3 deletions examples/blocking.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
use std::time::Duration;

use nusb::transfer::{Control, ControlType, Recipient};
use nusb::{
transfer::{Control, ControlType, Recipient},
IoAction,
};

fn main() {
env_logger::init();
let di = nusb::list_devices()
.wait()
.unwrap()
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
.expect("device should be connected");

println!("Device info: {di:?}");

let device = di.open().unwrap();
let device = di.open().wait().unwrap();

// Linux can make control transfers without claiming an interface
#[cfg(any(target_os = "linux", target_os = "macos"))]
Expand Down Expand Up @@ -49,7 +53,7 @@ fn main() {
}

// but we also provide an API on the `Interface` to support Windows
let interface = device.claim_interface(0).unwrap();
let interface = device.claim_interface(0).wait().unwrap();

let result = interface.control_out_blocking(
Control {
Expand Down
7 changes: 4 additions & 3 deletions examples/bulk.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use futures_lite::future::block_on;
use nusb::transfer::RequestBuffer;
use nusb::{transfer::RequestBuffer, IoAction};

fn main() {
env_logger::init();
let di = nusb::list_devices()
.wait()
.unwrap()
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
.expect("device should be connected");

println!("Device info: {di:?}");

let device = di.open().unwrap();
let interface = device.claim_interface(0).unwrap();
let device = di.open().wait().unwrap();
let interface = device.claim_interface(0).wait().unwrap();

block_on(interface.bulk_out(0x02, Vec::from([1, 2, 3, 4, 5])))
.into_result()
Expand Down
4 changes: 3 additions & 1 deletion examples/buses.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use nusb::IoAction;

fn main() {
env_logger::init();
for dev in nusb::list_buses().unwrap() {
for dev in nusb::list_buses().wait().unwrap() {
println!("{:#?}", dev);
}
}
10 changes: 7 additions & 3 deletions examples/control.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use futures_lite::future::block_on;
use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient};
use nusb::{
transfer::{ControlIn, ControlOut, ControlType, Recipient},
IoAction,
};

fn main() {
env_logger::init();
let di = nusb::list_devices()
.wait()
.unwrap()
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
.expect("device should be connected");

println!("Device info: {di:?}");

let device = di.open().unwrap();
let device = di.open().wait().unwrap();

// Linux can make control transfers without claiming an interface
#[cfg(any(target_os = "linux", target_os = "macos"))]
Expand All @@ -37,7 +41,7 @@ fn main() {
}

// but we also provide an API on the `Interface` to support Windows
let interface = device.claim_interface(0).unwrap();
let interface = device.claim_interface(0).wait().unwrap();

let result = block_on(interface.control_out(ControlOut {
control_type: ControlType::Vendor,
Expand Down
6 changes: 3 additions & 3 deletions examples/descriptors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use nusb::DeviceInfo;
use nusb::{DeviceInfo, IoAction};

fn main() {
env_logger::init();
for dev in nusb::list_devices().unwrap() {
for dev in nusb::list_devices().wait().unwrap() {
inspect_device(dev);
}
}
Expand All @@ -17,7 +17,7 @@ fn inspect_device(dev: DeviceInfo) {
dev.manufacturer_string().unwrap_or(""),
dev.product_string().unwrap_or("")
);
let dev = match dev.open() {
let dev = match dev.open().wait() {
Ok(dev) => dev,
Err(e) => {
println!("Failed to open device: {}", e);
Expand Down
5 changes: 4 additions & 1 deletion examples/detach.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! Detach the kernel driver for an FTDI device and then reattach it.
use std::{thread::sleep, time::Duration};

use nusb::IoAction;
fn main() {
env_logger::init();
let di = nusb::list_devices()
.wait()
.unwrap()
.find(|d| d.vendor_id() == 0x0403 && d.product_id() == 0x6001)
.expect("device should be connected");

let device = di.open().unwrap();
let device = di.open().wait().unwrap();
device.detach_kernel_driver(0).unwrap();
sleep(Duration::from_secs(10));
device.attach_kernel_driver(0).unwrap();
Expand Down
7 changes: 5 additions & 2 deletions examples/detach_claim.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
//! Detach the kernel driver for an FTDI device, claim the USB interface, and
//! then reattach it.
use std::{thread::sleep, time::Duration};

use nusb::IoAction;
fn main() {
env_logger::init();
let di = nusb::list_devices()
.wait()
.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();
let device = di.open().wait().unwrap();
let interface = device.detach_and_claim_interface(0).wait().unwrap();
sleep(Duration::from_secs(1));
drop(interface);
}
4 changes: 3 additions & 1 deletion examples/list.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use nusb::IoAction;

fn main() {
env_logger::init();
for dev in nusb::list_devices().unwrap() {
for dev in nusb::list_devices().wait().unwrap() {
println!("{:#?}", dev);
}
}
6 changes: 3 additions & 3 deletions examples/string_descriptors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::time::Duration;

use nusb::{descriptors::language_id::US_ENGLISH, DeviceInfo};
use nusb::{descriptors::language_id::US_ENGLISH, DeviceInfo, IoAction};

fn main() {
env_logger::init();
for dev in nusb::list_devices().unwrap() {
for dev in nusb::list_devices().wait().unwrap() {
inspect_device(dev);
}
}
Expand All @@ -19,7 +19,7 @@ fn inspect_device(dev: DeviceInfo) {
dev.manufacturer_string().unwrap_or(""),
dev.product_string().unwrap_or("")
);
let dev = match dev.open() {
let dev = match dev.open().wait() {
Ok(dev) => dev,
Err(e) => {
println!("Failed to open device: {}", e);
Expand Down
81 changes: 48 additions & 33 deletions src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError,
TransferFuture,
},
DeviceInfo, Error,
DeviceInfo, Error, IoAction,
};
use log::error;
use std::{io::ErrorKind, sync::Arc, time::Duration};
Expand All @@ -18,12 +18,12 @@ use std::{io::ErrorKind, sync::Arc, time::Duration};
/// Obtain a `Device` by calling [`DeviceInfo::open`]:
///
/// ```no_run
/// use nusb;
/// let device_info = nusb::list_devices().unwrap()
/// use nusb::{self, IoAction};
/// let device_info = nusb::list_devices().wait().unwrap()
/// .find(|dev| dev.vendor_id() == 0xAAAA && dev.product_id() == 0xBBBB)
/// .expect("device not connected");
///
/// let device = device_info.open().expect("failed to open device");
/// let device = device_info.open().wait().expect("failed to open device");
/// let interface = device.claim_interface(0);
/// ```
///
Expand All @@ -39,33 +39,38 @@ pub struct Device {
}

impl Device {
pub(crate) fn open(d: &DeviceInfo) -> Result<Device, std::io::Error> {
let backend = platform::Device::from_device_info(d)?;
Ok(Device { backend })
pub(crate) fn wrap(backend: Arc<platform::Device>) -> Device {
Device { backend }
}

pub(crate) fn open(d: &DeviceInfo) -> impl IoAction<Output = Result<Device, std::io::Error>> {
platform::Device::from_device_info(d)
}

/// Wraps a device that is already open.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result<Device, Error> {
Ok(Device {
backend: platform::Device::from_fd(fd)?,
})
pub fn from_fd(fd: std::os::fd::OwnedFd) -> impl IoAction<Output = Result<Device, Error>> {
platform::Device::from_fd(fd)
}

/// Open an interface of the device and claim it for exclusive use.
pub fn claim_interface(&self, interface: u8) -> Result<Interface, Error> {
let backend = self.backend.claim_interface(interface)?;
Ok(Interface { backend })
pub fn claim_interface(
&self,
interface: u8,
) -> impl IoAction<Output = Result<Interface, Error>> {
self.backend.clone().claim_interface(interface)
}

/// 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<Interface, Error> {
let backend = self.backend.detach_and_claim_interface(interface)?;
Ok(Interface { backend })
pub fn detach_and_claim_interface(
&self,
interface: u8,
) -> impl IoAction<Output = Result<Interface, Error>> {
self.backend.clone().detach_and_claim_interface(interface)
}

/// Detach kernel drivers for the specified interface.
Expand Down Expand Up @@ -126,8 +131,11 @@ impl Device {
///
/// ### Platform-specific notes
/// * Not supported on Windows
pub fn set_configuration(&self, configuration: u8) -> Result<(), Error> {
self.backend.set_configuration(configuration)
pub fn set_configuration(
&self,
configuration: u8,
) -> impl IoAction<Output = Result<(), Error>> {
self.backend.clone().set_configuration(configuration)
}

/// Request a descriptor from the device.
Expand Down Expand Up @@ -236,8 +244,8 @@ impl Device {
///
/// ### Platform-specific notes
/// * Not supported on Windows
pub fn reset(&self) -> Result<(), Error> {
self.backend.reset()
pub fn reset(&self) -> impl IoAction<Output = Result<(), Error>> {
self.backend.clone().reset()
}

/// Synchronously perform a single **IN (device-to-host)** transfer on the default **control** endpoint.
Expand Down Expand Up @@ -283,9 +291,10 @@ impl Device {
/// ```no_run
/// use futures_lite::future::block_on;
/// use nusb::transfer::{ ControlIn, ControlType, Recipient };
/// # use nusb::IoAction;
/// # fn main() -> Result<(), std::io::Error> {
/// # let di = nusb::list_devices().unwrap().next().unwrap();
/// # let device = di.open().unwrap();
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
///
/// let data: Vec<u8> = block_on(device.control_in(ControlIn {
/// control_type: ControlType::Vendor,
Expand Down Expand Up @@ -316,9 +325,10 @@ impl Device {
/// ```no_run
/// use futures_lite::future::block_on;
/// use nusb::transfer::{ ControlOut, ControlType, Recipient };
/// # use nusb::IoAction;
/// # fn main() -> Result<(), std::io::Error> {
/// # let di = nusb::list_devices().unwrap().next().unwrap();
/// # let device = di.open().unwrap();
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
///
/// block_on(device.control_out(ControlOut {
/// control_type: ControlType::Vendor,
Expand Down Expand Up @@ -356,13 +366,16 @@ pub struct Interface {
}

impl Interface {
pub(crate) fn wrap(backend: Arc<platform::Interface>) -> Self {
Interface { backend }
}
/// Select the alternate setting of this interface.
///
/// An alternate setting is a mode of the interface that makes particular endpoints available
/// and may enable or disable functionality of the device. The OS resets the device to the default
/// alternate setting when the interface is released or the program exits.
pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> {
self.backend.set_alt_setting(alt_setting)
pub fn set_alt_setting(&self, alt_setting: u8) -> impl IoAction<Output = Result<(), Error>> {
self.backend.clone().set_alt_setting(alt_setting)
}

/// Synchronously perform a single **IN (device-to-host)** transfer on the default **control** endpoint.
Expand Down Expand Up @@ -412,9 +425,10 @@ impl Interface {
/// ```no_run
/// use futures_lite::future::block_on;
/// use nusb::transfer::{ ControlIn, ControlType, Recipient };
/// # use nusb::IoAction;
/// # fn main() -> Result<(), std::io::Error> {
/// # let di = nusb::list_devices().unwrap().next().unwrap();
/// # let device = di.open().unwrap();
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
/// # let interface = device.claim_interface(0).unwrap();
///
/// let data: Vec<u8> = block_on(interface.control_in(ControlIn {
Expand Down Expand Up @@ -447,9 +461,10 @@ impl Interface {
/// ```no_run
/// use futures_lite::future::block_on;
/// use nusb::transfer::{ ControlOut, ControlType, Recipient };
/// # use nusb::IoAction;
/// # fn main() -> Result<(), std::io::Error> {
/// # let di = nusb::list_devices().unwrap().next().unwrap();
/// # let device = di.open().unwrap();
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
/// # let interface = device.claim_interface(0).unwrap();
///
/// block_on(interface.control_out(ControlOut {
Expand Down Expand Up @@ -555,8 +570,8 @@ impl Interface {
/// resume use of the endpoint.
///
/// This should not be called when transfers are pending on the endpoint.
pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> {
self.backend.clear_halt(endpoint)
pub fn clear_halt(&self, endpoint: u8) -> impl IoAction<Output = Result<(), Error>> {
self.backend.clone().clear_halt(endpoint)
}

/// Get the interface number.
Expand Down
4 changes: 2 additions & 2 deletions src/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ffi::{OsStr, OsString};
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::platform::SysfsPath;

use crate::{Device, Error};
use crate::{Device, Error, IoAction};

/// Opaque device identifier
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
Expand Down Expand Up @@ -277,7 +277,7 @@ impl DeviceInfo {
}

/// Open the device
pub fn open(&self) -> Result<Device, Error> {
pub fn open(&self) -> impl IoAction<Output = Result<Device, Error>> {
Device::open(self)
}
}
Expand Down
Loading

0 comments on commit d0a6f08

Please sign in to comment.