Merge pull request #15 from kevinmehall/get-descriptor
Blocking control transfers and `Device::get_descriptor`, `Device::get_string_descriptor`
This commit is contained in:
commit
2c5526bed1
16 changed files with 811 additions and 54 deletions
83
examples/blocking.rs
Normal file
83
examples/blocking.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use nusb::transfer::{Control, ControlType, Recipient};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let di = nusb::list_devices()
|
||||
.unwrap()
|
||||
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
|
||||
.expect("device should be connected");
|
||||
|
||||
println!("Device info: {di:?}");
|
||||
|
||||
let device = di.open().unwrap();
|
||||
|
||||
// Linux can make control transfers without claiming an interface
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let result = device.control_out_blocking(
|
||||
Control {
|
||||
control_type: ControlType::Vendor,
|
||||
recipient: Recipient::Device,
|
||||
request: 0x81,
|
||||
value: 0x9999,
|
||||
index: 0x9999,
|
||||
},
|
||||
&[1, 2, 3, 4],
|
||||
Duration::from_secs(1),
|
||||
);
|
||||
println!("{result:?}");
|
||||
|
||||
let mut buf = [0; 64];
|
||||
|
||||
let len = device
|
||||
.control_in_blocking(
|
||||
Control {
|
||||
control_type: ControlType::Vendor,
|
||||
recipient: Recipient::Device,
|
||||
request: 0x81,
|
||||
value: 0x9999,
|
||||
index: 0x9999,
|
||||
},
|
||||
&mut buf,
|
||||
Duration::from_secs(1),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("{result:?}, {data:?}", data = &buf[..len]);
|
||||
}
|
||||
|
||||
// but we also provide an API on the `Interface` to support Windows
|
||||
let interface = device.claim_interface(0).unwrap();
|
||||
|
||||
let result = interface.control_out_blocking(
|
||||
Control {
|
||||
control_type: ControlType::Vendor,
|
||||
recipient: Recipient::Device,
|
||||
request: 0x81,
|
||||
value: 0x9999,
|
||||
index: 0x9999,
|
||||
},
|
||||
&[1, 2, 3, 4, 5],
|
||||
Duration::from_secs(1),
|
||||
);
|
||||
println!("{result:?}");
|
||||
|
||||
let mut buf = [0; 64];
|
||||
|
||||
let len = interface
|
||||
.control_in_blocking(
|
||||
Control {
|
||||
control_type: ControlType::Vendor,
|
||||
recipient: Recipient::Device,
|
||||
request: 0x81,
|
||||
value: 0x9999,
|
||||
index: 0x9999,
|
||||
},
|
||||
&mut buf,
|
||||
Duration::from_secs(1),
|
||||
)
|
||||
.unwrap();
|
||||
println!("{data:?}", data = &buf[..len]);
|
||||
}
|
||||
68
examples/string_descriptors.rs
Normal file
68
examples/string_descriptors.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use nusb::{descriptors::language_id::US_ENGLISH, DeviceInfo};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
for dev in nusb::list_devices().unwrap() {
|
||||
inspect_device(dev);
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect_device(dev: DeviceInfo) {
|
||||
println!(
|
||||
"Device {:03}.{:03} ({:04x}:{:04x}) {} {}",
|
||||
dev.bus_number(),
|
||||
dev.device_address(),
|
||||
dev.vendor_id(),
|
||||
dev.product_id(),
|
||||
dev.manufacturer_string().unwrap_or(""),
|
||||
dev.product_string().unwrap_or("")
|
||||
);
|
||||
let dev = match dev.open() {
|
||||
Ok(dev) => dev,
|
||||
Err(e) => {
|
||||
println!("Failed to open device: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let timeout = Duration::from_millis(100);
|
||||
|
||||
let dev_descriptor = dev.get_descriptor(0x01, 0, 0, timeout).unwrap();
|
||||
if dev_descriptor.len() < 18
|
||||
|| dev_descriptor[0] as usize > dev_descriptor.len()
|
||||
|| dev_descriptor[1] != 0x01
|
||||
{
|
||||
println!(" Invalid device descriptor: {dev_descriptor:?}");
|
||||
return;
|
||||
}
|
||||
|
||||
let languages: Vec<u16> = dev
|
||||
.get_string_descriptor_supported_languages(timeout)
|
||||
.map(|i| i.collect())
|
||||
.unwrap_or_default();
|
||||
println!(" Languages: {languages:02x?}");
|
||||
|
||||
let language = languages.first().copied().unwrap_or(US_ENGLISH);
|
||||
|
||||
let i_manufacturer = dev_descriptor[14];
|
||||
if i_manufacturer != 0 {
|
||||
let s = dev.get_string_descriptor(i_manufacturer, language, timeout);
|
||||
println!(" Manufacturer({i_manufacturer}): {s:?}");
|
||||
}
|
||||
|
||||
let i_product = dev_descriptor[15];
|
||||
if i_product != 0 {
|
||||
let s = dev.get_string_descriptor(i_product, language, timeout);
|
||||
println!(" Product({i_product}): {s:?}");
|
||||
}
|
||||
|
||||
let i_serial = dev_descriptor[16];
|
||||
if i_serial != 0 {
|
||||
let s = dev.get_string_descriptor(i_serial, language, timeout);
|
||||
println!(" Serial({i_serial}): {s:?}");
|
||||
}
|
||||
|
||||
println!("");
|
||||
}
|
||||
|
|
@ -2,7 +2,13 @@
|
|||
//!
|
||||
//! Descriptors are blocks of data that describe the functionality of a USB device.
|
||||
|
||||
use std::{collections::BTreeMap, fmt::{Display, Debug}, io::ErrorKind, iter, ops::Deref};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt::{Debug, Display},
|
||||
io::ErrorKind,
|
||||
iter,
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use log::warn;
|
||||
|
||||
|
|
@ -22,6 +28,16 @@ pub(crate) const DESCRIPTOR_LEN_INTERFACE: u8 = 9;
|
|||
pub(crate) const DESCRIPTOR_TYPE_ENDPOINT: u8 = 0x05;
|
||||
pub(crate) const DESCRIPTOR_LEN_ENDPOINT: u8 = 7;
|
||||
|
||||
/// USB defined language IDs for string descriptors.
|
||||
///
|
||||
/// In practice, different language IDs are not used,
|
||||
/// and device string descriptors are only provided
|
||||
/// with [`language_id::US_ENGLISH`].
|
||||
pub mod language_id {
|
||||
/// US English
|
||||
pub const US_ENGLISH: u16 = 0x0409;
|
||||
}
|
||||
|
||||
/// A raw USB descriptor.
|
||||
///
|
||||
/// Wraps a byte slice to provide access to the bytes of a descriptor by implementing `Deref` to `[u8]`,
|
||||
|
|
@ -287,7 +303,12 @@ impl<'a> Configuration<'a> {
|
|||
|
||||
struct DebugEntries<F>(F);
|
||||
|
||||
impl<F, I> Debug for DebugEntries<F> where F: Fn() -> I, I: Iterator, I::Item: Debug {
|
||||
impl<F, I> Debug for DebugEntries<F>
|
||||
where
|
||||
F: Fn() -> I,
|
||||
I: Iterator,
|
||||
I::Item: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_list().entries(self.0()).finish()
|
||||
}
|
||||
|
|
@ -301,7 +322,10 @@ impl<'a> Debug for Configuration<'a> {
|
|||
.field("attributes", &self.attributes())
|
||||
.field("max_power", &self.max_power())
|
||||
.field("string_index", &self.string_index())
|
||||
.field("interface_alt_settings", &DebugEntries(|| self.interface_alt_settings()))
|
||||
.field(
|
||||
"interface_alt_settings",
|
||||
&DebugEntries(|| self.interface_alt_settings()),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
188
src/device.rs
188
src/device.rs
|
|
@ -1,12 +1,20 @@
|
|||
use std::sync::Arc;
|
||||
use std::{io::ErrorKind, sync::Arc, time::Duration};
|
||||
|
||||
use log::error;
|
||||
|
||||
use crate::{
|
||||
descriptors::{ActiveConfigurationError, Configuration},
|
||||
platform,
|
||||
transfer::{ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferFuture},
|
||||
transfer::{
|
||||
Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError,
|
||||
TransferFuture,
|
||||
},
|
||||
DeviceInfo, Error,
|
||||
};
|
||||
|
||||
const STANDARD_REQUEST_GET_DESCRIPTOR: u8 = 0x06;
|
||||
const DESCRIPTOR_TYPE_STRING: u8 = 0x03;
|
||||
|
||||
/// An opened USB device.
|
||||
///
|
||||
/// Obtain a `Device` by calling [`DeviceInfo::open`]:
|
||||
|
|
@ -74,6 +82,103 @@ impl Device {
|
|||
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"))]
|
||||
{
|
||||
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)?;
|
||||
validate_string_descriptor(&data)?;
|
||||
|
||||
//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)?;
|
||||
validate_string_descriptor(&data)?;
|
||||
|
||||
Ok(char::decode_utf16(
|
||||
data[2..]
|
||||
.chunks_exact(2)
|
||||
.map(|c| u16::from_le_bytes(c.try_into().unwrap())),
|
||||
)
|
||||
.map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
|
||||
.collect::<String>())
|
||||
}
|
||||
|
||||
/// Reset the device, forcing it to re-enumerate.
|
||||
///
|
||||
/// This `Device` will no longer be usable, and you should drop it and call
|
||||
|
|
@ -85,7 +190,43 @@ impl Device {
|
|||
self.backend.reset()
|
||||
}
|
||||
|
||||
/// Submit a single **IN (device-to-host)** transfer on the default **control** endpoint.
|
||||
/// Synchronously submit 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"))]
|
||||
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 submit 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"))]
|
||||
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
|
||||
///
|
||||
|
|
@ -152,6 +293,17 @@ impl Device {
|
|||
}
|
||||
}
|
||||
|
||||
fn validate_string_descriptor(data: &[u8]) -> Result<(), Error> {
|
||||
if data.len() < 2 || data[0] as usize != data.len() || data[1] != DESCRIPTOR_TYPE_STRING {
|
||||
error!("String descriptor language list read {data:?}, not a valid string descriptor");
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"string descriptor data was invalid",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An opened interface of a USB device.
|
||||
///
|
||||
/// Obtain an `Interface` with the [`Device::claim_interface`] method.
|
||||
|
|
@ -174,6 +326,36 @@ impl Interface {
|
|||
self.backend.set_alt_setting(alt_setting)
|
||||
}
|
||||
|
||||
/// Synchronously submit 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.
|
||||
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 submit 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.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::{ffi::c_void, time::Duration};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
|
|
@ -17,13 +18,15 @@ use rustix::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
events,
|
||||
errno_to_transfer_error, events,
|
||||
usbfs::{self, Urb},
|
||||
SysfsPath,
|
||||
};
|
||||
use crate::{
|
||||
descriptors::{parse_concatenated_config_descriptors, DESCRIPTOR_LEN_DEVICE},
|
||||
transfer::{notify_completion, EndpointType, TransferHandle},
|
||||
transfer::{
|
||||
notify_completion, Control, Direction, EndpointType, TransferError, TransferHandle,
|
||||
},
|
||||
DeviceInfo, Error,
|
||||
};
|
||||
|
||||
|
|
@ -144,6 +147,68 @@ impl LinuxDevice {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction`
|
||||
unsafe fn control_blocking(
|
||||
&self,
|
||||
direction: Direction,
|
||||
control: Control,
|
||||
data: *mut u8,
|
||||
len: usize,
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
let r = usbfs::control(
|
||||
&self.fd,
|
||||
usbfs::CtrlTransfer {
|
||||
bRequestType: control.request_type(direction),
|
||||
bRequest: control.request,
|
||||
wValue: control.value,
|
||||
wIndex: control.index,
|
||||
wLength: len.try_into().expect("length must fit in u16"),
|
||||
timeout: timeout
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("timeout must fit in u32 ms"),
|
||||
data: data as *mut c_void,
|
||||
},
|
||||
);
|
||||
|
||||
r.map_err(errno_to_transfer_error)
|
||||
}
|
||||
|
||||
pub fn control_in_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
unsafe {
|
||||
self.control_blocking(
|
||||
Direction::In,
|
||||
control,
|
||||
data.as_mut_ptr(),
|
||||
data.len(),
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn control_out_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &[u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
unsafe {
|
||||
self.control_blocking(
|
||||
Direction::Out,
|
||||
control,
|
||||
data.as_ptr() as *mut u8,
|
||||
data.len(),
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_control_transfer(self: &Arc<Self>) -> TransferHandle<super::TransferData> {
|
||||
TransferHandle::new(super::TransferData::new(
|
||||
self.clone(),
|
||||
|
|
@ -223,6 +288,24 @@ impl LinuxInterface {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn control_in_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
self.device.control_in_blocking(control, data, timeout)
|
||||
}
|
||||
|
||||
pub fn control_out_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &[u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
self.device.control_out_blocking(control, data, timeout)
|
||||
}
|
||||
|
||||
pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> {
|
||||
debug!(
|
||||
"Set interface {} alt setting to {alt_setting}",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
mod transfer;
|
||||
use rustix::io::Errno;
|
||||
pub(crate) use transfer::TransferData;
|
||||
mod usbfs;
|
||||
|
||||
|
|
@ -9,3 +10,17 @@ pub use enumeration::{list_devices, SysfsPath};
|
|||
mod device;
|
||||
pub(crate) use device::LinuxDevice as Device;
|
||||
pub(crate) use device::LinuxInterface as Interface;
|
||||
|
||||
use crate::transfer::TransferError;
|
||||
|
||||
fn errno_to_transfer_error(e: Errno) -> TransferError {
|
||||
match e {
|
||||
Errno::NODEV | Errno::SHUTDOWN => TransferError::Disconnected,
|
||||
Errno::PIPE => TransferError::Stall,
|
||||
Errno::NOENT | Errno::CONNRESET | Errno::TIMEDOUT => TransferError::Cancelled,
|
||||
Errno::PROTO | Errno::ILSEQ | Errno::OVERFLOW | Errno::COMM | Errno::TIME => {
|
||||
TransferError::Fault
|
||||
}
|
||||
_ => TransferError::Unknown,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,12 @@ use crate::transfer::{
|
|||
RequestBuffer, ResponseBuffer, TransferError, SETUP_PACKET_SIZE,
|
||||
};
|
||||
|
||||
use super::usbfs::{
|
||||
Urb, USBDEVFS_URB_TYPE_BULK, USBDEVFS_URB_TYPE_CONTROL, USBDEVFS_URB_TYPE_INTERRUPT,
|
||||
USBDEVFS_URB_TYPE_ISO,
|
||||
use super::{
|
||||
errno_to_transfer_error,
|
||||
usbfs::{
|
||||
Urb, USBDEVFS_URB_TYPE_BULK, USBDEVFS_URB_TYPE_CONTROL, USBDEVFS_URB_TYPE_INTERRUPT,
|
||||
USBDEVFS_URB_TYPE_ISO,
|
||||
},
|
||||
};
|
||||
|
||||
/// Linux-specific transfer state.
|
||||
|
|
@ -213,13 +216,7 @@ fn urb_status(urb: &Urb) -> Result<(), TransferError> {
|
|||
}
|
||||
|
||||
// It's sometimes positive, sometimes negative, but rustix panics if negative.
|
||||
Err(match Errno::from_raw_os_error(urb.status.abs()) {
|
||||
Errno::NODEV | Errno::SHUTDOWN => TransferError::Disconnected,
|
||||
Errno::PIPE => TransferError::Stall,
|
||||
Errno::NOENT | Errno::CONNRESET => TransferError::Cancelled,
|
||||
Errno::PROTO | Errno::ILSEQ | Errno::OVERFLOW | Errno::COMM | Errno::TIME => {
|
||||
TransferError::Fault
|
||||
}
|
||||
_ => TransferError::Unknown,
|
||||
})
|
||||
Err(errno_to_transfer_error(Errno::from_raw_os_error(
|
||||
urb.status.abs(),
|
||||
)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,3 +150,53 @@ pub struct Urb {
|
|||
pub usercontext: *mut c_void,
|
||||
// + variable size array of iso_packet_desc
|
||||
}
|
||||
|
||||
pub struct Transfer<Opcode, Input> {
|
||||
input: Input,
|
||||
_opcode: PhantomData<Opcode>,
|
||||
}
|
||||
|
||||
impl<Opcode: CompileTimeOpcode, Input> Transfer<Opcode, Input> {
|
||||
#[inline]
|
||||
pub unsafe fn new(input: Input) -> Self {
|
||||
Self {
|
||||
input,
|
||||
_opcode: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<Opcode: CompileTimeOpcode, Input> Ioctl for Transfer<Opcode, Input> {
|
||||
type Output = usize;
|
||||
|
||||
const IS_MUTATING: bool = true;
|
||||
const OPCODE: rustix::ioctl::Opcode = Opcode::OPCODE;
|
||||
|
||||
fn as_ptr(&mut self) -> *mut c_void {
|
||||
&mut self.input as *mut Input as *mut c_void
|
||||
}
|
||||
|
||||
unsafe fn output_from_ptr(r: IoctlOutput, _: *mut c_void) -> io::Result<usize> {
|
||||
Ok(r as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct CtrlTransfer {
|
||||
pub bRequestType: u8,
|
||||
pub bRequest: u8,
|
||||
pub wValue: u16,
|
||||
pub wIndex: u16,
|
||||
pub wLength: u16,
|
||||
pub timeout: u32, /* in milliseconds */
|
||||
pub data: *mut c_void,
|
||||
}
|
||||
|
||||
pub fn control<Fd: AsFd>(fd: Fd, transfer: CtrlTransfer) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let ctl =
|
||||
Transfer::<ioctl::ReadWriteOpcode<b'U', 0, CtrlTransfer>, CtrlTransfer>::new(transfer);
|
||||
ioctl::ioctl(fd, ctl)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
ffi::c_void,
|
||||
io::ErrorKind,
|
||||
sync::{
|
||||
atomic::{AtomicU8, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::{debug, error};
|
||||
|
||||
use crate::{
|
||||
platform::macos_iokit::events::add_event_source,
|
||||
transfer::{EndpointType, TransferHandle},
|
||||
transfer::{Control, Direction, EndpointType, TransferError, TransferHandle},
|
||||
DeviceInfo, Error,
|
||||
};
|
||||
|
||||
|
|
@ -19,7 +21,9 @@ use super::{
|
|||
enumeration::service_by_registry_id,
|
||||
events::EventRegistration,
|
||||
iokit::{call_iokit_function, check_iokit_return},
|
||||
iokit_c::IOUSBDevRequestTO,
|
||||
iokit_usb::{EndpointInfo, IoKitDevice, IoKitInterface},
|
||||
status_to_transfer_result,
|
||||
};
|
||||
|
||||
pub(crate) struct MacDevice {
|
||||
|
|
@ -93,6 +97,67 @@ impl MacDevice {
|
|||
}
|
||||
}
|
||||
|
||||
/// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction`
|
||||
unsafe fn control_blocking(
|
||||
&self,
|
||||
direction: Direction,
|
||||
control: Control,
|
||||
data: *mut u8,
|
||||
len: usize,
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
|
||||
let mut req = IOUSBDevRequestTO {
|
||||
bmRequestType: control.request_type(direction),
|
||||
bRequest: control.request,
|
||||
wValue: control.value,
|
||||
wIndex: control.index,
|
||||
wLength: len.try_into().expect("length must fit in u16"),
|
||||
pData: data.cast::<c_void>(),
|
||||
wLenDone: 0,
|
||||
noDataTimeout: timeout_ms,
|
||||
completionTimeout: timeout_ms,
|
||||
};
|
||||
|
||||
let r = unsafe { call_iokit_function!(self.device.raw, DeviceRequestTO(&mut req)) };
|
||||
|
||||
status_to_transfer_result(r).map(|()| req.wLenDone as usize)
|
||||
}
|
||||
|
||||
pub fn control_in_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
unsafe {
|
||||
self.control_blocking(
|
||||
Direction::In,
|
||||
control,
|
||||
data.as_mut_ptr(),
|
||||
data.len(),
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn control_out_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &[u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
unsafe {
|
||||
self.control_blocking(
|
||||
Direction::Out,
|
||||
control,
|
||||
data.as_ptr() as *mut u8,
|
||||
data.len(),
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_control_transfer(self: &Arc<Self>) -> TransferHandle<super::TransferData> {
|
||||
TransferHandle::new(super::TransferData::new_control(self.clone()))
|
||||
}
|
||||
|
|
@ -154,6 +219,24 @@ impl MacInterface {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn control_in_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
self.device.control_in_blocking(control, data, timeout)
|
||||
}
|
||||
|
||||
pub fn control_out_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &[u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
self.device.control_out_blocking(control, data, timeout)
|
||||
}
|
||||
|
||||
pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> {
|
||||
debug!(
|
||||
"Set interface {} alt setting to {alt_setting}",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
mod transfer;
|
||||
use io_kit_sys::ret::{
|
||||
kIOReturnAborted, kIOReturnNoDevice, kIOReturnSuccess, kIOReturnUnderrun, IOReturn,
|
||||
};
|
||||
pub(crate) use transfer::TransferData;
|
||||
|
||||
mod enumeration;
|
||||
|
|
@ -9,6 +12,19 @@ mod device;
|
|||
pub(crate) use device::MacDevice as Device;
|
||||
pub(crate) use device::MacInterface as Interface;
|
||||
|
||||
use crate::transfer::TransferError;
|
||||
|
||||
mod iokit;
|
||||
mod iokit_c;
|
||||
mod iokit_usb;
|
||||
|
||||
fn status_to_transfer_result(status: IOReturn) -> Result<(), TransferError> {
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[deny(unreachable_patterns)]
|
||||
match status {
|
||||
kIOReturnSuccess | kIOReturnUnderrun => Ok(()),
|
||||
kIOReturnNoDevice => Err(TransferError::Disconnected),
|
||||
kIOReturnAborted => Err(TransferError::Cancelled),
|
||||
_ => Err(TransferError::Unknown),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use io_kit_sys::ret::{
|
||||
kIOReturnAborted, kIOReturnNoDevice, kIOReturnSuccess, kIOReturnUnderrun, IOReturn,
|
||||
};
|
||||
use io_kit_sys::ret::{kIOReturnSuccess, IOReturn};
|
||||
use log::{error, info};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -18,7 +16,7 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
use super::{iokit::call_iokit_function, iokit_usb::EndpointInfo};
|
||||
use super::{iokit::call_iokit_function, iokit_usb::EndpointInfo, status_to_transfer_result};
|
||||
|
||||
extern "C" fn transfer_callback(refcon: *mut c_void, result: IOReturn, len: *mut c_void) {
|
||||
info!(
|
||||
|
|
@ -146,16 +144,7 @@ impl TransferData {
|
|||
unsafe fn take_status(&mut self) -> (Result<(), TransferError>, usize) {
|
||||
let inner = unsafe { &*self.inner };
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[deny(unreachable_patterns)]
|
||||
let status = match inner.status {
|
||||
kIOReturnSuccess | kIOReturnUnderrun => Ok(()),
|
||||
kIOReturnNoDevice => Err(TransferError::Disconnected),
|
||||
kIOReturnAborted => Err(TransferError::Cancelled),
|
||||
_ => Err(TransferError::Unknown),
|
||||
};
|
||||
|
||||
(status, inner.actual_len)
|
||||
(status_to_transfer_result(inner.status), inner.actual_len)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,30 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::OsString,
|
||||
ffi::{c_void, OsString},
|
||||
io::{self, ErrorKind},
|
||||
mem::size_of_val,
|
||||
os::windows::prelude::OwnedHandle,
|
||||
ptr::null_mut,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::{debug, error};
|
||||
use log::{debug, error, info};
|
||||
use windows_sys::Win32::{
|
||||
Devices::{
|
||||
Properties::DEVPKEY_Device_Address,
|
||||
Usb::{
|
||||
WinUsb_Free, WinUsb_Initialize, WinUsb_SetCurrentAlternateSetting,
|
||||
GUID_DEVINTERFACE_USB_DEVICE, WINUSB_INTERFACE_HANDLE,
|
||||
WinUsb_ControlTransfer, WinUsb_Free, WinUsb_Initialize,
|
||||
WinUsb_SetCurrentAlternateSetting, WinUsb_SetPipePolicy, GUID_DEVINTERFACE_USB_DEVICE,
|
||||
PIPE_TRANSFER_TIMEOUT, WINUSB_INTERFACE_HANDLE, WINUSB_SETUP_PACKET,
|
||||
},
|
||||
},
|
||||
Foundation::{FALSE, TRUE},
|
||||
Foundation::{GetLastError, FALSE, TRUE},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
descriptors::{validate_config_descriptor, DESCRIPTOR_TYPE_CONFIGURATION},
|
||||
transfer::{EndpointType, TransferHandle},
|
||||
transfer::{Control, Direction, EndpointType, TransferError, TransferHandle},
|
||||
DeviceInfo, Error,
|
||||
};
|
||||
|
||||
|
|
@ -34,6 +38,8 @@ pub(crate) struct WindowsDevice {
|
|||
config_descriptors: Vec<Vec<u8>>,
|
||||
interface_paths: HashMap<u8, OsString>,
|
||||
active_config: u8,
|
||||
hub_handle: HubHandle,
|
||||
hub_port_number: u32,
|
||||
}
|
||||
|
||||
impl WindowsDevice {
|
||||
|
|
@ -79,6 +85,8 @@ impl WindowsDevice {
|
|||
interface_paths: d.interfaces.clone(),
|
||||
config_descriptors,
|
||||
active_config: connection_info.CurrentConfigurationValue,
|
||||
hub_handle,
|
||||
hub_port_number,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +105,19 @@ impl WindowsDevice {
|
|||
))
|
||||
}
|
||||
|
||||
pub(crate) fn get_descriptor(
|
||||
&self,
|
||||
desc_type: u8,
|
||||
desc_index: u8,
|
||||
language_id: u16,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
//TODO: this has a race condition in that the device is identified only by port number. If the device is
|
||||
// disconnected and another device connects to the same port while this `Device` exists, it may return
|
||||
// descriptors from the new device.
|
||||
self.hub_handle
|
||||
.get_descriptor(self.hub_port_number, desc_type, desc_index, language_id)
|
||||
}
|
||||
|
||||
pub(crate) fn reset(&self) -> Result<(), Error> {
|
||||
Err(io::Error::new(
|
||||
ErrorKind::Unsupported,
|
||||
|
|
@ -146,6 +167,111 @@ impl WindowsInterface {
|
|||
TransferHandle::new(super::TransferData::new(self.clone(), endpoint, ep_type))
|
||||
}
|
||||
|
||||
/// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction`
|
||||
unsafe fn control_blocking(
|
||||
&self,
|
||||
direction: Direction,
|
||||
control: Control,
|
||||
data: *mut u8,
|
||||
len: usize,
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
info!("Blocking control {direction:?}, {len} bytes");
|
||||
let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
|
||||
let r = WinUsb_SetPipePolicy(
|
||||
self.winusb_handle,
|
||||
0,
|
||||
PIPE_TRANSFER_TIMEOUT,
|
||||
size_of_val(&timeout_ms) as u32,
|
||||
&timeout_ms as *const u32 as *const c_void,
|
||||
);
|
||||
|
||||
if r != TRUE {
|
||||
error!(
|
||||
"WinUsb_SetPipePolicy PIPE_TRANSFER_TIMEOUT failed: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
let pkt = WINUSB_SETUP_PACKET {
|
||||
RequestType: control.request_type(direction),
|
||||
Request: control.request,
|
||||
Value: control.value,
|
||||
Index: control.index,
|
||||
Length: len.try_into().expect("request size too large"),
|
||||
};
|
||||
|
||||
let mut actual_len = 0;
|
||||
|
||||
let r = WinUsb_ControlTransfer(
|
||||
self.winusb_handle,
|
||||
pkt,
|
||||
data,
|
||||
len.try_into().expect("request size too large"),
|
||||
&mut actual_len,
|
||||
null_mut(),
|
||||
);
|
||||
|
||||
if r == TRUE {
|
||||
Ok(actual_len as usize)
|
||||
} else {
|
||||
error!(
|
||||
"WinUsb_ControlTransfer failed: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
Err(super::transfer::map_error(GetLastError()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn control_in_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
unsafe {
|
||||
self.control_blocking(
|
||||
Direction::In,
|
||||
control,
|
||||
data.as_mut_ptr(),
|
||||
data.len(),
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn control_out_blocking(
|
||||
&self,
|
||||
control: Control,
|
||||
data: &[u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, TransferError> {
|
||||
// When passed a pointer to read-only memory (e.g. a constant slice),
|
||||
// WinUSB fails with "Invalid access to memory location. (os error 998)".
|
||||
// I assume the kernel is checking the pointer for write access
|
||||
// regardless of the transfer direction. Copy the data to the stack to ensure
|
||||
// we give it a pointer to writable memory.
|
||||
let mut buf = [0; 4096];
|
||||
let Some(buf) = buf.get_mut(..data.len()) else {
|
||||
error!(
|
||||
"Control transfer length {} exceeds limit of 4096",
|
||||
data.len()
|
||||
);
|
||||
return Err(TransferError::Unknown);
|
||||
};
|
||||
buf.copy_from_slice(data);
|
||||
|
||||
unsafe {
|
||||
self.control_blocking(
|
||||
Direction::Out,
|
||||
control,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len(),
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> {
|
||||
unsafe {
|
||||
let r = WinUsb_SetCurrentAlternateSetting(raw_handle(&self.handle), alt_setting.into());
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
use std::{
|
||||
alloc::{self, Layout},
|
||||
ffi::{c_void, OsStr},
|
||||
io, mem,
|
||||
io::ErrorKind,
|
||||
mem,
|
||||
os::windows::prelude::OwnedHandle,
|
||||
ptr::{addr_of, null_mut},
|
||||
slice,
|
||||
};
|
||||
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use windows_sys::Win32::{
|
||||
Devices::Usb::{
|
||||
GUID_DEVINTERFACE_USB_HUB, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
|
||||
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, USB_DESCRIPTOR_REQUEST,
|
||||
USB_DESCRIPTOR_REQUEST_0, USB_NODE_CONNECTION_INFORMATION_EX,
|
||||
},
|
||||
Foundation::TRUE,
|
||||
Foundation::{GetLastError, ERROR_GEN_FAILURE, TRUE},
|
||||
System::IO::DeviceIoControl,
|
||||
};
|
||||
|
||||
|
|
@ -69,7 +70,7 @@ impl HubHandle {
|
|||
if r == TRUE {
|
||||
Ok(info)
|
||||
} else {
|
||||
let err = io::Error::last_os_error();
|
||||
let err = Error::last_os_error();
|
||||
error!("Hub DeviceIoControl failed: {err:?}");
|
||||
Err(err)
|
||||
}
|
||||
|
|
@ -83,7 +84,10 @@ impl HubHandle {
|
|||
descriptor_index: u8,
|
||||
language_id: u16,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let length = 4096;
|
||||
// Experimentally determined on Windows 10 19045.3803 that this fails
|
||||
// with ERROR_INVALID_PARAMETER for non-cached descriptors when
|
||||
// requesting length greater than 4095.
|
||||
let length = 4095;
|
||||
|
||||
unsafe {
|
||||
let layout = Layout::from_size_align(
|
||||
|
|
@ -125,9 +129,15 @@ impl HubHandle {
|
|||
let vec = slice::from_raw_parts(start, len).to_owned();
|
||||
Ok(vec)
|
||||
} else {
|
||||
let err = io::Error::last_os_error();
|
||||
error!("Hub get descriptor failed: {err:?}");
|
||||
Err(err)
|
||||
let err = GetLastError();
|
||||
warn!("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION failed: type={descriptor_type} index={descriptor_index} error={err:?}");
|
||||
Err(match err {
|
||||
ERROR_GEN_FAILURE => Error::new(
|
||||
ErrorKind::Other,
|
||||
"Descriptor request failed. Device might be suspended.",
|
||||
),
|
||||
_ => Error::from_raw_os_error(err as i32),
|
||||
})
|
||||
};
|
||||
|
||||
alloc::dealloc(req as *mut _, layout);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ use windows_sys::Win32::{
|
|||
},
|
||||
Foundation::{
|
||||
GetLastError, ERROR_DEVICE_NOT_CONNECTED, ERROR_FILE_NOT_FOUND, ERROR_GEN_FAILURE,
|
||||
ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_SUCH_DEVICE, ERROR_REQUEST_ABORTED, FALSE,
|
||||
TRUE, WIN32_ERROR,
|
||||
ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_SUCH_DEVICE, ERROR_REQUEST_ABORTED,
|
||||
ERROR_SEM_TIMEOUT, ERROR_TIMEOUT, FALSE, TRUE, WIN32_ERROR,
|
||||
},
|
||||
System::IO::{CancelIoEx, OVERLAPPED},
|
||||
};
|
||||
|
|
@ -321,10 +321,10 @@ pub(super) fn handle_event(completion: *mut OVERLAPPED) {
|
|||
}
|
||||
}
|
||||
|
||||
fn map_error(err: WIN32_ERROR) -> TransferError {
|
||||
pub(crate) fn map_error(err: WIN32_ERROR) -> TransferError {
|
||||
match err {
|
||||
ERROR_GEN_FAILURE => TransferError::Stall,
|
||||
ERROR_REQUEST_ABORTED => TransferError::Cancelled,
|
||||
ERROR_REQUEST_ABORTED | ERROR_TIMEOUT | ERROR_SEM_TIMEOUT => TransferError::Cancelled,
|
||||
ERROR_FILE_NOT_FOUND | ERROR_DEVICE_NOT_CONNECTED | ERROR_NO_SUCH_DEVICE => {
|
||||
TransferError::Disconnected
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,37 @@ pub enum Recipient {
|
|||
Other = 3,
|
||||
}
|
||||
|
||||
/// SETUP packet without direction or buffers
|
||||
pub struct Control {
|
||||
/// Request type used for the `bmRequestType` field sent in the SETUP packet.
|
||||
#[doc(alias = "bmRequestType")]
|
||||
pub control_type: ControlType,
|
||||
|
||||
/// Recipient used for the `bmRequestType` field sent in the SETUP packet.
|
||||
#[doc(alias = "bmRequestType")]
|
||||
pub recipient: Recipient,
|
||||
|
||||
/// `bRequest` field sent in the SETUP packet.
|
||||
#[doc(alias = "bRequest")]
|
||||
pub request: u8,
|
||||
|
||||
/// `wValue` field sent in the SETUP packet.
|
||||
#[doc(alias = "wValue")]
|
||||
pub value: u16,
|
||||
|
||||
/// `wIndex` field sent in the SETUP packet.
|
||||
///
|
||||
/// For [`Recipient::Interface`] this is the interface number. For [`Recipient::Endpoint`] this is the endpoint number.
|
||||
#[doc(alias = "wIndex")]
|
||||
pub index: u16,
|
||||
}
|
||||
|
||||
impl Control {
|
||||
pub(crate) fn request_type(&self, direction: Direction) -> u8 {
|
||||
request_type(direction, self.control_type, self.recipient)
|
||||
}
|
||||
}
|
||||
|
||||
/// SETUP packet and associated data to make an **OUT** request on a control endpoint.
|
||||
pub struct ControlOut<'a> {
|
||||
/// Request type used for the `bmRequestType` field sent in the SETUP packet.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub use buffer::{RequestBuffer, ResponseBuffer};
|
|||
mod control;
|
||||
#[allow(unused)]
|
||||
pub(crate) use control::SETUP_PACKET_SIZE;
|
||||
pub use control::{ControlIn, ControlOut, ControlType, Direction, Recipient};
|
||||
pub use control::{Control, ControlIn, ControlOut, ControlType, Direction, Recipient};
|
||||
|
||||
mod internal;
|
||||
pub(crate) use internal::{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue