nusb/src/device.rs
2024-09-05 15:32:56 +00:00

593 lines
23 KiB
Rust
Raw Blame History

use crate::{
descriptors::{
decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError,
Configuration, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING,
},
platform,
transfer::{
Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError,
TransferFuture,
},
DeviceInfo, Error,
};
use log::error;
use rustix::fd::OwnedFd;
use std::{io::ErrorKind, sync::Arc, time::Duration};
/// An opened USB device.
///
/// 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
/// associated [`Interface`]s are dropped.
///
/// Use [`.claim_interface(i)`][`Device::claim_interface`] to open an interface to submit
/// transfers.
#[derive(Clone)]
pub struct Device {
backend: Arc<crate::platform::Device>,
}
impl Device {
pub(crate) fn open(d: &DeviceInfo) -> Result<Device, std::io::Error> {
let backend = platform::Device::from_device_info(d)?;
Ok(Device { backend })
}
/// Wraps a device that is already open.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn from_fd(fd: OwnedFd) -> Result<Device, Error> {
Ok(Device {
backend: 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<Interface, Error> {
let backend = self.backend.claim_interface(interface)?;
Ok(Interface { backend })
}
/// Detach kernel drivers and open an interface of the device and claim it for exclusive use.
///
/// ### 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<Interface, Error> {
let backend = self.backend.detach_and_claim_interface(interface)?;
Ok(Interface { backend })
}
/// Detach kernel drivers for the specified interface.
///
/// ### Platform notes
/// 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> {
#[cfg(any(target_os = "linux", target_os = "android"))]
self.backend.detach_kernel_driver(interface)?;
let _ = interface;
Ok(())
}
/// Attach kernel drivers for the specified interface.
///
/// ### Platform notes
/// 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> {
#[cfg(any(target_os = "linux", target_os = "android"))]
self.backend.attach_kernel_driver(interface)?;
let _ = interface;
Ok(())
}
/// Get information about the active configuration.
///
/// This returns cached data and does not perform IO. However, it can fail if the
/// device is unconfigured, or if it can't find a configuration descriptor for
/// the configuration reported as active by the OS.
pub fn active_configuration(&self) -> Result<Configuration, ActiveConfigurationError> {
let active = self.backend.active_configuration_value();
self.configurations()
.find(|c| c.configuration_value() == active)
.ok_or_else(|| ActiveConfigurationError {
configuration_value: active,
})
}
/// Get an iterator returning information about each configuration of the device.
///
/// This returns cached data and does not perform IO.
pub fn configurations(&self) -> impl Iterator<Item = Configuration> {
self.backend
.configuration_descriptors()
.map(Configuration::new)
}
/// Set the device configuration.
///
/// The argument is the desired configuration's `bConfigurationValue`
/// descriptor field from [`Configuration::configuration_value`] or `0` to
/// unconfigure the device.
///
/// ### Platform-specific notes
/// * Not supported on Windows
pub fn set_configuration(&self, configuration: u8) -> Result<(), Error> {
self.backend.set_configuration(configuration)
}
/// Request a descriptor from the device.
///
/// The `language_id` should be `0` unless you are requesting a string descriptor.
///
/// ### Platform-specific details
///
/// * On Windows, the timeout argument is ignored, and an OS-defined timeout is used.
/// * On Windows, this does not wake suspended devices. Reading their
/// descriptors will return an error.
pub fn get_descriptor(
&self,
desc_type: u8,
desc_index: u8,
language_id: u16,
timeout: Duration,
) -> Result<Vec<u8>, Error> {
#[cfg(target_os = "windows")]
{
let _ = timeout;
self.backend
.get_descriptor(desc_type, desc_index, language_id)
}
#[cfg(not(target_os = "windows"))]
{
const STANDARD_REQUEST_GET_DESCRIPTOR: u8 = 0x06;
use crate::transfer::{ControlType, Recipient};
let mut buf = vec![0; 4096];
let len = self.control_in_blocking(
Control {
control_type: ControlType::Standard,
recipient: Recipient::Device,
request: STANDARD_REQUEST_GET_DESCRIPTOR,
value: ((desc_type as u16) << 8) | desc_index as u16,
index: language_id,
},
&mut buf,
timeout,
)?;
buf.truncate(len);
Ok(buf)
}
}
/// Request the list of supported languages for string descriptors.
///
/// ### Platform-specific details
///
/// See notes on [`get_descriptor`][`Self::get_descriptor`].
pub fn get_string_descriptor_supported_languages(
&self,
timeout: Duration,
) -> Result<impl Iterator<Item = u16>, Error> {
let data = self.get_descriptor(DESCRIPTOR_TYPE_STRING, 0, 0, timeout)?;
if !validate_string_descriptor(&data) {
error!("String descriptor language list read {data:?}, not a valid string descriptor");
return Err(Error::new(
ErrorKind::InvalidData,
"string descriptor data was invalid",
));
}
//TODO: Use array_chunks once stable
let mut iter = data.into_iter().skip(2);
Ok(std::iter::from_fn(move || {
Some(u16::from_le_bytes([iter.next()?, iter.next()?]))
}))
}
/// Request a string descriptor from the device.
///
/// Almost all devices support only the language ID [`US_ENGLISH`][`crate::descriptors::language_id::US_ENGLISH`].
///
/// Unpaired UTF-16 surrogates will be replaced with `<60>`, like [`String::from_utf16_lossy`].
///
/// ### Platform-specific details
///
/// See notes on [`get_descriptor`][`Self::get_descriptor`].
pub fn get_string_descriptor(
&self,
desc_index: u8,
language_id: u16,
timeout: Duration,
) -> Result<String, Error> {
if desc_index == 0 {
return Err(Error::new(
ErrorKind::InvalidInput,
"string descriptor index 0 is reserved for the language table",
));
}
let data = self.get_descriptor(DESCRIPTOR_TYPE_STRING, desc_index, language_id, timeout)?;
decode_string_descriptor(&data)
.map_err(|_| Error::new(ErrorKind::InvalidData, "string descriptor data was invalid"))
}
/// Reset the device, forcing it to re-enumerate.
///
/// This `Device` will no longer be usable, and you should drop it and call
/// [`super::list_devices`] to find and re-open it again.
///
/// ### Platform-specific notes
/// * Not supported on Windows
pub fn reset(&self) -> Result<(), Error> {
self.backend.reset()
}
/// Synchronously perform a single **IN (device-to-host)** transfer on the default **control** endpoint.
///
/// ### Platform-specific notes
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
/// * On Linux, this takes a device-wide lock, so if you have multiple threads, you
/// are better off using the async methods.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_in_blocking(
&self,
control: Control,
data: &mut [u8],
timeout: Duration,
) -> Result<usize, TransferError> {
self.backend.control_in_blocking(control, data, timeout)
}
/// Synchronously perform a single **OUT (host-to-device)** transfer on the default **control** endpoint.
///
/// ### Platform-specific notes
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
/// * On Linux, this takes a device-wide lock, so if you have multiple threads, you
/// are better off using the async methods.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_out_blocking(
&self,
control: Control,
data: &[u8],
timeout: Duration,
) -> Result<usize, TransferError> {
self.backend.control_out_blocking(control, data, timeout)
}
/// Asynchronously 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 data: Vec<u8> = block_on(device.control_in(ControlIn {
/// control_type: ControlType::Vendor,
/// recipient: Recipient::Device,
/// request: 0x30,
/// value: 0x0,
/// index: 0x0,
/// length: 64,
/// })).into_result()?;
/// # Ok(()) }
/// ```
///
/// ### Platform-specific notes
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_in(&self, data: ControlIn) -> TransferFuture<ControlIn> {
let mut t = self.backend.make_control_transfer();
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();
///
/// block_on(device.control_out(ControlOut {
/// control_type: ControlType::Vendor,
/// recipient: Recipient::Device,
/// request: 0x32,
/// value: 0x0,
/// index: 0x0,
/// data: &[0x01, 0x02, 0x03, 0x04],
/// })).into_result()?;
/// # Ok(()) }
/// ```
///
/// ### Platform-specific notes
///
/// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`]
/// and use the interface handle to submit transfers.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))]
pub fn control_out(&self, data: ControlOut) -> TransferFuture<ControlOut> {
let mut t = self.backend.make_control_transfer();
t.submit::<ControlOut>(data);
TransferFuture::new(t)
}
}
/// An opened interface of a USB device.
///
/// Obtain an `Interface` with the [`Device::claim_interface`] method.
///
/// This type is reference-counted with an [`Arc`] internally, and can be cloned cheaply for
/// use in multiple places in your program. The interface is released when all clones, and all
/// associated [`TransferFuture`]s and [`Queue`]s are dropped.
#[derive(Clone)]
pub struct Interface {
backend: Arc<platform::Interface>,
}
impl Interface {
/// 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)
}
/// Synchronously perform a single **IN (device-to-host)** transfer on the default **control** endpoint.
///
/// ### Platform-specific notes
///
/// * On Linux, this takes a device-wide lock, so if you have multiple
/// threads, you are better off using the async methods.
/// * 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.
pub fn control_in_blocking(
&self,
control: Control,
data: &mut [u8],
timeout: Duration,
) -> Result<usize, TransferError> {
self.backend.control_in_blocking(control, data, timeout)
}
/// Synchronously perform a single **OUT (host-to-device)** transfer on the default **control** endpoint.
///
/// ### Platform-specific notes
///
/// * On Linux, this takes a device-wide lock, so if you have multiple
/// threads, you are better off using the async methods.
/// * 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.
pub fn control_out_blocking(
&self,
control: Control,
data: &[u8],
timeout: Duration,
) -> Result<usize, TransferError> {
self.backend.control_out_blocking(control, data, timeout)
}
/// 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(()) }
/// ```
///
/// ### 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.
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(()) }
/// ```
///
/// ### 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.
pub fn control_out(&self, data: ControlOut) -> TransferFuture<ControlOut> {
let mut t = self.backend.make_transfer(0, EndpointType::Control);
t.submit::<ControlOut>(data);
TransferFuture::new(t)
}
/// Submit a single **IN (device-to-host)** transfer on the specified **bulk** endpoint.
///
/// * The requested length must be a multiple of the endpoint's maximum packet size
/// * An IN endpoint address must have the top (`0x80`) bit set.
pub fn bulk_in(&self, endpoint: u8, buf: RequestBuffer) -> TransferFuture<RequestBuffer> {
let mut t = self.backend.make_transfer(endpoint, EndpointType::Bulk);
t.submit(buf);
TransferFuture::new(t)
}
/// Submit a single **OUT (host-to-device)** transfer on the specified **bulk** endpoint.
///
/// * An OUT endpoint address must have the top (`0x80`) bit clear.
pub fn bulk_out(&self, endpoint: u8, buf: Vec<u8>) -> TransferFuture<Vec<u8>> {
let mut t = self.backend.make_transfer(endpoint, EndpointType::Bulk);
t.submit(buf);
TransferFuture::new(t)
}
/// Create a queue for managing multiple **IN (device-to-host)** transfers on a **bulk** endpoint.
///
/// * An IN endpoint address must have the top (`0x80`) bit set.
pub fn bulk_in_queue(&self, endpoint: u8) -> Queue<RequestBuffer> {
Queue::new(self.backend.clone(), endpoint, EndpointType::Bulk)
}
/// Create a queue for managing multiple **OUT (host-to-device)** transfers on a **bulk** endpoint.
///
/// * An OUT endpoint address must have the top (`0x80`) bit clear.
pub fn bulk_out_queue(&self, endpoint: u8) -> Queue<Vec<u8>> {
Queue::new(self.backend.clone(), endpoint, EndpointType::Bulk)
}
/// Submit a single **IN (device-to-host)** transfer on the specified **interrupt** endpoint.
///
/// * The requested length must be a multiple of the endpoint's maximum packet size
/// * An IN endpoint address must have the top (`0x80`) bit set.
pub fn interrupt_in(&self, endpoint: u8, buf: RequestBuffer) -> TransferFuture<RequestBuffer> {
let mut t = self
.backend
.make_transfer(endpoint, EndpointType::Interrupt);
t.submit(buf);
TransferFuture::new(t)
}
/// Submit a single **OUT (host-to-device)** transfer on the specified **interrupt** endpoint.
///
/// * An OUT endpoint address must have the top (`0x80`) bit clear.
pub fn interrupt_out(&self, endpoint: u8, buf: Vec<u8>) -> TransferFuture<Vec<u8>> {
let mut t = self
.backend
.make_transfer(endpoint, EndpointType::Interrupt);
t.submit(buf);
TransferFuture::new(t)
}
/// Create a queue for managing multiple **IN (device-to-host)** transfers on an **interrupt** endpoint.
///
/// * An IN endpoint address must have the top (`0x80`) bit set.
pub fn interrupt_in_queue(&self, endpoint: u8) -> Queue<RequestBuffer> {
Queue::new(self.backend.clone(), endpoint, EndpointType::Interrupt)
}
/// Create a queue for managing multiple **OUT (device-to-host)** transfers on an **interrupt** endpoint.
///
/// * An OUT endpoint address must have the top (`0x80`) bit clear.
pub fn interrupt_out_queue(&self, endpoint: u8) -> Queue<Vec<u8>> {
Queue::new(self.backend.clone(), endpoint, EndpointType::Interrupt)
}
/// Clear a bulk or interrupt endpoint's halt / stall condition.
///
/// Sends a `CLEAR_FEATURE` `ENDPOINT_HALT` control transfer to tell the
/// device to reset the endpoint's data toggle and clear the halt / stall
/// condition, and resets the host-side data toggle.
///
/// Use this after receiving [`TransferError::Stall`] to clear the error and
/// 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)
}
/// Get the interface number.
pub fn interface_number(&self) -> u8 {
self.backend.interface_number
}
/// Get the interface descriptors for the alternate settings of this interface.
///
/// This returns cached data and does not perform IO.
pub fn descriptors(&self) -> impl Iterator<Item = InterfaceAltSetting> {
let active = self.backend.device.active_configuration_value();
let configuration = self
.backend
.device
.configuration_descriptors()
.map(Configuration::new)
.find(|c| c.configuration_value() == active);
configuration
.into_iter()
.flat_map(|i| i.interface_alt_settings())
.filter(|g| g.interface_number() == self.backend.interface_number)
}
}
#[test]
fn assert_send_sync() {
fn require_send_sync<T: Send + Sync>() {}
require_send_sync::<Interface>();
require_send_sync::<Device>();
}