diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8406b5a..66d35b8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} override: true @@ -36,3 +36,16 @@ jobs: run: cargo build --verbose - name: Run tests run: cargo test --verbose + + build_android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: 'aarch64-linux-android, armv7-linux-androideabi' + - name: build + run: | + cargo build --target aarch64-linux-android --all-features + cargo build --target armv7-linux-androideabi --all-features diff --git a/Cargo.toml b/Cargo.toml index 8195261..1ae34fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ slab = "0.4.9" env_logger = "0.10.0" futures-lite = "1.13.0" -[target.'cfg(target_os="linux")'.dependencies] +[target.'cfg(any(target_os="linux", target_os="android"))'.dependencies] rustix = { version = "0.38.17", features = ["fs", "event", "net"] } libc = "0.2.155" diff --git a/src/device.rs b/src/device.rs index 2f9198a..b3d6227 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,7 +1,3 @@ -use std::{io::ErrorKind, sync::Arc, time::Duration}; - -use log::error; - use crate::{ descriptors::{ decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError, @@ -14,6 +10,8 @@ use crate::{ }, DeviceInfo, Error, }; +use log::error; +use std::{io::ErrorKind, sync::Arc, time::Duration}; /// An opened USB device. /// @@ -46,6 +44,14 @@ impl Device { Ok(Device { backend }) } + /// Wraps a device that is already open. + #[cfg(any(target_os = "android", target_os = "linux"))] + pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result { + Ok(Device { + backend: platform::Device::from_fd(fd)?, + }) + } + /// Open an interface of the device and claim it for exclusive use. pub fn claim_interface(&self, interface: u8) -> Result { let backend = self.backend.claim_interface(interface)?; @@ -242,7 +248,7 @@ impl Device { /// and use the interface handle to submit transfers. /// * On Linux, this takes a device-wide lock, so if you have multiple threads, you /// are better off using the async methods. - #[cfg(any(target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_in_blocking( &self, control: Control, @@ -260,7 +266,7 @@ impl Device { /// and use the interface handle to submit transfers. /// * On Linux, this takes a device-wide lock, so if you have multiple threads, you /// are better off using the async methods. - #[cfg(any(target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_out_blocking( &self, control: Control, @@ -296,7 +302,7 @@ impl Device { /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. - #[cfg(any(target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_in(&self, data: ControlIn) -> TransferFuture { let mut t = self.backend.make_control_transfer(); t.submit::(data); @@ -329,7 +335,7 @@ impl Device { /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. - #[cfg(any(target_os = "linux", target_os = "macos"))] + #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_out(&self, data: ControlOut) -> TransferFuture { let mut t = self.backend.make_control_transfer(); t.submit::(data); diff --git a/src/enumeration.rs b/src/enumeration.rs index 996094f..f5fecfd 100644 --- a/src/enumeration.rs +++ b/src/enumeration.rs @@ -1,7 +1,7 @@ #[cfg(target_os = "windows")] use std::ffi::{OsStr, OsString}; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] use crate::platform::SysfsPath; use crate::{Device, Error}; @@ -22,10 +22,10 @@ pub struct DeviceId(pub(crate) crate::platform::DeviceId); /// * macOS: `registry_id`, `location_id` #[derive(Clone)] pub struct DeviceInfo { - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) path: SysfsPath, - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) busnum: u8, #[cfg(target_os = "windows")] @@ -83,7 +83,7 @@ impl DeviceInfo { DeviceId(self.devinst) } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] { DeviceId(crate::platform::DeviceId { bus: self.busnum, @@ -114,7 +114,7 @@ impl DeviceInfo { /// *(Linux-only)* Bus number. /// /// On Linux, the `bus_id` is an integer and this provides the value as `u8`. - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] pub fn busnum(&self) -> u8 { self.busnum } @@ -314,6 +314,9 @@ impl std::fmt::Debug for DeviceInfo { #[cfg(target_os = "linux")] { s.field("sysfs_path", &self.path); + } + #[cfg(any(target_os = "linux", target_os = "android"))] + { s.field("busnum", &self.busnum); } diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index dc130b9..0db566f 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -1,3 +1,4 @@ +use std::io::{ErrorKind, Seek}; use std::{ffi::c_void, time::Duration}; use std::{ fs::File, @@ -24,7 +25,9 @@ use super::{ usbfs::{self, Urb}, SysfsPath, }; +use crate::descriptors::Configuration; use crate::platform::linux_usbfs::events::Watch; +use crate::transfer::{ControlType, Recipient}; use crate::{ descriptors::{parse_concatenated_config_descriptors, DESCRIPTOR_LEN_DEVICE}, transfer::{ @@ -54,13 +57,39 @@ impl LinuxDevice { let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty()) .inspect_err(|e| warn!("Failed to open device {path:?}: {e}"))?; + let inner = Self::create_inner(fd, Some(d.path.clone()), Some(active_config)); + if inner.is_ok() { + debug!("Opened device bus={busnum} addr={devnum}",); + } + inner + } + + pub(crate) fn from_fd(fd: OwnedFd) -> Result, Error> { + debug!("Wrapping fd {} as usbfs device", fd.as_raw_fd()); + + Self::create_inner(fd, None, None) + } + + pub(crate) fn create_inner( + fd: OwnedFd, + sysfs: Option, + active_config: Option, + ) -> Result, 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 active_config = if let Some(active_config) = active_config { + active_config + } else { + Self::get_config(&descriptors, &fd)? + }; + // because there's no Arc::try_new_cyclic let mut events_err = None; let arc = Arc::new_cyclic(|weak| { @@ -71,11 +100,14 @@ impl LinuxDevice { ); let events_id = *res.as_ref().unwrap_or(&usize::MAX); events_err = res.err(); + if events_err.is_none() { + debug!("Opened device fd={} with id {}", fd.as_raw_fd(), events_id,); + } LinuxDevice { fd, events_id, descriptors, - sysfs: Some(d.path.clone()), + sysfs, active_config: AtomicU8::new(active_config), } }); @@ -84,10 +116,6 @@ impl LinuxDevice { error!("Failed to initialize event loop: {err}"); Err(err) } else { - debug!( - "Opened device bus={busnum} addr={devnum} with id {}", - arc.events_id - ); Ok(arc) } } @@ -262,6 +290,7 @@ impl LinuxDevice { })) } + #[cfg(target_os = "linux")] pub(crate) fn detach_kernel_driver( self: &Arc, interface_number: u8, @@ -269,6 +298,7 @@ impl LinuxDevice { usbfs::detach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into()) } + #[cfg(target_os = "linux")] pub(crate) fn attach_kernel_driver( self: &Arc, interface_number: u8, @@ -303,6 +333,78 @@ impl LinuxDevice { } } } + + fn get_config(descriptors: &[u8], fd: &OwnedFd) -> Result { + const REQUEST_GET_CONFIGURATION: u8 = 0x08; + + let mut dst = [0u8; 1]; + + let control = Control { + control_type: ControlType::Standard, + recipient: Recipient::Device, + request: REQUEST_GET_CONFIGURATION, + value: 0, + index: 0, + }; + + let r = usbfs::control( + &fd, + usbfs::CtrlTransfer { + bRequestType: control.request_type(Direction::In), + bRequest: control.request, + wValue: control.value, + wIndex: control.index, + 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..]) + .map(Configuration::new); + + if let Some(config) = descriptors.next() { + return Ok(config.configuration_value()); + } + + error!( + "No available configurations for device fd {}", + fd.as_raw_fd() + ); + return Err(ErrorKind::Other.into()); + } } impl Drop for LinuxDevice { diff --git a/src/platform/linux_usbfs/usbfs.rs b/src/platform/linux_usbfs/usbfs.rs index 166bd54..22725db 100644 --- a/src/platform/linux_usbfs/usbfs.rs +++ b/src/platform/linux_usbfs/usbfs.rs @@ -95,7 +95,8 @@ mod opcodes { pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { let command = UsbFsIoctl { interface: interface.into(), - ioctl_code: opcodes::nested::USBDEVFS_DISCONNECT::OPCODE.raw(), + // NOTE: Cast needed since on android this type is i32 vs u32 on linux + ioctl_code: opcodes::nested::USBDEVFS_DISCONNECT::OPCODE.raw() as _, data: std::ptr::null_mut(), }; unsafe { @@ -107,7 +108,7 @@ pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { let command = UsbFsIoctl { interface: interface.into(), - ioctl_code: opcodes::nested::USBDEVFS_CONNECT::OPCODE.raw(), + ioctl_code: opcodes::nested::USBDEVFS_CONNECT::OPCODE.raw() as _, data: std::ptr::null_mut(), }; unsafe { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 53a2f21..83ef39d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,7 +1,7 @@ -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] mod linux_usbfs; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] pub use linux_usbfs::*; #[cfg(target_os = "windows")]