From a4662e6ca67188f4fa911947548294b9368ba323 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 29 Dec 2024 23:04:47 -0700 Subject: [PATCH] Make blocking APIs optionally async `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. --- Cargo.toml | 3 + examples/blocking.rs | 10 +- examples/bulk.rs | 7 +- examples/buses.rs | 4 +- examples/control.rs | 10 +- examples/descriptors.rs | 6 +- examples/detach.rs | 5 +- examples/detach_claim.rs | 7 +- examples/list.rs | 4 +- examples/string_descriptors.rs | 6 +- src/device.rs | 85 ++++--- src/enumeration.rs | 4 +- src/ioaction.rs | 75 ++++++ src/lib.rs | 20 +- src/platform/linux_usbfs/device.rs | 154 +++++++----- src/platform/linux_usbfs/enumeration.rs | 90 +++---- src/platform/macos_iokit/device.rs | 264 +++++++++++---------- src/platform/macos_iokit/enumeration.rs | 14 +- src/platform/windows_winusb/device.rs | 143 ++++++----- src/platform/windows_winusb/enumeration.rs | 43 ++-- src/transfer/queue.rs | 20 +- 21 files changed, 588 insertions(+), 386 deletions(-) create mode 100644 src/ioaction.rs diff --git a/Cargo.toml b/Cargo.toml index a077dca..202f35e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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)'] } diff --git a/examples/blocking.rs b/examples/blocking.rs index 01e8bb4..d220e0b 100644 --- a/examples/blocking.rs +++ b/examples/blocking.rs @@ -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 { diff --git a/examples/bulk.rs b/examples/bulk.rs index 5b06757..b947b7b 100644 --- a/examples/bulk.rs +++ b/examples/bulk.rs @@ -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() diff --git a/examples/buses.rs b/examples/buses.rs index 1c4aac7..48468f9 100644 --- a/examples/buses.rs +++ b/examples/buses.rs @@ -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); } } diff --git a/examples/control.rs b/examples/control.rs index fce8d71..f531fad 100644 --- a/examples/control.rs +++ b/examples/control.rs @@ -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, diff --git a/examples/descriptors.rs b/examples/descriptors.rs index 98f46ff..859ec67 100644 --- a/examples/descriptors.rs +++ b/examples/descriptors.rs @@ -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); diff --git a/examples/detach.rs b/examples/detach.rs index 1c43055..b450d5a 100644 --- a/examples/detach.rs +++ b/examples/detach.rs @@ -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(); diff --git a/examples/detach_claim.rs b/examples/detach_claim.rs index 908a053..f85e862 100644 --- a/examples/detach_claim.rs +++ b/examples/detach_claim.rs @@ -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); } diff --git a/examples/list.rs b/examples/list.rs index a84fa56..1210f7a 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -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); } } diff --git a/examples/string_descriptors.rs b/examples/string_descriptors.rs index a2fdcc2..e2b1016 100644 --- a/examples/string_descriptors.rs +++ b/examples/string_descriptors.rs @@ -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); diff --git a/src/device.rs b/src/device.rs index a54d0ec..471b55e 100644 --- a/src/device.rs +++ b/src/device.rs @@ -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,23 +39,26 @@ pub struct Device { } impl Device { - pub(crate) fn open(d: &DeviceInfo) -> Result { - let backend = platform::Device::from_device_info(d)?; - Ok(Device { backend }) + pub(crate) fn wrap(backend: Arc) -> Device { + Device { backend } + } + + pub(crate) fn open(d: &DeviceInfo) -> impl IoAction> { + 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 { - Ok(Device { - backend: platform::Device::from_fd(fd)?, - }) + pub fn from_fd(fd: std::os::fd::OwnedFd) -> impl IoAction> { + 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 { - let backend = self.backend.claim_interface(interface)?; - Ok(Interface { backend }) + pub fn claim_interface( + &self, + interface: u8, + ) -> impl IoAction> { + self.backend.clone().claim_interface(interface) } /// Detach kernel drivers and open an interface of the device and claim it for exclusive use. @@ -63,9 +66,11 @@ impl Device { /// ### 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 }) + pub fn detach_and_claim_interface( + &self, + interface: u8, + ) -> impl IoAction> { + 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> { + 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> { + 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 = 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) -> 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> { + 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 = 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> { + self.backend.clone().clear_halt(endpoint) } /// Get the interface number. diff --git a/src/enumeration.rs b/src/enumeration.rs index 4acc573..3b288db 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -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 { + pub fn open(&self) -> impl IoAction> { Device::open(self) } } diff --git a/src/ioaction.rs b/src/ioaction.rs new file mode 100644 index 0000000..167c685 --- /dev/null +++ b/src/ioaction.rs @@ -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, + } + + impl Blocking { + pub fn new(f: F) -> Self { + Self { f } + } + } + + impl IntoFuture for Blocking + where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, + { + type Output = R; + + type IntoFuture = blocking::Task; + + fn into_future(self) -> Self::IntoFuture { + blocking::unblock(self.f) + } + } + + impl IoAction for Blocking + where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, + { + fn wait(self) -> R { + (self.f)() + } + } +} + +pub(crate) struct Ready(pub(crate) T); + +impl IntoFuture for Ready { + type Output = T; + type IntoFuture = std::future::Ready; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.0) + } +} + +impl IoAction for Ready { + fn wait(self) -> Self::Output { + self.0 + } +} diff --git a/src/lib.rs b/src/lib.rs index a4ab9cf..bc187f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, Error> { +pub fn list_devices() -> impl IoAction, Error>> { platform::list_devices() } @@ -154,9 +157,10 @@ pub fn list_devices() -> Result, Error> { /// /// ```no_run /// use std::collections::HashMap; +/// use nusb::IoAction; /// -/// let devices = nusb::list_devices().unwrap().collect::>(); -/// let buses: HashMap)> = nusb::list_buses().unwrap() +/// let devices = nusb::list_devices().wait().unwrap().collect::>(); +/// let buses: HashMap)> = 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, 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, Error> { +pub fn list_buses() -> impl IoAction, Error>> { platform::list_buses() } @@ -184,9 +188,9 @@ pub fn list_buses() -> Result, 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 = nusb::list_devices().unwrap() +/// let mut devices: HashMap = nusb::list_devices().wait().unwrap() /// .map(|d| (d.id(), d)).collect(); /// for event in futures_lite::stream::block_on(watch) { /// match event { diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index a078e04..ae9f16b 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -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, Error> { + pub(crate) fn from_device_info( + d: &DeviceInfo, + ) -> impl IoAction> { 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, 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> { + 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, active_config: Option, - ) -> Result, Error> { + ) -> Result { 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, + configuration: u8, + ) -> impl IoAction> { + 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) -> impl IoAction> { + 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: Arc, interface_number: u8, - ) -> Result, 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> { + 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: Arc, interface_number: u8, - ) -> Result, 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> { + 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, + alt_setting: u8, + ) -> impl IoAction> { + 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, endpoint: u8) -> impl IoAction> { + Blocking::new(move || { + debug!("Clear halt, endpoint {endpoint:02x}"); + Ok(usbfs::clear_halt(&self.device.fd, endpoint)?) + }) } } diff --git a/src/platform/linux_usbfs/enumeration.rs b/src/platform/linux_usbfs/enumeration.rs index 5fbca77..28fb6de 100644 --- a/src/platform/linux_usbfs/enumeration.rs +++ b/src/platform/linux_usbfs/enumeration.rs @@ -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, 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, 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, Error> { @@ -158,29 +162,31 @@ pub fn list_root_hubs() -> Result, Error> { })) } -pub fn list_buses() -> Result, 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, 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 { diff --git a/src/platform/macos_iokit/device.rs b/src/platform/macos_iokit/device.rs index 65eb3c9..645dd92 100644 --- a/src/platform/macos_iokit/device.rs +++ b/src/platform/macos_iokit/device.rs @@ -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 { } impl MacDevice { - pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, 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> { + 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, + configuration: u8, + ) -> impl IoAction> { + 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) -> impl IoAction> { + 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: Arc, interface_number: u8, - ) -> Result, 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> { + 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: Arc, interface: u8, - ) -> Result, Error> { + ) -> impl IoAction> { 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, + alt_setting: u8, + ) -> impl IoAction> { + 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, endpoint: u8) -> impl IoAction> { + 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) + )) + } + }) } } diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index 0db3fd7..1f945c2 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -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, Error> { - Ok(usb_service_iter()?.filter_map(probe_device)) +pub fn list_devices() -> impl IoAction, Error>> { + Ready(usb_service_iter().map(|i| i.filter_map(probe_device))) } -pub fn list_buses() -> Result, Error> { +pub fn list_buses() -> impl IoAction, 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, 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 { diff --git a/src/platform/windows_winusb/device.rs b/src/platform/windows_winusb/device.rs index e0a3b2f..39bcddc 100644 --- a/src/platform/windows_winusb/device.rs +++ b/src/platform/windows_winusb/device.rs @@ -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, 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> { + 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> { + 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> { + Ready(Err(io::Error::new( ErrorKind::Unsupported, "reset not supported by WinUSB", - )) + ))) } pub(crate) fn claim_interface( + self: Arc, + interface_number: u8, + ) -> impl IoAction> { + Blocking::new(move || { + self.claim_interface_blocking(interface_number) + .map(crate::Interface::wrap) + }) + } + + fn claim_interface_blocking( self: &Arc, interface_number: u8, ) -> Result, Error> { @@ -177,9 +197,9 @@ impl WindowsDevice { } pub(crate) fn detach_and_claim_interface( - self: &Arc, + self: Arc, interface: u8, - ) -> Result, Error> { + ) -> impl IoAction> { 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, + alt_setting: u8, + ) -> impl IoAction> { + 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, endpoint: u8) -> impl IoAction> { + 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()) + } } - } + }) } } diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index e45b9f6..6d13137 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -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, Error> { - let devs: Vec = 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::(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, Error>> { + Blocking::new(|| { + let devs: Vec = 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::(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, Error> { - let devs: Vec = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None) - .iter() - .flat_map(|i| get_device_interface_property::(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, Error>> { + Blocking::new(|| { + let devs: Vec = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_HUB, None) + .iter() + .flat_map(|i| get_device_interface_property::(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 { diff --git a/src/transfer/queue.rs b/src/transfer/queue.rs index a6e1310..758e530 100644 --- a/src/transfer/queue.rs +++ b/src/transfer/queue.rs @@ -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) {} /// # 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> { + self.interface.clone().clear_halt(self.endpoint) } }