Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
kevinmehall committed Jan 20, 2025
1 parent a34c449 commit a4662e6
Showing 21 changed files with 588 additions and 386 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"))]
@@ -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 {
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()
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"))]
@@ -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,
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);
}
}
@@ -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);
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();
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);
}
}
@@ -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);
85 changes: 50 additions & 35 deletions src/device.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ use crate::{
Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError,
TransferFuture,
},
DeviceInfo, Error, Speed,
DeviceInfo, Error, IoAction, Speed,
};
use log::error;
use std::{io::ErrorKind, sync::Arc, time::Duration};
@@ -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);
/// ```
///
@@ -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.
@@ -138,8 +143,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.
@@ -248,8 +256,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.
@@ -295,9 +303,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,
@@ -328,9 +337,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,
@@ -368,13 +378,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.
@@ -424,10 +437,11 @@ 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 interface = device.claim_interface(0).unwrap();
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
/// # let interface = device.claim_interface(0).wait().unwrap();
///
/// let data: Vec<u8> = block_on(interface.control_in(ControlIn {
/// control_type: ControlType::Vendor,
@@ -459,10 +473,11 @@ 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 interface = device.claim_interface(0).unwrap();
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
/// # let interface = device.claim_interface(0).wait().unwrap();
///
/// block_on(interface.control_out(ControlOut {
/// control_type: ControlType::Vendor,
@@ -567,8 +582,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.
4 changes: 2 additions & 2 deletions src/enumeration.rs
Original file line number Diff line number Diff line change
@@ -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)]
@@ -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)
}
}
75 changes: 75 additions & 0 deletions src/ioaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use std::future::IntoFuture;

/// IO that may be performed synchronously or asynchronously.
///
/// An `IOAction` can be run asynchronously with `.await`, or
/// run synchronously (blocking the current thread) with `.wait()`.
pub trait IoAction: IntoFuture {
/// Block waiting for the action to complete
#[cfg(not(target_arch = "wasm32"))]
fn wait(self) -> Self::Output;
}

#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "windows",
target_os = "macos"
))]
pub mod blocking {
use super::IoAction;
use std::future::IntoFuture;

/// Wrapper that invokes a FnOnce on a background thread when
/// called asynchronously, or directly when called synchronously.
pub struct Blocking<F> {
f: F,
}

impl<F> Blocking<F> {
pub fn new(f: F) -> Self {
Self { f }
}
}

impl<F, R> IntoFuture for Blocking<F>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
type Output = R;

type IntoFuture = blocking::Task<R, ()>;

fn into_future(self) -> Self::IntoFuture {
blocking::unblock(self.f)
}
}

impl<F, R> IoAction for Blocking<F>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
fn wait(self) -> R {
(self.f)()
}
}
}

pub(crate) struct Ready<T>(pub(crate) T);

impl<T> IntoFuture for Ready<T> {
type Output = T;
type IntoFuture = std::future::Ready<T>;

fn into_future(self) -> Self::IntoFuture {
std::future::ready(self.0)
}
}

impl<T> IoAction for Ready<T> {
fn wait(self) -> Self::Output {
self.0
}
}
20 changes: 12 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -129,6 +129,9 @@ pub mod transfer;

pub mod hotplug;

mod ioaction;
pub use ioaction::IoAction;

/// OS error returned from operations other than transfers.
pub type Error = io::Error;

@@ -137,12 +140,12 @@ pub type Error = io::Error;
/// ### Example
///
/// ```no_run
/// use nusb;
/// let device = nusb::list_devices().unwrap()
/// use nusb::{self, IoAction};
/// let device = nusb::list_devices().wait().unwrap()
/// .find(|dev| dev.vendor_id() == 0xAAAA && dev.product_id() == 0xBBBB)
/// .expect("device not connected");
/// ```
pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
pub fn list_devices() -> impl IoAction<Output = Result<impl Iterator<Item = DeviceInfo>, Error>> {
platform::list_devices()
}

@@ -154,9 +157,10 @@ pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
///
/// ```no_run
/// use std::collections::HashMap;
/// use nusb::IoAction;
///
/// let devices = nusb::list_devices().unwrap().collect::<Vec<_>>();
/// let buses: HashMap<String, (nusb::BusInfo, Vec::<nusb::DeviceInfo>)> = nusb::list_buses().unwrap()
/// let devices = nusb::list_devices().wait().unwrap().collect::<Vec<_>>();
/// let buses: HashMap<String, (nusb::BusInfo, Vec::<nusb::DeviceInfo>)> = nusb::list_buses().wait().unwrap()
/// .map(|bus| {
/// let bus_id = bus.bus_id().to_owned();
/// (bus.bus_id().to_owned(), (bus, devices.clone().into_iter().filter(|dev| dev.bus_id() == bus_id).collect()))
@@ -167,7 +171,7 @@ pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
/// ### Platform-specific notes
/// * On Linux, the abstraction of the "bus" is a phony device known as the root hub. This device is available at bus.root_hub()
/// * On Android, this will only work on rooted devices due to sysfs path usage
pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
pub fn list_buses() -> impl IoAction<Output = Result<impl Iterator<Item = BusInfo>, Error>> {
platform::list_buses()
}

@@ -184,9 +188,9 @@ pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
///
/// ```no_run
/// use std::collections::HashMap;
/// use nusb::{DeviceInfo, DeviceId, hotplug::HotplugEvent};
/// use nusb::{IoAction, DeviceInfo, DeviceId, hotplug::HotplugEvent};
/// let watch = nusb::watch_devices().unwrap();
/// let mut devices: HashMap<DeviceId, DeviceInfo> = nusb::list_devices().unwrap()
/// let mut devices: HashMap<DeviceId, DeviceInfo> = nusb::list_devices().wait().unwrap()
/// .map(|d| (d.id(), d)).collect();
/// for event in futures_lite::stream::block_on(watch) {
/// match event {
154 changes: 90 additions & 64 deletions src/platform/linux_usbfs/device.rs
Original file line number Diff line number Diff line change
@@ -26,8 +26,10 @@ use super::{
SysfsPath,
};
use crate::descriptors::{validate_device_descriptor, Configuration, DeviceDescriptor};
use crate::ioaction::blocking::Blocking;
use crate::platform::linux_usbfs::events::Watch;
use crate::transfer::{ControlType, Recipient};
use crate::IoAction;
use crate::{
descriptors::{parse_concatenated_config_descriptors, DESCRIPTOR_LEN_DEVICE},
transfer::{
@@ -48,33 +50,39 @@ pub(crate) struct LinuxDevice {
}

impl LinuxDevice {
pub(crate) fn from_device_info(d: &DeviceInfo) -> Result<Arc<LinuxDevice>, Error> {
pub(crate) fn from_device_info(
d: &DeviceInfo,
) -> impl IoAction<Output = Result<crate::Device, Error>> {
let busnum = d.busnum();
let devnum = d.device_address();
let active_config = d.path.read_attr("bConfigurationValue")?;
let sysfs_path = d.path.clone();

let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"));
let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())
.inspect_err(|e| warn!("Failed to open device {path:?}: {e}"))?;
Blocking::new(move || {
let active_config = sysfs_path.read_attr("bConfigurationValue")?;
let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"));
let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())
.inspect_err(|e| warn!("Failed to open device {path:?}: {e}"))?;

let inner = Self::create_inner(fd, Some(d.path.clone()), Some(active_config));
if inner.is_ok() {
debug!("Opened device bus={busnum} addr={devnum}",);
}
inner
let inner = Self::create_inner(fd, Some(sysfs_path), Some(active_config));
if inner.is_ok() {
debug!("Opened device bus={busnum} addr={devnum}",);
}
inner
})
}

pub(crate) fn from_fd(fd: OwnedFd) -> Result<Arc<LinuxDevice>, Error> {
debug!("Wrapping fd {} as usbfs device", fd.as_raw_fd());

Self::create_inner(fd, None, None)
pub(crate) fn from_fd(fd: OwnedFd) -> impl IoAction<Output = Result<crate::Device, Error>> {
Blocking::new(move || {
debug!("Wrapping fd {} as usbfs device", fd.as_raw_fd());
Self::create_inner(fd, None, None)
})
}

pub(crate) fn create_inner(
fd: OwnedFd,
sysfs: Option<SysfsPath>,
active_config: Option<u8>,
) -> Result<Arc<LinuxDevice>, Error> {
) -> Result<crate::Device, Error> {
let descriptors = {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd.as_raw_fd())) };
// NOTE: Seek required on android
@@ -123,7 +131,7 @@ impl LinuxDevice {
error!("Failed to initialize event loop: {err}");
Err(err)
} else {
Ok(arc)
Ok(crate::Device::wrap(arc))
}
}

@@ -182,15 +190,22 @@ impl LinuxDevice {
self.active_config.load(Ordering::SeqCst)
}

pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> {
usbfs::set_configuration(&self.fd, configuration)?;
self.active_config.store(configuration, Ordering::SeqCst);
Ok(())
pub(crate) fn set_configuration(
self: Arc<Self>,
configuration: u8,
) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
usbfs::set_configuration(&self.fd, configuration)?;
self.active_config.store(configuration, Ordering::SeqCst);
Ok(())
})
}

pub(crate) fn reset(&self) -> Result<(), Error> {
usbfs::reset(&self.fd)?;
Ok(())
pub(crate) fn reset(self: Arc<Self>) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
usbfs::reset(&self.fd)?;
Ok(())
})
}

/// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction`
@@ -265,40 +280,44 @@ impl LinuxDevice {
}

pub(crate) fn claim_interface(
self: &Arc<Self>,
self: Arc<Self>,
interface_number: u8,
) -> Result<Arc<LinuxInterface>, Error> {
usbfs::claim_interface(&self.fd, interface_number).inspect_err(|e| {
warn!(
"Failed to claim interface {interface_number} on device id {dev}: {e}",
) -> impl IoAction<Output = Result<crate::Interface, Error>> {
Blocking::new(move || {
usbfs::claim_interface(&self.fd, interface_number).inspect_err(|e| {
warn!(
"Failed to claim interface {interface_number} on device id {dev}: {e}",
dev = self.events_id
)
})?;
debug!(
"Claimed interface {interface_number} on device id {dev}",
dev = self.events_id
)
})?;
debug!(
"Claimed interface {interface_number} on device id {dev}",
dev = self.events_id
);
Ok(Arc::new(LinuxInterface {
device: self.clone(),
interface_number,
reattach: false,
}))
);
Ok(crate::Interface::wrap(Arc::new(LinuxInterface {
device: self,
interface_number,
reattach: false,
})))
})
}

pub(crate) fn detach_and_claim_interface(
self: &Arc<Self>,
self: Arc<Self>,
interface_number: u8,
) -> Result<Arc<LinuxInterface>, Error> {
usbfs::detach_and_claim_interface(&self.fd, interface_number)?;
debug!(
"Detached and claimed interface {interface_number} on device id {dev}",
dev = self.events_id
);
Ok(Arc::new(LinuxInterface {
device: self.clone(),
interface_number,
reattach: true,
}))
) -> impl IoAction<Output = Result<crate::Interface, Error>> {
Blocking::new(move || {
usbfs::detach_and_claim_interface(&self.fd, interface_number)?;
debug!(
"Detached and claimed interface {interface_number} on device id {dev}",
dev = self.events_id
);
Ok(crate::Interface::wrap(Arc::new(LinuxInterface {
device: self,
interface_number,
reattach: true,
})))
})
}

#[cfg(target_os = "linux")]
@@ -478,21 +497,28 @@ impl LinuxInterface {
self.device.control_out_blocking(control, data, timeout)
}

pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> {
debug!(
"Set interface {} alt setting to {alt_setting}",
self.interface_number
);
Ok(usbfs::set_interface(
&self.device.fd,
self.interface_number,
alt_setting,
)?)
pub fn set_alt_setting(
self: Arc<Self>,
alt_setting: u8,
) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
debug!(
"Set interface {} alt setting to {alt_setting}",
self.interface_number
);
Ok(usbfs::set_interface(
&self.device.fd,
self.interface_number,
alt_setting,
)?)
})
}

pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> {
debug!("Clear halt, endpoint {endpoint:02x}");
Ok(usbfs::clear_halt(&self.device.fd, endpoint)?)
pub fn clear_halt(self: Arc<Self>, endpoint: u8) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
debug!("Clear halt, endpoint {endpoint:02x}");
Ok(usbfs::clear_halt(&self.device.fd, endpoint)?)
})
}
}

90 changes: 48 additions & 42 deletions src/platform/linux_usbfs/enumeration.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@ use log::debug;
use log::warn;

use crate::enumeration::InterfaceInfo;
use crate::ioaction::Ready;
use crate::IoAction;
use crate::{BusInfo, DeviceInfo, Error, Speed, UsbControllerType};

#[derive(Debug, Clone)]
@@ -119,27 +121,29 @@ impl FromHexStr for u16 {

const SYSFS_USB_PREFIX: &'static str = "/sys/bus/usb/devices/";

pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
Ok(fs::read_dir(SYSFS_USB_PREFIX)?.flat_map(|entry| {
let path = entry.ok()?.path();
let name = path.file_name()?;
pub fn list_devices() -> impl IoAction<Output = Result<impl Iterator<Item = DeviceInfo>, Error>> {
Ready((|| {
Ok(fs::read_dir(SYSFS_USB_PREFIX)?.flat_map(|entry| {
let path = entry.ok()?.path();
let name = path.file_name()?;

// Device names look like `1-6` or `1-6.4.2`
// We'll ignore:
// * root hubs (`usb1`) -- they're not useful to talk to and are not exposed on other platforms
// * interfaces (`1-6:1.0`)
if !name
.as_encoded_bytes()
.iter()
.all(|c| matches!(c, b'0'..=b'9' | b'-' | b'.'))
{
return None;
}
// Device names look like `1-6` or `1-6.4.2`
// We'll ignore:
// * root hubs (`usb1`) -- they're not useful to talk to and are not exposed on other platforms
// * interfaces (`1-6:1.0`)
if !name
.as_encoded_bytes()
.iter()
.all(|c| matches!(c, b'0'..=b'9' | b'-' | b'.'))
{
return None;
}

probe_device(SysfsPath(path))
.inspect_err(|e| warn!("{e}; ignoring device"))
.ok()
}))
probe_device(SysfsPath(path))
.inspect_err(|e| warn!("{e}; ignoring device"))
.ok()
}))
})())
}

pub fn list_root_hubs() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
@@ -158,29 +162,31 @@ pub fn list_root_hubs() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
}))
}

pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
Ok(list_root_hubs()?.filter_map(|rh| {
// get the parent by following the absolute symlink; root hub in /bus/usb is a symlink to a dir in parent bus
let parent_path = rh
.path
.0
.canonicalize()
.ok()
.and_then(|p| p.parent().map(|p| SysfsPath(p.to_owned())))?;

debug!("Probing parent device {:?}", parent_path.0);
let driver = parent_path.readlink_attr_filename("driver").ok();

Some(BusInfo {
bus_id: rh.bus_id.to_owned(),
path: rh.path.to_owned(),
parent_path: parent_path.to_owned(),
busnum: rh.busnum,
controller_type: driver.as_ref().and_then(|p| UsbControllerType::from_str(p)),
driver,
root_hub: rh,
})
}))
pub fn list_buses() -> impl IoAction<Output = Result<impl Iterator<Item = BusInfo>, Error>> {
Ready((|| {
Ok(list_root_hubs()?.filter_map(|rh| {
// get the parent by following the absolute symlink; root hub in /bus/usb is a symlink to a dir in parent bus
let parent_path = rh
.path
.0
.canonicalize()
.ok()
.and_then(|p| p.parent().map(|p| SysfsPath(p.to_owned())))?;

debug!("Probing parent device {:?}", parent_path.0);
let driver = parent_path.readlink_attr_filename("driver").ok();

Some(BusInfo {
bus_id: rh.bus_id.to_owned(),
path: rh.path.to_owned(),
parent_path: parent_path.to_owned(),
busnum: rh.busnum,
controller_type: driver.as_ref().and_then(|p| UsbControllerType::from_str(p)),
driver,
root_hub: rh,
})
}))
})())
}

pub fn probe_device(path: SysfsPath) -> Result<DeviceInfo, SysfsError> {
264 changes: 143 additions & 121 deletions src/platform/macos_iokit/device.rs
Original file line number Diff line number Diff line change
@@ -13,14 +13,14 @@ use log::{debug, error};

use crate::{
descriptors::DeviceDescriptor,
platform::macos_iokit::{enumeration::device_descriptor_from_fields, events::add_event_source},
ioaction::blocking::Blocking,
transfer::{Control, Direction, EndpointType, TransferError, TransferHandle},
DeviceInfo, Error, Speed,
DeviceInfo, Error, IoAction, Speed,
};

use super::{
enumeration::service_by_registry_id,
events::EventRegistration,
enumeration::{device_descriptor_from_fields, service_by_registry_id},
events::{add_event_source, EventRegistration},
iokit::{call_iokit_function, check_iokit_return},
iokit_c::IOUSBDevRequestTO,
iokit_usb::{EndpointInfo, IoKitDevice, IoKitInterface},
@@ -51,47 +51,53 @@ fn guess_active_config(dev: &IoKitDevice) -> Option<u8> {
}

impl MacDevice {
pub(crate) fn from_device_info(d: &DeviceInfo) -> Result<Arc<MacDevice>, Error> {
log::info!("Opening device from registry id {}", d.registry_id);
let service = service_by_registry_id(d.registry_id)?;
let device = IoKitDevice::new(&service)?;
let _event_registration = add_event_source(device.create_async_event_source()?);

let opened = match unsafe { call_iokit_function!(device.raw, USBDeviceOpen()) } {
io_kit_sys::ret::kIOReturnSuccess => true,
err => {
// Most methods don't require USBDeviceOpen() so this can be ignored
// to allow different processes to open different interfaces.
log::debug!("Could not open device for exclusive access: {err:x}");
false
}
};

let device_descriptor = device_descriptor_from_fields(&service).ok_or_else(|| {
Error::new(
ErrorKind::Other,
"could not read properties for device descriptor",
)
})?;
pub(crate) fn from_device_info(
d: &DeviceInfo,
) -> impl IoAction<Output = Result<crate::Device, Error>> {
let registry_id = d.registry_id;
let speed = d.speed;
Blocking::new(move || {
log::info!("Opening device from registry id {}", registry_id);
let service = service_by_registry_id(registry_id)?;
let device = IoKitDevice::new(&service)?;
let _event_registration = add_event_source(device.create_async_event_source()?);

let opened = match unsafe { call_iokit_function!(device.raw, USBDeviceOpen()) } {
io_kit_sys::ret::kIOReturnSuccess => true,
err => {
// Most methods don't require USBDeviceOpen() so this can be ignored
// to allow different processes to open different interfaces.
log::debug!("Could not open device for exclusive access: {err:x}");
false
}
};

let active_config = if let Some(active_config) = guess_active_config(&device) {
log::debug!("Active config from single descriptor is {}", active_config);
active_config
} else {
let res = device.get_configuration();
log::debug!("Active config from request is {:?}", res);
res.unwrap_or(0)
};
let device_descriptor = device_descriptor_from_fields(&service).ok_or_else(|| {
Error::new(
ErrorKind::Other,
"could not read properties for device descriptor",
)
})?;

let active_config = if let Some(active_config) = guess_active_config(&device) {
log::debug!("Active config from single descriptor is {}", active_config);
active_config
} else {
let res = device.get_configuration();
log::debug!("Active config from request is {:?}", res);
res.unwrap_or(0)
};

Ok(Arc::new(MacDevice {
_event_registration,
device,
device_descriptor,
speed: d.speed,
active_config: AtomicU8::new(active_config),
is_open_exclusive: Mutex::new(opened),
claimed_interfaces: AtomicUsize::new(0),
}))
Ok(crate::Device::wrap(Arc::new(MacDevice {
_event_registration,
device,
device_descriptor,
speed,
active_config: AtomicU8::new(active_config),
is_open_exclusive: Mutex::new(opened),
claimed_interfaces: AtomicUsize::new(0),
})))
})
}

pub(crate) fn device_descriptor(&self) -> DeviceDescriptor {
@@ -128,27 +134,34 @@ impl MacDevice {
Ok(())
}

pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
SetConfiguration(configuration)
))?
}
log::debug!("Set configuration {configuration}");
self.active_config.store(configuration, Ordering::SeqCst);
Ok(())
pub(crate) fn set_configuration(
self: Arc<Self>,
configuration: u8,
) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
SetConfiguration(configuration)
))?
}
log::debug!("Set configuration {configuration}");
self.active_config.store(configuration, Ordering::SeqCst);
Ok(())
})
}

pub(crate) fn reset(&self) -> Result<(), Error> {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
USBDeviceReEnumerate(0)
))
}
pub(crate) fn reset(self: Arc<Self>) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
USBDeviceReEnumerate(0)
))
}
})
}

/// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction`
@@ -217,38 +230,40 @@ impl MacDevice {
}

pub(crate) fn claim_interface(
self: &Arc<Self>,
self: Arc<Self>,
interface_number: u8,
) -> Result<Arc<MacInterface>, Error> {
let intf_service = self
.device
.create_interface_iterator()?
.nth(interface_number as usize)
.ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?;

let mut interface = IoKitInterface::new(intf_service)?;
let _event_registration = add_event_source(interface.create_async_event_source()?);

interface.open()?;

let endpoints = interface.endpoints()?;
debug!("Found endpoints: {endpoints:?}");

self.claimed_interfaces.fetch_add(1, Ordering::Acquire);

Ok(Arc::new(MacInterface {
device: self.clone(),
interface_number,
interface,
endpoints: Mutex::new(endpoints),
_event_registration,
}))
) -> impl IoAction<Output = Result<crate::Interface, Error>> {
Blocking::new(move || {
let intf_service = self
.device
.create_interface_iterator()?
.nth(interface_number as usize)
.ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?;

let mut interface = IoKitInterface::new(intf_service)?;
let _event_registration = add_event_source(interface.create_async_event_source()?);

interface.open()?;

let endpoints = interface.endpoints()?;
debug!("Found endpoints: {endpoints:?}");

self.claimed_interfaces.fetch_add(1, Ordering::Acquire);

Ok(crate::Interface::wrap(Arc::new(MacInterface {
device: self.clone(),
interface_number,
interface,
endpoints: Mutex::new(endpoints),
_event_registration,
})))
})
}

pub(crate) fn detach_and_claim_interface(
self: &Arc<Self>,
self: Arc<Self>,
interface: u8,
) -> Result<Arc<MacInterface>, Error> {
) -> impl IoAction<Output = Result<crate::Interface, Error>> {
self.claim_interface(interface)
}
}
@@ -317,44 +332,51 @@ impl MacInterface {
self.device.control_out_blocking(control, data, timeout)
}

pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> {
debug!(
"Set interface {} alt setting to {alt_setting}",
self.interface_number
);

let mut endpoints = self.endpoints.lock().unwrap();

unsafe {
check_iokit_return(call_iokit_function!(
self.interface.raw,
SetAlternateInterface(alt_setting)
))?;
}
pub fn set_alt_setting(
self: Arc<Self>,
alt_setting: u8,
) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
debug!(
"Set interface {} alt setting to {alt_setting}",
self.interface_number
);

let mut endpoints = self.endpoints.lock().unwrap();

unsafe {
check_iokit_return(call_iokit_function!(
self.interface.raw,
SetAlternateInterface(alt_setting)
))?;
}

*endpoints = self.interface.endpoints()?;
debug!("Found endpoints: {endpoints:?}");
*endpoints = self.interface.endpoints()?;
debug!("Found endpoints: {endpoints:?}");

Ok(())
Ok(())
})
}

pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> {
debug!("Clear halt, endpoint {endpoint:02x}");
pub fn clear_halt(self: Arc<Self>, endpoint: u8) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
debug!("Clear halt, endpoint {endpoint:02x}");

let pipe_ref = {
let endpoints = self.endpoints.lock().unwrap();
let ep = endpoints
.get(&endpoint)
.ok_or_else(|| Error::new(ErrorKind::NotFound, "Endpoint not found"))?;
ep.pipe_ref
};
let pipe_ref = {
let endpoints = self.endpoints.lock().unwrap();
let ep = endpoints
.get(&endpoint)
.ok_or_else(|| Error::new(ErrorKind::NotFound, "Endpoint not found"))?;
ep.pipe_ref
};

unsafe {
check_iokit_return(call_iokit_function!(
self.interface.raw,
ClearPipeStallBothEnds(pipe_ref)
))
}
unsafe {
check_iokit_return(call_iokit_function!(
self.interface.raw,
ClearPipeStallBothEnds(pipe_ref)
))
}
})
}
}

14 changes: 7 additions & 7 deletions src/platform/macos_iokit/enumeration.rs
Original file line number Diff line number Diff line change
@@ -16,8 +16,8 @@ use io_kit_sys::{
use log::debug;

use crate::{
descriptors::DeviceDescriptor, BusInfo, DeviceInfo, Error, InterfaceInfo, Speed,
UsbControllerType,
descriptors::DeviceDescriptor, ioaction::Ready, BusInfo, DeviceInfo, Error, InterfaceInfo,
IoAction, Speed, UsbControllerType,
};

use super::iokit::{IoService, IoServiceIterator};
@@ -79,14 +79,14 @@ fn usb_controller_service_iter(
}
}

pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
Ok(usb_service_iter()?.filter_map(probe_device))
pub fn list_devices() -> impl IoAction<Output = Result<impl Iterator<Item = DeviceInfo>, Error>> {
Ready(usb_service_iter().map(|i| i.filter_map(probe_device)))
}

pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
pub fn list_buses() -> impl IoAction<Output = Result<impl Iterator<Item = BusInfo>, Error>> {
// Chain all the HCI types into one iterator
// A bit of a hack, could maybe probe IOPCIDevice and filter on children with IOClass.starts_with("AppleUSB")
Ok([
Ready(Ok([
UsbControllerType::XHCI,
UsbControllerType::EHCI,
UsbControllerType::OHCI,
@@ -97,7 +97,7 @@ pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
usb_controller_service_iter(hci_type)
.map(|iter| iter.flat_map(|dev| probe_bus(dev, hci_type)))
})
.flatten())
.flatten()))
}

pub(crate) fn service_by_registry_id(registry_id: u64) -> Result<IoService, Error> {
143 changes: 84 additions & 59 deletions src/platform/windows_winusb/device.rs
Original file line number Diff line number Diff line change
@@ -27,8 +27,9 @@ use crate::{
validate_config_descriptor, DeviceDescriptor, DESCRIPTOR_LEN_DEVICE,
DESCRIPTOR_TYPE_CONFIGURATION,
},
ioaction::{blocking::Blocking, Ready},
transfer::{Control, Direction, EndpointType, Recipient, TransferError, TransferHandle},
DeviceInfo, Error, Speed,
DeviceInfo, Error, IoAction, Speed,
};

use super::{
@@ -50,45 +51,51 @@ pub(crate) struct WindowsDevice {
}

impl WindowsDevice {
pub(crate) fn from_device_info(d: &DeviceInfo) -> Result<Arc<WindowsDevice>, Error> {
debug!("Creating device for {:?}", d.instance_id);

// Look up the device again in case the DeviceInfo is stale. In
// particular, don't trust its `port_number` because another device
// might now be connected to that port, and we'd get its descriptors
// instead.
let hub_port = HubPort::by_child_devinst(d.devinst)?;
let connection_info = hub_port.get_info()?;

// Safety: Windows API struct is repr(C), packed, and we're assuming Windows is little-endian
let device_descriptor = unsafe {
DeviceDescriptor::new(&transmute::<_, [u8; DESCRIPTOR_LEN_DEVICE as usize]>(
connection_info.device_desc,
))
};

let num_configurations = connection_info.device_desc.bNumConfigurations;
let config_descriptors = (0..num_configurations)
.flat_map(|i| {
let res = hub_port.get_descriptor(DESCRIPTOR_TYPE_CONFIGURATION, i, 0);
match res {
Ok(v) => validate_config_descriptor(&v[..]).map(|_| v),
Err(e) => {
error!("Failed to read config descriptor {}: {}", i, e);
None
pub(crate) fn from_device_info(
d: &DeviceInfo,
) -> impl IoAction<Output = Result<crate::Device, Error>> {
let instance_id = d.instance_id.clone();
let devinst = d.devinst;
Blocking::new(move || {
debug!("Creating device for {:?}", instance_id);

// Look up the device again in case the DeviceInfo is stale. In
// particular, don't trust its `port_number` because another device
// might now be connected to that port, and we'd get its descriptors
// instead.
let hub_port = HubPort::by_child_devinst(devinst)?;
let connection_info = hub_port.get_info()?;

// Safety: Windows API struct is repr(C), packed, and we're assuming Windows is little-endian
let device_descriptor = unsafe {
DeviceDescriptor::new(&transmute::<_, [u8; DESCRIPTOR_LEN_DEVICE as usize]>(
connection_info.device_desc,
))
};

let num_configurations = connection_info.device_desc.bNumConfigurations;
let config_descriptors = (0..num_configurations)
.flat_map(|i| {
let res = hub_port.get_descriptor(DESCRIPTOR_TYPE_CONFIGURATION, i, 0);
match res {
Ok(v) => validate_config_descriptor(&v[..]).map(|_| v),
Err(e) => {
error!("Failed to read config descriptor {}: {}", i, e);
None
}
}
}
})
.collect();

Ok(Arc::new(WindowsDevice {
device_descriptor,
config_descriptors,
speed: connection_info.speed,
active_config: connection_info.active_config,
devinst: d.devinst,
handles: Mutex::new(BTreeMap::new()),
}))
})
.collect();

Ok(crate::Device::wrap(Arc::new(WindowsDevice {
device_descriptor,
config_descriptors,
speed: connection_info.speed,
active_config: connection_info.active_config,
devinst: devinst,
handles: Mutex::new(BTreeMap::new()),
})))
})
}

pub(crate) fn device_descriptor(&self) -> DeviceDescriptor {
@@ -107,11 +114,14 @@ impl WindowsDevice {
self.config_descriptors.iter().map(|d| &d[..])
}

pub(crate) fn set_configuration(&self, _configuration: u8) -> Result<(), Error> {
Err(io::Error::new(
pub(crate) fn set_configuration(
&self,
_configuration: u8,
) -> impl IoAction<Output = Result<(), Error>> {
Ready(Err(io::Error::new(
ErrorKind::Unsupported,
"set_configuration not supported by WinUSB",
))
)))
}

pub(crate) fn get_descriptor(
@@ -123,14 +133,24 @@ impl WindowsDevice {
HubPort::by_child_devinst(self.devinst)?.get_descriptor(desc_type, desc_index, language_id)
}

pub(crate) fn reset(&self) -> Result<(), Error> {
Err(io::Error::new(
pub(crate) fn reset(&self) -> impl IoAction<Output = Result<(), Error>> {
Ready(Err(io::Error::new(
ErrorKind::Unsupported,
"reset not supported by WinUSB",
))
)))
}

pub(crate) fn claim_interface(
self: Arc<Self>,
interface_number: u8,
) -> impl IoAction<Output = Result<crate::Interface, Error>> {
Blocking::new(move || {
self.claim_interface_blocking(interface_number)
.map(crate::Interface::wrap)
})
}

fn claim_interface_blocking(
self: &Arc<Self>,
interface_number: u8,
) -> Result<Arc<WindowsInterface>, Error> {
@@ -177,9 +197,9 @@ impl WindowsDevice {
}

pub(crate) fn detach_and_claim_interface(
self: &Arc<Self>,
self: Arc<Self>,
interface: u8,
) -> Result<Arc<WindowsInterface>, Error> {
) -> impl IoAction<Output = Result<crate::Interface, Error>> {
self.claim_interface(interface)
}
}
@@ -478,26 +498,31 @@ impl WindowsInterface {
}
}

pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> {
unsafe {
pub fn set_alt_setting(
self: Arc<Self>,
alt_setting: u8,
) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || unsafe {
let r = WinUsb_SetCurrentAlternateSetting(self.winusb_handle, alt_setting.into());
if r == TRUE {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
})
}

pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> {
debug!("Clear halt, endpoint {endpoint:02x}");
unsafe {
let r = WinUsb_ResetPipe(self.winusb_handle, endpoint);
if r == TRUE {
Ok(())
} else {
Err(io::Error::last_os_error())
pub fn clear_halt(self: Arc<Self>, endpoint: u8) -> impl IoAction<Output = Result<(), Error>> {
Blocking::new(move || {
debug!("Clear halt, endpoint {endpoint:02x}");
unsafe {
let r = WinUsb_ResetPipe(self.winusb_handle, endpoint);
if r == TRUE {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
})
}
}
43 changes: 24 additions & 19 deletions src/platform/windows_winusb/enumeration.rs
Original file line number Diff line number Diff line change
@@ -18,7 +18,8 @@ use crate::{
decode_string_descriptor, language_id::US_ENGLISH, validate_config_descriptor,
Configuration, DESCRIPTOR_TYPE_CONFIGURATION, DESCRIPTOR_TYPE_STRING,
},
BusInfo, DeviceInfo, Error, InterfaceInfo, UsbControllerType,
ioaction::blocking::Blocking,
BusInfo, DeviceInfo, Error, InterfaceInfo, IoAction, UsbControllerType,
};

use super::{
@@ -27,26 +28,30 @@ use super::{
util::WCString,
};

pub fn list_devices() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
let devs: Vec<DeviceInfo> = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_DEVICE, None)
// get USB_HUB devices as well, like other platforms. ROOT_HUBs will be dropped by probe_device
.iter()
.chain(cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None).iter())
.flat_map(|i| get_device_interface_property::<WCString>(i, DEVPKEY_Device_InstanceId))
.flat_map(|d| DevInst::from_instance_id(&d))
.flat_map(probe_device)
.collect();
Ok(devs.into_iter())
pub fn list_devices() -> impl IoAction<Output = Result<impl Iterator<Item = DeviceInfo>, Error>> {
Blocking::new(|| {
let devs: Vec<DeviceInfo> = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_DEVICE, None)
// get USB_HUB devices as well, like other platforms. ROOT_HUBs will be dropped by probe_device
.iter()
.chain(cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None).iter())
.flat_map(|i| get_device_interface_property::<WCString>(i, DEVPKEY_Device_InstanceId))
.flat_map(|d| DevInst::from_instance_id(&d))
.flat_map(probe_device)
.collect();
Ok(devs.into_iter())
})
}

pub fn list_buses() -> Result<impl Iterator<Item = BusInfo>, Error> {
let devs: Vec<BusInfo> = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None)
.iter()
.flat_map(|i| get_device_interface_property::<WCString>(i, DEVPKEY_Device_InstanceId))
.flat_map(|d| DevInst::from_instance_id(&d))
.flat_map(probe_bus)
.collect();
Ok(devs.into_iter())
pub fn list_buses() -> impl IoAction<Output = Result<impl Iterator<Item = BusInfo>, Error>> {
Blocking::new(|| {
let devs: Vec<BusInfo> = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None)
.iter()
.flat_map(|i| get_device_interface_property::<WCString>(i, DEVPKEY_Device_InstanceId))
.flat_map(|d| DevInst::from_instance_id(&d))
.flat_map(probe_bus)
.collect();
Ok(devs.into_iter())
})
}

pub fn probe_device(devinst: DevInst) -> Option<DeviceInfo> {
20 changes: 11 additions & 9 deletions src/transfer/queue.rs
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ use std::{
task::{Context, Poll},
};

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

use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRequest};

@@ -52,9 +52,10 @@ use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRe
/// ```no_run
/// use futures_lite::future::block_on;
/// use nusb::transfer::RequestBuffer;
/// # let di = nusb::list_devices().unwrap().next().unwrap();
/// # let device = di.open().unwrap();
/// # let interface = device.claim_interface(0).unwrap();
/// # use nusb::IoAction;
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
/// # let interface = device.claim_interface(0).wait().unwrap();
/// # fn handle_data(_: &[u8]) {}
/// let mut queue = interface.bulk_in_queue(0x81);
///
@@ -81,9 +82,10 @@ use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRe
/// ```no_run
/// use std::mem;
/// use futures_lite::future::block_on;
/// # let di = nusb::list_devices().unwrap().next().unwrap();
/// # let device = di.open().unwrap();
/// # let interface = device.claim_interface(0).unwrap();
/// # use nusb::IoAction;
/// # let di = nusb::list_devices().wait().unwrap().next().unwrap();
/// # let device = di.open().wait().unwrap();
/// # let interface = device.claim_interface(0).wait().unwrap();
/// # fn fill_data(_: &mut Vec<u8>) {}
/// # fn data_confirmed_sent(_: usize) {}
/// let mut queue = interface.bulk_out_queue(0x02);
@@ -221,8 +223,8 @@ where
/// the error and resume use of the endpoint.
///
/// This should not be called when transfers are pending on the endpoint.
pub fn clear_halt(&mut self) -> Result<(), Error> {
self.interface.clear_halt(self.endpoint)
pub fn clear_halt(&mut self) -> impl IoAction<Output = Result<(), Error>> {
self.interface.clone().clear_halt(self.endpoint)
}
}

0 comments on commit a4662e6

Please sign in to comment.