Documentation improvements

This commit is contained in:
Kevin Mehall 2025-07-21 22:44:56 -06:00
parent 426516549a
commit 4009138dc7
9 changed files with 162 additions and 67 deletions

View file

@ -657,7 +657,7 @@ descriptor_fields! {
/// Get the raw value of the `bmAttributes` descriptor field.
///
/// See [`transfer_type``][Self::transfer_type] for the transfer type field.
/// See [`transfer_type`][Self::transfer_type] for the transfer type field.
#[doc(alias = "bmAttributes")]
pub fn attributes at 3 -> u8;

View file

@ -56,7 +56,15 @@ impl Device {
platform::Device::from_device_info(d).map(|d| d.map(Device::wrap))
}
/// Wraps a device that is already open.
/// Wrap a usbdevfs file descriptor that is already open.
///
/// This opens a device from a file descriptor for a `/dev/bus/usb/*` device
/// provided externally, such as from
/// [Android](https://developer.android.com/reference/android/hardware/usb/UsbDeviceConnection#getFileDescriptor()),
/// [xdg-desktop-portal](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Usb.html),
/// etc.
///
/// *Supported on Linux and Android only.*
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn from_fd(fd: std::os::fd::OwnedFd) -> impl MaybeFuture<Output = Result<Device, Error>> {
platform::Device::from_fd(fd).map(|d| d.map(Device::wrap))
@ -75,7 +83,7 @@ impl Device {
/// Detach kernel drivers and open an interface of the device and claim it for exclusive use.
///
/// ### Platform notes
/// ### Platform-specific details
/// 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(
@ -90,7 +98,7 @@ impl Device {
/// Detach kernel drivers for the specified interface.
///
/// ### Platform notes
/// ### Platform-specific details
/// This function can only detach kernel drivers on Linux. Calling on other platforms has
/// no effect.
pub fn detach_kernel_driver(&self, interface: u8) -> Result<(), Error> {
@ -103,7 +111,7 @@ impl Device {
/// Attach kernel drivers for the specified interface.
///
/// ### Platform notes
/// ### Platform-specific details
/// This function can only attach kernel drivers on Linux. Calling on other platforms has
/// no effect.
pub fn attach_kernel_driver(&self, interface: u8) -> Result<(), Error> {
@ -121,7 +129,7 @@ impl Device {
self.backend.device_descriptor()
}
/// Get device speed.
/// Get the device's connection speed.
pub fn speed(&self) -> Option<Speed> {
self.backend.speed()
}
@ -156,7 +164,7 @@ impl Device {
/// descriptor field from [`ConfigurationDescriptor::configuration_value`] or `0` to
/// unconfigure the device.
///
/// ### Platform-specific notes
/// ### Platform-specific details
/// * Not supported on Windows
pub fn set_configuration(
&self,
@ -267,7 +275,7 @@ impl Device {
/// This `Device` will no longer be usable, and you should drop it and call
/// [`list_devices`][`super::list_devices`] to find and re-open it again.
///
/// ### Platform-specific notes
/// ### Platform-specific details
/// * Not supported on Windows
pub fn reset(&self) -> impl MaybeFuture<Output = Result<(), Error>> {
self.backend.clone().reset()
@ -297,7 +305,7 @@ impl Device {
/// # Ok(()) }
/// ```
///
/// ### Platform-specific notes
/// ### Platform-specific details
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
@ -334,7 +342,7 @@ impl Device {
/// # Ok(()) }
/// ```
///
/// ### Platform-specific notes
/// ### Platform-specific details
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
@ -376,6 +384,9 @@ impl 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.
///
/// You must not have any pending transfers or open `Endpoints` on this interface when changing
/// the alternate setting.
pub fn set_alt_setting(&self, alt_setting: u8) -> impl MaybeFuture<Output = Result<(), Error>> {
self.backend.clone().set_alt_setting(alt_setting)
}
@ -410,14 +421,13 @@ impl Interface {
/// # Ok(()) }
/// ```
///
/// ### Platform-specific notes
/// * On Windows, if the `recipient` is `Interface`, the WinUSB driver sends
/// the interface number in the least significant byte of `index`,
/// overriding any value passed. A warning is logged if the passed `index`
/// least significant byte differs from the interface number, and this may
/// become an error in the future.
/// * On Windows, the timeout is currently fixed to 5 seconds and the timeout
/// argument is ignored.
/// ### Platform-specific details
/// * On Windows, if the `recipient` is `Interface`, the least significant
/// byte of `index` must match the interface number, or
/// `TransferError::InvalidArgument` will be returned. This is a WinUSB
/// limitation.
/// * On Windows, the timeout is currently fixed to 5 seconds and the
/// timeout argument is ignored.
pub fn control_in(
&self,
data: ControlIn,
@ -452,14 +462,13 @@ impl Interface {
/// # Ok(()) }
/// ```
///
/// ### Platform-specific notes
/// * On Windows, if the `recipient` is `Interface`, the WinUSB driver sends
/// the interface number in the least significant byte of `index`,
/// overriding any value passed. A warning is logged if the passed `index`
/// least significant byte differs from the interface number, and this may
/// become an error in the future.
/// * On Windows, the timeout is currently fixed to 5 seconds and the timeout
/// argument is ignored.
/// ### Platform-specific details
/// * On Windows, if the `recipient` is `Interface`, the least significant
/// byte of `index` must match the interface number, or
/// `TransferError::InvalidArgument` will be returned. This is a WinUSB
/// limitation.
/// * On Windows, the timeout is currently fixed to 5 seconds and the
/// timeout argument is ignored.
pub fn control_out(
&self,
data: ControlOut,
@ -498,6 +507,11 @@ impl Interface {
}
/// Open an endpoint.
///
/// This claims exclusive access to the endpoint and returns an [`Endpoint`]
/// that can be used to submit transfers. The type-level `EndpointType` and
/// `EndpointDirection` parameters must match the endpoint type and
/// direction.
pub fn endpoint<EpType: EndpointType, Dir: EndpointDirection>(
&self,
address: u8,
@ -629,6 +643,10 @@ impl<EpType: BulkOrInterrupt, Dir: EndpointDirection> Endpoint<EpType, Dir> {
/// with the system allocator, it cannot be converted to a [`Vec`] without
/// copying.
///
/// This is a somewhat expensive operation, requiring a `mmap` system call,
/// so is likely only beneficial for buffers that will be used repeatedly.
/// Consider using [`Buffer::new`] for one-off transfers.
///
/// This is currently only supported on Linux, falling back to [`Buffer::new`]
/// on other platforms, or if the memory allocation fails.
pub fn allocate(&self, len: usize) -> Buffer {

View file

@ -12,12 +12,12 @@ pub struct DeviceId(pub(crate) crate::platform::DeviceId);
/// Information about a device that can be obtained without opening it.
///
/// Found in the results of [`crate::list_devices`].
/// `DeviceInfo` is returned by [`list_devices`][crate::list_devices].
///
/// ### Platform-specific notes
///
/// * Some fields are platform-specific
/// * Linux: `sysfs_path`
/// * Linux: `sysfs_path`, `busnum`
/// * Windows: `instance_id`, `parent_instance_id`, `port_number`, `driver`
/// * macOS: `registry_id`, `location_id`
#[derive(Clone)]
@ -149,7 +149,7 @@ impl DeviceInfo {
/// Path of port numbers identifying the port where the device is connected.
///
/// Together with the bus ID, it identifies a physical port. The path is
/// expected to remain stable across device insertions or reboots.
/// expected to remain stable across device insertions or reboots.
///
/// Since USB SuperSpeed is a separate topology from USB 2.0 speeds, a
/// physical port may be identified differently depending on speed.
@ -213,10 +213,9 @@ impl DeviceInfo {
self.usb_version
}
/// Code identifying the standard device class, from the `bDeviceClass` device descriptor field.
///
/// `0x00`: specified at the interface level.\
/// `0xFF`: vendor-defined.
/// Code identifying the [standard device
/// class](https://www.usb.org/defined-class-codes), from the `bDeviceClass`
/// device descriptor field.
#[doc(alias = "bDeviceClass")]
pub fn class(&self) -> u8 {
self.class
@ -473,7 +472,7 @@ impl UsbControllerType {
/// Information about a system USB bus.
///
/// Platform-specific fields:
/// * Linux: `path`, `parent_path`, `busnum`, `root_hub`
/// * Linux: `path`, `busnum`, `root_hub`
/// * Windows: `instance_id`, `parent_instance_id`, `location_paths`, `devinst`, `root_hub_description`
/// * macOS: `registry_id`, `location_id`, `name`, `provider_class_name`, `class_name`
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
@ -616,12 +615,6 @@ impl BusInfo {
/// Detected USB controller type
///
/// None means the controller type could not be determined.
///
/// ### Platform-specific notes
///
/// * Linux: Parsed from driver in use.
/// * macOS: The IOService entry matched.
/// * Windows: Parsed from the numbers following ROOT_HUB in the instance_id.
pub fn controller_type(&self) -> Option<UsbControllerType> {
self.controller_type
}

View file

@ -97,7 +97,7 @@ pub enum ErrorKind {
Other,
}
/// Error from [`crate::Device::active_configuration`]
/// Error from [`Device::active_configuration`][crate::Device::active_configuration].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ActiveConfigurationError {
pub(crate) configuration_value: u8,

View file

@ -16,8 +16,28 @@ use crate::{
};
/// Wrapper for a Bulk or Interrupt IN [`Endpoint`](crate::Endpoint) that
/// implements [`Read`](std::io::Read) and [`BufRead`](std::io::BufRead) and
/// their `tokio` and `smol` async equivalents.
/// manages transfers to provide a higher-level buffered API.
///
/// Most of the functionality of this type is provided through standard IO
/// traits; you'll want to use one of the following:
///
/// * [`std::io::Read`](std::io::Read) and [`BufRead`](std::io::BufRead) for
/// blocking IO.
/// * With the `tokio` cargo feature,
/// [`tokio::io::AsyncRead`](tokio::io::AsyncRead) and
/// [`AsyncBufRead`](tokio::io::AsyncBufRead) for async IO. Tokio also
/// provides `AsyncReadExt` and `AsyncBufReadExt` with additional methods.
/// * With the `smol` cargo feature,
/// [`futures_io::AsyncRead`](futures_io::AsyncRead) and
/// [`AsyncBufRead`](futures_io::AsyncBufRead) for async IO.
/// `futures_lite` provides `AsyncReadExt` and `AsyncBufReadExt` with
/// additional methods.
///
/// By default, this type ignores USB packet lengths and boundaries. For protocols
/// that use short or zero-length packets as delimiters, you can use the
/// [`until_short_packet()`](Self::until_short_packet) method to get an
/// [`EndpointReadUntilShortPacket`](EndpointReadUntilShortPacket) adapter
/// that observes these delimiters.
pub struct EndpointRead<EpType: BulkOrInterrupt> {
endpoint: Endpoint<EpType, In>,
reading: Option<ReadBuffer>,
@ -381,6 +401,9 @@ impl<EpType: BulkOrInterrupt> futures_io::AsyncBufRead for EndpointRead<EpType>
/// have any state other than that of the underlying [`EndpointRead`], so
/// dropping and re-creating with another call to
/// [`EndpointRead::until_short_packet()`] has no effect.
///
/// This implements the same traits as `EndpointRead` but observes packet
/// boundaries instead of ignoring them.
pub struct EndpointReadUntilShortPacket<'a, EpType: BulkOrInterrupt> {
reader: &'a mut EndpointRead<EpType>,
}
@ -399,10 +422,11 @@ impl std::fmt::Display for ExpectedShortPacket {
impl Error for ExpectedShortPacket {}
impl<EpType: BulkOrInterrupt> EndpointReadUntilShortPacket<'_, EpType> {
/// Check if the underlying endpoint has reached the end of a short packet.
/// Check if the endpoint has reached the end of a short packet.
///
/// Upon reading the end of a short packet, the next `read()` or `fill_buf()`
/// will return 0 bytes (EOF). To read the next message, call `consume_end()`.
/// Upon reading the end of a short packet, the next `read()` or
/// `fill_buf()` will return 0 bytes (EOF) and this method will return
/// `true`. To begin reading the next message, call `consume_end()`.
pub fn is_end(&self) -> bool {
self.reader
.reading

View file

@ -13,7 +13,22 @@ use std::{
use std::{pin::Pin, task::ready};
/// Wrapper for a Bulk or Interrupt OUT [`Endpoint`](crate::Endpoint) that
/// implements [`Write`](std::io::Write) and its `tokio` and `smol` async equivalents.
/// manages transfers to provide a higher-level buffered API.
///
/// Most of the functionality of this type is provided through standard IO
/// traits; you'll want to use one of the following:
///
/// * [`std::io::Write`](std::io::Write) for blocking IO.
/// * With the `tokio` cargo feature,
/// [`tokio::io::AsyncWrite`](tokio::io::AsyncWrite). Tokio also provides
/// `AsyncWriteExt` with additional methods.
/// * With the `smol` cargo feature,
/// [`futures_io::AsyncWrite`](futures_io::AsyncWrite) for async IO.
/// `futures_lite` provides `AsyncWriteExt` with additional methods.
///
/// Written data is buffered and may not be sent until the buffer is full or
/// [`submit`](Self::submit) / [`submit_end`](Self::submit_end) or
/// [`flush`](Self::flush) / [`flush_end`](Self::flush_end) are called.
pub struct EndpointWrite<EpType: BulkOrInterrupt> {
endpoint: Endpoint<EpType, Out>,
writing: Option<Buffer>,

View file

@ -1,9 +1,13 @@
#![warn(missing_docs)]
//! A new library for cross-platform low-level access to USB devices.
//!
//! `nusb` supports Windows, macOS, and Linux, and provides both async and
//! blocking APIs for listing and watching USB devices, reading descriptor
//! details, opening and managing devices and interfaces, and performing
//! transfers on control, bulk, and interrupt endpoints.
//!
//! `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.
//! but written in pure Rust.
//!
//! [libusb]: https://libusb.info
//! [rusb]: https://docs.rs/rusb/
@ -16,6 +20,41 @@
//! 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.)
//!
//! ## Example usage
//!
//! ```no_run
//! use nusb::{list_devices, MaybeFuture};
//! use nusb::transfer::{Bulk, In, Out, ControlOut, ControlType, Recipient};
//! use std::io::{Read, Write, Error, ErrorKind};
//! use std::time::Duration;
//!
//! # fn main() -> Result<(), std::io::Error> {
//! let device = list_devices().wait()?
//! .find(|dev| dev.vendor_id() == 0xAAAA && dev.product_id() == 0xBBBB)
//! .ok_or(Error::new(ErrorKind::NotFound, "device not found"))?;
//!
//! let device = device.open().wait()?;
//! let interface = device.claim_interface(0).wait()?;
//!
//! interface.control_out(ControlOut {
//! control_type: ControlType::Vendor,
//! recipient: Recipient::Device,
//! request: 0x10,
//! value: 0x0,
//! index: 0x0,
//! data: &[0x01, 0x02, 0x03, 0x04],
//! }, Duration::from_millis(100)).wait()?;
//!
//! let mut writer = interface.endpoint::<Bulk, Out>(0x01)?.writer(4096);
//! writer.write_all(&[0x00, 0xff])?;
//! writer.flush()?;
//!
//! let mut reader = interface.endpoint::<Bulk, In>(0x81)?.reader(4096);
//! let mut buf = [0; 64];
//! reader.read_exact(&mut buf)?;
//! # Ok(()) }
//! ```
//!
//! ## USB and usage overview
//!
//! When a USB device connects, the OS queries the device descriptor containing
@ -29,10 +68,10 @@
//! 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.
//! USB devices consist of one or more interfaces. 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
@ -40,6 +79,11 @@
//! internal queue for the endpoint. Completed transfers can be popped
//! from the queue synchronously or asynchronously.
//!
//! The [`EndpointRead`][io::EndpointRead] and
//! [`EndpointWrite`][io::EndpointWrite] types wrap the endpoint and
//! manage transfers and buffers to implement the standard `Read` and
//! `Write` traits and their async equivalents.
//!
//! *For more details on how USB works, [USB in a
//! Nutshell](https://beyondlogic.org/usbnutshell/usb1.shtml) is a good
//! overview.*
@ -188,10 +232,11 @@ pub fn list_devices() -> impl MaybeFuture<Output = Result<impl Iterator<Item = D
/// 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()
/// 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()))
/// let devs: Vec<_> = devices.iter().filter(|dev| dev.bus_id() == bus_id).cloned().collect();
/// (bus_id, (bus, devs))
/// })
/// .collect();
/// ```

View file

@ -133,7 +133,7 @@ pub mod blocking {
}
#[cfg(not(any(feature = "smol", feature = "tokio")))]
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
unreachable!()
}
}