Disable DeviceInfo fields and BusInfo for future compatibility with Android and WebUSB backends

This commit is contained in:
Kevin Mehall 2025-07-26 13:52:05 -06:00
parent 84616c749e
commit 5262bad233
4 changed files with 109 additions and 58 deletions

View file

@ -1,7 +1,7 @@
#[cfg(target_os = "windows")]
use std::ffi::{OsStr, OsString};
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
use crate::platform::SysfsPath;
use crate::{Device, Error, MaybeFuture};
@ -22,7 +22,7 @@ pub struct DeviceId(pub(crate) crate::platform::DeviceId);
/// * macOS: `registry_id`, `location_id`
#[derive(Clone)]
pub struct DeviceInfo {
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(target_os = "linux")]
pub(crate) path: SysfsPath,
#[cfg(any(target_os = "linux", target_os = "android"))]
@ -52,12 +52,24 @@ pub struct DeviceInfo {
#[cfg(target_os = "macos")]
pub(crate) location_id: u32,
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) bus_id: String,
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
target_os = "android"
))]
pub(crate) device_address: u8,
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) port_chain: Vec<u8>,
pub(crate) vendor_id: u16,
pub(crate) product_id: u16,
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) device_version: u16,
pub(crate) usb_version: u16,
@ -96,14 +108,6 @@ impl DeviceInfo {
}
}
/// *(Linux-only)* Sysfs path for the device.
#[doc(hidden)]
#[deprecated = "use `sysfs_path()` instead"]
#[cfg(target_os = "linux")]
pub fn path(&self) -> &SysfsPath {
&self.path
}
/// *(Linux-only)* Sysfs path for the device.
#[cfg(target_os = "linux")]
pub fn sysfs_path(&self) -> &std::path::Path {
@ -113,7 +117,7 @@ impl DeviceInfo {
/// *(Linux-only)* Bus number.
///
/// On Linux, the `bus_id` is an integer and this provides the value as `u8`.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
pub fn busnum(&self) -> u8 {
self.busnum
}
@ -149,6 +153,7 @@ impl DeviceInfo {
///
/// Since USB SuperSpeed is a separate topology from USB 2.0 speeds, a
/// physical port may be identified differently depending on speed.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn port_chain(&self) -> &[u8] {
&self.port_chain
}
@ -172,11 +177,13 @@ impl DeviceInfo {
}
/// Identifier for the bus / host controller where the device is connected.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn bus_id(&self) -> &str {
&self.bus_id
}
/// Number identifying the device within the bus.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn device_address(&self) -> u8 {
self.device_address
}
@ -195,6 +202,7 @@ impl DeviceInfo {
/// The device version, normally encoded as BCD, from the `bcdDevice` device descriptor field.
#[doc(alias = "bcdDevice")]
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn device_version(&self) -> u16 {
self.device_version
}
@ -227,6 +235,7 @@ impl DeviceInfo {
}
/// Connection speed
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn speed(&self) -> Option<Speed> {
self.speed
}
@ -286,16 +295,21 @@ impl std::fmt::Debug for DeviceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("DeviceInfo");
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
s.field("bus_id", &self.bus_id)
.field("device_address", &self.device_address)
.field("port_chain", &format_args!("{:?}", self.port_chain))
.field("vendor_id", &format_args!("0x{:04X}", self.vendor_id))
.field("product_id", &format_args!("0x{:04X}", self.product_id))
.field(
"device_version",
&format_args!("0x{:04X}", self.device_version),
)
.field("usb_version", &format_args!("0x{:04X}", self.usb_version))
.field("port_chain", &format_args!("{:?}", self.port_chain));
s.field("vendor_id", &format_args!("0x{:04X}", self.vendor_id))
.field("product_id", &format_args!("0x{:04X}", self.product_id));
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
s.field(
"device_version",
&format_args!("0x{:04X}", self.device_version),
);
s.field("usb_version", &format_args!("0x{:04X}", self.usb_version))
.field("class", &format_args!("0x{:02X}", self.class))
.field("subclass", &format_args!("0x{:02X}", self.subclass))
.field("protocol", &format_args!("0x{:02X}", self.protocol))
@ -308,10 +322,6 @@ impl std::fmt::Debug for DeviceInfo {
{
s.field("sysfs_path", &self.path);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
s.field("busnum", &self.busnum);
}
#[cfg(target_os = "windows")]
{
@ -466,15 +476,16 @@ impl UsbControllerType {
/// * Linux: `path`, `parent_path`, `busnum`, `root_hub`
/// * Windows: `instance_id`, `parent_instance_id`, `location_paths`, `devinst`, `root_hub_description`
/// * macOS: `registry_id`, `location_id`, `name`, `provider_class_name`, `class_name`
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub struct BusInfo {
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
pub(crate) path: SysfsPath,
/// The phony root hub device
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
pub(crate) root_hub: DeviceInfo,
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
pub(crate) busnum: u8,
#[cfg(target_os = "windows")]
@ -516,9 +527,10 @@ pub struct BusInfo {
pub(crate) controller_type: Option<UsbControllerType>,
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
impl BusInfo {
/// *(Linux-only)* Sysfs path for the bus.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
pub fn sysfs_path(&self) -> &std::path::Path {
&self.path.0
}
@ -526,13 +538,13 @@ impl BusInfo {
/// *(Linux-only)* Bus number.
///
/// On Linux, the `bus_id` is an integer and this provides the value as `u8`.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
pub fn busnum(&self) -> u8 {
self.busnum
}
/// *(Linux-only)* The root hub [`DeviceInfo`] representing the bus.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
pub fn root_hub(&self) -> &DeviceInfo {
&self.root_hub
}
@ -622,7 +634,7 @@ impl BusInfo {
/// * macOS: The [IONameMatched](https://developer.apple.com/documentation/bundleresources/information_property_list/ionamematch) key of the IOService entry.
/// * Windows: Description field of the root hub device. How the bus will appear in Device Manager.
pub fn system_name(&self) -> Option<&str> {
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
{
self.root_hub.product_string()
}
@ -639,11 +651,12 @@ impl BusInfo {
}
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
impl std::fmt::Debug for BusInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("BusInfo");
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(any(target_os = "linux"))]
{
s.field("sysfs_path", &self.path);
s.field("busnum", &self.busnum);

View file

@ -139,13 +139,16 @@ mod platform;
pub mod descriptors;
mod enumeration;
pub use enumeration::{BusInfo, DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType};
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub use enumeration::BusInfo;
pub use enumeration::{DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType};
mod device;
pub use device::{Device, Endpoint, Interface};
pub mod transfer;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub mod hotplug;
mod maybe_future;
@ -168,6 +171,7 @@ pub use error::{ActiveConfigurationError, Error, ErrorKind, GetDescriptorError};
/// .find(|dev| dev.vendor_id() == 0xAAAA && dev.product_id() == 0xBBBB)
/// .expect("device not connected");
/// ```
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn list_devices() -> impl MaybeFuture<Output = Result<impl Iterator<Item = DeviceInfo>, Error>>
{
platform::list_devices()
@ -191,10 +195,7 @@ pub fn list_devices() -> impl MaybeFuture<Output = Result<impl Iterator<Item = D
/// })
/// .collect();
/// ```
///
/// ### Platform-specific notes
/// * On Linux, the abstraction of the "bus" is a phony device known as the root hub. This device is available at bus.root_hub()
/// * On Android, this will only work on rooted devices due to sysfs path usage
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn list_buses() -> impl MaybeFuture<Output = Result<impl Iterator<Item = BusInfo>, Error>> {
platform::list_buses()
}
@ -234,6 +235,7 @@ pub fn list_buses() -> impl MaybeFuture<Output = Result<impl Iterator<Item = Bus
/// when the `Connected` event is emitted. If you are immediately opening the device
/// and claiming an interface when receiving a `Connected` event,
/// you should retry after a short delay if opening or claiming fails.
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub fn watch_devices() -> Result<hotplug::HotplugWatch, Error> {
Ok(hotplug::HotplugWatch(platform::HotplugWatch::new()?))
}

View file

@ -4,7 +4,6 @@ use std::{
fs::File,
io::{Read, Seek},
mem::ManuallyDrop,
path::PathBuf,
sync::{
atomic::{AtomicU8, Ordering},
Arc, Mutex, MutexGuard, Weak,
@ -17,7 +16,7 @@ use log::{debug, error, warn};
use rustix::{
event::epoll::EventFlags,
fd::{AsFd, AsRawFd, FromRawFd, OwnedFd},
fs::{Mode, OFlags, Timespec},
fs::Timespec,
io::Errno,
time::{timerfd_create, timerfd_settime, Itimerspec, TimerfdFlags, TimerfdTimerFlags},
};
@ -26,8 +25,12 @@ use slab::Slab;
use super::{
errno_to_transfer_error, events,
usbfs::{self, Urb},
SysfsPath, TransferData,
TransferData,
};
#[cfg(not(target_os = "android"))]
use super::SysfsPath;
use crate::{
bitset::EndpointBitSet,
descriptors::{
@ -63,7 +66,9 @@ pub(crate) struct LinuxDevice {
/// Read from the fd, consists of device descriptor followed by configuration descriptors
descriptors: Vec<u8>,
#[cfg(not(target_os = "android"))]
sysfs: Option<SysfsPath>,
active_config: AtomicU8,
timerfd: OwnedFd,
@ -71,15 +76,18 @@ pub(crate) struct LinuxDevice {
}
impl LinuxDevice {
#[cfg(not(target_os = "android"))]
pub(crate) fn from_device_info(
d: &DeviceInfo,
) -> impl MaybeFuture<Output = Result<Arc<LinuxDevice>, Error>> {
use rustix::fs::{Mode, OFlags};
let busnum = d.busnum();
let devnum = d.device_address();
let sysfs_path = d.path.clone();
Blocking::new(move || {
let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"));
let path = std::path::PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}"));
let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())
.map_err(|e| {
match e {
@ -98,18 +106,29 @@ impl LinuxDevice {
})
}
#[cfg(target_os = "android")]
pub(crate) fn from_device_info(
_d: &DeviceInfo,
) -> impl MaybeFuture<Output = Result<Arc<LinuxDevice>, Error>> {
Blocking::new(move || unimplemented!())
}
pub(crate) fn from_fd(
fd: OwnedFd,
) -> 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)
Self::create_inner(
fd,
#[cfg(not(target_os = "android"))]
None,
)
})
}
pub(crate) fn create_inner(
fd: OwnedFd,
sysfs: Option<SysfsPath>,
#[cfg(not(target_os = "android"))] sysfs: Option<SysfsPath>,
) -> Result<Arc<LinuxDevice>, Error> {
let descriptors = read_all_from_fd(&fd).map_err(|e| {
Error::new_io(ErrorKind::Other, "failed to read descriptors", e).log_error()
@ -119,26 +138,19 @@ impl LinuxDevice {
return Err(Error::new(ErrorKind::Other, "invalid device descriptor"));
};
#[cfg(not(target_os = "android"))]
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 {
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
}
})
guess_active_configuration(&fd, &descriptors)
};
#[cfg(target_os = "android")]
let active_config = guess_active_configuration(&fd, &descriptors);
let timerfd = timerfd_create(
rustix::time::TimerfdClockId::Monotonic,
TimerfdFlags::CLOEXEC | TimerfdFlags::NONBLOCK,
@ -151,6 +163,7 @@ impl LinuxDevice {
fd,
events_id,
descriptors,
#[cfg(not(target_os = "android"))]
sysfs,
active_config: AtomicU8::new(active_config),
timerfd,
@ -305,6 +318,7 @@ impl LinuxDevice {
}
pub(crate) fn active_configuration_value(&self) -> u8 {
#[cfg(not(target_os = "android"))]
if let Some(sysfs) = self.sysfs.as_ref() {
match sysfs.read_attr("bConfigurationValue") {
Ok(v) => {
@ -529,6 +543,21 @@ fn read_all_from_fd(fd: &OwnedFd) -> Result<Vec<u8>, std::io::Error> {
Ok(buf)
}
/// Try a request to get the active configuration or fall back to a guess.
fn guess_active_configuration(fd: &OwnedFd, descriptors: &[u8]) -> u8 {
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
}
})
}
/// Get the active configuration with a blocking request to the device.
fn request_configuration(fd: &OwnedFd) -> Result<u8, ()> {
const REQUEST_GET_CONFIGURATION: u8 = 0x08;

View file

@ -6,18 +6,25 @@ use rustix::io::Errno;
pub(crate) use transfer::TransferData;
mod usbfs;
#[cfg(not(target_os = "android"))]
mod enumeration;
mod events;
#[cfg(not(target_os = "android"))]
pub use enumeration::{list_buses, list_devices, SysfsPath};
#[cfg(not(target_os = "android"))]
mod hotplug;
#[cfg(not(target_os = "android"))]
pub(crate) use hotplug::LinuxHotplugWatch as HotplugWatch;
mod events;
mod device;
pub(crate) use device::LinuxDevice as Device;
pub(crate) use device::LinuxEndpoint as Endpoint;
pub(crate) use device::LinuxInterface as Interface;
mod hotplug;
pub(crate) use hotplug::LinuxHotplugWatch as HotplugWatch;
use crate::transfer::TransferError;
use crate::ErrorKind;