Skip to content

Commit

Permalink
Pre-release docs tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinmehall committed Oct 8, 2023
1 parent efd9c34 commit 99dec97
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 40 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
edition = "2021"
license = "Apache-2.0 OR MIT"
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
54 changes: 53 additions & 1 deletion src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,13 +84,55 @@ 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<u8> = 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<ControlIn> {
let mut t = self.backend.make_transfer(0, EndpointType::Control);
t.submit::<ControlIn>(data);
TransferFuture::new(t)
}

/// 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<ControlOut> {
let mut t = self.backend.make_transfer(0, EndpointType::Control);
t.submit::<ControlOut>(data);
Expand Down
32 changes: 18 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
//!
Expand Down Expand Up @@ -78,19 +82,19 @@
//!
//! ### 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
//! 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. 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
Expand Down
5 changes: 4 additions & 1 deletion src/platform/linux_usbfs/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/platform/linux_usbfs/usbfs.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
4 changes: 3 additions & 1 deletion src/platform/windows_winusb/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};

Expand Down
6 changes: 4 additions & 2 deletions src/transfer/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
34 changes: 17 additions & 17 deletions src/transfer/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -47,26 +47,26 @@ 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;
/// }
///
/// queue.submit(RequestBuffer::reuse(completion.data, transfer_size))
/// }
/// ```
///
///
/// ### Example (write to an endpoint)
/// ```no_run
/// use std::mem;
Expand All @@ -77,18 +77,18 @@ use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRe
/// # fn fill_data(_: &mut Vec<u8>) {}
/// # 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();
Expand Down Expand Up @@ -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<u8>`].
pub fn submit(&mut self, data: R) {
Expand All @@ -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<u8>`].\
/// For an `OUT` endpoint, the completion contains a [`ResponseBuffer`][`super::ResponseBuffer`].
///
Expand All @@ -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) {
Expand Down

0 comments on commit 99dec97

Please sign in to comment.