239 lines
9.5 KiB
Rust
239 lines
9.5 KiB
Rust
#![warn(missing_docs)]
|
|
//! A new library for cross-platform low-level access to USB devices.
|
|
//!
|
|
//! `nusb` is comparable to the C library [libusb] and its Rust bindings [rusb],
|
|
//! but written in pure Rust. It supports usage from both async and
|
|
//! blocking contexts, and transfers are natively async.
|
|
//!
|
|
//! [libusb]: https://libusb.info
|
|
//! [rusb]: https://docs.rs/rusb/
|
|
//!
|
|
//! Use `nusb` to write user-space drivers in Rust for non-standard USB devices
|
|
//! or those without kernel support. For devices implementing a standard USB
|
|
//! class such as Mass Storage, CDC (Serial), HID, Audio, or Video, this is
|
|
//! probably not the library you're looking for -- use something built on the
|
|
//! existing kernel driver instead. (On some platforms you could detach or
|
|
//! replace the kernel driver and program the device from user-space using this
|
|
//! library, but you'd have to re-implement the class functionality yourself.)
|
|
//!
|
|
//! ## USB and usage overview
|
|
//!
|
|
//! When a USB device connects, the OS queries the device descriptor containing
|
|
//! basic device information such as the vendor and product ID (VID / PID) and
|
|
//! string descriptors like the manufacturer, product, and serial number
|
|
//! strings. [`list_devices`] returns an iterator listing connected USB devices,
|
|
//! which can be filtered by these fields to identify and select the desired
|
|
//! device.
|
|
//!
|
|
//! Call [`device_info.open()`](`DeviceInfo::open`) to open a selected device.
|
|
//! Additional information about the device can be queried with
|
|
//! [`device.active_configuration()`](`Device::active_configuration`).
|
|
//!
|
|
//! USB devices consist of one or more interfaces exposing a group of
|
|
//! functionality. A device with multiple interfaces is known as a composite
|
|
//! device. To open an interface, call [`Device::claim_interface`]. Only one
|
|
//! program (or kernel driver) may claim an interface at a time.
|
|
//!
|
|
//! Use the resulting [`Interface`] to perform control transfers or open
|
|
//! an [`Endpoint`] to perform bulk or interrupt transfers. Submitting a
|
|
//! transfer is a non-blocking operation that adds the transfer to an
|
|
//! internal queue for the endpoint. Completed transfers can be popped
|
|
//! from the queue synchronously or asynchronously.
|
|
//!
|
|
//! *For more details on how USB works, [USB in a
|
|
//! Nutshell](https://beyondlogic.org/usbnutshell/usb1.shtml) is a good
|
|
//! overview.*
|
|
//!
|
|
//! ## Logging
|
|
//!
|
|
//! `nusb` uses the [`log`](https://docs.rs/log) crate to log debug and error
|
|
//! information.
|
|
//!
|
|
//! When [submitting a bug report][gh-issues], please include the logs: use a
|
|
//! `log` backend like [`env_logger`](https://docs.rs/env_logger) and configure
|
|
//! it to enable log output for this crate (for `env_logger` set environment
|
|
//! variable `RUST_LOG=nusb=debug`.)
|
|
//!
|
|
//! [gh-issues]: https://github.com/kevinmehall/nusb/issues
|
|
//!
|
|
//! ## Platform support
|
|
//!
|
|
//! ### Linux
|
|
//!
|
|
//! `nusb` is built on the kernel's [usbfs] API.
|
|
//!
|
|
//! A user must have write access on the `/dev/bus/usb/XXX/YYY` nodes to
|
|
//! successfully open a device. Use [udev rules] to configure these permissions.
|
|
//!
|
|
//! For a single-user system used for development, it may be desirable to give
|
|
//! your user account access to all USB devices by placing the following in
|
|
//! `/etc/udev/rules.d/70-plugdev-usb.rules`:
|
|
//!
|
|
//! ```not_rust
|
|
//! SUBSYSTEM=="usb", MODE="0660", GROUP="plugdev"
|
|
//! ```
|
|
//!
|
|
//! This grants access on all USB devices to the `plugdev` group, which your
|
|
//! user may be a member of by default on Debian/Ubuntu-based distros. If you
|
|
//! are developing an app for others to install, you should scope the
|
|
//! permissions more narrowly using the `ATTRS{idVendor}=="ZZZZ",
|
|
//! ATTRS{idProduct}=="ZZZZ"` filters to only apply to your device.
|
|
//!
|
|
//! [usbfs]:
|
|
//! https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#the-usb-character-device-nodes
|
|
//! [udev rules]: https://www.reactivated.net/writing_udev_rules.html
|
|
//!
|
|
//! ### Windows
|
|
//!
|
|
//! `nusb` uses [WinUSB] on Windows.
|
|
//!
|
|
//! On Windows, devices are associated with a particular driver, which persists
|
|
//! across connections and reboots. Composite devices appear as multiple devices
|
|
//! in the Windows device model, and each interface can be associated with a
|
|
//! separate driver.
|
|
//!
|
|
//! To use `nusb`, your device or interface must be associated with the `WinUSB`
|
|
//! driver. If you control the device firmware, the recommended way is to use a
|
|
//! [WCID] descriptor to tell Windows to install the WinUSB driver automatically
|
|
//! when the device is first connected. Alternatively [Zadig] (GUI) or [libwdi]
|
|
//! (CLI / C library) can be used to manually install the WinUSB driver for a
|
|
//! device.
|
|
//!
|
|
//! [SetupAPI]:
|
|
//! https://learn.microsoft.com/en-us/windows-hardware/drivers/install/setupapi
|
|
//! [WinUSB]: https://learn.microsoft.com/en-us/windows/win32/api/winusb/
|
|
//! [WCID]: https://github.com/pbatard/libwdi/wiki/WCID-Devices
|
|
//! [Zadig]:https://zadig.akeo.ie/
|
|
//! [libwdi]: https://github.com/pbatard/libwdi
|
|
//!
|
|
//! ### macOS
|
|
//!
|
|
//! `nusb` uses IOKit on macOS.
|
|
//!
|
|
//! Users have access to USB devices by default, with no permission
|
|
//! configuration needed. Devices with a kernel driver are not accessible.
|
|
//!
|
|
//! ## Async support
|
|
//!
|
|
//! Many methods in `nusb` return a [`MaybeFuture`] type, which can either be
|
|
//! `.await`ed (via `IntoFuture`) or `.wait()`ed (blocking the current thread).
|
|
//! This allows for async usage in an async context, or blocking usage in a
|
|
//! non-async context.
|
|
//!
|
|
//! Operations such as [`list_devices`], [`list_buses`], [`DeviceInfo::open`],
|
|
//! [`Device::set_configuration`], [`Device::reset`],
|
|
//! [`Device::claim_interface`], [`Interface::set_alt_setting`], and
|
|
//! [`Endpoint::clear_halt`] require blocking system calls. To use these in an
|
|
//! asynchronous context, `nusb` requires an async runtime to run these
|
|
//! operations on an IO thread to avoid blocking in async code. Enable the cargo
|
|
//! feature `tokio` or `smol` to use the corresponding runtime for blocking IO.
|
|
//! If neither feature is enabled, `.await` on these methods will panic.
|
|
//!
|
|
//! For blocking usage, `.wait()` always runs the blocking operation directly
|
|
//! without the overhead of handing off to an IO thread.
|
|
//!
|
|
//! These features do not affect and are not required for transfers, which are
|
|
//! implemented on top of natively-async OS APIs.
|
|
|
|
mod platform;
|
|
|
|
pub mod descriptors;
|
|
mod enumeration;
|
|
pub use enumeration::{BusInfo, DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType};
|
|
|
|
mod device;
|
|
pub use device::{Device, Endpoint, Interface};
|
|
|
|
pub mod transfer;
|
|
|
|
pub mod hotplug;
|
|
|
|
mod maybe_future;
|
|
pub use maybe_future::MaybeFuture;
|
|
|
|
mod bitset;
|
|
|
|
pub mod io;
|
|
|
|
mod error;
|
|
pub use error::{ActiveConfigurationError, Error, ErrorKind, GetDescriptorError};
|
|
|
|
/// Get an iterator listing the connected devices.
|
|
///
|
|
/// ### Example
|
|
///
|
|
/// ```no_run
|
|
/// use nusb::{self, MaybeFuture};
|
|
/// 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() -> impl MaybeFuture<Output = Result<impl Iterator<Item = DeviceInfo>, Error>>
|
|
{
|
|
platform::list_devices()
|
|
}
|
|
|
|
/// Get an iterator listing the system USB buses.
|
|
///
|
|
/// ### Example
|
|
///
|
|
/// Group devices by bus:
|
|
///
|
|
/// ```no_run
|
|
/// use std::collections::HashMap;
|
|
/// use nusb::MaybeFuture;
|
|
///
|
|
/// 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()))
|
|
/// })
|
|
/// .collect();
|
|
/// ```
|
|
///
|
|
/// ### 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() -> impl MaybeFuture<Output = Result<impl Iterator<Item = BusInfo>, Error>> {
|
|
platform::list_buses()
|
|
}
|
|
|
|
/// Get a [`Stream`][`futures_core::Stream`] that yields an
|
|
/// [event][`hotplug::HotplugEvent`] when a USB device is connected or
|
|
/// disconnected from the system.
|
|
///
|
|
/// Events will be returned for devices connected or disconnected beginning at
|
|
/// the time this function is called. To maintain a list of connected devices,
|
|
/// call [`list_devices`] after creating the watch with this function to avoid
|
|
/// potentially missing a newly-attached device:
|
|
///
|
|
/// ## Example
|
|
///
|
|
/// ```no_run
|
|
/// use std::collections::HashMap;
|
|
/// use nusb::{MaybeFuture, DeviceInfo, DeviceId, hotplug::HotplugEvent};
|
|
/// let watch = nusb::watch_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 {
|
|
/// HotplugEvent::Connected(d) => {
|
|
/// devices.insert(d.id(), d);
|
|
/// }
|
|
/// HotplugEvent::Disconnected(id) => {
|
|
/// devices.remove(&id);
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// ### Platform-specific notes:
|
|
///
|
|
/// * On Windows, the interfaces of a composite device might not be ready
|
|
/// when the `Connected` event is emitted. If you are immediately opening the device
|
|
/// and claiming an interface when receiving a `Connected` event,
|
|
/// you should retry after a short delay if opening or claiming fails.
|
|
pub fn watch_devices() -> Result<hotplug::HotplugWatch, Error> {
|
|
Ok(hotplug::HotplugWatch(platform::HotplugWatch::new()?))
|
|
}
|