linux: refactor device initial get_configuration

This commit is contained in:
Kevin Mehall 2025-05-31 14:31:37 -06:00
parent f1f4bec4a1
commit 592f57a3e0

View file

@ -80,11 +80,10 @@ 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))
Self::create_inner(fd, Some(sysfs_path))
})
}
@ -93,23 +92,15 @@ 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)?;
let Some(_) = DeviceDescriptor::new(&descriptors) else {
return Err(Error::new(
@ -118,10 +109,23 @@ impl LinuxDevice {
));
};
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").inspect_err(|e| {
warn!("failed to read sysfs bConfigurationValue: {e}");
})?
} 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(
@ -467,67 +471,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 +487,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);