Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android support for non-rooted phones #80

Merged
merged 11 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,24 @@ 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
- name: Build
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
22 changes: 14 additions & 8 deletions src/device.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -14,6 +10,8 @@ use crate::{
},
DeviceInfo, Error,
};
use log::error;
use std::{io::ErrorKind, sync::Arc, time::Duration};

/// An opened USB device.
///
Expand Down Expand Up @@ -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<Device, Error> {
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<Interface, Error> {
let backend = self.backend.claim_interface(interface)?;
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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<ControlIn> {
let mut t = self.backend.make_control_transfer();
t.submit::<ControlIn>(data);
Expand Down Expand Up @@ -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<ControlOut> {
let mut t = self.backend.make_control_transfer();
t.submit::<ControlOut>(data);
Expand Down
13 changes: 8 additions & 5 deletions src/enumeration.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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")]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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"))]
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this, but @tuna-f1sh noticed when trying to rebase on top of it -- what's your reasoning for splitting this to exclude sysfs_path here, and similarly not adding target_os = "android" to the DeviceInfo::sysfs_path getter?

Un-rooted Android doesn't allow sysfs, but everything in DeviceInfo comes from sysfs, so list_devices isn't expected to work there at all; that's why you added from_fd. I was assuming you added target_os = "android" in this file for use on rooted devices, or just consistency with Linux, in which case it seems like sysfs_path should be included. Alternatively, should we instead cfg out list_devices and DeviceInfo entirely?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Yeah this is something I missed.

I feel like the correct thing to do would be to add target_os = "android" to the getter, fmt::Debug impl etc. for consistency between linux and rooted devices. Then add some docs in list_devices noting that this will fail on unrooted android, with a link to wrap_device like what libusb does: https://libusb.sourceforge.io/api-1.0/group__libusb__lib.html#gga07d4ec54cf575d672ba94c72b3c0de7ca5534030a8299c85555aebfae39161ef7

s.field("busnum", &self.busnum);
}

Expand Down
112 changes: 107 additions & 5 deletions src/platform/linux_usbfs/device.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::io::{ErrorKind, Seek};
use std::{ffi::c_void, time::Duration};
use std::{
fs::File,
Expand All @@ -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::{
Expand Down Expand Up @@ -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<Arc<LinuxDevice>, 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<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 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| {
Expand All @@ -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),
}
});
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -262,13 +290,15 @@ impl LinuxDevice {
}))
}

#[cfg(target_os = "linux")]
pub(crate) fn detach_kernel_driver(
self: &Arc<Self>,
interface_number: u8,
) -> Result<(), Error> {
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<Self>,
interface_number: u8,
Expand Down Expand Up @@ -303,6 +333,78 @@ impl LinuxDevice {
}
}
}

fn get_config(descriptors: &[u8], fd: &OwnedFd) -> Result<u8, Error> {
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 {
Expand Down
5 changes: 3 additions & 2 deletions src/platform/linux_usbfs/usbfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ mod opcodes {
pub fn detach_kernel_driver<Fd: AsFd>(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 {
Expand All @@ -107,7 +108,7 @@ pub fn detach_kernel_driver<Fd: AsFd>(fd: Fd, interface: u8) -> io::Result<()> {
pub fn attach_kernel_driver<Fd: AsFd>(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 {
Expand Down
4 changes: 2 additions & 2 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
@@ -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")]
Expand Down
Loading