Merge pull request #142 from kevinmehall/errors

Define a new Error type instead of re-exporting std::io::Error
This commit is contained in:
Kevin Mehall 2025-06-14 14:21:11 -06:00 committed by GitHub
commit bb39ff521d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 824 additions and 423 deletions

View file

@ -2,17 +2,11 @@
//!
//! Descriptors are blocks of data that describe the functionality of a USB device.
use std::{
collections::BTreeMap,
fmt::{Debug, Display},
iter,
num::NonZeroU8,
ops::Deref,
};
use std::{collections::BTreeMap, fmt::Debug, iter, num::NonZeroU8, ops::Deref};
use log::warn;
use crate::{transfer::Direction, Error};
use crate::transfer::Direction;
pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01;
pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18;
@ -710,34 +704,6 @@ pub enum TransferType {
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`
#[allow(unused)]
pub(crate) fn parse_concatenated_config_descriptors(
@ -914,7 +880,7 @@ fn test_linux_root_hub() {
fn test_dell_webcam() {
let c = ConfigurationDescriptor(&[
0x09, 0x02, 0xa3, 0x02, 0x02, 0x01, 0x00, 0x80, 0xfa,
// unknown (skipped)
0x28, 0xff, 0x42, 0x49, 0x53, 0x54, 0x00, 0x01, 0x06, 0x01, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0xd1, 0x10, 0xd0, 0x07, 0xd2, 0x11, 0xf4, 0x01,

View file

@ -1,19 +1,19 @@
use crate::{
descriptors::{
decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError,
ConfigurationDescriptor, DeviceDescriptor, InterfaceDescriptor, DESCRIPTOR_TYPE_STRING,
decode_string_descriptor, validate_string_descriptor, ConfigurationDescriptor,
DeviceDescriptor, InterfaceDescriptor, DESCRIPTOR_TYPE_STRING,
},
platform,
transfer::{
Buffer, BulkOrInterrupt, Completion, ControlIn, ControlOut, Direction, EndpointDirection,
EndpointType, TransferError,
},
DeviceInfo, Error, MaybeFuture, Speed,
ActiveConfigurationError, DeviceInfo, Error, ErrorKind, GetDescriptorError, MaybeFuture, Speed,
};
use log::{error, warn};
use std::{
fmt::Debug,
future::{poll_fn, Future},
io::ErrorKind,
marker::PhantomData,
num::NonZeroU8,
sync::Arc,
@ -51,9 +51,7 @@ impl Device {
Device { backend }
}
pub(crate) fn open(
d: &DeviceInfo,
) -> impl MaybeFuture<Output = Result<Device, std::io::Error>> {
pub(crate) fn open(d: &DeviceInfo) -> impl MaybeFuture<Output = Result<Device, Error>> {
platform::Device::from_device_info(d).map(|d| d.map(Device::wrap))
}
@ -181,13 +179,14 @@ impl Device {
desc_index: u8,
language_id: u16,
timeout: Duration,
) -> impl MaybeFuture<Output = Result<Vec<u8>, Error>> {
) -> impl MaybeFuture<Output = Result<Vec<u8>, GetDescriptorError>> {
#[cfg(target_os = "windows")]
{
let _ = timeout;
self.backend
.clone()
.get_descriptor(desc_type, desc_index, language_id)
.map(|r| r.map_err(GetDescriptorError::Transfer))
}
#[cfg(not(target_os = "windows"))]
@ -206,7 +205,7 @@ impl Device {
},
timeout,
)
.map(|r| Ok(r?))
.map(|r| r.map_err(GetDescriptorError::Transfer))
}
}
@ -218,16 +217,13 @@ impl Device {
pub fn get_string_descriptor_supported_languages(
&self,
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)
.map(move |r| {
let data = r?;
if !validate_string_descriptor(&data) {
error!("String descriptor language list read {data:?}, not a valid string descriptor");
return Err(Error::new(
ErrorKind::InvalidData,
"string descriptor data was invalid",
));
return Err(GetDescriptorError::InvalidDescriptor)
}
//TODO: Use array_chunks once stable
@ -252,7 +248,7 @@ impl Device {
desc_index: NonZeroU8,
language_id: u16,
timeout: Duration,
) -> impl MaybeFuture<Output = Result<String, Error>> {
) -> impl MaybeFuture<Output = Result<String, GetDescriptorError>> {
self.get_descriptor(
DESCRIPTOR_TYPE_STRING,
desc_index.get(),
@ -261,9 +257,7 @@ impl Device {
)
.map(|r| {
let data = r?;
decode_string_descriptor(&data).map_err(|_| {
Error::new(ErrorKind::InvalidData, "string descriptor data was invalid")
})
decode_string_descriptor(&data).map_err(|_| GetDescriptorError::InvalidDescriptor)
})
}
@ -353,6 +347,12 @@ impl Device {
}
}
impl Debug for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Device").finish()
}
}
/// An opened interface of a USB device.
///
/// Obtain an `Interface` with the [`Device::claim_interface`] method.
@ -500,16 +500,23 @@ impl Interface {
pub fn endpoint<EpType: EndpointType, Dir: EndpointDirection>(
&self,
address: u8,
) -> Result<Endpoint<EpType, Dir>, ClaimEndpointError> {
) -> Result<Endpoint<EpType, Dir>, Error> {
let intf_desc = self.descriptor();
let ep_desc =
intf_desc.and_then(|desc| desc.endpoints().find(|ep| ep.address() == address));
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 {
return Err(ClaimEndpointError::InvalidType);
if address & Direction::MASK != Dir::DIR as u8 {
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)?;
@ -521,32 +528,14 @@ 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 {
impl Debug for Interface {
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"),
}
f.debug_struct("Interface")
.field("number", &self.backend.interface_number)
.finish()
}
}
impl std::error::Error for ClaimEndpointError {}
/// Exclusive access to an endpoint of a USB device.
///
/// Obtain an `Endpoint` with the [`Interface::endpoint`] method.
@ -719,6 +708,19 @@ impl<EpType: BulkOrInterrupt, Dir: EndpointDirection> Endpoint<EpType, Dir> {
}
}
impl<EpType: BulkOrInterrupt, Dir: EndpointDirection> Debug for Endpoint<EpType, Dir> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Endpoint")
.field(
"address",
&format_args!("0x{:02x}", self.endpoint_address()),
)
.field("type", &EpType::TYPE)
.field("direction", &Dir::DIR)
.finish()
}
}
#[test]
fn assert_send_sync() {
use crate::transfer::{Bulk, In, Interrupt, Out};

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
//! implemented on top of natively-async OS APIs.
use std::io;
mod platform;
pub mod descriptors;
@ -143,7 +141,7 @@ mod enumeration;
pub use enumeration::{BusInfo, DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType};
mod device;
pub use device::{ClaimEndpointError, Device, Endpoint, Interface};
pub use device::{Device, Endpoint, Interface};
pub mod transfer;
@ -154,8 +152,8 @@ pub use maybe_future::MaybeFuture;
mod bitset;
/// OS error returned from operations other than transfers.
pub type Error = io::Error;
mod error;
pub use error::{ActiveConfigurationError, Error, ErrorKind, GetDescriptorError};
/// Get an iterator listing the connected devices.
///

View file

@ -2,7 +2,7 @@ use std::{
collections::{BTreeMap, VecDeque},
ffi::c_void,
fs::File,
io::{ErrorKind, Read, Seek},
io::{Read, Seek},
mem::ManuallyDrop,
path::PathBuf,
sync::{
@ -34,7 +34,6 @@ use crate::{
parse_concatenated_config_descriptors, ConfigurationDescriptor, DeviceDescriptor,
EndpointDescriptor, TransferType, DESCRIPTOR_LEN_DEVICE,
},
device::ClaimEndpointError,
maybe_future::{blocking::Blocking, MaybeFuture},
transfer::{
internal::{
@ -43,7 +42,7 @@ use crate::{
request_type, Buffer, Completion, ControlIn, ControlOut, ControlType, Direction, Recipient,
TransferError,
},
DeviceInfo, Error, Speed,
DeviceInfo, Error, ErrorKind, Speed,
};
#[derive(PartialEq, Eq, PartialOrd, Ord)]
@ -80,11 +79,22 @@ impl LinuxDevice {
let sysfs_path = d.path.clone();
Blocking::new(move || {
let active_config = sysfs_path.read_attr("bConfigurationValue")?;
let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"));
let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())
.inspect_err(|e| warn!("Failed to open device {path:?}: {e}"))?;
Self::create_inner(fd, Some(sysfs_path), Some(active_config))
.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))
})
}
@ -93,42 +103,47 @@ impl LinuxDevice {
) -> impl MaybeFuture<Output = Result<Arc<LinuxDevice>, Error>> {
Blocking::new(move || {
debug!("Wrapping fd {} as usbfs device", fd.as_raw_fd());
Self::create_inner(fd, None, None)
Self::create_inner(fd, None)
})
}
pub(crate) fn create_inner(
fd: OwnedFd,
sysfs: Option<SysfsPath>,
active_config: Option<u8>,
) -> Result<Arc<LinuxDevice>, Error> {
let descriptors = {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd.as_raw_fd())) };
// NOTE: Seek required on android
file.seek(std::io::SeekFrom::Start(0))?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
buf
};
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 {
return Err(Error::new(
ErrorKind::InvalidData,
"invalid device descriptor",
));
return Err(Error::new(ErrorKind::Other, "invalid device descriptor"));
};
let active_config = if let Some(active_config) = active_config {
active_config
let active_config: u8 = if let Some(sysfs) = sysfs.as_ref() {
sysfs.read_attr("bConfigurationValue").map_err(|e| {
warn!("failed to read sysfs bConfigurationValue: {e}");
Error::new(ErrorKind::Other, "failed to read sysfs bConfigurationValue")
})?
} else {
Self::get_config(&descriptors, &fd)?
request_configuration(&fd).unwrap_or_else(|()| {
let mut config_descriptors = parse_concatenated_config_descriptors(
&descriptors[DESCRIPTOR_LEN_DEVICE as usize..],
);
if let Some(config) = config_descriptors.next() {
config.configuration_value()
} else {
error!("no configurations for device fd {}", fd.as_raw_fd());
0
}
})
};
let timerfd = timerfd_create(
rustix::time::TimerfdClockId::Monotonic,
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 events_id = DEVICES.lock().unwrap().insert(weak.clone());
@ -309,7 +324,12 @@ impl LinuxDevice {
configuration: u8,
) -> impl MaybeFuture<Output = Result<(), Error>> {
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);
Ok(())
})
@ -317,8 +337,11 @@ impl LinuxDevice {
pub(crate) fn reset(self: Arc<Self>) -> impl MaybeFuture<Output = Result<(), Error>> {
Blocking::new(move || {
usbfs::reset(&self.fd)?;
Ok(())
usbfs::reset(&self.fd).map_err(|e| match 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 reset device", e),
})
})
}
@ -348,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(
self: Arc<Self>,
interface_number: u8,
) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> {
Blocking::new(move || {
usbfs::claim_interface(&self.fd, interface_number).inspect_err(|e| {
warn!(
"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()),
}))
let result = usbfs::claim_interface(&self.fd, interface_number);
self.handle_claim_interface_result(interface_number, result, false)
})
}
@ -377,17 +413,8 @@ impl LinuxDevice {
interface_number: u8,
) -> impl MaybeFuture<Output = Result<Arc<LinuxInterface>, Error>> {
Blocking::new(move || {
usbfs::detach_and_claim_interface(&self.fd, interface_number)?;
debug!(
"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()),
}))
let result = usbfs::detach_and_claim_interface(&self.fd, interface_number);
self.handle_claim_interface_result(interface_number, result, true)
})
}
@ -396,7 +423,12 @@ impl LinuxDevice {
self: &Arc<Self>,
interface_number: u8,
) -> 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")]
@ -404,7 +436,12 @@ impl LinuxDevice {
self: &Arc<Self>,
interface_number: u8,
) -> 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> {
@ -467,67 +504,6 @@ impl LinuxDevice {
}
}
fn get_config(descriptors: &[u8], fd: &OwnedFd) -> Result<u8, Error> {
const REQUEST_GET_CONFIGURATION: u8 = 0x08;
let mut dst = [0u8; 1];
let r = usbfs::control(
fd,
usbfs::CtrlTransfer {
bRequestType: request_type(Direction::In, ControlType::Standard, Recipient::Device),
bRequest: REQUEST_GET_CONFIGURATION,
wValue: 0,
wIndex: 0,
wLength: dst.len() as u16,
timeout: Duration::from_millis(50)
.as_millis()
.try_into()
.expect("timeout must fit in u32 ms"),
data: &mut dst[0] as *mut u8 as *mut c_void,
},
);
match r {
Ok(n) => {
if n == dst.len() {
let active_config = dst[0];
debug!("Obtained active configuration for fd {} from GET_CONFIGURATION request: {active_config}", fd.as_raw_fd());
return Ok(active_config);
} else {
warn!("GET_CONFIGURATION request returned incorrect length: {n}, expected 1",);
}
}
Err(e) => {
warn!(
"GET_CONFIGURATION request failed: {:?}",
errno_to_transfer_error(e)
);
}
}
if descriptors.len() < DESCRIPTOR_LEN_DEVICE as usize {
warn!(
"Descriptors for device fd {} too short to use fallback configuration",
fd.as_raw_fd()
);
return Err(ErrorKind::Other.into());
}
// Assume the current configuration is the first one
// See: https://github.com/libusb/libusb/blob/467b6a8896daea3d104958bf0887312c5d14d150/libusb/os/linux_usbfs.c#L865
let mut descriptors =
parse_concatenated_config_descriptors(&descriptors[DESCRIPTOR_LEN_DEVICE as usize..]);
if let Some(config) = descriptors.next() {
return Ok(config.configuration_value());
}
error!(
"No available configurations for device fd {}",
fd.as_raw_fd()
);
Err(ErrorKind::Other.into())
}
pub(crate) fn speed(&self) -> Option<Speed> {
usbfs::get_speed(&self.fd)
.inspect_err(|e| log::error!("USBDEVFS_GET_SPEED failed: {e}"))
@ -544,6 +520,52 @@ impl LinuxDevice {
}
}
fn read_all_from_fd(fd: &OwnedFd) -> Result<Vec<u8>, std::io::Error> {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd.as_raw_fd())) };
file.seek(std::io::SeekFrom::Start(0))?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
}
/// Get the active configuration with a blocking request to the device.
fn request_configuration(fd: &OwnedFd) -> Result<u8, ()> {
const REQUEST_GET_CONFIGURATION: u8 = 0x08;
let mut dst = [0u8; 1];
let r = usbfs::control(
fd,
usbfs::CtrlTransfer {
bRequestType: request_type(Direction::In, ControlType::Standard, Recipient::Device),
bRequest: REQUEST_GET_CONFIGURATION,
wValue: 0,
wIndex: 0,
wLength: dst.len() as u16,
timeout: 50,
data: &mut dst[0] as *mut u8 as *mut c_void,
},
);
match r {
Ok(1) => {
let active_config = dst[0];
debug!("Obtained active configuration for fd {} from GET_CONFIGURATION request: {active_config}", fd.as_raw_fd());
return Ok(active_config);
}
Ok(n) => {
warn!("GET_CONFIGURATION request returned unexpected length: {n}, expected 1");
Err(())
}
Err(e) => {
warn!(
"GET_CONFIGURATION request failed: {:?}",
errno_to_transfer_error(e)
);
Err(())
}
}
}
impl Drop for LinuxDevice {
fn drop(&mut self) {
debug!("Closing device {}", self.events_id);
@ -594,16 +616,26 @@ impl LinuxInterface {
Blocking::new(move || {
let mut state = self.state.lock().unwrap();
if !state.endpoints.is_empty() {
// TODO: Use ErrorKind::ResourceBusy once compatible with MSRV
return Err(Error::other(
"must drop endpoints before changing alt setting",
return Err(Error::new(
ErrorKind::Busy,
"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!(
"Set interface {} alt setting to {alt_setting}",
self.interface_number
);
usbfs::set_interface(&self.device.fd, self.interface_number, alt_setting)?;
state.alt_setting = alt_setting;
Ok(())
})
@ -612,7 +644,7 @@ impl LinuxInterface {
pub fn endpoint(
self: &Arc<Self>,
descriptor: EndpointDescriptor,
) -> Result<LinuxEndpoint, ClaimEndpointError> {
) -> Result<LinuxEndpoint, Error> {
let address = descriptor.address();
let ep_type = descriptor.transfer_type();
let max_packet_size = descriptor.max_packet_size();
@ -620,7 +652,7 @@ impl LinuxInterface {
let mut state = self.state.lock().unwrap();
if state.endpoints.is_set(address) {
return Err(ClaimEndpointError::Busy);
return Err(Error::new(ErrorKind::Busy, "endpoint already in use"));
}
state.endpoints.set(address);
@ -741,7 +773,10 @@ impl LinuxEndpoint {
Blocking::new(move || {
let endpoint = inner.address;
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::maybe_future::{MaybeFuture, Ready};
use crate::ErrorKind;
use crate::{BusInfo, DeviceInfo, Error, Speed, UsbControllerType};
#[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 {
fn parse_attr<T, E>(
&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>>
{
Ready((|| {
Ok(fs::read_dir(SYSFS_USB_PREFIX)?.flat_map(|entry| {
Ok(sysfs_list_usb()?.flat_map(|entry| {
let path = entry.ok()?.path();
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> {
Ok(fs::read_dir(SYSFS_USB_PREFIX)?.filter_map(|entry| {
Ok(sysfs_list_usb()?.filter_map(|entry| {
let path = entry.ok()?.path();
let name = path.file_name()?;

View file

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

View file

@ -10,7 +10,7 @@ use rustix::{
};
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};
@ -28,8 +28,15 @@ impl LinuxHotplugWatch {
SocketType::RAW,
SocketFlags::CLOEXEC,
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 {
fd: Async::new(fd)?,
})

View file

@ -1,4 +1,7 @@
mod transfer;
use std::io;
use std::num::NonZeroU32;
use rustix::io::Errno;
pub(crate) use transfer::TransferData;
mod usbfs;
@ -16,6 +19,7 @@ mod hotplug;
pub(crate) use hotplug::LinuxHotplugWatch as HotplugWatch;
use crate::transfer::TransferError;
use crate::ErrorKind;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct DeviceId {
@ -35,3 +39,25 @@ fn errno_to_transfer_error(e: Errno) -> TransferError {
_ => 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::{
collections::VecDeque,
ffi::c_void,
io::ErrorKind,
mem::ManuallyDrop,
sync::{
atomic::{AtomicU8, AtomicUsize, Ordering},
@ -17,7 +16,6 @@ use log::{debug, error};
use crate::{
bitset::EndpointBitSet,
descriptors::{ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor},
device::ClaimEndpointError,
maybe_future::blocking::Blocking,
transfer::{
internal::{
@ -25,13 +23,13 @@ use crate::{
},
Buffer, Completion, ControlIn, ControlOut, Direction, TransferError,
},
DeviceInfo, Error, MaybeFuture, Speed,
DeviceInfo, Error, ErrorKind, MaybeFuture, Speed,
};
use super::{
enumeration::{device_descriptor_from_fields, get_integer_property, service_by_registry_id},
events::{add_event_source, EventRegistration},
iokit::{call_iokit_function, check_iokit_return},
iokit::call_iokit_function,
iokit_c::IOUSBDevRequestTO,
iokit_usb::{IoKitDevice, IoKitInterface},
TransferData,
@ -70,23 +68,32 @@ impl MacDevice {
log::info!("Opening device from registry id {}", registry_id);
let service = service_by_registry_id(registry_id)?;
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()) } {
io_kit_sys::ret::kIOReturnSuccess => true,
err => {
// Most methods don't require USBDeviceOpen() so this can be ignored
// to allow different processes to open different interfaces.
log::debug!("Could not open device for exclusive access: {err:x}");
false
}
};
let opened = device
.open()
.inspect_err(|err| {
log::debug!("Could not open device for exclusive access: 0x{err:08x}");
})
.is_ok();
let device_descriptor = device_descriptor_from_fields(&service)
.ok_or_else(|| Error::other("could not read properties for device descriptor"))?;
let device_descriptor = device_descriptor_from_fields(&service).ok_or_else(|| {
Error::new(
ErrorKind::Other,
"could not read properties for device descriptor",
)
})?;
let num_configs = device.get_number_of_configurations().inspect_err(|e| {
log::warn!("failed to get number of configurations: {e}");
let num_configs = device.get_number_of_configurations().map_err(|e| {
Error::new_os(
ErrorKind::Other,
"failed to get number of configurations",
e,
)
})?;
let config_descriptors: Vec<Vec<u8>> = (0..num_configs)
@ -149,12 +156,22 @@ impl MacDevice {
fn require_open_exclusive(&self) -> Result<(), Error> {
let mut is_open_exclusive = self.is_open_exclusive.lock().unwrap();
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;
}
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",
));
}
@ -168,12 +185,17 @@ impl MacDevice {
) -> impl MaybeFuture<Output = Result<(), Error>> {
Blocking::new(move || {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
SetConfiguration(configuration)
))?
}
self.device
.set_configuration(configuration)
.map_err(|e| match e {
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}");
self.active_config.store(configuration, Ordering::SeqCst);
Ok(())
@ -183,12 +205,12 @@ impl MacDevice {
pub(crate) fn reset(self: Arc<Self>) -> impl MaybeFuture<Output = Result<(), Error>> {
Blocking::new(move || {
self.require_open_exclusive()?;
unsafe {
check_iokit_return(call_iokit_function!(
self.device.raw,
USBDeviceReEnumerate(0)
))
}
self.device.reset().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 reset device", e),
})
})
}
@ -199,7 +221,10 @@ impl MacDevice {
Blocking::new(move || {
let intf_service = self
.device
.create_interface_iterator()?
.create_interface_iterator()
.map_err(|e| {
Error::new_os(ErrorKind::Other, "failed to create interface iterator", e)
})?
.find(|io_service| {
get_integer_property(io_service, "bInterfaceNumber")
== Some(interface_number as i64)
@ -207,9 +232,23 @@ impl MacDevice {
.ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?;
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);
Ok(Arc::new(MacInterface {
@ -357,19 +396,20 @@ impl MacInterface {
let mut state = self.state.lock().unwrap();
if !state.endpoints_used.is_empty() {
// TODO: Use ErrorKind::ResourceBusy once compatible with MSRV
return Err(Error::other(
"must drop endpoints before changing alt setting",
return Err(Error::new(
ErrorKind::Busy,
"can't change alternate setting while endpoints are in use",
));
}
unsafe {
check_iokit_return(call_iokit_function!(
self.interface.raw,
SetAlternateInterface(alt_setting)
))?;
}
self.interface
.set_alternate_interface(alt_setting)
.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 set alternate interface", e),
})?;
debug!(
"Set interface {} alt setting to {alt_setting}",
@ -405,7 +445,7 @@ impl MacInterface {
pub fn endpoint(
self: &Arc<Self>,
descriptor: EndpointDescriptor,
) -> Result<MacEndpoint, ClaimEndpointError> {
) -> Result<MacEndpoint, Error> {
let address = descriptor.address();
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 {
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) {
return Err(ClaimEndpointError::Busy);
return Err(Error::new(ErrorKind::Busy, "endpoint already in use"));
}
state.endpoints_used.set(address);
@ -589,12 +632,16 @@ impl MacEndpoint {
Blocking::new(move || {
debug!("Clear halt, endpoint {:02x}", inner.address);
unsafe {
check_iokit_return(call_iokit_function!(
inner.interface.interface.raw,
ClearPipeStallBothEnds(inner.pipe_ref)
))
}
inner
.interface
.interface
.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::{
base::{CFType, TCFType},
data::CFData,
@ -18,7 +16,7 @@ use log::debug;
use crate::{
descriptors::DeviceDescriptor,
maybe_future::{MaybeFuture, Ready},
BusInfo, DeviceInfo, Error, InterfaceInfo, Speed, UsbControllerType,
BusInfo, DeviceInfo, Error, ErrorKind, InterfaceInfo, Speed, UsbControllerType,
};
use super::iokit::{IoService, IoServiceIterator};
@ -43,13 +41,17 @@ fn usb_service_iter() -> Result<IoServiceIterator, Error> {
unsafe {
let dictionary = IOServiceMatching(kIOUSBDeviceClassName);
if dictionary.is_null() {
return Err(Error::other("IOServiceMatching failed"));
return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed"));
}
let mut iterator = 0;
let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator);
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))
@ -67,13 +69,17 @@ fn usb_controller_service_iter(
UsbControllerType::VHCI => IOServiceMatching(kAppleUSBVHCI),
};
if dictionary.is_null() {
return Err(Error::other("IOServiceMatching failed"));
return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed"));
}
let mut iterator = 0;
let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator);
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))
@ -245,7 +251,11 @@ fn get_children(device: &IoService) -> Result<IoServiceIterator, Error> {
IORegistryEntryGetChildIterator(device.get(), kIOServicePlane as *mut _, &mut iterator);
if r != kIOReturnSuccess {
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))

View file

@ -18,7 +18,7 @@ use io_kit_sys::{
use log::debug;
use slab::Slab;
use crate::{hotplug::HotplugEvent, DeviceId, Error};
use crate::{hotplug::HotplugEvent, DeviceId, Error, ErrorKind};
use super::{
enumeration::{get_registry_id, probe_device},
@ -79,7 +79,7 @@ impl MacHotplugWatch {
let dictionary = unsafe {
let d = IOServiceMatching(kIOUSBDeviceClassName);
if d.is_null() {
return Err(Error::other("IOServiceMatching failed"));
return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed"));
}
CFDictionary::wrap_under_create_rule(d)
};
@ -162,7 +162,9 @@ fn register_notification(
);
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);

View file

@ -5,9 +5,6 @@
use core_foundation_sys::uuid::CFUUIDBytes;
use io_kit_sys::{ret::IOReturn, IOIteratorNext, IOObjectRelease};
use std::io::ErrorKind;
use crate::Error;
use super::iokit_c::{self, CFUUIDGetUUIDBytes, IOCFPlugInInterface};
@ -117,15 +114,9 @@ pub(crate) fn usb_interface_type_id() -> CFUUIDBytes {
unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBInterfaceInterfaceID500()) }
}
pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), Error> {
#[allow(non_upper_case_globals)]
#[deny(unreachable_patterns)]
pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), IOReturn> {
match r {
io_kit_sys::ret::kIOReturnSuccess => Ok(()),
io_kit_sys::ret::kIOReturnExclusiveAccess => {
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)),
e => Err(e),
}
}

View file

@ -3,12 +3,12 @@
//! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs)
//! 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_sys::runloop::CFRunLoopSourceRef;
use io_kit_sys::{
ret::{kIOReturnNoResources, kIOReturnSuccess},
ret::{kIOReturnNoResources, kIOReturnSuccess, IOReturn},
types::io_iterator_t,
};
use log::error;
@ -17,7 +17,7 @@ use crate::{
platform::macos_iokit::{
iokit::usb_interface_type_id, iokit_c::kIOUsbInterfaceUserClientTypeID,
},
Error,
Error, ErrorKind,
};
use super::{
@ -75,12 +75,15 @@ impl IoKitDevice {
}
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() {
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);
@ -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 {
let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut();
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.
pub(crate) fn create_interface_iterator(&self) -> Result<IoServiceIterator, Error> {
pub(crate) fn create_interface_iterator(&self) -> Result<IoServiceIterator, IOReturn> {
unsafe {
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 {
let mut num = 0;
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 {
let mut ptr: *mut IOUSBConfigurationDescriptor = ptr::null_mut();
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 {
let mut val = 0;
check_iokit_return(call_iokit_function!(self.raw, GetConfiguration(&mut val)))?;
@ -208,12 +228,19 @@ impl IoKitInterface {
);
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() {
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);
@ -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())) }
}
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())) }
}
pub(crate) fn create_async_event_source(&self) -> Result<CFRunLoopSource, Error> {
pub(crate) fn create_async_event_source(&self) -> Result<CFRunLoopSource, IOReturn> {
unsafe {
let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut();
check_iokit_return(call_iokit_function!(
@ -291,6 +318,24 @@ impl IoKitInterface {
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 {

View file

@ -1,4 +1,7 @@
use std::num::NonZeroU32;
use crate::transfer::TransferError;
use crate::ErrorKind;
mod transfer;
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)),
}
}
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::{
collections::{btree_map::Entry, BTreeMap, VecDeque},
ffi::c_void,
io::{self, ErrorKind},
io,
mem::{size_of_val, transmute},
os::windows::{
io::{AsRawHandle, RawHandle},
@ -23,7 +23,8 @@ use windows_sys::Win32::{
},
Foundation::{
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},
};
@ -34,7 +35,6 @@ use crate::{
ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor, DESCRIPTOR_LEN_DEVICE,
DESCRIPTOR_TYPE_CONFIGURATION,
},
device::ClaimEndpointError,
maybe_future::{blocking::Blocking, Ready},
transfer::{
internal::{
@ -42,7 +42,7 @@ use crate::{
},
Buffer, Completion, ControlIn, ControlOut, Direction, Recipient, TransferError,
},
DeviceInfo, Error, MaybeFuture, Speed,
DeviceInfo, Error, ErrorKind, MaybeFuture, Speed,
};
use super::{
@ -87,7 +87,7 @@ impl WindowsDevice {
)
};
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 config_descriptors = (0..num_configurations)
@ -136,7 +136,7 @@ impl WindowsDevice {
&self,
_configuration: u8,
) -> impl MaybeFuture<Output = Result<(), Error>> {
Ready(Err(io::Error::new(
Ready(Err(Error::new(
ErrorKind::Unsupported,
"set_configuration not supported by WinUSB",
)))
@ -147,18 +147,24 @@ impl WindowsDevice {
desc_type: u8,
desc_index: u8,
language_id: u16,
) -> impl MaybeFuture<Output = Result<Vec<u8>, Error>> {
) -> impl MaybeFuture<Output = Result<Vec<u8>, TransferError>> {
Blocking::new(move || {
HubPort::by_child_devinst(self.devinst)?.get_descriptor(
desc_type,
desc_index,
language_id,
)
fn to_transfer_error(e: Error) -> TransferError {
match e.kind() {
ErrorKind::Disconnected => TransferError::Disconnected,
_ => 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>> {
Ready(Err(io::Error::new(
Ready(Err(Error::new(
ErrorKind::Unsupported,
"reset not supported by WinUSB",
)))
@ -204,9 +210,10 @@ impl WindowsDevice {
}
}
} else {
debug!("Device driver is {driver:?}, not WinUSB or USBCCGP");
Err(Error::new(
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 {
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)?;
let winusb_handle = unsafe {
let mut h = ptr::null_mut();
if WinUsb_Initialize(raw_handle(&handle), &mut h) == FALSE {
error!("WinUsb_Initialize failed: {:?}", io::Error::last_os_error());
return Err(io::Error::last_os_error());
return Err(Error::new_os(
ErrorKind::Other,
"failed to initialize WinUSB",
GetLastError(),
)
.log_debug());
}
h
};
@ -296,10 +308,7 @@ impl WinusbFileHandle {
assert!(interface_number >= self.first_interface);
if self.claimed_interfaces.is_set(interface_number) {
return Err(Error::new(
ErrorKind::AddrInUse,
"Interface is already claimed",
));
Error::new(ErrorKind::Busy, "interface is already claimed");
}
let winusb_handle = if self.first_interface == interface_number {
@ -310,13 +319,22 @@ impl WinusbFileHandle {
let idx = interface_number - self.first_interface - 1;
if WinUsb_GetAssociatedInterface(self.winusb_handle, idx, &mut out_handle) == FALSE
{
let err = GetLastError();
error!(
"WinUsb_GetAssociatedInterface for {} on {} failed: {:?}",
interface_number,
self.first_interface,
io::Error::last_os_error()
interface_number, self.first_interface, err
);
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
}
@ -463,9 +481,9 @@ impl WindowsInterface {
Blocking::new(move || unsafe {
let mut state = self.state.lock().unwrap();
if !state.endpoints.is_empty() {
// TODO: Use ErrorKind::ResourceBusy once compatible with MSRV
return Err(Error::other(
"must drop endpoints before changing alt setting",
return Err(Error::new(
ErrorKind::Busy,
"can't change alternate setting while endpoints are in use",
));
}
let r = WinUsb_SetCurrentAlternateSetting(self.winusb_handle, alt_setting);
@ -477,7 +495,15 @@ impl WindowsInterface {
state.alt_setting = alt_setting;
Ok(())
} 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(
self: &Arc<Self>,
descriptor: EndpointDescriptor,
) -> Result<WindowsEndpoint, ClaimEndpointError> {
) -> Result<WindowsEndpoint, Error> {
let address = descriptor.address();
let max_packet_size = descriptor.max_packet_size();
let mut state = self.state.lock().unwrap();
if state.endpoints.is_set(address) {
return Err(ClaimEndpointError::Busy);
return Err(Error::new(ErrorKind::Busy, "endpoint already in use"));
}
state.endpoints.set(address);
@ -730,11 +756,12 @@ impl WindowsEndpoint {
let endpoint = inner.address;
debug!("Clear halt, endpoint {endpoint:02x}");
unsafe {
let r = WinUsb_ResetPipe(inner.interface.winusb_handle, endpoint);
if r == TRUE {
if WinUsb_ResetPipe(inner.interface.winusb_handle, endpoint) == TRUE {
Ok(())
} 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::{
ffi::{OsStr, OsString},
io::ErrorKind,
};
use std::ffi::{OsStr, OsString};
use log::debug;
use windows_sys::Win32::Devices::{
@ -19,7 +16,7 @@ use crate::{
DESCRIPTOR_TYPE_CONFIGURATION, DESCRIPTOR_TYPE_STRING,
},
maybe_future::{blocking::Blocking, MaybeFuture},
BusInfo, DeviceInfo, Error, InterfaceInfo, UsbControllerType,
BusInfo, DeviceInfo, Error, ErrorKind, InterfaceInfo, UsbControllerType,
};
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 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())
@ -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 {
return Err(Error::new(
ErrorKind::Unsupported,
"Could not determine driver for interface",
"could not determine driver for interface",
));
};
if !driver.eq_ignore_ascii_case("winusb") {
debug!("Incompatible driver {driver:?} for interface, not WinUSB");
return Err(Error::new(
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 Some(path) = paths.iter().next() else {
return Err(Error::other(
"Failed to find device path for WinUSB interface",
));
return Err(Error::new(
ErrorKind::Unsupported,
"failed to find device path for WinUSB interface",
)
.log_debug());
};
Ok(path.to_owned())

View file

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

View file

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

View file

@ -1,7 +1,6 @@
use std::{
alloc::{self, Layout},
ffi::c_void,
io::ErrorKind,
mem,
os::windows::prelude::OwnedHandle,
ptr::{addr_of, null_mut},
@ -25,6 +24,13 @@ use windows_sys::Win32::{
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
const USB110: u32 = 0x01;
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_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.
pub struct HubHandle(OwnedHandle);
@ -83,19 +82,25 @@ impl HubHandle {
);
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 {
// When the device is disconnected during this call, Windows is observed to
// 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",
));
}
Ok(info)
} else {
let err = Error::last_os_error();
debug!("Hub DeviceIoControl failed: {err:?}");
Err(err)
Err(Error::new_os(
ErrorKind::Other,
"hub DeviceIoControl failed",
GetLastError(),
)
.log_debug())
}
}
}
@ -124,9 +129,12 @@ impl HubHandle {
if r == TRUE {
Ok(info)
} else {
let err = Error::last_os_error();
debug!("Hub DeviceIoControl failed: {err:?}");
Err(err)
Err(Error::new_os(
ErrorKind::Other,
"hub DeviceIoControl failed",
GetLastError(),
)
.log_debug())
}
}
}
@ -186,10 +194,12 @@ impl HubHandle {
let err = GetLastError();
debug!("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION failed: type={descriptor_type} index={descriptor_index} error={err:?}");
Err(match err {
ERROR_GEN_FAILURE => {
Error::other("Descriptor request failed. Device might be suspended.")
}
_ => Error::from_raw_os_error(err as i32),
ERROR_GEN_FAILURE => Error::new_os(
ErrorKind::Other,
"descriptor request failed: device might be suspended.",
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> {
let parent_hub = devinst
.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)
.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 {
return Err(Error::new(
ErrorKind::NotConnected,
ErrorKind::Disconnected,
"Could not find hub port number",
));
};

View file

@ -1,4 +1,6 @@
mod enumeration;
use std::num::NonZeroU32;
pub use enumeration::{list_buses, list_devices};
mod events;
@ -14,7 +16,24 @@ mod cfgmgr32;
mod hub;
mod registry;
pub(crate) use cfgmgr32::DevInst;
use windows_sys::Win32::Foundation::WIN32_ERROR;
pub(crate) use DevInst as DeviceId;
mod hotplug;
mod util;
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::{
alloc::{self, Layout},
ffi::OsStr,
io::ErrorKind,
mem,
ptr::{null, null_mut},
};
@ -9,7 +8,7 @@ use std::{
use windows_sys::{
core::GUID,
Win32::{
Foundation::{ERROR_SUCCESS, S_OK},
Foundation::{GetLastError, ERROR_SUCCESS, S_OK},
System::{
Com::IIDFromString,
Registry::{RegCloseKey, RegQueryValueExW, HKEY, REG_MULTI_SZ, REG_SZ},
@ -45,13 +44,18 @@ impl RegKey {
);
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 {
return Err(Error::new(
ErrorKind::InvalidInput,
"registry value type not string",
return Err(Error::new_os(
crate::ErrorKind::Other,
"failed to read registry value: expected string",
GetLastError(),
));
}
@ -63,7 +67,11 @@ impl RegKey {
if r != ERROR_SUCCESS {
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);
@ -74,7 +82,10 @@ impl RegKey {
if r == S_OK {
Ok(guid)
} 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,
ffi::{OsStr, OsString},
fmt::{Display, Write},
io,
ops::Deref,
os::windows::prelude::{
AsHandle, AsRawHandle, HandleOrInvalid, OsStrExt, OsStringExt, OwnedHandle, RawHandle,
@ -12,14 +11,14 @@ use std::{
};
use windows_sys::Win32::{
Foundation::{GENERIC_READ, GENERIC_WRITE, HANDLE},
Foundation::{GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE, WIN32_ERROR},
Storage::FileSystem::{
CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
},
};
/// Wrapper around `CreateFile`
pub fn create_file(path: &WCStr) -> Result<OwnedHandle, io::Error> {
pub fn create_file(path: &WCStr) -> Result<OwnedHandle, WIN32_ERROR> {
unsafe {
let r = CreateFileW(
path.as_ptr(),
@ -32,7 +31,7 @@ pub fn create_file(path: &WCStr) -> Result<OwnedHandle, io::Error> {
);
HandleOrInvalid::from_raw_handle(r as RawHandle)
.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;
use crate::descriptors::TransferType;
use crate::{descriptors::TransferType, platform};
/// Transfer error.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -60,11 +60,9 @@ impl Display for TransferError {
TransferError::Fault => write!(f, "hardware fault or protocol violation"),
TransferError::InvalidArgument => write!(f, "invalid or unsupported argument"),
TransferError::Unknown(e) => {
if cfg!(target_os = "macos") {
write!(f, "unknown error (0x{e:08x})")
} else {
write!(f, "unknown error ({e})")
}
write!(f, "unknown (")?;
platform::format_os_error_code(f, *e)?;
write!(f, ")")
}
}
}