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

Use rusb (libusb) or nusb instead of JNI calls #1

Closed
dgramop opened this issue Nov 6, 2024 · 6 comments
Closed

Use rusb (libusb) or nusb instead of JNI calls #1

dgramop opened this issue Nov 6, 2024 · 6 comments
Assignees

Comments

@dgramop
Copy link

dgramop commented Nov 6, 2024

In the TODO list README, you have the following item. I just wanted to make an issue so that we could track it

  • Currently, the usb module handles everything with JNI calls. libusb and its wrapper rusb have limited functionalities on unrooted Android, without being able to check for connected devices before opening one of them. Even in android-usbser-rs, create_rusb_connection() needs debugging.

This will help us with performance while actively communicating with the device.

I've written several USB drivers for Android, including native drivers. libusb does work on an unrooted android, but it does not have enough permissions to scan for devices. Instead, we must open a device through android's APIs (get permissions too), then pass the file descriptor down into native code. This strategy is sound for rusb (and I'm sure also nusb will work too).

Anyhow I'm happy to help out with this implementation. Can you assign this issue to me? Thanks for you work on this repo :)

@wuwbobo2021
Copy link
Owner

libusb_wrap_sys_device() currently fails on my devices (currently I'm not giving root permission to the test application).

See README.md for preparing the building environment. Test code:

Cargo.toml:

[package]
name = "android-rusb-test"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
log = "0.4"
android_logger = "0.14"
android-activity = { version = "0.6", features = ["native-activity"] }
android-usbser = { version = "0.1.1", features = ["unsafe-rusb"] }

[lib]
name = "android_rusb_test"
crate-type = ["cdylib"]
path = "android_rusb_test.rs"

[package.metadata.android]
package = "com.example.android_rusb_test"
build_targets = [ "aarch64-linux-android" ]

[package.metadata.android.sdk]
min_sdk_version = 16
target_sdk_version = 30

[[package.metadata.android.uses_feature]]
name = "android.hardware.usb.host"
required = true

android_rusb_test.rs:

use android_activity::{AndroidApp, MainEvent, PollEvent};
use android_usbser::usb;

#[no_mangle]
fn android_main(app: AndroidApp) {
    // TODO: `println` or `eprintln` macro must be used prior to any direct `libusb` call,
    // even if `android_logger` is preferred. Otherwise it `create_rusb_context()` will crash
    // while calling `libusb`. It might be fixed by putting `println` inside this function.
    //
    // `adb logcat <app_name>:D '*:S'` is for macros from `log` backed by `android_logger`;
    // `adb logcat RustStdoutStderr:D '*:S'` is for `println` or `eprintln`.
    let mut permitted_device = None;
    let mut on_destroy = false;
    loop {
        app.poll_events(
            Some(std::time::Duration::from_secs(1)), // timeout
            |event| match event {
                PollEvent::Main(MainEvent::Resume { loader: _, .. }) => {
                    println!("Main Resume.");
                    let usb_devs = usb::list_devices().unwrap();
                    if let Some(dev) = usb_devs.into_iter().next() {
                        if !dev.has_permission().unwrap_or(false) {
                            let _ = dev.request_permission();
                        } else {
                            permitted_device.replace(dev);
                        }
                    }
                }
                PollEvent::Main(MainEvent::Destroy) => {
                    println!("Main Destroy.");
                    on_destroy = true;
                }
                _ => (),
            },
        );
        if on_destroy {
            return;
        }
        if let Some(dev) = permitted_device.take() {
            println!("opening device {}", dev.path_name());
            let conn = dev.open_device().unwrap();
            println!("open_device() success.");
            unsafe {
                println!("before create_rusb_context().");
                if let Ok(mut ctx) = usb::create_rusb_context() {
                    println!("create_rusb_context() success.");
                    match conn.create_rusb_connection(&mut ctx) {
                        Ok(_) => {
                            println!("Success!"); // desired
                            return; // exit on success
                        }
                        Err(e) => eprintln!("Error: {e}"),
                    }
                }
            }
        }
    }
}

Log:

adb logcat RustStdoutStderr:D '*:S'
--------- beginning of crash
--------- beginning of system
--------- beginning of main
11-07 19:37:53.637 21286 21299 I RustStdoutStderr: Main Resume.
11-07 19:37:55.283 21286 21299 I RustStdoutStderr: Main Resume.
11-07 19:37:56.196 21286 21299 I RustStdoutStderr: Main Resume.
11-07 19:37:56.202 21286 21299 I RustStdoutStderr: opening device /dev/bus/usb/001/009
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: open_device() success.
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: before create_rusb_context().
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: create_rusb_context() success.
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: before libusb_wrap_sys_device(). fd: 0x0000001c
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [timestamp] [threadID] facility level [function call] <message>
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: --------------------------------------------------------------------------------
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [ 0.000517] [00005334] libusb: debug [libusb_wrap_sys_device] wrap_sys_device 0x7f940c6eec
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [ 0.000552] [00005334] libusb: debug [linux_get_device_address] getting address for device: (null) detached: 1
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [ 0.000563] [00005334] libusb: error [op_wrap_sys_device] connectinfo failed, errno=9
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [ 0.000571] [00005334] libusb: debug [libusb_wrap_sys_device] wrap_sys_device 0x7f940c6eec returns -1
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: Error: Input/Output Error
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [ 0.000586] [00005334] libusb: debug [libusb_exit]  
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [ 0.000594] [00005334] libusb: debug [usbi_remove_event_source] remove fd 31
11-07 19:37:56.206 21286 21299 I RustStdoutStderr: [ 0.000608] [00005334] libusb: debug [usbi_remove_event_source] remove fd 30

(Another problem: it calls dev.request_permission() for double times before dev.has_permission() returns true.)

@dgramop
Copy link
Author

dgramop commented Nov 7, 2024 via email

@wuwbobo2021
Copy link
Owner

wuwbobo2021 commented Nov 7, 2024

I think create_rusb_context() already did it.

I just flashed my STM32F103 Blue Pill with a USB CDC maximum speed test (data transfer from device to host), On my PC the speed reaches 960KB/s with the Linux CDC-ACM driver, 950KB/s with libusb (kernel driver detached), but it's 445KB/s with my Android library. My current use case requires at least 530KB/s. All of them passed my integrity check with 4MB of data (loops of 256 bytes, 0x00~0xfe incremental byte values, frame number in the last byte).

Well, while rusb makes me frustrated, I just realized the new version of nusb is published. I tried it and it seems like it works! Just add nusb dependency and replace the block if let Some(dev) = permitted_device.take() with the code below, it will print out some configuration information in nusb format.

        if let Some(dev) = permitted_device.take() {
            println!("opening device {}", dev.path_name());
            let conn = dev.open_device().unwrap();
            println!("open_device() success.");
            let nusb_dev = unsafe {
                use std::os::fd::*;
                let raw_fd = conn.get_raw_fd().unwrap();
                let owned_fd = OwnedFd::from_raw_fd(raw_fd as RawFd);
                drop(conn);
                nusb::Device::from_fd(owned_fd)
            };
            if let Err(e) = nusb_dev {
                println!("{:?}", e);
                return;
            }

            println!("Success!");
            let nusb_dev = nusb_dev.unwrap();
            for conf in nusb_dev.configurations() {
                println!("{:#?}", conf);
            }
        }

@wuwbobo2021 wuwbobo2021 self-assigned this Nov 8, 2024
@wuwbobo2021
Copy link
Owner

wuwbobo2021 commented Nov 10, 2024

Have you opened the rusb connection successfully? Otherwise the rusb feature will be removed. nusb and serialport will become necessary dependencies, reducing code length. I just write an synchronous wrapper for nusb bulk transfer queue, and I’ll be testing for it.

@dgramop
Copy link
Author

dgramop commented Nov 10, 2024

I have, but not in the context of your serial port codebase. Honestly nusb is nice since it eliminates the native dependency. One my projects has switched over the nusb already (from rusb), so i say go for it and ditch rusb

@wuwbobo2021
Copy link
Owner

wuwbobo2021 commented Nov 11, 2024

I have published the new version that uses nusb. However, it doesn't make USB full-speed bulk transfers any faster.

Would you like to check out the unsafe operations for avoiding copy_from_slice() in my synchronous wrapper of nusb queue? Currently I don't dare to do so in my library (I tested it for myself but it didn’t make full-speed transfers faster): kevinmehall/nusb#4

Besides, if you'd like to point out my mistake in create_rusb_connection() or show me the context where you created it successfully on Android, I would really appreciate it.

PS: you will become an owner of this crate if you want to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants