Skip to content

Commit

Permalink
Linux: Support getting DeviceDescriptor and DeviceInfo from a opened …
Browse files Browse the repository at this point in the history
…device
  • Loading branch information
kirisauce committed Jan 8, 2025
1 parent ffd8202 commit 088781f
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 3 deletions.
30 changes: 30 additions & 0 deletions src/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,36 @@ macro_rules! descriptor_fields {
}
}

/// Check whether the buffer contains a valid device descriptor.
/// On success, it will return length of the descriptor, or returns `None`.
pub(crate) fn validate_device_descriptor(buf: &[u8]) -> Option<usize> {
if buf.len() < DESCRIPTOR_LEN_DEVICE as usize {
if buf.len() != 0 {
warn!(
"device descriptor buffer is {} bytes, need {}",
buf.len(),
DESCRIPTOR_LEN_DEVICE
);
}
return None;
}

if buf[0] < DESCRIPTOR_LEN_DEVICE {
warn!("invalid device descriptor bLength");
return None;
}

if buf[1] != DESCRIPTOR_TYPE_DEVICE {
warn!(
"device bDescriptorType is {}, not a device descriptor",
buf[1]
);
return None;
}

return Some(buf[0] as usize);
}

/// Information about a USB device.
#[derive(Clone)]
pub struct DeviceDescriptor<'a>(&'a [u8]);
Expand Down
75 changes: 73 additions & 2 deletions src/device.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
descriptors::{
decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError,
Configuration, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING,
decode_string_descriptor, language_id::US_ENGLISH, validate_device_descriptor,
validate_string_descriptor, ActiveConfigurationError, Configuration, DeviceDescriptor,
InterfaceAltSetting, DESCRIPTOR_TYPE_STRING,
},
platform,
transfer::{
Expand Down Expand Up @@ -341,6 +342,76 @@ impl Device {
t.submit::<ControlOut>(data);
TransferFuture::new(t)
}

/// Get the device descriptor.
///
/// The only situation when it returns `None` is
/// that the cached descriptors contain no valid device descriptor.
///
/// ### Platform-specific notes
///
/// * This is only supported on Linux.
/// * On Linux, this method uses descriptors cached in memory, instead
/// of sending a request to the device for a descriptor.
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_device_descriptor(&self) -> Option<DeviceDescriptor> {
let buf = self.backend.get_descriptors();
validate_device_descriptor(&buf).map(|len| DeviceDescriptor::new(&buf[0..len]))
}

/// Get [`DeviceInfo`] of this device.
/// This method calls [`Self::get_device_descriptor`] and [`Self::get_string_descriptor`]
/// for some information internally.
///
/// **NOTICE: This method always returns a [`DeviceInfo`] with empty
/// `interfaces`, `port_chain` and `path` fields.**
///
/// ### Platform-specific notes
///
/// * Only available on Linux.
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_device_info(&self) -> Result<DeviceInfo, Error> {
let Some(desc) = self.get_device_descriptor() else {
error!("No any valid device descriptor was found");
return Err(ErrorKind::Other.into());
};

let (busnum, device_address) = self.backend.get_busnum_and_devnum()?;
dbg!(&desc);
let manufacturer_string = self
.get_string_descriptor(desc.manufacturer_string_index(), US_ENGLISH, Duration::ZERO)
.ok();
let product_string = self
.get_string_descriptor(desc.product_string_index(), US_ENGLISH, Duration::ZERO)
.ok();
let serial_number = self
.get_string_descriptor(
desc.serial_number_string_index(),
US_ENGLISH,
Duration::ZERO,
)
.ok();

Ok(DeviceInfo {
path: platform::SysfsPath(std::path::PathBuf::default()),
busnum,
bus_id: format!("{busnum:03}"),
device_address,
port_chain: Vec::new(),
vendor_id: desc.vendor_id(),
product_id: desc.product_id(),
device_version: desc.device_version(),
class: desc.class(),
subclass: desc.subclass(),
protocol: desc.protocol(),
max_packet_size_0: desc.max_packet_size_0(),
speed: desc.speed(),
manufacturer_string,
product_string,
serial_number,
interfaces: Vec::new(),
})
}
}

/// An opened interface of a USB device.
Expand Down
38 changes: 37 additions & 1 deletion src/platform/linux_usbfs/device.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::io::{ErrorKind, Seek};
use std::{ffi::c_void, time::Duration};
use std::{
fs::File,
fs::{read_link, File},
io::Read,
mem::ManuallyDrop,
path::PathBuf,
Expand Down Expand Up @@ -36,6 +36,8 @@ use crate::{
DeviceInfo, Error,
};

const USBFS_DEVICE_PREFIX: &'static str = "/dev/bus/usb/";

pub(crate) struct LinuxDevice {
fd: OwnedFd,
events_id: usize,
Expand Down Expand Up @@ -405,6 +407,40 @@ impl LinuxDevice {
);
return Err(ErrorKind::Other.into());
}

pub(crate) fn get_descriptors(&self) -> &[u8] {
&self.descriptors
}

pub(crate) fn get_busnum_and_devnum(&self) -> Result<(u8, u8), Error> {
let raw_fd = self.fd.as_raw_fd();
// The symbolic link points to the device file in `devfs` (usually mounted on /dev)
let fd_path = format!("/proc/self/fd/{}", raw_fd);

// Device path is like "/dev/bus/usb/002/003"
// The first number is the bus number, and the second is the device address on the bus
let device_path = read_link(&fd_path)?;

device_path
.as_os_str()
.to_str()
.and_then(|device_location| device_location.strip_prefix(USBFS_DEVICE_PREFIX))
.and_then(|device_location| device_location.split_once('/'))
.map(|(busnum, devnum)| {
(
busnum.trim_start_matches('0').parse::<u8>().unwrap_or(0),
devnum.trim_start_matches('0').parse::<u8>().unwrap_or(0),
)
})
.ok_or_else(|| {
warn!(
"Unable to parse path \"{}\" for device fd {}",
device_path.display(),
raw_fd
);
ErrorKind::Other.into()
})
}
}

impl Drop for LinuxDevice {
Expand Down

0 comments on commit 088781f

Please sign in to comment.