Skip to content

Conversation

@StephanvanSchaik
Copy link

This is a continuation of PR #153 with the feedback provided there integrated. This PR mostly draws inspiration from: #62 (comment) and PR #143.

  • This now lives in its own rusb-async crate with version "0.0.1-alpha-2" as suggested in PR implement asynchronous bulk/control transfers #153.
  • Provides its own Context running an event handling thread in the background for convenience. In addition it implements the UsbContext trait, such that it can be used everywhere rusb::Context can be used. This way the user can simply use rusb_async::Context instead of rusb::Context for asynchronous I/O without having to take care of the event handling.
  • Since Transfer::poll() shouldn't be calling handle_events(), that part has been removed as it is being handled by the background thread now.
  • Implements InnerTransfer and Transfer like in Support libusb async api #62 (comment).
  • Wraps NonNull<libusb_transfer> and implements Send such it can be sent across threads, since it is effectively guarded by an Arc<Mutex<T>>.
  • Uses Rust's ownership model to claim and pin the buffer and return it to the user once the transfer completes, gets cancelled or errors out to avoid unnecessary allocations, i.e. to put the user in control of these allocations. This should allow for zero copy, as far as zero copy is possible with libusb.
  • Implements CancellationToken that can be constructed from Transfer, such that the transfer can be cancelled from other tasks.
  • Implements an example showing how to asynchronously read from a bulk endpoint using tokio.
  • Implements asynchronous bulk, control and interrupt transfers.

This integrates nicely with tokio, but should be agnostic of the run-time used since it just relies on the std crate for futures, i.e. any asynchronous executor should work.

I have tried this with a device that supports USB control packets and it seems to work nicely. However, I don't have any experience with isochronous packets, so this PR does not implement that part.

@bmulder-innoseis
Copy link

Very interesting!

I have one question: how can zerocopy be achieved with this? As I looked at the code, the caller of new_bulk_transfer or new_control_transfer has to allocate and provide a Vec, which is moved into the function and then converted into a boxed slice and then into a pin, which involves at least one heap allocation. When the callback has completed, it will turn it back into a Vec and passed to the caller.
Are you sure this will not lead to unneeded heap allocations and copying? Wouldn't you be better off by having the caller pass a Pin instead of a Vec?

@StephanvanSchaik
Copy link
Author

Very interesting!

I have one question: how can zerocopy be achieved with this? As I looked at the code, the caller of new_bulk_transfer or new_control_transfer has to allocate and provide a Vec, which is moved into the function and then converted into a boxed slice and then into a pin, which involves at least one heap allocation. When the callback has completed, it will turn it back into a Vec and passed to the caller.

Are you sure this will not lead to unneeded heap allocations and copying? Wouldn't you be better off by having the caller pass a Pin instead of a Vec?

After looking a bit at the internal representation of Vec<u8> vs. Box<[u8]>, I think you are right, actually. Since resizing is not required, I pushed a commit that changes the functions to accept a boxed slice instead. Thus preventing any possible allocations from converting it between a vec and a boxed slice. Thanks for the remark!

From what I read the Pin should not be necessary for a boxed slice either, so that greatly simplifies things too.

@mcuee
Copy link

mcuee commented May 1, 2023

I'd like to carry out test for this PR. Just wondering what is the test device used.

Usually I will use the bulk loopback (eg: using Cypress EZUSB FX3 or FX2LP) to test libusb related library.

@mcuee
Copy link

mcuee commented May 1, 2023

Example run.

mcuee@mcuees-Mac-mini libusb % ./examples/fxload -t fx3 -d 0x04b4:0x00f3 -i ../../../Cypress/cyusb_mac_1.0/fx3_images/cyfxbulklpautoenum.img
microcontroller type: fx3
../../../Cypress/cyusb_mac_1.0/fx3_images/cyfxbulklpautoenum.img: type Cypress IMG format
open firmware image ../../../Cypress/cyusb_mac_1.0/fx3_images/cyfxbulklpautoenum.img for RAM upload
normal FW binary executable image with checksum
FX3 bootloader version: 0x000000A9
writing image...
transfer execution to Program Entry at 0x40019028

mcuee@mcuees-Mac-mini rusb_pr155 % ./target/debug/examples/list_devices
...
Bus 002 Device 022 ID 04b4:00f0 5000 Mbps
Device Descriptor:
  bcdUSB              3.00
  bDeviceClass        0x00
  bDeviceSubClass     0x00
  bDeviceProtocol     0x00
  bMaxPacketSize0        9
  idVendor          0x04b4 Cypress Semiconductor Corp.
  idProduct         0x00f0 Unknown product
  bcdDevice           0.00
  iManufacturer          1 Cypress
  iProduct               2 Cypress
  iSerialNumber          0 
  bNumConfigurations     1
  Config Descriptor:
    bNumInterfaces         1
    bConfigurationValue    1
    iConfiguration         0 
    bmAttributes:
      Self Powered     false
      Remote Wakeup    false
    bMaxPower            100mW
    no extra data
    Interface Descriptor:
      bInterfaceNumber       0
      bAlternateSetting      0
      bNumEndpoints          2
      bInterfaceClass     0xff
      bInterfaceSubClass  0x00
      bInterfaceProtocol  0x00
      iInterface             0 
    []
      Endpoint Descriptor:
        bEndpointAddress    0x01 EP 1 Out
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0400
        bInterval              0
      Endpoint Descriptor:
        bEndpointAddress    0x81 EP 1 In
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0400
        bInterval              0

cuee@mcuees-Mac-mini rusb_pr155 % ./target/debug/examples/read_async
Usage: read_async <base-10/0xbase-16> <base-10/0xbase-16> <endpoint>
mcuee@mcuees-Mac-mini rusb_pr155 % ./target/debug/examples/read_async 0x004b4 0x00f0 0x81
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', rusb-async/examples/read_async.rs:29:60
stack backtrace:
   0:        0x1043f7c50 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h554558af9dcaf5d8
   1:        0x104407f94 - core::fmt::write::h503a82454aa26fb4
   2:        0x1043e1ba0 - std::io::Write::write_fmt::he97d6853eddb17d2
   3:        0x1043f7a60 - std::sys_common::backtrace::print::h669032be8d7b2a7c
   4:        0x1043e8d6c - std::panicking::default_hook::{{closure}}::h974276930a474e6a
   5:        0x1043e8a30 - std::panicking::default_hook::hb0d2c45a1e792803
   6:        0x1043e9458 - std::panicking::rust_panic_with_hook::hf3b9a3e44fe5f275
   7:        0x1043f7f84 - std::panicking::begin_panic_handler::{{closure}}::ha19c90e1160a0cf9
   8:        0x1043f7d90 - std::sys_common::backtrace::__rust_end_short_backtrace::h3f85ad00ac73ff55
   9:        0x1043e90b8 - _rust_begin_unwind
  10:        0x10440c664 - core::panicking::panic_fmt::hb0625f6d6e3c09e4
  11:        0x10440c90c - core::result::unwrap_failed::hdb332f7457252498
  12:        0x1043658bc - core::result::Result<T,E>::unwrap::he2ca8e6db741978e
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/core/src/result.rs:1112:23
  13:        0x104363af4 - read_async::main::{{closure}}::hbd5ca2ee92873989
                               at /Users/mcuee/build/libusb/rusb_test/rusb_pr155/rusb-async/examples/read_async.rs:29:24
  14:        0x1043634f0 - tokio::runtime::park::CachedParkThread::block_on::{{closure}}::he6e72b1efc5ce3e3
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/park.rs:283:63
  15:        0x104363344 - tokio::runtime::coop::with_budget::h393a79f5b2553b67
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/coop.rs:107:5
  16:        0x104363344 - tokio::runtime::coop::budget::hf660876f9262fb6a
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/coop.rs:73:5
  17:        0x104363344 - tokio::runtime::park::CachedParkThread::block_on::hd2664e438afc9c3a
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/park.rs:283:31
  18:        0x104364a04 - tokio::runtime::context::BlockingRegionGuard::block_on::hb87519a5494ba74a
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/context.rs:315:13
  19:        0x1043675b4 - tokio::runtime::scheduler::multi_thread::MultiThread::block_on::hf122b909beabb4d5
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/scheduler/multi_thread/mod.rs:66:9
  20:        0x104364e64 - tokio::runtime::runtime::Runtime::block_on::h69bf69242d5dca86
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/runtime.rs:304:45
  21:        0x1043673f8 - read_async::main::hcff6c8338313ca35
                               at /Users/mcuee/build/libusb/rusb_test/rusb_pr155/rusb-async/examples/read_async.rs:40:5
  22:        0x10436b080 - core::ops::function::FnOnce::call_once::hf10a0cf45903d5f9
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/core/src/ops/function.rs:250:5
  23:        0x1043629fc - std::sys_common::backtrace::__rust_begin_short_backtrace::hcd683e3d6aa43c2d
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/std/src/sys_common/backtrace.rs:121:18
  24:        0x10436cc5c - std::rt::lang_start::{{closure}}::hc8488cc4b3cf79f1
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/std/src/rt.rs:166:18
  25:        0x1043e7a4c - std::rt::lang_start_internal::h2a63b0d73f6d9885
  26:        0x10436cc28 - std::rt::lang_start::h8b99886c255f99e6
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/std/src/rt.rs:165:17
  27:        0x104367480 - _main

mcuee@mcuees-Mac-mini libusbdotnet_v2 % ./stage/BenchmarkCon/bin/Release/net6.0/BenchmarkCon notestselect vid=0x04b4 pid=0x00f0 mode=Async 
LibUsbDotNet USB Benchmark v2.2.0.0
Copyright (c) 2010 Travis Robinson. <[email protected]>
website: http://sourceforge.net/projects/libusbdotnet
Benchmark device 04B4:00F0 opened..
Loop Test Information
	Vid / Pid       : 04B4h / 00F0h
	Interface #     : 00h
	Alt Interface # : 00h
	Priority        : Normal
	Buffer Size     : 4096
	Buffer Count    : 1
	Display Refresh : 1000 (ms)
	Transfer Timeout: 5000 (ms)
	Retry Count     : 0
	Verify Data     : Off

Bulk Read (Ep81h) max packet size: 1024
Bulk Write (Ep01h) max packet size: 1024

While the test is running:
Press 'Q' to quit
Press 'T' for test details
Press 'I' for status information
Press 'R' to reset averages

Press 'Q' to exit, any other key to begin..
Avg. Bytes/s: 38076316.65 Transfers: 9063 Bytes/s: 38076316.65
Avg. Bytes/s: 38701906.15 Transfers: 18710 Bytes/s: 39308645.71
Avg. Bytes/s: 38829365.48 Transfers: 28300 Bytes/s: 39080469.83
Avg. Bytes/s: 38741627.88 Transfers: 37743 Bytes/s: 38481043.29
Avg. Bytes/s: 38682908.43 Transfers: 47178 Bytes/s: 38449781.46
Avg. Bytes/s: 38633575.86 Transfers: 56598 Bytes/s: 38388385.68
Avg. Bytes/s: 38609148.60 Transfers: 66036 Bytes/s: 38463308.27
Avg. Bytes/s: 38584768.06 Transfers: 75462 Bytes/s: 38414824.31
Avg. Bytes/s: 38577455.72 Transfers: 84914 Bytes/s: 38519175.32
Avg. Bytes/s: 38689787.14 Transfers: 94656 Bytes/s: 39697321.99
Avg. Bytes/s: 38671704.66 Transfers: 104101 Bytes/s: 38491414.56
Avg. Bytes/s: 38694561.40 Transfers: 113658 Bytes/s: 38945293.19
Avg. Bytes/s: 38678448.79 Transfers: 123101 Bytes/s: 38485561.40
Avg. Bytes/s: 38667521.29 Transfers: 132554 Bytes/s: 38525780.38
qstopped Ep81h thread.
stopped Ep01h thread.
Loop Test Information
	Vid / Pid       : 04B4h / 00F0h
	Interface #     : 00h
	Alt Interface # : 00h
	Priority        : Normal
	Buffer Size     : 4096
	Buffer Count    : 1
	Display Refresh : 1000 (ms)
	Transfer Timeout: 5000 (ms)
	Retry Count     : 0
	Verify Data     : Off

Bulk Read (Ep81h) max packet size: 1024
	Total Bytes     : 581750784
	Total Transfers : 142029
	Avg. Bytes/sec  : 38663163.78
	Elapsed Time    : 15.05 seconds

Bulk Write (Ep01h) max packet size: 1024
	Total Bytes     : 581750784
	Total Transfers : 142029
	Avg. Bytes/sec  : 38663274.27
	Elapsed Time    : 15.05 seconds

Press any key to exit..

@mcuee
Copy link

mcuee commented May 1, 2023

I tried to modified the examples to match Cypress BulkSrcSink example. Now read_device works but not read_async.
Tested under Windows 11.

PS C:\work\libusb\rust\rusb_pr155> git diff
diff --git a/examples/read_device.rs b/examples/read_device.rs
index bf1d943..dde230c 100644
--- a/examples/read_device.rs
+++ b/examples/read_device.rs
@@ -105,11 +105,6 @@ fn read_device<T: UsbContext>(
         );
     }

-    match find_readable_endpoint(device, device_desc, TransferType::Interrupt) {
-        Some(endpoint) => read_endpoint(handle, endpoint, TransferType::Interrupt),
-        None => println!("No readable interrupt endpoint"),
-    }
-
     match find_readable_endpoint(device, device_desc, TransferType::Bulk) {
         Some(endpoint) => read_endpoint(handle, endpoint, TransferType::Bulk),
         None => println!("No readable bulk endpoint"),
@@ -173,14 +168,6 @@ fn read_endpoint<T: UsbContext>(
             let timeout = Duration::from_secs(1);

             match transfer_type {
-                TransferType::Interrupt => {
-                    match handle.read_interrupt(endpoint.address, &mut buf, timeout) {
-                        Ok(len) => {
-                            println!(" - read: {:?}", &buf[..len]);
-                        }
-                        Err(err) => println!("could not read from endpoint: {}", err),
-                    }
-                }
                 TransferType::Bulk => match handle.read_bulk(endpoint.address, &mut buf, timeout) {
                     Ok(len) => {
                         println!(" - read: {:?}", &buf[..len]);
diff --git a/rusb-async/examples/read_async.rs b/rusb-async/examples/read_async.rs
index 7a9262f..6856142 100644
--- a/rusb-async/examples/read_async.rs
+++ b/rusb-async/examples/read_async.rs
@@ -5,7 +5,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 use std::time::Duration;

-const BUF_SIZE: usize = 64;
+const BUF_SIZE: usize = 256;

 fn convert_argument(input: &str) -> u16 {
     if input.starts_with("0x") {

PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\read_device 0x04b4 0x00f1
Active configuration: 1
Languages: [Language { raw: 1033 }]
Manufacturer: Some("Cypress")
Product: Some("FX3")
Serial Number: None
Reading from endpoint: Endpoint { config: 1, iface: 0, setting: 0, address: 129 }
 - kernel driver? false
 - read: [170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170]
 - 
PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\read_async 0x04b4 0x00f1 0x81
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', rusb-async\examples\read_async.rs:29:60
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\libusb_info.exe
libusb v1.0.26.11724
has capability? true
has hotplug? false
has HID access? true
supports detach kernel driver? false

PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\list_devices
Bus 001 Device 014 ID 04b4:00f1  480 Mbps
Device Descriptor:
  bcdUSB              2.10
  bDeviceClass        0x00
  bDeviceSubClass     0x00
  bDeviceProtocol     0x00
  bMaxPacketSize0       64
  idVendor          0x04b4 Cypress Semiconductor Corp.
  idProduct         0x00f1 Unknown product
  bcdDevice           0.00
  iManufacturer          1 Cypress
  iProduct               2 FX3
  iSerialNumber          0
  bNumConfigurations     1
  Config Descriptor:
    bNumInterfaces         1
    bConfigurationValue    1
    iConfiguration         0
    bmAttributes:
      Self Powered     false
      Remote Wakeup    false
    bMaxPower            100mW
    no extra data
    Interface Descriptor:
      bInterfaceNumber       0
      bAlternateSetting      0
      bNumEndpoints          2
      bInterfaceClass     0xff
      bInterfaceSubClass  0x00
      bInterfaceProtocol  0x00
      iInterface             0
    []
      Endpoint Descriptor:
        bEndpointAddress    0x01 EP 1 Out
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0200
        bInterval              0
      Endpoint Descriptor:
        bEndpointAddress    0x81 EP 1 In
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0200
        bInterval              0

@StephanvanSchaik
Copy link
Author

I'd like to carry out test for this PR. Just wondering what is the test device used.

Usually I will use the bulk loopback (eg: using Cypress EZUSB FX3 or FX2LP) to test libusb related library.

I am using a Cypress EZUSB FX3 myself too. In fact, I am using the code of this PR to implement the firmware upload (control packets) as well as bulk packets afterwards (not loopback, though).

Example run.

mcuee@mcuees-Mac-mini libusb % ./examples/fxload -t fx3 -d 0x04b4:0x00f3 -i ../../../Cypress/cyusb_mac_1.0/fx3_images/cyfxbulklpautoenum.img
microcontroller type: fx3
../../../Cypress/cyusb_mac_1.0/fx3_images/cyfxbulklpautoenum.img: type Cypress IMG format
open firmware image ../../../Cypress/cyusb_mac_1.0/fx3_images/cyfxbulklpautoenum.img for RAM upload
normal FW binary executable image with checksum
FX3 bootloader version: 0x000000A9
writing image...
transfer execution to Program Entry at 0x40019028

mcuee@mcuees-Mac-mini rusb_pr155 % ./target/debug/examples/list_devices
...
Bus 002 Device 022 ID 04b4:00f0 5000 Mbps
Device Descriptor:
  bcdUSB              3.00
  bDeviceClass        0x00
  bDeviceSubClass     0x00
  bDeviceProtocol     0x00
  bMaxPacketSize0        9
  idVendor          0x04b4 Cypress Semiconductor Corp.
  idProduct         0x00f0 Unknown product
  bcdDevice           0.00
  iManufacturer          1 Cypress
  iProduct               2 Cypress
  iSerialNumber          0 
  bNumConfigurations     1
  Config Descriptor:
    bNumInterfaces         1
    bConfigurationValue    1
    iConfiguration         0 
    bmAttributes:
      Self Powered     false
      Remote Wakeup    false
    bMaxPower            100mW
    no extra data
    Interface Descriptor:
      bInterfaceNumber       0
      bAlternateSetting      0
      bNumEndpoints          2
      bInterfaceClass     0xff
      bInterfaceSubClass  0x00
      bInterfaceProtocol  0x00
      iInterface             0 
    []
      Endpoint Descriptor:
        bEndpointAddress    0x01 EP 1 Out
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0400
        bInterval              0
      Endpoint Descriptor:
        bEndpointAddress    0x81 EP 1 In
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0400
        bInterval              0

cuee@mcuees-Mac-mini rusb_pr155 % ./target/debug/examples/read_async
Usage: read_async <base-10/0xbase-16> <base-10/0xbase-16> <endpoint>
mcuee@mcuees-Mac-mini rusb_pr155 % ./target/debug/examples/read_async 0x004b4 0x00f0 0x81
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', rusb-async/examples/read_async.rs:29:60
stack backtrace:
   0:        0x1043f7c50 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h554558af9dcaf5d8
   1:        0x104407f94 - core::fmt::write::h503a82454aa26fb4
   2:        0x1043e1ba0 - std::io::Write::write_fmt::he97d6853eddb17d2
   3:        0x1043f7a60 - std::sys_common::backtrace::print::h669032be8d7b2a7c
   4:        0x1043e8d6c - std::panicking::default_hook::{{closure}}::h974276930a474e6a
   5:        0x1043e8a30 - std::panicking::default_hook::hb0d2c45a1e792803
   6:        0x1043e9458 - std::panicking::rust_panic_with_hook::hf3b9a3e44fe5f275
   7:        0x1043f7f84 - std::panicking::begin_panic_handler::{{closure}}::ha19c90e1160a0cf9
   8:        0x1043f7d90 - std::sys_common::backtrace::__rust_end_short_backtrace::h3f85ad00ac73ff55
   9:        0x1043e90b8 - _rust_begin_unwind
  10:        0x10440c664 - core::panicking::panic_fmt::hb0625f6d6e3c09e4
  11:        0x10440c90c - core::result::unwrap_failed::hdb332f7457252498
  12:        0x1043658bc - core::result::Result<T,E>::unwrap::he2ca8e6db741978e
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/core/src/result.rs:1112:23
  13:        0x104363af4 - read_async::main::{{closure}}::hbd5ca2ee92873989
                               at /Users/mcuee/build/libusb/rusb_test/rusb_pr155/rusb-async/examples/read_async.rs:29:24
  14:        0x1043634f0 - tokio::runtime::park::CachedParkThread::block_on::{{closure}}::he6e72b1efc5ce3e3
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/park.rs:283:63
  15:        0x104363344 - tokio::runtime::coop::with_budget::h393a79f5b2553b67
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/coop.rs:107:5
  16:        0x104363344 - tokio::runtime::coop::budget::hf660876f9262fb6a
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/coop.rs:73:5
  17:        0x104363344 - tokio::runtime::park::CachedParkThread::block_on::hd2664e438afc9c3a
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/park.rs:283:31
  18:        0x104364a04 - tokio::runtime::context::BlockingRegionGuard::block_on::hb87519a5494ba74a
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/context.rs:315:13
  19:        0x1043675b4 - tokio::runtime::scheduler::multi_thread::MultiThread::block_on::hf122b909beabb4d5
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/scheduler/multi_thread/mod.rs:66:9
  20:        0x104364e64 - tokio::runtime::runtime::Runtime::block_on::h69bf69242d5dca86
                               at /Users/mcuee/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.28.0/src/runtime/runtime.rs:304:45
  21:        0x1043673f8 - read_async::main::hcff6c8338313ca35
                               at /Users/mcuee/build/libusb/rusb_test/rusb_pr155/rusb-async/examples/read_async.rs:40:5
  22:        0x10436b080 - core::ops::function::FnOnce::call_once::hf10a0cf45903d5f9
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/core/src/ops/function.rs:250:5
  23:        0x1043629fc - std::sys_common::backtrace::__rust_begin_short_backtrace::hcd683e3d6aa43c2d
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/std/src/sys_common/backtrace.rs:121:18
  24:        0x10436cc5c - std::rt::lang_start::{{closure}}::hc8488cc4b3cf79f1
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/std/src/rt.rs:166:18
  25:        0x1043e7a4c - std::rt::lang_start_internal::h2a63b0d73f6d9885
  26:        0x10436cc28 - std::rt::lang_start::h8b99886c255f99e6
                               at /private/tmp/rust-20230331-6620-1hct4xt/rustc-1.68.2-src/library/std/src/rt.rs:165:17
  27:        0x104367480 - _main

mcuee@mcuees-Mac-mini libusbdotnet_v2 % ./stage/BenchmarkCon/bin/Release/net6.0/BenchmarkCon notestselect vid=0x04b4 pid=0x00f0 mode=Async 
LibUsbDotNet USB Benchmark v2.2.0.0
Copyright (c) 2010 Travis Robinson. <[email protected]>
website: http://sourceforge.net/projects/libusbdotnet
Benchmark device 04B4:00F0 opened..
Loop Test Information
	Vid / Pid       : 04B4h / 00F0h
	Interface #     : 00h
	Alt Interface # : 00h
	Priority        : Normal
	Buffer Size     : 4096
	Buffer Count    : 1
	Display Refresh : 1000 (ms)
	Transfer Timeout: 5000 (ms)
	Retry Count     : 0
	Verify Data     : Off

Bulk Read (Ep81h) max packet size: 1024
Bulk Write (Ep01h) max packet size: 1024

While the test is running:
Press 'Q' to quit
Press 'T' for test details
Press 'I' for status information
Press 'R' to reset averages

Press 'Q' to exit, any other key to begin..
Avg. Bytes/s: 38076316.65 Transfers: 9063 Bytes/s: 38076316.65
Avg. Bytes/s: 38701906.15 Transfers: 18710 Bytes/s: 39308645.71
Avg. Bytes/s: 38829365.48 Transfers: 28300 Bytes/s: 39080469.83
Avg. Bytes/s: 38741627.88 Transfers: 37743 Bytes/s: 38481043.29
Avg. Bytes/s: 38682908.43 Transfers: 47178 Bytes/s: 38449781.46
Avg. Bytes/s: 38633575.86 Transfers: 56598 Bytes/s: 38388385.68
Avg. Bytes/s: 38609148.60 Transfers: 66036 Bytes/s: 38463308.27
Avg. Bytes/s: 38584768.06 Transfers: 75462 Bytes/s: 38414824.31
Avg. Bytes/s: 38577455.72 Transfers: 84914 Bytes/s: 38519175.32
Avg. Bytes/s: 38689787.14 Transfers: 94656 Bytes/s: 39697321.99
Avg. Bytes/s: 38671704.66 Transfers: 104101 Bytes/s: 38491414.56
Avg. Bytes/s: 38694561.40 Transfers: 113658 Bytes/s: 38945293.19
Avg. Bytes/s: 38678448.79 Transfers: 123101 Bytes/s: 38485561.40
Avg. Bytes/s: 38667521.29 Transfers: 132554 Bytes/s: 38525780.38
qstopped Ep81h thread.
stopped Ep01h thread.
Loop Test Information
	Vid / Pid       : 04B4h / 00F0h
	Interface #     : 00h
	Alt Interface # : 00h
	Priority        : Normal
	Buffer Size     : 4096
	Buffer Count    : 1
	Display Refresh : 1000 (ms)
	Transfer Timeout: 5000 (ms)
	Retry Count     : 0
	Verify Data     : Off

Bulk Read (Ep81h) max packet size: 1024
	Total Bytes     : 581750784
	Total Transfers : 142029
	Avg. Bytes/sec  : 38663163.78
	Elapsed Time    : 15.05 seconds

Bulk Write (Ep01h) max packet size: 1024
	Total Bytes     : 581750784
	Total Transfers : 142029
	Avg. Bytes/sec  : 38663274.27
	Elapsed Time    : 15.05 seconds

Press any key to exit..

Try running with ./target/debug/examples/read_async 0x004b4 0x00f0 129 instead. The panic is telling you where the program is failing:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', rusb-async\examples\read_async.rs:29:60
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Because it can't handle hexadecimal input, it ends up failing on this line: let endpoint: u8 = FromStr::from_str(args[3].as_ref()).unwrap();. I don't really remember why I ended up using FromStr::from_str() here. Probably because convert_argument() is written to parse u16, and didn't give much thought to it when parsing the endpoint.

I tried to modified the examples to match Cypress BulkSrcSink example. Now read_device works but not read_async. Tested under Windows 11.

PS C:\work\libusb\rust\rusb_pr155> git diff
diff --git a/examples/read_device.rs b/examples/read_device.rs
index bf1d943..dde230c 100644
--- a/examples/read_device.rs
+++ b/examples/read_device.rs
@@ -105,11 +105,6 @@ fn read_device<T: UsbContext>(
         );
     }

-    match find_readable_endpoint(device, device_desc, TransferType::Interrupt) {
-        Some(endpoint) => read_endpoint(handle, endpoint, TransferType::Interrupt),
-        None => println!("No readable interrupt endpoint"),
-    }
-
     match find_readable_endpoint(device, device_desc, TransferType::Bulk) {
         Some(endpoint) => read_endpoint(handle, endpoint, TransferType::Bulk),
         None => println!("No readable bulk endpoint"),
@@ -173,14 +168,6 @@ fn read_endpoint<T: UsbContext>(
             let timeout = Duration::from_secs(1);

             match transfer_type {
-                TransferType::Interrupt => {
-                    match handle.read_interrupt(endpoint.address, &mut buf, timeout) {
-                        Ok(len) => {
-                            println!(" - read: {:?}", &buf[..len]);
-                        }
-                        Err(err) => println!("could not read from endpoint: {}", err),
-                    }
-                }
                 TransferType::Bulk => match handle.read_bulk(endpoint.address, &mut buf, timeout) {
                     Ok(len) => {
                         println!(" - read: {:?}", &buf[..len]);
diff --git a/rusb-async/examples/read_async.rs b/rusb-async/examples/read_async.rs
index 7a9262f..6856142 100644
--- a/rusb-async/examples/read_async.rs
+++ b/rusb-async/examples/read_async.rs
@@ -5,7 +5,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 use std::time::Duration;

-const BUF_SIZE: usize = 64;
+const BUF_SIZE: usize = 256;

 fn convert_argument(input: &str) -> u16 {
     if input.starts_with("0x") {

PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\read_device 0x04b4 0x00f1
Active configuration: 1
Languages: [Language { raw: 1033 }]
Manufacturer: Some("Cypress")
Product: Some("FX3")
Serial Number: None
Reading from endpoint: Endpoint { config: 1, iface: 0, setting: 0, address: 129 }
 - kernel driver? false
 - read: [170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170]
 - 
PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\read_async 0x04b4 0x00f1 0x81
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', rusb-async\examples\read_async.rs:29:60
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\libusb_info.exe
libusb v1.0.26.11724
has capability? true
has hotplug? false
has HID access? true
supports detach kernel driver? false

PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\list_devices
Bus 001 Device 014 ID 04b4:00f1  480 Mbps
Device Descriptor:
  bcdUSB              2.10
  bDeviceClass        0x00
  bDeviceSubClass     0x00
  bDeviceProtocol     0x00
  bMaxPacketSize0       64
  idVendor          0x04b4 Cypress Semiconductor Corp.
  idProduct         0x00f1 Unknown product
  bcdDevice           0.00
  iManufacturer          1 Cypress
  iProduct               2 FX3
  iSerialNumber          0
  bNumConfigurations     1
  Config Descriptor:
    bNumInterfaces         1
    bConfigurationValue    1
    iConfiguration         0
    bmAttributes:
      Self Powered     false
      Remote Wakeup    false
    bMaxPower            100mW
    no extra data
    Interface Descriptor:
      bInterfaceNumber       0
      bAlternateSetting      0
      bNumEndpoints          2
      bInterfaceClass     0xff
      bInterfaceSubClass  0x00
      bInterfaceProtocol  0x00
      iInterface             0
    []
      Endpoint Descriptor:
        bEndpointAddress    0x01 EP 1 Out
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0200
        bInterval              0
      Endpoint Descriptor:
        bEndpointAddress    0x81 EP 1 In
        bmAttributes:
          Transfer Type          Bulk
          Synch Type             NoSync
          Usage Type             Data
        wMaxPacketSize    0x0200
        bInterval              0

This is failing on the same problem.

@mcuee
Copy link

mcuee commented May 1, 2023

I am using a Cypress EZUSB FX3 myself too. In fact, I am using the code of this PR to implement the firmware upload (control packets) as well as bulk packets afterwards (not loopback, though).

That is great to know. It is one of the best tool to test libusb related projects. I use it to test libusb, libusb-win32, libusbK, libusbdotnet and pyusb.
https://github.com/libusb/libusb/wiki/FAQ#user-content-Do_you_have_a_list_of_known_good_devices_to_test_libusb

Other than the firmware upload (control transfer), it will be great to provide example for bulk_loop and bulk_src_sink. Basically Cypress have three main applications (Control Center, BulkLoop and Streamer) which kind of for the firmware upload, Bulk_loop FW and Bulk_Src_Sink and Iso_Src_Sink FW examples.

Under Linux and macOS, I actually use libusb fxload example for the FW downloading. If you have rusb based example, that will be great as well.

@StephanvanSchaik
Copy link
Author

I am using a Cypress EZUSB FX3 myself too. In fact, I am using the code of this PR to implement the firmware upload (control packets) as well as bulk packets afterwards (not loopback, though).

That is great to know. It is one of the best tool to test libusb related projects. https://github.com/libusb/libusb/wiki/FAQ#user-content-Do_you_have_a_list_of_known_good_devices_to_test_libusb

Other than the firmware upload (control transfer), it will be great to provide example for bulk_loop and bulk_src_sink. Basically Cypress have three main applications (Control Center, BulkLoop and Streamer) which kind of for the firmware upload, Bulk_loop FW and Bulk_Src_Sink and Iso_Src_Sink FW examples.

I think this would be another PR, since the synchronous version of rusb is also lacking these. The example I am using in this PR is simply based off the examples in the synchronous version of rusb. However, having these examples would be good for getting started and testing.

Under Linux and macOS, I actually use libusb fxload example for the FW downloading. If you have rusb based example, that will be great as well.

Yes, I am just using an existing device that integrates the Cypress EZUSB FX3, and the firmware blobs don't use a format that is compatible with fxload. I have a library that is based on this PR to handle the firmware download, upload and execution. I was hoping to get this merged first, but if not, I can probably publish the library anyway.

@mcuee
Copy link

mcuee commented May 1, 2023

Try running with ./target/debug/examples/read_async 0x004b4 0x00f0 129 instead.

Thanks It goes a bit further.

PS C:\work\libusb\rust\rusb_pr155> .\target\debug\examples\read_async 0x04b4 0x00f1 129
thread 'main' panicked at 'Failed to submit transfer: ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], Other)', rusb-async\examples\read_async.rs:44:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

@mcuee
Copy link

mcuee commented May 1, 2023

I am using a Cypress EZUSB FX3 myself too. In fact, I am using the code of this PR to implement the firmware upload (control packets) as well as bulk packets afterwards (not loopback, though).

That is great to know. It is one of the best tool to test libusb related projects. https://github.com/libusb/libusb/wiki/FAQ#user-content-Do_you_have_a_list_of_known_good_devices_to_test_libusb
Other than the firmware upload (control transfer), it will be great to provide example for bulk_loop and bulk_src_sink. Basically Cypress have three main applications (Control Center, BulkLoop and Streamer) which kind of for the firmware upload, Bulk_loop FW and Bulk_Src_Sink and Iso_Src_Sink FW examples.

I think this would be another PR, since the synchronous version of rusb is also lacking these. The example I am using in this PR is simply based off the examples in the synchronous version of rusb. However, having these examples would be good for getting started and testing.

I see. Maybe simple examples like read_async is also good. Probably two more simple examples will help as well, like write_async and read_write_async.

There are two examples in the rusb-async branch: read_async and read_write_async.
https://github.com/a1ien/rusb/tree/rusb-async/rusb-async/examples

Under Linux and macOS, I actually use libusb fxload example for the FW downloading. If you have rusb based example, that will be great as well.

Yes, I am just using an existing device that integrates the Cypress EZUSB FX3, and the firmware blobs don't use a format that is compatible with fxload. I have a library that is based on this PR to handle the firmware download, upload and execution. I was hoping to get this merged first, but if not, I can probably publish the library anyway.

I understand. It is indeed nice to have a rust based tool for the EZUSB FX3.

@ganeshrvel
Copy link

ganeshrvel commented Aug 22, 2024

I can vouch for this PR by @StephanvanSchaik.

I've implemented the async APIs from this PR in one of my projects, and they work flawlessly. While the initial setup and learning curve required some time, with proper documentation and examples, this PR could be incredibly valuable.

Kudos to @StephanvanSchaik! 🎉

Machine: Macbook pro M1, Sonoma 14.5

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

Successfully merging this pull request may close these issues.

4 participants