From 99dec9729de204572830ad51ee0f0087bb333312 Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Sun, 8 Oct 2023 15:36:06 -0600 Subject: [PATCH] Pre-release docs tweaks --- Cargo.toml | 4 +- README.md | 7 ++-- src/device.rs | 54 ++++++++++++++++++++++++++- src/lib.rs | 32 +++++++++------- src/platform/linux_usbfs/device.rs | 5 ++- src/platform/linux_usbfs/usbfs.rs | 1 + src/platform/windows_winusb/device.rs | 4 +- src/transfer/mod.rs | 6 ++- src/transfer/queue.rs | 34 ++++++++--------- 9 files changed, 107 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80d23f0..0b5d72b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "nusb" version = "0.1.0" -description = "Low-level access to USB devices in pure Rust" +description = "Cross-platform low-level access to USB devices in pure Rust" +categories = ["hardware-support"] +keywords = ["usb", "hardware"] authors = ["Kevin Mehall "] edition = "2021" license = "Apache-2.0 OR MIT" diff --git a/README.md b/README.md index a821027..57a4882 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ nusb A new pure-Rust library for cross-platform low-level access to USB devices. +[Documentation](https://docs.rs/nusb) + ### Compared to [rusb](https://docs.rs/rusb/latest/rusb/) and libusb * Pure Rust, no dependency on libusb or any other C library. @@ -11,14 +13,13 @@ A new pure-Rust library for cross-platform low-level access to USB devices. `futures_lite::block_on`. * No context object. You just open a device. There is a global event loop thread that is started when opening the first device. -* Doesn't try to paper over OS differences. For example, on Windows, you must open - a specific interface, not a device as a whole. `nusb`'s API makes working with interfaces - a required step so that it can map directly to Windows APIs. +* Thinner layer over OS APIs, with less internal state. ### :construction: Current status * Linux: Control, bulk and interrupt transfers work, minimally tested * Windows: Control, bulk and interrupt transfers work, minimally tested +* macOS : Not yet implemented ### License MIT or Apache 2.0, at your option diff --git a/src/device.rs b/src/device.rs index fedbc4c..398f87d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -8,7 +8,17 @@ use crate::{ /// An opened USB device. /// -/// Obtain a `Device` by calling [`DeviceInfo::open`]. +/// Obtain a `Device` by calling [`DeviceInfo::open`]: +/// +/// ```no_run +/// use nusb; +/// let device_info = nusb::list_devices().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 interface = device.claim_interface(0); +/// ``` /// /// This type is reference-counted with an [`Arc`] internally, and can be cloned cheaply for /// use in multiple places in your program. The device is closed when all clones and all @@ -74,6 +84,27 @@ impl Interface { } /// Submit a single **IN (device-to-host)** transfer on the default **control** endpoint. + /// + /// ### Example + /// + /// ```no_run + /// use futures_lite::future::block_on; + /// use nusb::transfer::{ ControlIn, ControlType, Recipient }; + /// # 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 data: Vec = block_on(interface.control_in(ControlIn { + /// control_type: ControlType::Vendor, + /// recipient: Recipient::Device, + /// request: 0x30, + /// value: 0x0, + /// index: 0x0, + /// length: 64, + /// })).into_result()?; + /// # Ok(()) } + /// ``` pub fn control_in(&self, data: ControlIn) -> TransferFuture { let mut t = self.backend.make_transfer(0, EndpointType::Control); t.submit::(data); @@ -81,6 +112,27 @@ impl Interface { } /// Submit a single **OUT (host-to-device)** transfer on the default **control** endpoint. + /// + /// ### Example + /// + /// ```no_run + /// use futures_lite::future::block_on; + /// use nusb::transfer::{ ControlOut, ControlType, Recipient }; + /// # 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(); + /// + /// block_on(interface.control_out(ControlOut { + /// control_type: ControlType::Vendor, + /// recipient: Recipient::Device, + /// request: 0x32, + /// value: 0x0, + /// index: 0x0, + /// data: &[0x01, 0x02, 0x03, 0x04], + /// })).into_result()?; + /// # Ok(()) } + /// ``` pub fn control_out(&self, data: ControlOut) -> TransferFuture { let mut t = self.backend.make_transfer(0, EndpointType::Control); t.submit::(data); diff --git a/src/lib.rs b/src/lib.rs index 45475ff..247c380 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,10 +32,10 @@ //! 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`] struct to transfer data on the device's -//! control, bulk or interrupt endpoints. Transfers are async by default, and -//! can be awaited as individual `Future`s, or use a [`transfer::Queue`] to -//! manage streams of data. +//! Use the resulting [`Interface`] to transfer data on the device's control, +//! bulk or interrupt endpoints. Transfers are async by default, and can be +//! awaited as individual [`Future`][`transfer::TransferFuture`]s, or use a +//! [`Queue`][`transfer::Queue`] to manage streams of data. //! //! *For more details on how USB works, [USB in a //! Nutshell](https://beyondlogic.org/usbnutshell/usb1.shtml) is a good @@ -44,10 +44,14 @@ //! ## Logging //! //! `nusb` uses the [`log`](https://docs.rs/log) crate to log debug and error -//! information. When submitting a bug report, please include the logs: include -//! a logging 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`.) +//! 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 //! @@ -78,7 +82,7 @@ //! //! ### Windows //! -//! `nusb` uses [SetupAPI] to find devices and [WinUSB] to access them. +//! `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 @@ -86,11 +90,11 @@ //! separate driver. //! //! To use `nusb`, your device or interface must be associated with the `WinUSB` -//! driver. The recommended way to this if you control the device firmware 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. +//! 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 diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index edf52b4..896a6a7 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -132,7 +132,10 @@ impl LinuxInterface { } pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> { - debug!("Set interface {} alt setting to {alt_setting}", self.interface); + debug!( + "Set interface {} alt setting to {alt_setting}", + self.interface + ); Ok(usbfs::set_interface( &self.device.fd, self.interface, diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index c450948..5accdb4 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -1,5 +1,6 @@ //! Wrappers for the [usbfs] character device ioctls, translated from the //! [C structures and ioctl definitions][uapi]. +//! //! [usbfs]: https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#the-usb-character-device-nodes //! [uapi]: https://github.com/torvalds/linux/blob/master/tools/include/uapi/linux/usbdevice_fs.h #![allow(dead_code)] diff --git a/src/platform/windows_winusb/device.rs b/src/platform/windows_winusb/device.rs index bdcc10f..1d956f8 100644 --- a/src/platform/windows_winusb/device.rs +++ b/src/platform/windows_winusb/device.rs @@ -8,7 +8,9 @@ use std::{ use log::{debug, error}; use windows_sys::Win32::{ - Devices::Usb::{WinUsb_Free, WinUsb_Initialize, WINUSB_INTERFACE_HANDLE, WinUsb_SetCurrentAlternateSetting}, + Devices::Usb::{ + WinUsb_Free, WinUsb_Initialize, WinUsb_SetCurrentAlternateSetting, WINUSB_INTERFACE_HANDLE, + }, Foundation::{FALSE, TRUE}, }; diff --git a/src/transfer/mod.rs b/src/transfer/mod.rs index 7d3d924..37c702e 100644 --- a/src/transfer/mod.rs +++ b/src/transfer/mod.rs @@ -1,12 +1,14 @@ //! Transfer-related types. -//! +//! //! Use the methods on an [`Interface`][`super::Interface`] to make individual //! transfers or obtain a [`Queue`] to manage multiple transfers. use std::{ + fmt::Display, future::Future, + io, marker::PhantomData, - task::{Context, Poll}, io, fmt::Display, + task::{Context, Poll}, }; use crate::platform; diff --git a/src/transfer/queue.rs b/src/transfer/queue.rs index 76700c5..f4148e3 100644 --- a/src/transfer/queue.rs +++ b/src/transfer/queue.rs @@ -10,17 +10,17 @@ use crate::platform; use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRequest}; /// Manages a stream of transfers on an endpoint. -/// +/// /// A `Queue` optimizes a common pattern when streaming data to or from a USB /// endpoint: To maximize throughput and minimize latency, the host controller /// needs to attempt a transfer in every possible frame. That requires always /// having a transfer request pending with the kernel by submitting multiple /// transfer requests and re-submitting them as they complete. -/// +/// /// When the `Queue` is dropped, all pending transfers are cancelled. -/// -/// ### Why use a `Queue` instead of submitting multiple transfers individually -/// with the methods on [`Interface`][`crate::Interface`]? +/// +/// ### Why use a `Queue` instead of submitting multiple transfers individually with the methods on [`Interface`][`crate::Interface`]? +/// /// * Individual transfers give you individual `Future`s, which you then have /// to keep track of and poll using something like `FuturesUnordered`. /// * A `Queue` provides better cancellation semantics than `Future`'s @@ -36,9 +36,9 @@ use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRe /// * A queue caches the internal transfer data structures of the last /// completed transfer, meaning that if you re-use the data buffer there is /// no memory allocation involved in continued streaming. -/// +/// /// ### Example (read from an endpoint) -/// +/// /// ```no_run /// use futures_lite::future::block_on; /// use nusb::transfer::RequestBuffer; @@ -47,18 +47,18 @@ use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRe /// # let interface = device.claim_interface(0).unwrap(); /// # fn handle_data(_: &[u8]) {} /// let mut queue = interface.bulk_in_queue(0x81); -/// +/// /// let n_transfers = 8; /// let transfer_size = 256; /// /// while queue.pending() < n_transfers { /// queue.submit(RequestBuffer::new(transfer_size)); /// } -/// +/// /// loop { /// let completion = block_on(queue.next_complete()); /// handle_data(&completion.data); // your function -/// +/// /// if completion.status.is_err() { /// break; /// } @@ -66,7 +66,7 @@ use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRe /// queue.submit(RequestBuffer::reuse(completion.data, transfer_size)) /// } /// ``` -/// +/// /// ### Example (write to an endpoint) /// ```no_run /// use std::mem; @@ -77,18 +77,18 @@ use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRe /// # fn fill_data(_: &mut Vec) {} /// # fn data_confirmed_sent(_: usize) {} /// let mut queue = interface.bulk_out_queue(0x02); -/// +/// /// let n_transfers = 8; /// /// let mut next_buf = Vec::new(); -/// +/// /// loop { /// while queue.pending() < n_transfers { /// let mut buf = mem::replace(&mut next_buf, Vec::new()); /// fill_data(&mut buf); // your function /// queue.submit(buf); /// } -/// +/// /// let completion = block_on(queue.next_complete()); /// data_confirmed_sent(completion.data.actual_length()); // your function /// next_buf = completion.data.reuse(); @@ -133,7 +133,7 @@ where } /// Submit a new transfer on the endpoint. - /// + /// /// For an `IN` endpoint, pass a [`RequestBuffer`][`super::RequestBuffer`].\ /// For an `OUT` endpoint, pass a [`Vec`]. pub fn submit(&mut self, data: R) { @@ -147,7 +147,7 @@ where /// Return a `Future` that waits for the next pending transfer to complete, and yields its /// buffer and status. - /// + /// /// For an `IN` endpoint, the completion contains a [`Vec`].\ /// For an `OUT` endpoint, the completion contains a [`ResponseBuffer`][`super::ResponseBuffer`]. /// @@ -173,7 +173,7 @@ where } /// Cancel all pending transfers. - /// + /// /// They will still be returned from subsequent calls to `next_complete` so /// you can tell which were completed, partially-completed, or cancelled. pub fn cancel_all(&mut self) {