Define a new Error type instead of re-exporting std::io::Error

The default mapping from OS error codes to io::Error's messages is often wrong
or misleading. We can do a lot better, but it takes a lot of error-handling code.
This commit is contained in:
Kevin Mehall 2025-05-29 21:22:54 -06:00
parent 592f57a3e0
commit 6895347f4b
24 changed files with 737 additions and 353 deletions

View file

@ -2,17 +2,11 @@
//! //!
//! Descriptors are blocks of data that describe the functionality of a USB device. //! Descriptors are blocks of data that describe the functionality of a USB device.
use std::{ use std::{collections::BTreeMap, fmt::Debug, iter, num::NonZeroU8, ops::Deref};
collections::BTreeMap,
fmt::{Debug, Display},
iter,
num::NonZeroU8,
ops::Deref,
};
use log::warn; use log::warn;
use crate::{transfer::Direction, Error}; use crate::transfer::Direction;
pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01; pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01;
pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18; pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18;
@ -710,34 +704,6 @@ pub enum TransferType {
Interrupt = 3, Interrupt = 3,
} }
/// Error from [`crate::Device::active_configuration`]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ActiveConfigurationError {
pub(crate) configuration_value: u8,
}
impl Display for ActiveConfigurationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.configuration_value == 0 {
write!(f, "device is not configured")
} else {
write!(
f,
"no descriptor found for active configuration {}",
self.configuration_value
)
}
}
}
impl std::error::Error for ActiveConfigurationError {}
impl From<ActiveConfigurationError> for Error {
fn from(value: ActiveConfigurationError) -> Self {
Error::other(value)
}
}
/// Split a chain of concatenated configuration descriptors by `wTotalLength` /// Split a chain of concatenated configuration descriptors by `wTotalLength`
#[allow(unused)] #[allow(unused)]
pub(crate) fn parse_concatenated_config_descriptors( pub(crate) fn parse_concatenated_config_descriptors(
@ -914,7 +880,7 @@ fn test_linux_root_hub() {
fn test_dell_webcam() { fn test_dell_webcam() {
let c = ConfigurationDescriptor(&[ let c = ConfigurationDescriptor(&[
0x09, 0x02, 0xa3, 0x02, 0x02, 0x01, 0x00, 0x80, 0xfa, 0x09, 0x02, 0xa3, 0x02, 0x02, 0x01, 0x00, 0x80, 0xfa,
// unknown (skipped) // unknown (skipped)
0x28, 0xff, 0x42, 0x49, 0x53, 0x54, 0x00, 0x01, 0x06, 0x01, 0x10, 0x00, 0x28, 0xff, 0x42, 0x49, 0x53, 0x54, 0x00, 0x01, 0x06, 0x01, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0xd1, 0x10, 0xd0, 0x07, 0xd2, 0x11, 0xf4, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x10, 0xd0, 0x07, 0xd2, 0x11, 0xf4, 0x01,

View file

@ -1,19 +1,18 @@
use crate::{ use crate::{
descriptors::{ descriptors::{
decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError, decode_string_descriptor, validate_string_descriptor, ConfigurationDescriptor,
ConfigurationDescriptor, DeviceDescriptor, InterfaceDescriptor, DESCRIPTOR_TYPE_STRING, DeviceDescriptor, InterfaceDescriptor, DESCRIPTOR_TYPE_STRING,
}, },
platform, platform,
transfer::{ transfer::{
Buffer, BulkOrInterrupt, Completion, ControlIn, ControlOut, Direction, EndpointDirection, Buffer, BulkOrInterrupt, Completion, ControlIn, ControlOut, Direction, EndpointDirection,
EndpointType, TransferError, EndpointType, TransferError,
}, },
DeviceInfo, Error, MaybeFuture, Speed, ActiveConfigurationError, DeviceInfo, Error, ErrorKind, GetDescriptorError, MaybeFuture, Speed,
}; };
use log::{error, warn}; use log::{error, warn};
use std::{ use std::{
future::{poll_fn, Future}, future::{poll_fn, Future},
io::ErrorKind,
marker::PhantomData, marker::PhantomData,
num::NonZeroU8, num::NonZeroU8,
sync::Arc, sync::Arc,
@ -51,9 +50,7 @@ impl Device {
Device { backend } Device { backend }
} }
pub(crate) fn open( pub(crate) fn open(d: &DeviceInfo) -> impl MaybeFuture<Output = Result<Device, Error>> {
d: &DeviceInfo,
) -> impl MaybeFuture<Output = Result<Device, std::io::Error>> {
platform::Device::from_device_info(d).map(|d| d.map(Device::wrap)) platform::Device::from_device_info(d).map(|d| d.map(Device::wrap))
} }
@ -181,13 +178,14 @@ impl Device {
desc_index: u8, desc_index: u8,
language_id: u16, language_id: u16,
timeout: Duration, timeout: Duration,
) -> impl MaybeFuture<Output = Result<Vec<u8>, Error>> { ) -> impl MaybeFuture<Output = Result<Vec<u8>, GetDescriptorError>> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
let _ = timeout; let _ = timeout;
self.backend self.backend
.clone() .clone()
.get_descriptor(desc_type, desc_index, language_id) .get_descriptor(desc_type, desc_index, language_id)
.map(|r| r.map_err(GetDescriptorError::Transfer))
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
@ -206,7 +204,7 @@ impl Device {
}, },
timeout, timeout,
) )
.map(|r| Ok(r?)) .map(|r| r.map_err(GetDescriptorError::Transfer))
} }
} }
@ -218,16 +216,13 @@ impl Device {
pub fn get_string_descriptor_supported_languages( pub fn get_string_descriptor_supported_languages(
&self, &self,
timeout: Duration, timeout: Duration,
) -> impl MaybeFuture<Output = Result<impl Iterator<Item = u16>, Error>> { ) -> impl MaybeFuture<Output = Result<impl Iterator<Item = u16>, GetDescriptorError>> {
self.get_descriptor(DESCRIPTOR_TYPE_STRING, 0, 0, timeout) self.get_descriptor(DESCRIPTOR_TYPE_STRING, 0, 0, timeout)
.map(move |r| { .map(move |r| {
let data = r?; let data = r?;
if !validate_string_descriptor(&data) { if !validate_string_descriptor(&data) {
error!("String descriptor language list read {data:?}, not a valid string descriptor"); error!("String descriptor language list read {data:?}, not a valid string descriptor");
return Err(Error::new( return Err(GetDescriptorError::InvalidDescriptor)
ErrorKind::InvalidData,
"string descriptor data was invalid",
));
} }
//TODO: Use array_chunks once stable //TODO: Use array_chunks once stable
@ -252,7 +247,7 @@ impl Device {
desc_index: NonZeroU8, desc_index: NonZeroU8,
language_id: u16, language_id: u16,
timeout: Duration, timeout: Duration,
) -> impl MaybeFuture<Output = Result<String, Error>> { ) -> impl MaybeFuture<Output = Result<String, GetDescriptorError>> {
self.get_descriptor( self.get_descriptor(
DESCRIPTOR_TYPE_STRING, DESCRIPTOR_TYPE_STRING,
desc_index.get(), desc_index.get(),
@ -261,9 +256,7 @@ impl Device {
) )
.map(|r| { .map(|r| {
let data = r?; let data = r?;
decode_string_descriptor(&data).map_err(|_| { decode_string_descriptor(&data).map_err(|_| GetDescriptorError::InvalidDescriptor)
Error::new(ErrorKind::InvalidData, "string descriptor data was invalid")
})
}) })
} }
@ -500,16 +493,23 @@ impl Interface {
pub fn endpoint<EpType: EndpointType, Dir: EndpointDirection>( pub fn endpoint<EpType: EndpointType, Dir: EndpointDirection>(
&self, &self,
address: u8, address: u8,
) -> Result<Endpoint<EpType, Dir>, ClaimEndpointError> { ) -> Result<Endpoint<EpType, Dir>, Error> {
let intf_desc = self.descriptor(); let intf_desc = self.descriptor();
let ep_desc = let ep_desc =
intf_desc.and_then(|desc| desc.endpoints().find(|ep| ep.address() == address)); intf_desc.and_then(|desc| desc.endpoints().find(|ep| ep.address() == address));
let Some(ep_desc) = ep_desc else { let Some(ep_desc) = ep_desc else {
return Err(ClaimEndpointError::InvalidAddress); return Err(Error::new(
ErrorKind::NotFound,
"specified endpoint does not exist on this interface",
));
}; };
if ep_desc.transfer_type() != EpType::TYPE || address & Direction::MASK != Dir::DIR as u8 { if address & Direction::MASK != Dir::DIR as u8 {
return Err(ClaimEndpointError::InvalidType); return Err(Error::new(ErrorKind::Other, "incorrect endpoint direction"));
}
if ep_desc.transfer_type() != EpType::TYPE {
return Err(Error::new(ErrorKind::Other, "incorrect endpoint type"));
} }
let backend = self.backend.endpoint(ep_desc)?; let backend = self.backend.endpoint(ep_desc)?;
@ -521,32 +521,6 @@ impl Interface {
} }
} }
/// Error from [`Interface::endpoint`].
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ClaimEndpointError {
/// The specified address does not exist on this interface and alternate setting
InvalidAddress,
/// The type or direction does not match the endpoint descriptor for this address
InvalidType,
/// The endpoint is already claimed
Busy,
}
impl std::fmt::Display for ClaimEndpointError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClaimEndpointError::InvalidAddress => write!(f, "invalid endpoint address"),
ClaimEndpointError::InvalidType => write!(f, "incorrect endpoint type or direction"),
ClaimEndpointError::Busy => write!(f, "endpoint is already claimed"),
}
}
}
impl std::error::Error for ClaimEndpointError {}
/// Exclusive access to an endpoint of a USB device. /// Exclusive access to an endpoint of a USB device.
/// ///
/// Obtain an `Endpoint` with the [`Interface::endpoint`] method. /// Obtain an `Endpoint` with the [`Interface::endpoint`] method.

169
src/error.rs Normal file
View file

@ -0,0 +1,169 @@
use std::{fmt::Display, io, num::NonZeroU32};
use crate::{platform::format_os_error_code, transfer::TransferError};
/// Error returned from `nusb` operations other than transfers.
#[derive(Debug, Clone)]
pub struct Error {
pub(crate) kind: ErrorKind,
pub(crate) code: Option<NonZeroU32>,
pub(crate) message: &'static str,
}
impl Error {
pub(crate) fn new(kind: ErrorKind, message: &'static str) -> Self {
Self {
kind,
code: None,
message,
}
}
#[track_caller]
pub(crate) fn log_error(self) -> Self {
log::error!("{}", self);
self
}
#[track_caller]
pub(crate) fn log_debug(self) -> Self {
log::debug!("{}", self);
self
}
/// Get the error kind.
pub fn kind(&self) -> ErrorKind {
self.kind
}
/// Get the error code from the OS, if applicable.
///
/// * On Linux this is the `errno` value.
/// * On Windows this is the `WIN32_ERROR` value.
/// * On macOS this is the `IOReturn` value.
pub fn os_error(&self) -> Option<u32> {
self.code.map(|c| c.get())
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)?;
if let Some(code) = self.code {
write!(f, " (")?;
format_os_error_code(f, code.get())?;
write!(f, ")")?;
}
Ok(())
}
}
impl std::error::Error for Error {}
impl From<Error> for io::Error {
fn from(err: Error) -> Self {
let kind = match err.kind {
ErrorKind::Disconnected => io::ErrorKind::NotConnected,
ErrorKind::Busy => io::ErrorKind::Other, // TODO: ResourceBusy
ErrorKind::PermissionDenied => io::ErrorKind::PermissionDenied,
ErrorKind::NotFound => io::ErrorKind::NotFound,
ErrorKind::Unsupported => io::ErrorKind::Unsupported,
ErrorKind::Other => io::ErrorKind::Other,
};
io::Error::new(kind, err)
}
}
/// General category of error as part of an [`Error`].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
/// Device is disconnected.
Disconnected,
/// Device, interface, or endpoint is in use by another application, kernel driver, or handle.
Busy,
/// This user or application does not have permission to perform the requested operation.
PermissionDenied,
/// Requested configuration, interface, or alternate setting not found
NotFound,
/// The requested operation is not supported by the platform or its currently-configured driver.
Unsupported,
/// Uncategorized error.
Other,
}
/// Error from [`crate::Device::active_configuration`]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ActiveConfigurationError {
pub(crate) configuration_value: u8,
}
impl Display for ActiveConfigurationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.configuration_value == 0 {
write!(f, "device is not configured")
} else {
write!(
f,
"no descriptor found for active configuration {}",
self.configuration_value
)
}
}
}
impl std::error::Error for ActiveConfigurationError {}
impl From<ActiveConfigurationError> for Error {
fn from(value: ActiveConfigurationError) -> Self {
let message = if value.configuration_value == 0 {
"device is not configured"
} else {
"no descriptor found for active configuration"
};
Error::new(ErrorKind::Other, message)
}
}
impl From<ActiveConfigurationError> for std::io::Error {
fn from(value: ActiveConfigurationError) -> Self {
std::io::Error::other(value)
}
}
/// Error for descriptor reads.
#[derive(Debug, Copy, Clone)]
pub enum GetDescriptorError {
/// Transfer error when getting the descriptor.
Transfer(TransferError),
/// Invalid descriptor data
InvalidDescriptor,
}
impl Display for GetDescriptorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GetDescriptorError::Transfer(e) => write!(f, "{}", e),
GetDescriptorError::InvalidDescriptor => write!(f, "invalid descriptor"),
}
}
}
impl std::error::Error for GetDescriptorError {}
impl From<GetDescriptorError> for std::io::Error {
fn from(value: GetDescriptorError) -> Self {
match value {
GetDescriptorError::Transfer(e) => e.into(),
GetDescriptorError::InvalidDescriptor => {
std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid descriptor")
}
}
}
}

View file

@ -134,8 +134,6 @@
//! These features do not affect and are not required for transfers, which are //! These features do not affect and are not required for transfers, which are
//! implemented on top of natively-async OS APIs. //! implemented on top of natively-async OS APIs.
use std::io;
mod platform; mod platform;
pub mod descriptors; pub mod descriptors;
@ -143,7 +141,7 @@ mod enumeration;
pub use enumeration::{BusInfo, DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType}; pub use enumeration::{BusInfo, DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType};
mod device; mod device;
pub use device::{ClaimEndpointError, Device, Endpoint, Interface}; pub use device::{Device, Endpoint, Interface};
pub mod transfer; pub mod transfer;
@ -154,8 +152,8 @@ pub use maybe_future::MaybeFuture;
mod bitset; mod bitset;
/// OS error returned from operations other than transfers. mod error;
pub type Error = io::Error; pub use error::{ActiveConfigurationError, Error, ErrorKind, GetDescriptorError};
/// Get an iterator listing the connected devices. /// Get an iterator listing the connected devices.
/// ///

View file

@ -2,7 +2,7 @@ use std::{
collections::{BTreeMap, VecDeque}, collections::{BTreeMap, VecDeque},
ffi::c_void, ffi::c_void,
fs::File, fs::File,
io::{ErrorKind, Read, Seek}, io::{Read, Seek},
mem::ManuallyDrop, mem::ManuallyDrop,
path::PathBuf, path::PathBuf,
sync::{ sync::{
@ -34,7 +34,6 @@ use crate::{
parse_concatenated_config_descriptors, ConfigurationDescriptor, DeviceDescriptor, parse_concatenated_config_descriptors, ConfigurationDescriptor, DeviceDescriptor,
EndpointDescriptor, TransferType, DESCRIPTOR_LEN_DEVICE, EndpointDescriptor, TransferType, DESCRIPTOR_LEN_DEVICE,
}, },
device::ClaimEndpointError,
maybe_future::{blocking::Blocking, MaybeFuture}, maybe_future::{blocking::Blocking, MaybeFuture},
transfer::{ transfer::{
internal::{ internal::{
@ -43,7 +42,7 @@ use crate::{
request_type, Buffer, Completion, ControlIn, ControlOut, ControlType, Direction, Recipient, request_type, Buffer, Completion, ControlIn, ControlOut, ControlType, Direction, Recipient,
TransferError, TransferError,
}, },
DeviceInfo, Error, Speed, DeviceInfo, Error, ErrorKind, Speed,
}; };
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
@ -82,7 +81,19 @@ impl LinuxDevice {
Blocking::new(move || { Blocking::new(move || {
let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}")); let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"));
let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty()) let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())
.inspect_err(|e| warn!("Failed to open device {path:?}: {e}"))?; .map_err(|e| {
match e {
Errno::NOENT => {
Error::new_os(ErrorKind::Disconnected, "device not found", e)
}
Errno::PERM => {
Error::new_os(ErrorKind::PermissionDenied, "permission denied", e)
}
e => Error::new_os(ErrorKind::Other, "failed to open device", e),
}
.log_debug()
})?;
Self::create_inner(fd, Some(sysfs_path)) Self::create_inner(fd, Some(sysfs_path))
}) })
} }
@ -100,18 +111,18 @@ impl LinuxDevice {
fd: OwnedFd, fd: OwnedFd,
sysfs: Option<SysfsPath>, sysfs: Option<SysfsPath>,
) -> Result<Arc<LinuxDevice>, Error> { ) -> Result<Arc<LinuxDevice>, Error> {
let descriptors = read_all_from_fd(&fd)?; let descriptors = read_all_from_fd(&fd).map_err(|e| {
Error::new_io(ErrorKind::Other, "failed to read descriptors", e).log_error()
})?;
let Some(_) = DeviceDescriptor::new(&descriptors) else { let Some(_) = DeviceDescriptor::new(&descriptors) else {
return Err(Error::new( return Err(Error::new(ErrorKind::Other, "invalid device descriptor"));
ErrorKind::InvalidData,
"invalid device descriptor",
));
}; };
let active_config: u8 = if let Some(sysfs) = sysfs.as_ref() { let active_config: u8 = if let Some(sysfs) = sysfs.as_ref() {
sysfs.read_attr("bConfigurationValue").inspect_err(|e| { sysfs.read_attr("bConfigurationValue").map_err(|e| {
warn!("failed to read sysfs bConfigurationValue: {e}"); warn!("failed to read sysfs bConfigurationValue: {e}");
Error::new(ErrorKind::Other, "failed to read sysfs bConfigurationValue")
})? })?
} else { } else {
request_configuration(&fd).unwrap_or_else(|()| { request_configuration(&fd).unwrap_or_else(|()| {
@ -132,7 +143,7 @@ impl LinuxDevice {
rustix::time::TimerfdClockId::Monotonic, rustix::time::TimerfdClockId::Monotonic,
TimerfdFlags::CLOEXEC | TimerfdFlags::NONBLOCK, TimerfdFlags::CLOEXEC | TimerfdFlags::NONBLOCK,
) )
.inspect_err(|e| log::error!("Failed to create timerfd: {e}"))?; .map_err(|e| Error::new_os(ErrorKind::Other, "failed to create timerfd", e).log_error())?;
let arc = Arc::new_cyclic(|weak| { let arc = Arc::new_cyclic(|weak| {
let events_id = DEVICES.lock().unwrap().insert(weak.clone()); let events_id = DEVICES.lock().unwrap().insert(weak.clone());
@ -313,7 +324,12 @@ impl LinuxDevice {
configuration: u8, configuration: u8,
) -> impl MaybeFuture<Output = Result<(), Error>> { ) -> impl MaybeFuture<Output = Result<(), Error>> {
Blocking::new(move || { Blocking::new(move || {
usbfs::set_configuration(&self.fd, configuration)?; usbfs::set_configuration(&self.fd, configuration).map_err(|e| match e {
Errno::INVAL => Error::new_os(ErrorKind::NotFound, "configuration not found", e),
Errno::BUSY => Error::new_os(ErrorKind::Busy, "device is busy", e),
Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e),
_ => Error::new_os(ErrorKind::Other, "failed to set configuration", e),
})?;
self.active_config.store(configuration, Ordering::SeqCst); self.active_config.store(configuration, Ordering::SeqCst);
Ok(()) Ok(())
}) })
@ -321,8 +337,11 @@ impl LinuxDevice {
pub(crate) fn reset(self: Arc<Self>) -> impl MaybeFuture<Output = Result<(), Error>> { pub(crate) fn reset(self: Arc<Self>) -> impl MaybeFuture<Output = Result<(), Error>> {
Blocking::new(move || { Blocking::new(move || {
usbfs::reset(&self.fd)?; usbfs::reset(&self.fd).map_err(|e| match e {
Ok(()) Errno::BUSY => Error::new_os(ErrorKind::Busy, "device is busy", e),
Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e),
_ => Error::new_os(ErrorKind::Other, "failed to reset device", e),
})
}) })
} }
@ -352,27 +371,40 @@ impl LinuxDevice {
}) })
} }
fn handle_claim_interface_result(
self: Arc<Self>,
interface_number: u8,
result: Result<(), Errno>,
reattach: bool,
) -> Result<Arc<LinuxInterface>, Error> {
result.map_err(|e| {
match e {
Errno::INVAL => Error::new_os(ErrorKind::NotFound, "interface not found", e),
Errno::BUSY => Error::new_os(ErrorKind::Busy, "interface is busy", e),
Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e),
_ => Error::new_os(ErrorKind::Other, "failed to claim interface", e),
}
.log_error()
})?;
debug!(
"Claimed interface {interface_number} on device id {dev}",
dev = self.events_id
);
Ok(Arc::new(LinuxInterface {
device: self,
interface_number,
reattach,
state: Mutex::new(Default::default()),
}))
}
pub(crate) fn claim_interface( pub(crate) fn claim_interface(
self: Arc<Self>, self: Arc<Self>,
interface_number: u8, interface_number: u8,
) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> { ) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> {
Blocking::new(move || { Blocking::new(move || {
usbfs::claim_interface(&self.fd, interface_number).inspect_err(|e| { let result = usbfs::claim_interface(&self.fd, interface_number);
warn!( self.handle_claim_interface_result(interface_number, result, false)
"Failed to claim interface {interface_number} on device id {dev}: {e}",
dev = self.events_id
)
})?;
debug!(
"Claimed interface {interface_number} on device id {dev}",
dev = self.events_id
);
Ok(Arc::new(LinuxInterface {
device: self,
interface_number,
reattach: false,
state: Mutex::new(Default::default()),
}))
}) })
} }
@ -381,17 +413,8 @@ impl LinuxDevice {
interface_number: u8, interface_number: u8,
) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> { ) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> {
Blocking::new(move || { Blocking::new(move || {
usbfs::detach_and_claim_interface(&self.fd, interface_number)?; let result = usbfs::detach_and_claim_interface(&self.fd, interface_number);
debug!( self.handle_claim_interface_result(interface_number, result, true)
"Detached and claimed interface {interface_number} on device id {dev}",
dev = self.events_id
);
Ok(Arc::new(LinuxInterface {
device: self,
interface_number,
reattach: true,
state: Mutex::new(Default::default()),
}))
}) })
} }
@ -400,7 +423,12 @@ impl LinuxDevice {
self: &Arc<Self>, self: &Arc<Self>,
interface_number: u8, interface_number: u8,
) -> Result<(), Error> { ) -> Result<(), Error> {
usbfs::detach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into()) usbfs::detach_kernel_driver(&self.fd, interface_number).map_err(|e| match e {
Errno::INVAL => Error::new_os(ErrorKind::NotFound, "interface not found", e),
Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e),
Errno::NODATA => Error::new_os(ErrorKind::Other, "no kernel driver attached", e),
_ => Error::new_os(ErrorKind::Other, "failed to detach kernel driver", e),
})
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -408,7 +436,12 @@ impl LinuxDevice {
self: &Arc<Self>, self: &Arc<Self>,
interface_number: u8, interface_number: u8,
) -> Result<(), Error> { ) -> Result<(), Error> {
usbfs::attach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into()) usbfs::attach_kernel_driver(&self.fd, interface_number).map_err(|e| match e {
Errno::INVAL => Error::new_os(ErrorKind::NotFound, "interface not found", e),
Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e),
Errno::BUSY => Error::new_os(ErrorKind::Busy, "kernel driver already attached", e),
_ => Error::new_os(ErrorKind::Other, "failed to attach kernel driver", e),
})
} }
pub(crate) fn submit(&self, transfer: Idle<TransferData>) -> Pending<TransferData> { pub(crate) fn submit(&self, transfer: Idle<TransferData>) -> Pending<TransferData> {
@ -583,16 +616,26 @@ impl LinuxInterface {
Blocking::new(move || { Blocking::new(move || {
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
if !state.endpoints.is_empty() { if !state.endpoints.is_empty() {
// TODO: Use ErrorKind::ResourceBusy once compatible with MSRV return Err(Error::new(
return Err(Error::other( ErrorKind::Busy,
"must drop endpoints before changing alt setting", "can't change alternate setting while endpoints are in use",
)); ));
} }
usbfs::set_interface(&self.device.fd, self.interface_number, alt_setting).map_err(
|e| match e {
Errno::INVAL => {
Error::new_os(ErrorKind::NotFound, "alternate setting not found", e)
}
Errno::NODEV => {
Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
}
_ => Error::new_os(ErrorKind::Other, "failed to set alternate setting", e),
},
)?;
debug!( debug!(
"Set interface {} alt setting to {alt_setting}", "Set interface {} alt setting to {alt_setting}",
self.interface_number self.interface_number
); );
usbfs::set_interface(&self.device.fd, self.interface_number, alt_setting)?;
state.alt_setting = alt_setting; state.alt_setting = alt_setting;
Ok(()) Ok(())
}) })
@ -601,7 +644,7 @@ impl LinuxInterface {
pub fn endpoint( pub fn endpoint(
self: &Arc<Self>, self: &Arc<Self>,
descriptor: EndpointDescriptor, descriptor: EndpointDescriptor,
) -> Result<LinuxEndpoint, ClaimEndpointError> { ) -> Result<LinuxEndpoint, Error> {
let address = descriptor.address(); let address = descriptor.address();
let ep_type = descriptor.transfer_type(); let ep_type = descriptor.transfer_type();
let max_packet_size = descriptor.max_packet_size(); let max_packet_size = descriptor.max_packet_size();
@ -609,7 +652,7 @@ impl LinuxInterface {
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
if state.endpoints.is_set(address) { if state.endpoints.is_set(address) {
return Err(ClaimEndpointError::Busy); return Err(Error::new(ErrorKind::Busy, "endpoint already in use"));
} }
state.endpoints.set(address); state.endpoints.set(address);
@ -730,7 +773,10 @@ impl LinuxEndpoint {
Blocking::new(move || { Blocking::new(move || {
let endpoint = inner.address; let endpoint = inner.address;
debug!("Clear halt, endpoint {endpoint:02x}"); debug!("Clear halt, endpoint {endpoint:02x}");
Ok(usbfs::clear_halt(&inner.interface.device.fd, endpoint)?) usbfs::clear_halt(&inner.interface.device.fd, endpoint).map_err(|e| match e {
Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e),
_ => Error::new_os(ErrorKind::Other, "failed to clear halt", e),
})
}) })
} }

View file

@ -8,6 +8,7 @@ use std::str::FromStr;
use crate::enumeration::InterfaceInfo; use crate::enumeration::InterfaceInfo;
use crate::maybe_future::{MaybeFuture, Ready}; use crate::maybe_future::{MaybeFuture, Ready};
use crate::ErrorKind;
use crate::{BusInfo, DeviceInfo, Error, Speed, UsbControllerType}; use crate::{BusInfo, DeviceInfo, Error, Speed, UsbControllerType};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -41,12 +42,6 @@ impl std::error::Error for SysfsError {
} }
} }
impl From<SysfsError> for io::Error {
fn from(value: SysfsError) -> Self {
io::Error::other(Box::new(value))
}
}
impl SysfsPath { impl SysfsPath {
fn parse_attr<T, E>( fn parse_attr<T, E>(
&self, &self,
@ -116,12 +111,24 @@ impl FromHexStr for u16 {
} }
} }
const SYSFS_USB_PREFIX: &str = "/sys/bus/usb/devices/"; fn sysfs_list_usb() -> Result<fs::ReadDir, Error> {
fs::read_dir("/sys/bus/usb/devices/").map_err(|e| match e.kind() {
io::ErrorKind::NotFound => {
Error::new_io(ErrorKind::Other, "/sys/bus/usb/devices/ not found", e)
}
io::ErrorKind::PermissionDenied => Error::new_io(
ErrorKind::PermissionDenied,
"/sys/bus/usb/devices/ permission denied",
e,
),
_ => Error::new_io(ErrorKind::Other, "failed to open /sys/bus/usb/devices/", e),
})
}
pub fn list_devices() -> impl MaybeFuture<Output = Result<impl Iterator<Item = DeviceInfo>, Error>> pub fn list_devices() -> impl MaybeFuture<Output = Result<impl Iterator<Item = DeviceInfo>, Error>>
{ {
Ready((|| { Ready((|| {
Ok(fs::read_dir(SYSFS_USB_PREFIX)?.flat_map(|entry| { Ok(sysfs_list_usb()?.flat_map(|entry| {
let path = entry.ok()?.path(); let path = entry.ok()?.path();
let name = path.file_name()?; let name = path.file_name()?;
@ -147,7 +154,7 @@ pub fn list_devices() -> impl MaybeFuture<Output = Result<impl Iterator<Item = D
} }
pub fn list_root_hubs() -> Result<impl Iterator<Item = DeviceInfo>, Error> { pub fn list_root_hubs() -> Result<impl Iterator<Item = DeviceInfo>, Error> {
Ok(fs::read_dir(SYSFS_USB_PREFIX)?.filter_map(|entry| { Ok(sysfs_list_usb()?.filter_map(|entry| {
let path = entry.ok()?.path(); let path = entry.ok()?.path();
let name = path.file_name()?; let name = path.file_name()?;

View file

@ -11,7 +11,7 @@
//! on a device use the same file descriptor, putting USB-specific //! on a device use the same file descriptor, putting USB-specific
//! dispatch in the event loop avoids additonal synchronization. //! dispatch in the event loop avoids additonal synchronization.
use crate::Error; use crate::{Error, ErrorKind};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use rustix::{ use rustix::{
event::epoll::{self, EventData, EventFlags}, event::epoll::{self, EventData, EventFlags},
@ -19,7 +19,7 @@ use rustix::{
io::Errno, io::Errno,
}; };
use slab::Slab; use slab::Slab;
use std::{io, mem::MaybeUninit, sync::Mutex, task::Waker, thread}; use std::{mem::MaybeUninit, sync::Mutex, task::Waker, thread};
use super::Device; use super::Device;
@ -61,8 +61,8 @@ pub(super) fn register_fd(fd: BorrowedFd, tag: Tag, flags: EventFlags) -> Result
let mut start_thread = false; let mut start_thread = false;
let epoll_fd = EPOLL_FD.get_or_try_init(|| { let epoll_fd = EPOLL_FD.get_or_try_init(|| {
start_thread = true; start_thread = true;
epoll::create(epoll::CreateFlags::CLOEXEC).inspect_err(|e| { epoll::create(epoll::CreateFlags::CLOEXEC).map_err(|e| {
log::error!("Failed to initialize epoll: {e}"); Error::new_os(ErrorKind::Other, "failed to initialize epoll", e).log_error()
}) })
})?; })?;
@ -70,9 +70,8 @@ pub(super) fn register_fd(fd: BorrowedFd, tag: Tag, flags: EventFlags) -> Result
thread::spawn(event_loop); thread::spawn(event_loop);
} }
epoll::add(epoll_fd, fd, tag.as_event_data(), flags).inspect_err(|e| { epoll::add(epoll_fd, fd, tag.as_event_data(), flags)
log::error!("Failed to add epoll watch: {e}"); .map_err(|e| Error::new_os(ErrorKind::Other, "failed to add epoll watch", e).log_error())?;
})?;
Ok(()) Ok(())
} }
@ -115,13 +114,13 @@ pub(crate) struct Async<T: AsFd> {
} }
impl<T: AsFd> Async<T> { impl<T: AsFd> Async<T> {
pub fn new(inner: T) -> Result<Self, io::Error> { pub fn new(inner: T) -> Result<Self, Error> {
let id = WAKERS.lock().unwrap().insert(None); let id = WAKERS.lock().unwrap().insert(None);
register_fd(inner.as_fd(), Tag::Waker(id), EventFlags::empty())?; register_fd(inner.as_fd(), Tag::Waker(id), EventFlags::empty())?;
Ok(Async { inner, id }) Ok(Async { inner, id })
} }
pub fn register(&self, waker: &Waker) -> Result<(), io::Error> { pub fn register(&self, waker: &Waker) -> Result<(), Error> {
WAKERS WAKERS
.lock() .lock()
.unwrap() .unwrap()
@ -134,7 +133,10 @@ impl<T: AsFd> Async<T> {
self.inner.as_fd(), self.inner.as_fd(),
Tag::Waker(self.id).as_event_data(), Tag::Waker(self.id).as_event_data(),
EventFlags::ONESHOT | EventFlags::IN, EventFlags::ONESHOT | EventFlags::IN,
)?; )
.map_err(|e| {
Error::new_os(ErrorKind::Other, "failed to modify epoll watch", e).log_error()
})?;
Ok(()) Ok(())
} }
} }

View file

@ -10,7 +10,7 @@ use rustix::{
}; };
use std::{mem::MaybeUninit, os::unix::prelude::BorrowedFd, path::Path, task::Poll}; use std::{mem::MaybeUninit, os::unix::prelude::BorrowedFd, path::Path, task::Poll};
use crate::{hotplug::HotplugEvent, Error}; use crate::{hotplug::HotplugEvent, Error, ErrorKind};
use super::{enumeration::probe_device, events::Async, SysfsPath}; use super::{enumeration::probe_device, events::Async, SysfsPath};
@ -28,8 +28,15 @@ impl LinuxHotplugWatch {
SocketType::RAW, SocketType::RAW,
SocketFlags::CLOEXEC, SocketFlags::CLOEXEC,
Some(netlink::KOBJECT_UEVENT), Some(netlink::KOBJECT_UEVENT),
)?; )
bind(&fd, &SocketAddrNetlink::new(0, UDEV_MULTICAST_GROUP))?; .map_err(|e| {
Error::new_os(ErrorKind::Other, "failed to open udev netlink socket", e).log_error()
})?;
bind(&fd, &SocketAddrNetlink::new(0, UDEV_MULTICAST_GROUP)).map_err(|e| {
Error::new_os(ErrorKind::Other, "failed to bind udev netlink socket", e).log_error()
})?;
Ok(LinuxHotplugWatch { Ok(LinuxHotplugWatch {
fd: Async::new(fd)?, fd: Async::new(fd)?,
}) })

View file

@ -1,4 +1,7 @@
mod transfer; mod transfer;
use std::io;
use std::num::NonZeroU32;
use rustix::io::Errno; use rustix::io::Errno;
pub(crate) use transfer::TransferData; pub(crate) use transfer::TransferData;
mod usbfs; mod usbfs;
@ -16,6 +19,7 @@ mod hotplug;
pub(crate) use hotplug::LinuxHotplugWatch as HotplugWatch; pub(crate) use hotplug::LinuxHotplugWatch as HotplugWatch;
use crate::transfer::TransferError; use crate::transfer::TransferError;
use crate::ErrorKind;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct DeviceId { pub struct DeviceId {
@ -35,3 +39,25 @@ fn errno_to_transfer_error(e: Errno) -> TransferError {
_ => TransferError::Unknown(e.raw_os_error() as u32), _ => TransferError::Unknown(e.raw_os_error() as u32),
} }
} }
pub fn format_os_error_code(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result {
write!(f, "errno {}", code)
}
impl crate::error::Error {
pub(crate) fn new_os(kind: ErrorKind, message: &'static str, code: Errno) -> Self {
Self {
kind,
code: NonZeroU32::new(code.raw_os_error() as u32),
message,
}
}
pub(crate) fn new_io(kind: ErrorKind, message: &'static str, err: io::Error) -> Self {
Self {
kind,
code: err.raw_os_error().and_then(|i| NonZeroU32::new(i as u32)),
message,
}
}
}

View file

@ -1,7 +1,6 @@
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
ffi::c_void, ffi::c_void,
io::ErrorKind,
mem::ManuallyDrop, mem::ManuallyDrop,
sync::{ sync::{
atomic::{AtomicU8, AtomicUsize, Ordering}, atomic::{AtomicU8, AtomicUsize, Ordering},
@ -17,7 +16,6 @@ use log::{debug, error};
use crate::{ use crate::{
bitset::EndpointBitSet, bitset::EndpointBitSet,
descriptors::{ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor}, descriptors::{ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor},
device::ClaimEndpointError,
maybe_future::blocking::Blocking, maybe_future::blocking::Blocking,
transfer::{ transfer::{
internal::{ internal::{
@ -25,13 +23,13 @@ use crate::{
}, },
Buffer, Completion, ControlIn, ControlOut, Direction, TransferError, Buffer, Completion, ControlIn, ControlOut, Direction, TransferError,
}, },
DeviceInfo, Error, MaybeFuture, Speed, DeviceInfo, Error, ErrorKind, MaybeFuture, Speed,
}; };
use super::{ use super::{
enumeration::{device_descriptor_from_fields, get_integer_property, service_by_registry_id}, enumeration::{device_descriptor_from_fields, get_integer_property, service_by_registry_id},
events::{add_event_source, EventRegistration}, events::{add_event_source, EventRegistration},
iokit::{call_iokit_function, check_iokit_return}, iokit::call_iokit_function,
iokit_c::IOUSBDevRequestTO, iokit_c::IOUSBDevRequestTO,
iokit_usb::{IoKitDevice, IoKitInterface}, iokit_usb::{IoKitDevice, IoKitInterface},
TransferData, TransferData,
@ -70,23 +68,32 @@ impl MacDevice {
log::info!("Opening device from registry id {}", registry_id); log::info!("Opening device from registry id {}", registry_id);
let service = service_by_registry_id(registry_id)?; let service = service_by_registry_id(registry_id)?;
let device = IoKitDevice::new(&service)?; let device = IoKitDevice::new(&service)?;
let _event_registration = add_event_source(device.create_async_event_source()?); let event_source = device.create_async_event_source().map_err(|e| {
Error::new_os(ErrorKind::Other, "failed to create async event source", e)
.log_error()
})?;
let _event_registration = add_event_source(event_source);
let opened = match unsafe { call_iokit_function!(device.raw, USBDeviceOpen()) } { let opened = device
io_kit_sys::ret::kIOReturnSuccess => true, .open()
err => { .inspect_err(|err| {
// Most methods don't require USBDeviceOpen() so this can be ignored log::debug!("Could not open device for exclusive access: 0x{err:08x}");
// to allow different processes to open different interfaces. })
log::debug!("Could not open device for exclusive access: {err:x}"); .is_ok();
false
}
};
let device_descriptor = device_descriptor_from_fields(&service) let device_descriptor = device_descriptor_from_fields(&service).ok_or_else(|| {
.ok_or_else(|| Error::other("could not read properties for device descriptor"))?; Error::new(
ErrorKind::Other,
"could not read properties for device descriptor",
)
})?;
let num_configs = device.get_number_of_configurations().inspect_err(|e| { let num_configs = device.get_number_of_configurations().map_err(|e| {
log::warn!("failed to get number of configurations: {e}"); Error::new_os(
ErrorKind::Other,
"failed to get number of configurations",
e,
)
})?; })?;
let config_descriptors: Vec<Vec<u8>> = (0..num_configs) let config_descriptors: Vec<Vec<u8>> = (0..num_configs)
@ -149,12 +156,22 @@ impl MacDevice {
fn require_open_exclusive(&self) -> Result<(), Error> { fn require_open_exclusive(&self) -> Result<(), Error> {
let mut is_open_exclusive = self.is_open_exclusive.lock().unwrap(); let mut is_open_exclusive = self.is_open_exclusive.lock().unwrap();
if !*is_open_exclusive { if !*is_open_exclusive {
unsafe { check_iokit_return(call_iokit_function!(self.device.raw, USBDeviceOpen()))? }; self.device.open().map_err(|e| match e {
io_kit_sys::ret::kIOReturnNoDevice => {
Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
}
_ => Error::new_os(
ErrorKind::Other,
"could not open device for exclusive access",
e,
),
})?;
*is_open_exclusive = true; *is_open_exclusive = true;
} }
if self.claimed_interfaces.load(Ordering::Relaxed) != 0 { if self.claimed_interfaces.load(Ordering::Relaxed) != 0 {
return Err(Error::other( return Err(Error::new(
ErrorKind::Busy,
"cannot perform this operation while interfaces are claimed", "cannot perform this operation while interfaces are claimed",
)); ));
} }
@ -168,12 +185,17 @@ impl MacDevice {
) -> impl MaybeFuture<Output = Result<(), Error>> { ) -> impl MaybeFuture<Output = Result<(), Error>> {
Blocking::new(move || { Blocking::new(move || {
self.require_open_exclusive()?; self.require_open_exclusive()?;
unsafe { self.device
check_iokit_return(call_iokit_function!( .set_configuration(configuration)
self.device.raw, .map_err(|e| match e {
SetConfiguration(configuration) io_kit_sys::ret::kIOReturnNoDevice => {
))? Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
} }
io_kit_sys::ret::kIOReturnNotFound => {
Error::new_os(ErrorKind::NotFound, "configuration not found", e)
}
_ => Error::new_os(ErrorKind::Other, "failed to set configuration", e),
})?;
log::debug!("Set configuration {configuration}"); log::debug!("Set configuration {configuration}");
self.active_config.store(configuration, Ordering::SeqCst); self.active_config.store(configuration, Ordering::SeqCst);
Ok(()) Ok(())
@ -183,12 +205,12 @@ impl MacDevice {
pub(crate) fn reset(self: Arc<Self>) -> impl MaybeFuture<Output = Result<(), Error>> { pub(crate) fn reset(self: Arc<Self>) -> impl MaybeFuture<Output = Result<(), Error>> {
Blocking::new(move || { Blocking::new(move || {
self.require_open_exclusive()?; self.require_open_exclusive()?;
unsafe { self.device.reset().map_err(|e| match e {
check_iokit_return(call_iokit_function!( io_kit_sys::ret::kIOReturnNoDevice => {
self.device.raw, Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
USBDeviceReEnumerate(0) }
)) _ => Error::new_os(ErrorKind::Other, "failed to reset device", e),
} })
}) })
} }
@ -199,7 +221,10 @@ impl MacDevice {
Blocking::new(move || { Blocking::new(move || {
let intf_service = self let intf_service = self
.device .device
.create_interface_iterator()? .create_interface_iterator()
.map_err(|e| {
Error::new_os(ErrorKind::Other, "failed to create interface iterator", e)
})?
.find(|io_service| { .find(|io_service| {
get_integer_property(io_service, "bInterfaceNumber") get_integer_property(io_service, "bInterfaceNumber")
== Some(interface_number as i64) == Some(interface_number as i64)
@ -207,9 +232,23 @@ impl MacDevice {
.ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?; .ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?;
let mut interface = IoKitInterface::new(intf_service)?; let mut interface = IoKitInterface::new(intf_service)?;
let _event_registration = add_event_source(interface.create_async_event_source()?); let source = interface.create_async_event_source().map_err(|e| {
Error::new_os(ErrorKind::Other, "failed to create async event source", e)
.log_error()
})?;
let _event_registration = add_event_source(source);
interface.open()?; interface.open().map_err(|e| match e {
io_kit_sys::ret::kIOReturnExclusiveAccess => Error::new_os(
ErrorKind::Busy,
"could not open interface for exclusive access",
e,
),
io_kit_sys::ret::kIOReturnNoDevice => {
Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
}
_ => Error::new_os(ErrorKind::Other, "failed to open interface", e),
})?;
self.claimed_interfaces.fetch_add(1, Ordering::Acquire); self.claimed_interfaces.fetch_add(1, Ordering::Acquire);
Ok(Arc::new(MacInterface { Ok(Arc::new(MacInterface {
@ -357,19 +396,20 @@ impl MacInterface {
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
if !state.endpoints_used.is_empty() { if !state.endpoints_used.is_empty() {
// TODO: Use ErrorKind::ResourceBusy once compatible with MSRV return Err(Error::new(
ErrorKind::Busy,
return Err(Error::other( "can't change alternate setting while endpoints are in use",
"must drop endpoints before changing alt setting",
)); ));
} }
unsafe { self.interface
check_iokit_return(call_iokit_function!( .set_alternate_interface(alt_setting)
self.interface.raw, .map_err(|e| match e {
SetAlternateInterface(alt_setting) io_kit_sys::ret::kIOReturnNoDevice => {
))?; Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
} }
_ => Error::new_os(ErrorKind::Other, "failed to set alternate interface", e),
})?;
debug!( debug!(
"Set interface {} alt setting to {alt_setting}", "Set interface {} alt setting to {alt_setting}",
@ -405,7 +445,7 @@ impl MacInterface {
pub fn endpoint( pub fn endpoint(
self: &Arc<Self>, self: &Arc<Self>,
descriptor: EndpointDescriptor, descriptor: EndpointDescriptor,
) -> Result<MacEndpoint, ClaimEndpointError> { ) -> Result<MacEndpoint, Error> {
let address = descriptor.address(); let address = descriptor.address();
let max_packet_size = descriptor.max_packet_size(); let max_packet_size = descriptor.max_packet_size();
@ -413,11 +453,14 @@ impl MacInterface {
let Some(pipe_ref) = self.interface.find_pipe_ref(address) else { let Some(pipe_ref) = self.interface.find_pipe_ref(address) else {
debug!("Endpoint {address:02X} not found in iokit"); debug!("Endpoint {address:02X} not found in iokit");
return Err(ClaimEndpointError::InvalidAddress); return Err(Error::new(
ErrorKind::NotFound,
"specified endpoint does not exist on IOKit interface",
));
}; };
if state.endpoints_used.is_set(address) { if state.endpoints_used.is_set(address) {
return Err(ClaimEndpointError::Busy); return Err(Error::new(ErrorKind::Busy, "endpoint already in use"));
} }
state.endpoints_used.set(address); state.endpoints_used.set(address);
@ -589,12 +632,16 @@ impl MacEndpoint {
Blocking::new(move || { Blocking::new(move || {
debug!("Clear halt, endpoint {:02x}", inner.address); debug!("Clear halt, endpoint {:02x}", inner.address);
unsafe { inner
check_iokit_return(call_iokit_function!( .interface
inner.interface.interface.raw, .interface
ClearPipeStallBothEnds(inner.pipe_ref) .clear_pipe_stall_both_ends(inner.pipe_ref)
)) .map_err(|e| match e {
} io_kit_sys::ret::kIOReturnNoDevice => {
Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
}
_ => Error::new_os(ErrorKind::Other, "failed to clear halt on endpoint", e),
})
}) })
} }
} }

View file

@ -1,5 +1,3 @@
use std::io::ErrorKind;
use core_foundation::{ use core_foundation::{
base::{CFType, TCFType}, base::{CFType, TCFType},
data::CFData, data::CFData,
@ -18,7 +16,7 @@ use log::debug;
use crate::{ use crate::{
descriptors::DeviceDescriptor, descriptors::DeviceDescriptor,
maybe_future::{MaybeFuture, Ready}, maybe_future::{MaybeFuture, Ready},
BusInfo, DeviceInfo, Error, InterfaceInfo, Speed, UsbControllerType, BusInfo, DeviceInfo, Error, ErrorKind, InterfaceInfo, Speed, UsbControllerType,
}; };
use super::iokit::{IoService, IoServiceIterator}; use super::iokit::{IoService, IoServiceIterator};
@ -43,13 +41,17 @@ fn usb_service_iter() -> Result<IoServiceIterator, Error> {
unsafe { unsafe {
let dictionary = IOServiceMatching(kIOUSBDeviceClassName); let dictionary = IOServiceMatching(kIOUSBDeviceClassName);
if dictionary.is_null() { if dictionary.is_null() {
return Err(Error::other("IOServiceMatching failed")); return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed"));
} }
let mut iterator = 0; let mut iterator = 0;
let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator); let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator);
if r != kIOReturnSuccess { if r != kIOReturnSuccess {
return Err(Error::from_raw_os_error(r)); return Err(Error::new_os(
ErrorKind::Other,
"failed to create IOKit iterator",
r,
));
} }
Ok(IoServiceIterator::new(iterator)) Ok(IoServiceIterator::new(iterator))
@ -67,13 +69,17 @@ fn usb_controller_service_iter(
UsbControllerType::VHCI => IOServiceMatching(kAppleUSBVHCI), UsbControllerType::VHCI => IOServiceMatching(kAppleUSBVHCI),
}; };
if dictionary.is_null() { if dictionary.is_null() {
return Err(Error::other("IOServiceMatching failed")); return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed"));
} }
let mut iterator = 0; let mut iterator = 0;
let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator); let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator);
if r != kIOReturnSuccess { if r != kIOReturnSuccess {
return Err(Error::from_raw_os_error(r)); return Err(Error::new_os(
ErrorKind::Other,
"failed to create IOKit iterator",
r,
));
} }
Ok(IoServiceIterator::new(iterator)) Ok(IoServiceIterator::new(iterator))
@ -245,7 +251,11 @@ fn get_children(device: &IoService) -> Result<IoServiceIterator, Error> {
IORegistryEntryGetChildIterator(device.get(), kIOServicePlane as *mut _, &mut iterator); IORegistryEntryGetChildIterator(device.get(), kIOServicePlane as *mut _, &mut iterator);
if r != kIOReturnSuccess { if r != kIOReturnSuccess {
debug!("IORegistryEntryGetChildIterator failed: {r}"); debug!("IORegistryEntryGetChildIterator failed: {r}");
return Err(Error::from_raw_os_error(r)); return Err(Error::new_os(
ErrorKind::Other,
"failed to create IOKit child iterator",
r,
));
} }
Ok(IoServiceIterator::new(iterator)) Ok(IoServiceIterator::new(iterator))

View file

@ -18,7 +18,7 @@ use io_kit_sys::{
use log::debug; use log::debug;
use slab::Slab; use slab::Slab;
use crate::{hotplug::HotplugEvent, DeviceId, Error}; use crate::{hotplug::HotplugEvent, DeviceId, Error, ErrorKind};
use super::{ use super::{
enumeration::{get_registry_id, probe_device}, enumeration::{get_registry_id, probe_device},
@ -79,7 +79,7 @@ impl MacHotplugWatch {
let dictionary = unsafe { let dictionary = unsafe {
let d = IOServiceMatching(kIOUSBDeviceClassName); let d = IOServiceMatching(kIOUSBDeviceClassName);
if d.is_null() { if d.is_null() {
return Err(Error::other("IOServiceMatching failed")); return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed"));
} }
CFDictionary::wrap_under_create_rule(d) CFDictionary::wrap_under_create_rule(d)
}; };
@ -162,7 +162,9 @@ fn register_notification(
); );
if r != kIOReturnSuccess { if r != kIOReturnSuccess {
return Err(Error::other("Failed to register notification")); return Err(
Error::new_os(ErrorKind::Other, "failed to register notification", r).log_error(),
);
} }
let mut iter = IoServiceIterator::new(iter); let mut iter = IoServiceIterator::new(iter);

View file

@ -5,9 +5,6 @@
use core_foundation_sys::uuid::CFUUIDBytes; use core_foundation_sys::uuid::CFUUIDBytes;
use io_kit_sys::{ret::IOReturn, IOIteratorNext, IOObjectRelease}; use io_kit_sys::{ret::IOReturn, IOIteratorNext, IOObjectRelease};
use std::io::ErrorKind;
use crate::Error;
use super::iokit_c::{self, CFUUIDGetUUIDBytes, IOCFPlugInInterface}; use super::iokit_c::{self, CFUUIDGetUUIDBytes, IOCFPlugInInterface};
@ -117,15 +114,9 @@ pub(crate) fn usb_interface_type_id() -> CFUUIDBytes {
unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBInterfaceInterfaceID500()) } unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBInterfaceInterfaceID500()) }
} }
pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), Error> { pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), IOReturn> {
#[allow(non_upper_case_globals)]
#[deny(unreachable_patterns)]
match r { match r {
io_kit_sys::ret::kIOReturnSuccess => Ok(()), io_kit_sys::ret::kIOReturnSuccess => Ok(()),
io_kit_sys::ret::kIOReturnExclusiveAccess => { e => Err(e),
Err(Error::other("could not be opened for exclusive access"))
}
io_kit_sys::ret::kIOReturnNotFound => Err(Error::new(ErrorKind::NotFound, "not found")),
_ => Err(Error::from_raw_os_error(r)),
} }
} }

View file

@ -3,12 +3,12 @@
//! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) //! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs)
//! licensed under MIT OR Apache-2.0. //! licensed under MIT OR Apache-2.0.
use std::{io::ErrorKind, ptr, slice, time::Duration}; use std::{ptr, slice, time::Duration};
use core_foundation::{base::TCFType, runloop::CFRunLoopSource}; use core_foundation::{base::TCFType, runloop::CFRunLoopSource};
use core_foundation_sys::runloop::CFRunLoopSourceRef; use core_foundation_sys::runloop::CFRunLoopSourceRef;
use io_kit_sys::{ use io_kit_sys::{
ret::{kIOReturnNoResources, kIOReturnSuccess}, ret::{kIOReturnNoResources, kIOReturnSuccess, IOReturn},
types::io_iterator_t, types::io_iterator_t,
}; };
use log::error; use log::error;
@ -17,7 +17,7 @@ use crate::{
platform::macos_iokit::{ platform::macos_iokit::{
iokit::usb_interface_type_id, iokit_c::kIOUsbInterfaceUserClientTypeID, iokit::usb_interface_type_id, iokit_c::kIOUsbInterfaceUserClientTypeID,
}, },
Error, Error, ErrorKind,
}; };
use super::{ use super::{
@ -75,12 +75,15 @@ impl IoKitDevice {
} }
if rc != kIOReturnSuccess { if rc != kIOReturnSuccess {
return Err(Error::from_raw_os_error(rc)); return Err(Error::new_os(ErrorKind::Other, "failed to open device", rc));
} }
if raw_device_plugin.is_null() { if raw_device_plugin.is_null() {
error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL");
return Err(Error::other("Could not create PlugInInterface")); return Err(Error::new(
ErrorKind::Other,
"could not create PlugInInterface",
));
} }
let device_plugin = PluginInterface::new(raw_device_plugin); let device_plugin = PluginInterface::new(raw_device_plugin);
@ -113,7 +116,24 @@ impl IoKitDevice {
} }
} }
pub(crate) fn create_async_event_source(&self) -> Result<CFRunLoopSource, Error> { pub(crate) fn open(&self) -> Result<(), IOReturn> {
unsafe { check_iokit_return(call_iokit_function!(self.raw, USBDeviceOpen())) }
}
pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), IOReturn> {
unsafe {
check_iokit_return(call_iokit_function!(
self.raw,
SetConfiguration(configuration)
))
}
}
pub(crate) fn reset(&self) -> Result<(), IOReturn> {
unsafe { check_iokit_return(call_iokit_function!(self.raw, USBDeviceReEnumerate(0))) }
}
pub(crate) fn create_async_event_source(&self) -> Result<CFRunLoopSource, IOReturn> {
unsafe { unsafe {
let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut();
check_iokit_return(call_iokit_function!( check_iokit_return(call_iokit_function!(
@ -125,7 +145,7 @@ impl IoKitDevice {
} }
/// Returns an IOKit iterator that can be used to iterate over all interfaces on this device. /// Returns an IOKit iterator that can be used to iterate over all interfaces on this device.
pub(crate) fn create_interface_iterator(&self) -> Result<IoServiceIterator, Error> { pub(crate) fn create_interface_iterator(&self) -> Result<IoServiceIterator, IOReturn> {
unsafe { unsafe {
let mut iterator: io_iterator_t = 0; let mut iterator: io_iterator_t = 0;
@ -145,7 +165,7 @@ impl IoKitDevice {
} }
} }
pub(crate) fn get_number_of_configurations(&self) -> Result<u8, Error> { pub(crate) fn get_number_of_configurations(&self) -> Result<u8, IOReturn> {
unsafe { unsafe {
let mut num = 0; let mut num = 0;
check_iokit_return(call_iokit_function!( check_iokit_return(call_iokit_function!(
@ -156,7 +176,7 @@ impl IoKitDevice {
} }
} }
pub(crate) fn get_configuration_descriptor(&self, index: u8) -> Result<&[u8], Error> { pub(crate) fn get_configuration_descriptor(&self, index: u8) -> Result<&[u8], IOReturn> {
unsafe { unsafe {
let mut ptr: *mut IOUSBConfigurationDescriptor = ptr::null_mut(); let mut ptr: *mut IOUSBConfigurationDescriptor = ptr::null_mut();
check_iokit_return(call_iokit_function!( check_iokit_return(call_iokit_function!(
@ -168,7 +188,7 @@ impl IoKitDevice {
} }
} }
pub(crate) fn get_configuration(&self) -> Result<u8, Error> { pub(crate) fn get_configuration(&self) -> Result<u8, IOReturn> {
unsafe { unsafe {
let mut val = 0; let mut val = 0;
check_iokit_return(call_iokit_function!(self.raw, GetConfiguration(&mut val)))?; check_iokit_return(call_iokit_function!(self.raw, GetConfiguration(&mut val)))?;
@ -208,12 +228,19 @@ impl IoKitInterface {
); );
if rc != kIOReturnSuccess { if rc != kIOReturnSuccess {
return Err(Error::from_raw_os_error(rc)); return Err(Error::new_os(
ErrorKind::Other,
"failed to open interface",
rc,
));
} }
if raw_interface_plugin.is_null() { if raw_interface_plugin.is_null() {
error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL");
return Err(Error::other("Could not create PlugInInterface")); return Err(Error::new(
ErrorKind::Other,
"could not create PlugInInterface",
));
} }
let interface_plugin = PluginInterface::new(raw_interface_plugin); let interface_plugin = PluginInterface::new(raw_interface_plugin);
@ -238,15 +265,15 @@ impl IoKitInterface {
} }
} }
pub(crate) fn open(&mut self) -> Result<(), Error> { pub(crate) fn open(&mut self) -> Result<(), IOReturn> {
unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceOpen())) } unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceOpen())) }
} }
pub(crate) fn close(&mut self) -> Result<(), Error> { pub(crate) fn close(&mut self) -> Result<(), IOReturn> {
unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceClose())) } unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceClose())) }
} }
pub(crate) fn create_async_event_source(&self) -> Result<CFRunLoopSource, Error> { pub(crate) fn create_async_event_source(&self) -> Result<CFRunLoopSource, IOReturn> {
unsafe { unsafe {
let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut();
check_iokit_return(call_iokit_function!( check_iokit_return(call_iokit_function!(
@ -291,6 +318,24 @@ impl IoKitInterface {
None None
} }
} }
pub(crate) fn set_alternate_interface(&self, alt_setting: u8) -> Result<(), IOReturn> {
unsafe {
check_iokit_return(call_iokit_function!(
self.raw,
SetAlternateInterface(alt_setting)
))
}
}
pub(crate) fn clear_pipe_stall_both_ends(&self, pipe_ref: u8) -> Result<(), IOReturn> {
unsafe {
check_iokit_return(call_iokit_function!(
self.raw,
ClearPipeStallBothEnds(pipe_ref)
))
}
}
} }
impl Drop for IoKitInterface { impl Drop for IoKitInterface {

View file

@ -1,4 +1,7 @@
use std::num::NonZeroU32;
use crate::transfer::TransferError; use crate::transfer::TransferError;
use crate::ErrorKind;
mod transfer; mod transfer;
use io_kit_sys::ret::IOReturn; use io_kit_sys::ret::IOReturn;
@ -37,3 +40,17 @@ fn status_to_transfer_result(status: IOReturn) -> Result<(), TransferError> {
_ => Err(TransferError::Unknown(status as u32)), _ => Err(TransferError::Unknown(status as u32)),
} }
} }
pub fn format_os_error_code(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result {
write!(f, "error 0x{:08x}", code)
}
impl crate::error::Error {
pub(crate) fn new_os(kind: ErrorKind, message: &'static str, code: IOReturn) -> Self {
Self {
kind,
code: NonZeroU32::new(code as u32),
message,
}
}
}

View file

@ -1,7 +1,7 @@
use std::{ use std::{
collections::{btree_map::Entry, BTreeMap, VecDeque}, collections::{btree_map::Entry, BTreeMap, VecDeque},
ffi::c_void, ffi::c_void,
io::{self, ErrorKind}, io,
mem::{size_of_val, transmute}, mem::{size_of_val, transmute},
os::windows::{ os::windows::{
io::{AsRawHandle, RawHandle}, io::{AsRawHandle, RawHandle},
@ -23,7 +23,8 @@ use windows_sys::Win32::{
}, },
Foundation::{ Foundation::{
GetLastError, ERROR_BAD_COMMAND, ERROR_DEVICE_NOT_CONNECTED, ERROR_FILE_NOT_FOUND, GetLastError, ERROR_BAD_COMMAND, ERROR_DEVICE_NOT_CONNECTED, ERROR_FILE_NOT_FOUND,
ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_SUCH_DEVICE, FALSE, HANDLE, TRUE, ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_MORE_ITEMS, ERROR_NO_SUCH_DEVICE, FALSE,
HANDLE, TRUE,
}, },
System::IO::{CancelIoEx, OVERLAPPED}, System::IO::{CancelIoEx, OVERLAPPED},
}; };
@ -34,7 +35,6 @@ use crate::{
ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor, DESCRIPTOR_LEN_DEVICE, ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor, DESCRIPTOR_LEN_DEVICE,
DESCRIPTOR_TYPE_CONFIGURATION, DESCRIPTOR_TYPE_CONFIGURATION,
}, },
device::ClaimEndpointError,
maybe_future::{blocking::Blocking, Ready}, maybe_future::{blocking::Blocking, Ready},
transfer::{ transfer::{
internal::{ internal::{
@ -42,7 +42,7 @@ use crate::{
}, },
Buffer, Completion, ControlIn, ControlOut, Direction, Recipient, TransferError, Buffer, Completion, ControlIn, ControlOut, Direction, Recipient, TransferError,
}, },
DeviceInfo, Error, MaybeFuture, Speed, DeviceInfo, Error, ErrorKind, MaybeFuture, Speed,
}; };
use super::{ use super::{
@ -87,7 +87,7 @@ impl WindowsDevice {
) )
}; };
let device_descriptor = DeviceDescriptor::new(device_descriptor) let device_descriptor = DeviceDescriptor::new(device_descriptor)
.ok_or_else(|| Error::new(ErrorKind::InvalidData, "invalid device descriptor"))?; .ok_or_else(|| Error::new(ErrorKind::Other, "invalid device descriptor"))?;
let num_configurations = connection_info.device_desc.bNumConfigurations; let num_configurations = connection_info.device_desc.bNumConfigurations;
let config_descriptors = (0..num_configurations) let config_descriptors = (0..num_configurations)
@ -136,7 +136,7 @@ impl WindowsDevice {
&self, &self,
_configuration: u8, _configuration: u8,
) -> impl MaybeFuture<Output = Result<(), Error>> { ) -> impl MaybeFuture<Output = Result<(), Error>> {
Ready(Err(io::Error::new( Ready(Err(Error::new(
ErrorKind::Unsupported, ErrorKind::Unsupported,
"set_configuration not supported by WinUSB", "set_configuration not supported by WinUSB",
))) )))
@ -147,18 +147,24 @@ impl WindowsDevice {
desc_type: u8, desc_type: u8,
desc_index: u8, desc_index: u8,
language_id: u16, language_id: u16,
) -> impl MaybeFuture<Output = Result<Vec<u8>, Error>> { ) -> impl MaybeFuture<Output = Result<Vec<u8>, TransferError>> {
Blocking::new(move || { Blocking::new(move || {
HubPort::by_child_devinst(self.devinst)?.get_descriptor( fn to_transfer_error(e: Error) -> TransferError {
desc_type, match e.kind() {
desc_index, ErrorKind::Disconnected => TransferError::Disconnected,
language_id, _ => TransferError::Unknown(e.os_error().unwrap_or(0)),
) }
}
HubPort::by_child_devinst(self.devinst)
.map_err(to_transfer_error)?
.get_descriptor(desc_type, desc_index, language_id)
.map_err(to_transfer_error)
}) })
} }
pub(crate) fn reset(&self) -> impl MaybeFuture<Output = Result<(), Error>> { pub(crate) fn reset(&self) -> impl MaybeFuture<Output = Result<(), Error>> {
Ready(Err(io::Error::new( Ready(Err(Error::new(
ErrorKind::Unsupported, ErrorKind::Unsupported,
"reset not supported by WinUSB", "reset not supported by WinUSB",
))) )))
@ -204,9 +210,10 @@ impl WindowsDevice {
} }
} }
} else { } else {
debug!("Device driver is {driver:?}, not WinUSB or USBCCGP");
Err(Error::new( Err(Error::new(
ErrorKind::Unsupported, ErrorKind::Unsupported,
format!("Device driver is {driver:?}, not WinUSB or USBCCGP"), "incompatible driver is installed for this device",
)) ))
} }
}) })
@ -266,14 +273,19 @@ unsafe impl Sync for WinusbFileHandle {}
impl WinusbFileHandle { impl WinusbFileHandle {
fn new(path: &WCStr, first_interface: u8) -> Result<Self, Error> { fn new(path: &WCStr, first_interface: u8) -> Result<Self, Error> {
let handle = create_file(path)?; let handle = create_file(path)
.map_err(|e| Error::new_os(ErrorKind::Other, "failed to open device", e).log_debug())?;
super::events::register(&handle)?; super::events::register(&handle)?;
let winusb_handle = unsafe { let winusb_handle = unsafe {
let mut h = ptr::null_mut(); let mut h = ptr::null_mut();
if WinUsb_Initialize(raw_handle(&handle), &mut h) == FALSE { if WinUsb_Initialize(raw_handle(&handle), &mut h) == FALSE {
error!("WinUsb_Initialize failed: {:?}", io::Error::last_os_error()); return Err(Error::new_os(
return Err(io::Error::last_os_error()); ErrorKind::Other,
"failed to initialize WinUSB",
GetLastError(),
)
.log_debug());
} }
h h
}; };
@ -296,10 +308,7 @@ impl WinusbFileHandle {
assert!(interface_number >= self.first_interface); assert!(interface_number >= self.first_interface);
if self.claimed_interfaces.is_set(interface_number) { if self.claimed_interfaces.is_set(interface_number) {
return Err(Error::new( Error::new(ErrorKind::Busy, "interface is already claimed");
ErrorKind::AddrInUse,
"Interface is already claimed",
));
} }
let winusb_handle = if self.first_interface == interface_number { let winusb_handle = if self.first_interface == interface_number {
@ -310,13 +319,22 @@ impl WinusbFileHandle {
let idx = interface_number - self.first_interface - 1; let idx = interface_number - self.first_interface - 1;
if WinUsb_GetAssociatedInterface(self.winusb_handle, idx, &mut out_handle) == FALSE if WinUsb_GetAssociatedInterface(self.winusb_handle, idx, &mut out_handle) == FALSE
{ {
let err = GetLastError();
error!( error!(
"WinUsb_GetAssociatedInterface for {} on {} failed: {:?}", "WinUsb_GetAssociatedInterface for {} on {} failed: {:?}",
interface_number, interface_number, self.first_interface, err
self.first_interface,
io::Error::last_os_error()
); );
return Err(io::Error::last_os_error());
return Err(match err {
ERROR_NO_MORE_ITEMS => {
Error::new_os(ErrorKind::NotFound, "interface not found", err)
}
_ => Error::new_os(
ErrorKind::Other,
"failed to initialize WinUSB for associated interface",
err,
),
});
} }
out_handle out_handle
} }
@ -463,9 +481,9 @@ impl WindowsInterface {
Blocking::new(move || unsafe { Blocking::new(move || unsafe {
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
if !state.endpoints.is_empty() { if !state.endpoints.is_empty() {
// TODO: Use ErrorKind::ResourceBusy once compatible with MSRV return Err(Error::new(
return Err(Error::other( ErrorKind::Busy,
"must drop endpoints before changing alt setting", "can't change alternate setting while endpoints are in use",
)); ));
} }
let r = WinUsb_SetCurrentAlternateSetting(self.winusb_handle, alt_setting); let r = WinUsb_SetCurrentAlternateSetting(self.winusb_handle, alt_setting);
@ -477,7 +495,15 @@ impl WindowsInterface {
state.alt_setting = alt_setting; state.alt_setting = alt_setting;
Ok(()) Ok(())
} else { } else {
Err(io::Error::last_os_error()) Err(match GetLastError() {
e @ ERROR_NOT_FOUND => {
Error::new_os(ErrorKind::NotFound, "alternate setting not found", e)
}
e @ ERROR_BAD_COMMAND => {
Error::new_os(ErrorKind::Disconnected, "device disconnected", e)
}
e => Error::new_os(ErrorKind::Other, "failed to set alternate setting", e),
})
} }
}) })
} }
@ -489,14 +515,14 @@ impl WindowsInterface {
pub fn endpoint( pub fn endpoint(
self: &Arc<Self>, self: &Arc<Self>,
descriptor: EndpointDescriptor, descriptor: EndpointDescriptor,
) -> Result<WindowsEndpoint, ClaimEndpointError> { ) -> Result<WindowsEndpoint, Error> {
let address = descriptor.address(); let address = descriptor.address();
let max_packet_size = descriptor.max_packet_size(); let max_packet_size = descriptor.max_packet_size();
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
if state.endpoints.is_set(address) { if state.endpoints.is_set(address) {
return Err(ClaimEndpointError::Busy); return Err(Error::new(ErrorKind::Busy, "endpoint already in use"));
} }
state.endpoints.set(address); state.endpoints.set(address);
@ -730,11 +756,12 @@ impl WindowsEndpoint {
let endpoint = inner.address; let endpoint = inner.address;
debug!("Clear halt, endpoint {endpoint:02x}"); debug!("Clear halt, endpoint {endpoint:02x}");
unsafe { unsafe {
let r = WinUsb_ResetPipe(inner.interface.winusb_handle, endpoint); if WinUsb_ResetPipe(inner.interface.winusb_handle, endpoint) == TRUE {
if r == TRUE {
Ok(()) Ok(())
} else { } else {
Err(io::Error::last_os_error()) Err(match GetLastError() {
e => Error::new_os(ErrorKind::Other, "failed to clear halt", e),
})
} }
} }
}) })

View file

@ -1,7 +1,4 @@
use std::{ use std::ffi::{OsStr, OsString};
ffi::{OsStr, OsString},
io::ErrorKind,
};
use log::debug; use log::debug;
use windows_sys::Win32::Devices::{ use windows_sys::Win32::Devices::{
@ -19,7 +16,7 @@ use crate::{
DESCRIPTOR_TYPE_CONFIGURATION, DESCRIPTOR_TYPE_STRING, DESCRIPTOR_TYPE_CONFIGURATION, DESCRIPTOR_TYPE_STRING,
}, },
maybe_future::{blocking::Blocking, MaybeFuture}, maybe_future::{blocking::Blocking, MaybeFuture},
BusInfo, DeviceInfo, Error, InterfaceInfo, UsbControllerType, BusInfo, DeviceInfo, Error, ErrorKind, InterfaceInfo, UsbControllerType,
}; };
use super::{ use super::{
@ -241,7 +238,10 @@ pub(crate) fn get_winusb_device_path(dev: DevInst) -> Result<WCString, Error> {
let paths = dev.interfaces(GUID_DEVINTERFACE_USB_DEVICE); let paths = dev.interfaces(GUID_DEVINTERFACE_USB_DEVICE);
let Some(path) = paths.iter().next() else { let Some(path) = paths.iter().next() else {
return Err(Error::other("Failed to find device path for WinUSB device")); return Err(Error::new(
ErrorKind::Other,
"failed to find device path for WinUSB device",
));
}; };
Ok(path.to_owned()) Ok(path.to_owned())
@ -266,14 +266,15 @@ pub(crate) fn get_usbccgp_winusb_device_path(child: DevInst) -> Result<WCString,
let Some(driver) = child.get_property::<OsString>(DEVPKEY_Device_Service) else { let Some(driver) = child.get_property::<OsString>(DEVPKEY_Device_Service) else {
return Err(Error::new( return Err(Error::new(
ErrorKind::Unsupported, ErrorKind::Unsupported,
"Could not determine driver for interface", "could not determine driver for interface",
)); ));
}; };
if !driver.eq_ignore_ascii_case("winusb") { if !driver.eq_ignore_ascii_case("winusb") {
debug!("Incompatible driver {driver:?} for interface, not WinUSB");
return Err(Error::new( return Err(Error::new(
ErrorKind::Unsupported, ErrorKind::Unsupported,
format!("Interface driver is {driver:?}, not WinUSB"), "incompatible driver is installed for this interface",
)); ));
} }
@ -298,9 +299,11 @@ pub(crate) fn get_usbccgp_winusb_device_path(child: DevInst) -> Result<WCString,
let paths = child.interfaces(guid); let paths = child.interfaces(guid);
let Some(path) = paths.iter().next() else { let Some(path) = paths.iter().next() else {
return Err(Error::other( return Err(Error::new(
"Failed to find device path for WinUSB interface", ErrorKind::Unsupported,
)); "failed to find device path for WinUSB interface",
)
.log_debug());
}; };
Ok(path.to_owned()) Ok(path.to_owned())

View file

@ -1,4 +1,3 @@
use log::error;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::{ use std::{
os::windows::{ os::windows::{
@ -24,11 +23,12 @@ impl IoCompletionPort {
let port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 0); let port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, ptr::null_mut(), 0, 0);
match HandleOrNull::from_raw_handle(port as RawHandle).try_into() { match HandleOrNull::from_raw_handle(port as RawHandle).try_into() {
Ok(handle) => Ok(IoCompletionPort(handle)), Ok(handle) => Ok(IoCompletionPort(handle)),
Err(_) => { Err(_) => Err(Error::new_os(
let err = GetLastError(); crate::ErrorKind::Other,
error!("CreateIoCompletionPort (create) failed: {err:?}"); "failed to create IO completion port",
Err(std::io::Error::from_raw_os_error(err as i32)) GetLastError(),
} )
.log_error()),
} }
} }
} }
@ -37,9 +37,12 @@ impl IoCompletionPort {
unsafe { unsafe {
let r = CreateIoCompletionPort(raw_handle(handle), raw_handle(&self.0), 0, 0); let r = CreateIoCompletionPort(raw_handle(handle), raw_handle(&self.0), 0, 0);
if r.is_null() { if r.is_null() {
let err = std::io::Error::last_os_error(); Err(Error::new_os(
error!("CreateIoCompletionPort (register) failed: {err:?}"); crate::ErrorKind::Other,
Err(err) "failed to register IO completion port",
GetLastError(),
)
.log_error())
} else { } else {
Ok(()) Ok(())
} }
@ -62,9 +65,12 @@ impl IoCompletionPort {
); );
if r == FALSE { if r == FALSE {
let err = std::io::Error::last_os_error(); Err(Error::new_os(
error!("GetQueuedCompletionStatusEx failed: {err:?}"); crate::ErrorKind::Other,
Err(err) "failed to get events from IO completion port",
GetLastError(),
)
.log_error())
} else { } else {
events.set_len(event_count as usize); events.set_len(event_count as usize);
Ok(()) Ok(())

View file

@ -7,7 +7,7 @@ use std::{
task::{Context, Poll, Waker}, task::{Context, Poll, Waker},
}; };
use log::{debug, error}; use log::debug;
use windows_sys::Win32::{ use windows_sys::Win32::{
Devices::{ Devices::{
DeviceAndDriverInstallation::{ DeviceAndDriverInstallation::{
@ -78,8 +78,12 @@ impl WindowsHotplugWatch {
}; };
if cr != CR_SUCCESS { if cr != CR_SUCCESS {
error!("CM_Register_Notification failed: {cr}"); return Err(Error::new_os(
return Err(Error::other("Failed to initialize hotplug notifications")); crate::ErrorKind::Other,
"failed to initialize hotplug notifications",
cr,
)
.log_error());
} }
Ok(WindowsHotplugWatch { Ok(WindowsHotplugWatch {

View file

@ -1,7 +1,6 @@
use std::{ use std::{
alloc::{self, Layout}, alloc::{self, Layout},
ffi::c_void, ffi::c_void,
io::ErrorKind,
mem, mem,
os::windows::prelude::OwnedHandle, os::windows::prelude::OwnedHandle,
ptr::{addr_of, null_mut}, ptr::{addr_of, null_mut},
@ -25,6 +24,13 @@ use windows_sys::Win32::{
System::IO::DeviceIoControl, System::IO::DeviceIoControl,
}; };
use crate::{descriptors::DESCRIPTOR_TYPE_DEVICE, Error, ErrorKind, Speed};
use super::{
cfgmgr32::DevInst,
util::{create_file, raw_handle},
};
// flags for USB_NODE_CONNECTION_INFORMATION_EX_V2.SupportedUsbProtocols // flags for USB_NODE_CONNECTION_INFORMATION_EX_V2.SupportedUsbProtocols
const USB110: u32 = 0x01; const USB110: u32 = 0x01;
const USB200: u32 = 0x02; const USB200: u32 = 0x02;
@ -36,13 +42,6 @@ const DEVICE_IS_SUPER_SPEED_CAPABLE_OR_HIGHER: u32 = 0x02;
const DEVICE_IS_OPERATING_AT_SUPER_SPEED_PLUS_OR_HIGHER: u32 = 0x04; const DEVICE_IS_OPERATING_AT_SUPER_SPEED_PLUS_OR_HIGHER: u32 = 0x04;
const DEVICE_IS_SUPER_SPEED_PLUS_CAPABLE_OR_HIGHER: u32 = 0x08; const DEVICE_IS_SUPER_SPEED_PLUS_CAPABLE_OR_HIGHER: u32 = 0x08;
use crate::{descriptors::DESCRIPTOR_TYPE_DEVICE, Error, Speed};
use super::{
cfgmgr32::DevInst,
util::{create_file, raw_handle},
};
/// Safe wrapper around hub ioctls used to get descriptors for child devices. /// Safe wrapper around hub ioctls used to get descriptors for child devices.
pub struct HubHandle(OwnedHandle); pub struct HubHandle(OwnedHandle);
@ -83,19 +82,25 @@ impl HubHandle {
); );
if r == TRUE { if r == TRUE {
if info.ConnectionStatus != windows_sys::Win32::Devices::Usb::DeviceConnected {
return Err(Error::new(ErrorKind::Disconnected, "device disconnected"));
}
if info.DeviceDescriptor.bDescriptorType != DESCRIPTOR_TYPE_DEVICE { if info.DeviceDescriptor.bDescriptorType != DESCRIPTOR_TYPE_DEVICE {
// When the device is disconnected during this call, Windows is observed to // When the device is disconnected during this call, Windows is observed to
// sometimes return an all-zero device descriptor. // sometimes return an all-zero device descriptor.
return Err(Error::other( return Err(Error::new(ErrorKind::Other,
"IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX returned an invalid device descriptor", "IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX returned an invalid device descriptor",
)); ));
} }
Ok(info) Ok(info)
} else { } else {
let err = Error::last_os_error(); Err(Error::new_os(
debug!("Hub DeviceIoControl failed: {err:?}"); ErrorKind::Other,
Err(err) "hub DeviceIoControl failed",
GetLastError(),
)
.log_debug())
} }
} }
} }
@ -124,9 +129,12 @@ impl HubHandle {
if r == TRUE { if r == TRUE {
Ok(info) Ok(info)
} else { } else {
let err = Error::last_os_error(); Err(Error::new_os(
debug!("Hub DeviceIoControl failed: {err:?}"); ErrorKind::Other,
Err(err) "hub DeviceIoControl failed",
GetLastError(),
)
.log_debug())
} }
} }
} }
@ -186,10 +194,12 @@ impl HubHandle {
let err = GetLastError(); let err = GetLastError();
debug!("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION failed: type={descriptor_type} index={descriptor_index} error={err:?}"); debug!("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION failed: type={descriptor_type} index={descriptor_index} error={err:?}");
Err(match err { Err(match err {
ERROR_GEN_FAILURE => { ERROR_GEN_FAILURE => Error::new_os(
Error::other("Descriptor request failed. Device might be suspended.") ErrorKind::Other,
} "descriptor request failed: device might be suspended.",
_ => Error::from_raw_os_error(err as i32), err,
),
_ => Error::new_os(ErrorKind::Other, "descriptor request failed", err),
}) })
}; };
@ -215,12 +225,12 @@ impl HubPort {
pub fn by_child_devinst(devinst: DevInst) -> Result<HubPort, Error> { pub fn by_child_devinst(devinst: DevInst) -> Result<HubPort, Error> {
let parent_hub = devinst let parent_hub = devinst
.parent() .parent()
.ok_or_else(|| Error::other("failed to find parent hub"))?; .ok_or_else(|| Error::new(ErrorKind::Disconnected, "failed to find parent hub"))?;
let hub_handle = HubHandle::by_devinst(parent_hub) let hub_handle = HubHandle::by_devinst(parent_hub)
.ok_or_else(|| Error::other("failed to open parent hub"))?; .ok_or_else(|| Error::new(ErrorKind::Disconnected, "failed to open parent hub"))?;
let Some(port_number) = devinst.get_property::<u32>(DEVPKEY_Device_Address) else { let Some(port_number) = devinst.get_property::<u32>(DEVPKEY_Device_Address) else {
return Err(Error::new( return Err(Error::new(
ErrorKind::NotConnected, ErrorKind::Disconnected,
"Could not find hub port number", "Could not find hub port number",
)); ));
}; };

View file

@ -1,4 +1,6 @@
mod enumeration; mod enumeration;
use std::num::NonZeroU32;
pub use enumeration::{list_buses, list_devices}; pub use enumeration::{list_buses, list_devices};
mod events; mod events;
@ -14,7 +16,24 @@ mod cfgmgr32;
mod hub; mod hub;
mod registry; mod registry;
pub(crate) use cfgmgr32::DevInst; pub(crate) use cfgmgr32::DevInst;
use windows_sys::Win32::Foundation::WIN32_ERROR;
pub(crate) use DevInst as DeviceId; pub(crate) use DevInst as DeviceId;
mod hotplug; mod hotplug;
mod util; mod util;
pub(crate) use hotplug::WindowsHotplugWatch as HotplugWatch; pub(crate) use hotplug::WindowsHotplugWatch as HotplugWatch;
use crate::ErrorKind;
pub fn format_os_error_code(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result {
write!(f, "error {}", code)
}
impl crate::error::Error {
pub(crate) fn new_os(kind: ErrorKind, message: &'static str, code: WIN32_ERROR) -> Self {
Self {
kind,
code: NonZeroU32::new(code as u32),
message,
}
}
}

View file

@ -1,7 +1,6 @@
use std::{ use std::{
alloc::{self, Layout}, alloc::{self, Layout},
ffi::OsStr, ffi::OsStr,
io::ErrorKind,
mem, mem,
ptr::{null, null_mut}, ptr::{null, null_mut},
}; };
@ -9,7 +8,7 @@ use std::{
use windows_sys::{ use windows_sys::{
core::GUID, core::GUID,
Win32::{ Win32::{
Foundation::{ERROR_SUCCESS, S_OK}, Foundation::{GetLastError, ERROR_SUCCESS, S_OK},
System::{ System::{
Com::IIDFromString, Com::IIDFromString,
Registry::{RegCloseKey, RegQueryValueExW, HKEY, REG_MULTI_SZ, REG_SZ}, Registry::{RegCloseKey, RegQueryValueExW, HKEY, REG_MULTI_SZ, REG_SZ},
@ -45,13 +44,18 @@ impl RegKey {
); );
if r != ERROR_SUCCESS { if r != ERROR_SUCCESS {
return Err(Error::from_raw_os_error(r as i32)); return Err(Error::new_os(
crate::ErrorKind::Other,
"failed to read registry value",
GetLastError(),
));
} }
if ty != REG_MULTI_SZ && ty != REG_SZ { if ty != REG_MULTI_SZ && ty != REG_SZ {
return Err(Error::new( return Err(Error::new_os(
ErrorKind::InvalidInput, crate::ErrorKind::Other,
"registry value type not string", "failed to read registry value: expected string",
GetLastError(),
)); ));
} }
@ -63,7 +67,11 @@ impl RegKey {
if r != ERROR_SUCCESS { if r != ERROR_SUCCESS {
alloc::dealloc(buf, layout); alloc::dealloc(buf, layout);
return Err(Error::from_raw_os_error(r as i32)); return Err(Error::new_os(
crate::ErrorKind::Other,
"failed to read registry value data",
GetLastError(),
));
} }
let mut guid = GUID::from_u128(0); let mut guid = GUID::from_u128(0);
@ -74,7 +82,10 @@ impl RegKey {
if r == S_OK { if r == S_OK {
Ok(guid) Ok(guid)
} else { } else {
Err(Error::new(ErrorKind::InvalidData, "invalid UUID")) Err(Error::new(
crate::ErrorKind::Other,
"failed to parse GUID from registry value",
))
} }
} }
} }

View file

@ -2,7 +2,6 @@ use std::{
borrow::Borrow, borrow::Borrow,
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fmt::{Display, Write}, fmt::{Display, Write},
io,
ops::Deref, ops::Deref,
os::windows::prelude::{ os::windows::prelude::{
AsHandle, AsRawHandle, HandleOrInvalid, OsStrExt, OsStringExt, OwnedHandle, RawHandle, AsHandle, AsRawHandle, HandleOrInvalid, OsStrExt, OsStringExt, OwnedHandle, RawHandle,
@ -12,14 +11,14 @@ use std::{
}; };
use windows_sys::Win32::{ use windows_sys::Win32::{
Foundation::{GENERIC_READ, GENERIC_WRITE, HANDLE}, Foundation::{GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE, WIN32_ERROR},
Storage::FileSystem::{ Storage::FileSystem::{
CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
}, },
}; };
/// Wrapper around `CreateFile` /// Wrapper around `CreateFile`
pub fn create_file(path: &WCStr) -> Result<OwnedHandle, io::Error> { pub fn create_file(path: &WCStr) -> Result<OwnedHandle, WIN32_ERROR> {
unsafe { unsafe {
let r = CreateFileW( let r = CreateFileW(
path.as_ptr(), path.as_ptr(),
@ -32,7 +31,7 @@ pub fn create_file(path: &WCStr) -> Result<OwnedHandle, io::Error> {
); );
HandleOrInvalid::from_raw_handle(r as RawHandle) HandleOrInvalid::from_raw_handle(r as RawHandle)
.try_into() .try_into()
.map_err(|_| io::Error::last_os_error()) .map_err(|_| GetLastError())
} }
} }

View file

@ -16,7 +16,7 @@ pub use buffer::Buffer;
pub(crate) mod internal; pub(crate) mod internal;
use crate::descriptors::TransferType; use crate::{descriptors::TransferType, platform};
/// Transfer error. /// Transfer error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -60,11 +60,9 @@ impl Display for TransferError {
TransferError::Fault => write!(f, "hardware fault or protocol violation"), TransferError::Fault => write!(f, "hardware fault or protocol violation"),
TransferError::InvalidArgument => write!(f, "invalid or unsupported argument"), TransferError::InvalidArgument => write!(f, "invalid or unsupported argument"),
TransferError::Unknown(e) => { TransferError::Unknown(e) => {
if cfg!(target_os = "macos") { write!(f, "unknown (")?;
write!(f, "unknown error (0x{e:08x})") platform::format_os_error_code(f, *e)?;
} else { write!(f, ")")
write!(f, "unknown error ({e})")
}
} }
} }
} }