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(

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,16 +371,20 @@ impl LinuxDevice {
}) })
} }
pub(crate) fn claim_interface( fn handle_claim_interface_result(
self: Arc<Self>, self: Arc<Self>,
interface_number: u8, interface_number: u8,
) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> { result: Result<(), Errno>,
Blocking::new(move || { reattach: bool,
usbfs::claim_interface(&self.fd, interface_number).inspect_err(|e| { ) -> Result<Arc<LinuxInterface>, Error> {
warn!( result.map_err(|e| {
"Failed to claim interface {interface_number} on device id {dev}: {e}", match e {
dev = self.events_id 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!( debug!(
"Claimed interface {interface_number} on device id {dev}", "Claimed interface {interface_number} on device id {dev}",
@ -370,9 +393,18 @@ impl LinuxDevice {
Ok(Arc::new(LinuxInterface { Ok(Arc::new(LinuxInterface {
device: self, device: self,
interface_number, interface_number,
reattach: false, reattach,
state: Mutex::new(Default::default()), state: Mutex::new(Default::default()),
})) }))
}
pub(crate) fn claim_interface(
self: Arc<Self>,
interface_number: u8,
) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> {
Blocking::new(move || {
let result = usbfs::claim_interface(&self.fd, interface_number);
self.handle_claim_interface_result(interface_number, result, false)
}) })
} }
@ -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})")
}
} }
} }
} }