Merge pull request #15 from kevinmehall/get-descriptor

Blocking control transfers and `Device::get_descriptor`, `Device::get_string_descriptor`
This commit is contained in:
Kevin Mehall 2023-12-17 21:31:24 -07:00 committed by GitHub
commit 2c5526bed1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 811 additions and 54 deletions

83
examples/blocking.rs Normal file
View 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]);
}

View 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!("");
}

View file

@ -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()
}
}

View file

@ -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

View file

@ -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}",

View file

@ -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,
}
}

View file

@ -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(),
)))
}

View file

@ -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)
}
}

View file

@ -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}",

View file

@ -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),
}
}

View file

@ -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)
}
}

View file

@ -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());

View file

@ -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);

View file

@ -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
}

View file

@ -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.

View file

@ -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::{