From 212660f90574aaa212805a2ba6974fe1f284e8c5 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Fri, 14 May 2021 02:19:22 -0400 Subject: [PATCH 1/7] Implemented self-referential AsyncTransfer struct --- src/device_handle/async_api.rs | 93 +++++++++++++++++++ .../mod.rs} | 2 + 2 files changed, 95 insertions(+) create mode 100644 src/device_handle/async_api.rs rename src/{device_handle.rs => device_handle/mod.rs} (99%) diff --git a/src/device_handle/async_api.rs b/src/device_handle/async_api.rs new file mode 100644 index 0000000..b92d040 --- /dev/null +++ b/src/device_handle/async_api.rs @@ -0,0 +1,93 @@ +use crate::{DeviceHandle, UsbContext}; + +use libc::c_void; +use libusb1_sys as ffi; + +use std::convert::{TryFrom, TryInto}; +use std::marker::{PhantomData, PhantomPinned}; +use std::pin::Pin; +use std::ptr::NonNull; + +struct AsyncTransfer<'d, 'b, C: UsbContext, F: FnMut()> { + ptr: *mut ffi::libusb_transfer, + buf: &'b mut [u8], + closure: F, + _pin: PhantomPinned, // `ptr` holds a ptr to `buf` and `callback`, so we must ensure that we don't move + _device: PhantomData<&'d DeviceHandle>, +} +impl<'d, 'b, C: UsbContext, F: FnMut()> AsyncTransfer<'d, 'b, C, F> { + pub fn new_bulk( + device: &'d DeviceHandle, + endpoint: u8, + buffer: &'b mut [u8], + callback: F, + timeout: std::time::Duration, + ) -> Pin> { + // non-isochronous endpoints (e.g. control, bulk, interrupt) specify a value of 0 + let ptr = unsafe { ffi::libusb_alloc_transfer(0) }; + let ptr = NonNull::new(ptr).expect("Could not allocate transfer!"); + let timeout = libc::c_uint::try_from(timeout.as_millis()) + .expect("Duration was too long to fit into a c_uint"); + + // Safety: Pinning `result` ensures it doesn't move, but we know that we will + // want to access its fields mutably, we just don't want its memory location + // changing (or its fields moving!). So routinely we will unsafely interact with + // its fields mutably through a shared reference, but this is still sound. + let result = Box::pin(Self { + ptr: std::ptr::null_mut(), + buf: buffer, + closure: callback, + _pin: PhantomPinned, + _device: PhantomData, + }); + + let closure_as_ptr: *mut F = { + let mut_ref: *const F = &result.closure; + mut_ref as *mut F + }; + unsafe { + ffi::libusb_fill_bulk_transfer( + ptr.as_ptr(), + device.as_raw(), + endpoint, + result.buf.as_ptr() as *mut u8, + result.buf.len().try_into().unwrap(), + Self::transfer_cb, + closure_as_ptr as *mut c_void, + timeout, + ) + }; + result + } + + // We need to invoke our closure using a c-style function, so we store the closure + // inside the custom user data field of the transfer struct, and then call the + // user provided closure from there. + extern "system" fn transfer_cb(transfer: *mut ffi::libusb_transfer) { + // Safety: libusb should never make this null, so this is fine + let transfer = unsafe { &mut *transfer }; + + // sanity + debug_assert_eq!( + transfer.transfer_type, + ffi::constants::LIBUSB_TRANSFER_TYPE_BULK + ); + + // sanity + debug_assert_eq!( + std::mem::size_of::<*mut F>(), + std::mem::size_of::<*mut c_void>(), + ); + // Safety: The pointer shouldn't be a fat pointer, and should be valid, so + // this should be fine + let closure = unsafe { + let closure: *mut F = std::mem::transmute(transfer.user_data); + &mut *closure + }; + + // TODO: check some stuff + + // call user callback + (*closure)(); + } +} diff --git a/src/device_handle.rs b/src/device_handle/mod.rs similarity index 99% rename from src/device_handle.rs rename to src/device_handle/mod.rs index 767145d..e4102ac 100644 --- a/src/device_handle.rs +++ b/src/device_handle/mod.rs @@ -1,3 +1,5 @@ +mod async_api; + use std::{mem, ptr::NonNull, time::Duration, u8}; use libc::{c_int, c_uchar, c_uint}; From 331540d0a1b6a184640d6aea6fa00e1d80679486 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Fri, 14 May 2021 03:07:18 -0400 Subject: [PATCH 2/7] Fleshed out the drop code --- Cargo.toml | 8 +++-- src/device_handle/async_api.rs | 56 ++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f2396d..8ff14cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "rusb" version = "0.8.0" -authors = ["David Cuddeback ", "Ilya Averyanov "] +authors = [ + "David Cuddeback ", + "Ilya Averyanov ", +] description = "Rust library for accessing USB devices." license = "MIT" homepage = "https://github.com/a1ien/rusb" @@ -15,7 +18,7 @@ build = "build.rs" travis-ci = { repository = "a1ien/rusb" } [features] -vendored = [ "libusb1-sys/vendored" ] +vendored = ["libusb1-sys/vendored"] [workspace] members = ["libusb1-sys"] @@ -23,6 +26,7 @@ members = ["libusb1-sys"] [dependencies] libusb1-sys = { path = "libusb1-sys", version = "0.5.0" } libc = "0.2" +log = "0.4" [dev-dependencies] regex = "1" diff --git a/src/device_handle/async_api.rs b/src/device_handle/async_api.rs index b92d040..f777576 100644 --- a/src/device_handle/async_api.rs +++ b/src/device_handle/async_api.rs @@ -8,14 +8,18 @@ use std::marker::{PhantomData, PhantomPinned}; use std::pin::Pin; use std::ptr::NonNull; -struct AsyncTransfer<'d, 'b, C: UsbContext, F: FnMut()> { +// TODO: Make the Err variant useful +type CbResult = Result<(), i32>; + +struct AsyncTransfer<'d, 'b, C: UsbContext, F> { ptr: *mut ffi::libusb_transfer, buf: &'b mut [u8], closure: F, _pin: PhantomPinned, // `ptr` holds a ptr to `buf` and `callback`, so we must ensure that we don't move _device: PhantomData<&'d DeviceHandle>, } -impl<'d, 'b, C: UsbContext, F: FnMut()> AsyncTransfer<'d, 'b, C, F> { +impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { + #[allow(unused)] pub fn new_bulk( device: &'d DeviceHandle, endpoint: u8, @@ -53,7 +57,7 @@ impl<'d, 'b, C: UsbContext, F: FnMut()> AsyncTransfer<'d, 'b, C, F> { result.buf.as_ptr() as *mut u8, result.buf.len().try_into().unwrap(), Self::transfer_cb, - closure_as_ptr as *mut c_void, + closure_as_ptr.cast(), timeout, ) }; @@ -85,9 +89,49 @@ impl<'d, 'b, C: UsbContext, F: FnMut()> AsyncTransfer<'d, 'b, C, F> { &mut *closure }; - // TODO: check some stuff + use ffi::constants::*; + match transfer.status { + LIBUSB_TRANSFER_CANCELLED => { + // Transfer was cancelled, free the transfer + unsafe { ffi::libusb_free_transfer(transfer) } + } + LIBUSB_TRANSFER_COMPLETED => { + // call user callback + (*closure)(Ok(())); + } + LIBUSB_TRANSFER_ERROR => (*closure)(Err(LIBUSB_TRANSFER_ERROR)), + LIBUSB_TRANSFER_TIMED_OUT => (*closure)(Err(LIBUSB_TRANSFER_TIMED_OUT)), + LIBUSB_TRANSFER_STALL => (*closure)(Err(LIBUSB_TRANSFER_STALL)), + LIBUSB_TRANSFER_NO_DEVICE => (*closure)(Err(LIBUSB_TRANSFER_NO_DEVICE)), + LIBUSB_TRANSFER_OVERFLOW => unreachable!(), + _ => panic!("Found an unexpected error value for transfer status"), + } + } +} +impl AsyncTransfer<'_, '_, C, F> { + /// Helper function for the Drop impl. + fn drop_helper(this: Pin<&mut Self>) { + // Actual drop code goes here. + let errno = unsafe { ffi::libusb_cancel_transfer(this.ptr) }; + match errno { + 0 | ffi::constants::LIBUSB_ERROR_NOT_FOUND => (), + errno => { + log::warn!( + "Could not cancel USB transfer. Memory may be leaked. Errno: {}", + errno + ) + } + } + } +} - // call user callback - (*closure)(); +impl Drop for AsyncTransfer<'_, '_, C, F> { + fn drop(&mut self) { + // We call `drop_helper` because that function represents the actual type + // semantics that `self` has when being dropped. + // (see https://doc.rust-lang.org/std/pin/index.html#drop-implementation) + // Safety: `new_unchecked` is okay because we know this value is never used + // again after being dropped. + Self::drop_helper(unsafe { Pin::new_unchecked(self) }); } } From 57d2aa18be2be7eb2faeab05602b71302b404335 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Fri, 14 May 2021 16:34:53 -0400 Subject: [PATCH 3/7] Ensured soundness of code and fixed callback api --- Cargo.toml | 1 + src/device_handle/async_api.rs | 108 ++++++++++++++++++++++++--------- 2 files changed, 81 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ff14cd..deb08a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = ["libusb1-sys"] libusb1-sys = { path = "libusb1-sys", version = "0.5.0" } libc = "0.2" log = "0.4" +thiserror = "1" [dev-dependencies] regex = "1" diff --git a/src/device_handle/async_api.rs b/src/device_handle/async_api.rs index f777576..1d536cb 100644 --- a/src/device_handle/async_api.rs +++ b/src/device_handle/async_api.rs @@ -2,23 +2,38 @@ use crate::{DeviceHandle, UsbContext}; use libc::c_void; use libusb1_sys as ffi; +use thiserror::Error; use std::convert::{TryFrom, TryInto}; use std::marker::{PhantomData, PhantomPinned}; use std::pin::Pin; use std::ptr::NonNull; -// TODO: Make the Err variant useful -type CbResult = Result<(), i32>; +type CbResult<'a> = Result<&'a [u8], ReadError>; + +#[derive(Error, Debug)] +pub enum ReadError { + #[error("Transfer timed out")] + Timeout, + #[error("Transfer is stalled")] + Stall, + #[error("Device was disconnected")] + Disconnected, + #[error("Other Error: {0}")] + Other(&'static str), + #[error("{0}ERRNO: {1}")] + Errno(&'static str, i32), +} struct AsyncTransfer<'d, 'b, C: UsbContext, F> { - ptr: *mut ffi::libusb_transfer, - buf: &'b mut [u8], + ptr: NonNull, closure: F, - _pin: PhantomPinned, // `ptr` holds a ptr to `buf` and `callback`, so we must ensure that we don't move + _pin: PhantomPinned, // `ptr` holds a ptr to `closure`, so mark !Unpin _device: PhantomData<&'d DeviceHandle>, + _buf: PhantomData<&'b mut [u8]>, } -impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { +// TODO: should CbResult lifetime be different from 'b? +impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d, 'b, C, F> { #[allow(unused)] pub fn new_bulk( device: &'d DeviceHandle, @@ -28,6 +43,7 @@ impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { timeout: std::time::Duration, ) -> Pin> { // non-isochronous endpoints (e.g. control, bulk, interrupt) specify a value of 0 + // This is step 1 of async API let ptr = unsafe { ffi::libusb_alloc_transfer(0) }; let ptr = NonNull::new(ptr).expect("Could not allocate transfer!"); let timeout = libc::c_uint::try_from(timeout.as_millis()) @@ -38,24 +54,32 @@ impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { // changing (or its fields moving!). So routinely we will unsafely interact with // its fields mutably through a shared reference, but this is still sound. let result = Box::pin(Self { - ptr: std::ptr::null_mut(), - buf: buffer, + ptr, closure: callback, _pin: PhantomPinned, _device: PhantomData, + _buf: PhantomData, }); - let closure_as_ptr: *mut F = { - let mut_ref: *const F = &result.closure; - mut_ref as *mut F - }; unsafe { + // This casting, and passing it to the transfer struct, relies on + // the pointer being a regular pointer and not a fat pointer. + // Also, closure will be invoked from whatever thread polls, which + // may be different from the current thread. So it must be `Send`. + // Also, although many threads at once may poll concurrently, only + // one will actually ever execute the transfer at a time, so we do + // not need to worry about simultaneous writes to the buffer + let closure_as_ptr: *mut F = { + let ptr: *const F = &result.closure; + ptr as *mut F + }; + // Step 2 of async api ffi::libusb_fill_bulk_transfer( ptr.as_ptr(), device.as_raw(), endpoint, - result.buf.as_ptr() as *mut u8, - result.buf.len().try_into().unwrap(), + buffer.as_ptr() as *mut u8, + buffer.len().try_into().unwrap(), Self::transfer_cb, closure_as_ptr.cast(), timeout, @@ -64,9 +88,26 @@ impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { result } + /// Returns whether the transfer was submitted successfully + fn submit_helper(transfer: *mut ffi::libusb_transfer) -> Result<(), ReadError> { + let errno = unsafe { ffi::libusb_submit_transfer(transfer) }; + use ffi::constants::*; + match errno { + 0 => Ok(()), + LIBUSB_ERROR_BUSY => { + unreachable!("Should not be possible for us to double-submit a transfer") + } + LIBUSB_ERROR_NOT_SUPPORTED => Err(ReadError::Other("Unsupported transfer!")), + LIBUSB_ERROR_INVALID_PARAM => Err(ReadError::Other("Transfer size too large!")), + LIBUSB_ERROR_NO_DEVICE => Err(ReadError::Disconnected), + _ => Err(ReadError::Errno("Unable to submit transfer. ", errno)), + } + } + // We need to invoke our closure using a c-style function, so we store the closure // inside the custom user data field of the transfer struct, and then call the // user provided closure from there. + // Step 4 of async API extern "system" fn transfer_cb(transfer: *mut ffi::libusb_transfer) { // Safety: libusb should never make this null, so this is fine let transfer = unsafe { &mut *transfer }; @@ -83,7 +124,7 @@ impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { std::mem::size_of::<*mut c_void>(), ); // Safety: The pointer shouldn't be a fat pointer, and should be valid, so - // this should be fine + // this should be sound let closure = unsafe { let closure: *mut F = std::mem::transmute(transfer.user_data); &mut *closure @@ -92,17 +133,27 @@ impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { use ffi::constants::*; match transfer.status { LIBUSB_TRANSFER_CANCELLED => { - // Transfer was cancelled, free the transfer + // Step 5 of async API: Transfer was cancelled, free the transfer unsafe { ffi::libusb_free_transfer(transfer) } } LIBUSB_TRANSFER_COMPLETED => { - // call user callback - (*closure)(Ok(())); + debug_assert!(transfer.length >= transfer.actual_length); // sanity + let data = unsafe { + std::slice::from_raw_parts(transfer.buffer, transfer.actual_length as usize) + }; + (*closure)(Ok(data)); + } + LIBUSB_TRANSFER_ERROR => (*closure)(Err(ReadError::Other( + "Error occurred during transfer execution", + ))), + LIBUSB_TRANSFER_TIMED_OUT => { + (*closure)(Err(ReadError::Timeout)); + if let Err(err) = Self::submit_helper(transfer) { + (*closure)(Err(err)) + } } - LIBUSB_TRANSFER_ERROR => (*closure)(Err(LIBUSB_TRANSFER_ERROR)), - LIBUSB_TRANSFER_TIMED_OUT => (*closure)(Err(LIBUSB_TRANSFER_TIMED_OUT)), - LIBUSB_TRANSFER_STALL => (*closure)(Err(LIBUSB_TRANSFER_STALL)), - LIBUSB_TRANSFER_NO_DEVICE => (*closure)(Err(LIBUSB_TRANSFER_NO_DEVICE)), + LIBUSB_TRANSFER_STALL => (*closure)(Err(ReadError::Stall)), + LIBUSB_TRANSFER_NO_DEVICE => (*closure)(Err(ReadError::Disconnected)), LIBUSB_TRANSFER_OVERFLOW => unreachable!(), _ => panic!("Found an unexpected error value for transfer status"), } @@ -110,15 +161,16 @@ impl<'d, 'b, C: UsbContext, F: FnMut(CbResult)> AsyncTransfer<'d, 'b, C, F> { } impl AsyncTransfer<'_, '_, C, F> { /// Helper function for the Drop impl. - fn drop_helper(this: Pin<&mut Self>) { + fn drop_helper(self: Pin<&mut Self>) { // Actual drop code goes here. - let errno = unsafe { ffi::libusb_cancel_transfer(this.ptr) }; + let transfer_ptr = self.ptr.as_ptr(); + let errno = unsafe { ffi::libusb_cancel_transfer(transfer_ptr) }; match errno { 0 | ffi::constants::LIBUSB_ERROR_NOT_FOUND => (), errno => { log::warn!( - "Could not cancel USB transfer. Memory may be leaked. Errno: {}", - errno + "Could not cancel USB transfer. Memory may be leaked. Errno: {}, Error message: {}", + errno, unsafe{std::ffi::CStr::from_ptr( ffi::libusb_strerror(errno))}.to_str().unwrap() ) } } @@ -127,8 +179,8 @@ impl AsyncTransfer<'_, '_, C, F> { impl Drop for AsyncTransfer<'_, '_, C, F> { fn drop(&mut self) { - // We call `drop_helper` because that function represents the actual type - // semantics that `self` has when being dropped. + // We call `drop_helper` because that function represents the actualsemantics + // that `self` has when being dropped. // (see https://doc.rust-lang.org/std/pin/index.html#drop-implementation) // Safety: `new_unchecked` is okay because we know this value is never used // again after being dropped. From 37afa685d63d77f8ab01f8130cd2499a1225922f Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Fri, 14 May 2021 17:43:36 -0400 Subject: [PATCH 4/7] Implemented submit --- src/device_handle/async_api.rs | 38 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/device_handle/async_api.rs b/src/device_handle/async_api.rs index 1d536cb..54e99a1 100644 --- a/src/device_handle/async_api.rs +++ b/src/device_handle/async_api.rs @@ -9,10 +9,10 @@ use std::marker::{PhantomData, PhantomPinned}; use std::pin::Pin; use std::ptr::NonNull; -type CbResult<'a> = Result<&'a [u8], ReadError>; +type CbResult<'a> = Result<&'a [u8], TransferError>; #[derive(Error, Debug)] -pub enum ReadError { +pub enum TransferError { #[error("Transfer timed out")] Timeout, #[error("Transfer is stalled")] @@ -25,7 +25,7 @@ pub enum ReadError { Errno(&'static str, i32), } -struct AsyncTransfer<'d, 'b, C: UsbContext, F> { +pub struct AsyncTransfer<'d, 'b, C: UsbContext, F> { ptr: NonNull, closure: F, _pin: PhantomPinned, // `ptr` holds a ptr to `closure`, so mark !Unpin @@ -88,19 +88,24 @@ impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d result } - /// Returns whether the transfer was submitted successfully - fn submit_helper(transfer: *mut ffi::libusb_transfer) -> Result<(), ReadError> { - let errno = unsafe { ffi::libusb_submit_transfer(transfer) }; + /// Submits a transfer to libusb's event engine. + /// # Panics + /// A transfer should not be double-submitted! Only re-submit after a submission has + /// returned Err, or the callback has gotten an Err. + // Step 3 of async API + #[allow(unused)] + pub fn submit(&mut self) -> Result<(), TransferError> { + let errno = unsafe { ffi::libusb_submit_transfer(self.ptr.as_ptr()) }; use ffi::constants::*; match errno { 0 => Ok(()), LIBUSB_ERROR_BUSY => { - unreachable!("Should not be possible for us to double-submit a transfer") + panic!("Do not double-submit a transfer!") } - LIBUSB_ERROR_NOT_SUPPORTED => Err(ReadError::Other("Unsupported transfer!")), - LIBUSB_ERROR_INVALID_PARAM => Err(ReadError::Other("Transfer size too large!")), - LIBUSB_ERROR_NO_DEVICE => Err(ReadError::Disconnected), - _ => Err(ReadError::Errno("Unable to submit transfer. ", errno)), + LIBUSB_ERROR_NOT_SUPPORTED => Err(TransferError::Other("Unsupported transfer!")), + LIBUSB_ERROR_INVALID_PARAM => Err(TransferError::Other("Transfer size too large!")), + LIBUSB_ERROR_NO_DEVICE => Err(TransferError::Disconnected), + _ => Err(TransferError::Errno("Unable to submit transfer. ", errno)), } } @@ -143,17 +148,14 @@ impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d }; (*closure)(Ok(data)); } - LIBUSB_TRANSFER_ERROR => (*closure)(Err(ReadError::Other( + LIBUSB_TRANSFER_ERROR => (*closure)(Err(TransferError::Other( "Error occurred during transfer execution", ))), LIBUSB_TRANSFER_TIMED_OUT => { - (*closure)(Err(ReadError::Timeout)); - if let Err(err) = Self::submit_helper(transfer) { - (*closure)(Err(err)) - } + (*closure)(Err(TransferError::Timeout)); } - LIBUSB_TRANSFER_STALL => (*closure)(Err(ReadError::Stall)), - LIBUSB_TRANSFER_NO_DEVICE => (*closure)(Err(ReadError::Disconnected)), + LIBUSB_TRANSFER_STALL => (*closure)(Err(TransferError::Stall)), + LIBUSB_TRANSFER_NO_DEVICE => (*closure)(Err(TransferError::Disconnected)), LIBUSB_TRANSFER_OVERFLOW => unreachable!(), _ => panic!("Found an unexpected error value for transfer status"), } From 5fbe93c6b86586a07d3f8730865afe62fedbf58f Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Sat, 15 May 2021 18:31:09 -0400 Subject: [PATCH 5/7] Fixed &mut of submit() and added poll_transfers() --- examples/read_async.rs | 42 ++++++++++++++++++++++++++++++++++ src/device_handle/async_api.rs | 21 +++++++++++++++-- src/device_handle/mod.rs | 2 +- src/lib.rs | 8 ++++++- 4 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 examples/read_async.rs diff --git a/examples/read_async.rs b/examples/read_async.rs new file mode 100644 index 0000000..82d92ed --- /dev/null +++ b/examples/read_async.rs @@ -0,0 +1,42 @@ +use rusb::{AsyncTransfer, CbResult, Context, UsbContext}; + +use std::str::FromStr; +use std::time::Duration; + +fn main() { + let args: Vec = std::env::args().collect(); + + if args.len() < 4 { + eprintln!("Usage: read_async "); + return; + } + + let vid: u16 = FromStr::from_str(args[1].as_ref()).unwrap(); + let pid: u16 = FromStr::from_str(args[2].as_ref()).unwrap(); + let endpoint: u8 = FromStr::from_str(args[3].as_ref()).unwrap(); + + let ctx = Context::new().expect("Could not initialize libusb"); + let device = ctx + .open_device_with_vid_pid(vid, pid) + .expect("Could not find device"); + + const NUM_TRANSFERS: usize = 32; + const BUF_SIZE: usize = 1024; + let mut main_buffer = Box::new([0u8; BUF_SIZE * NUM_TRANSFERS]); + + let mut transfers = Vec::new(); + for buf in main_buffer.chunks_exact_mut(BUF_SIZE) { + let mut transfer = + AsyncTransfer::new_bulk(&device, endpoint, buf, callback, Duration::from_secs(10)); + transfer.submit().expect("Could not submit transfer"); + transfers.push(transfer); + } + + loop { + rusb::poll_transfers(&ctx, Duration::from_secs(10)); + } +} + +fn callback(result: CbResult) { + println!("{:?}", result) +} diff --git a/src/device_handle/async_api.rs b/src/device_handle/async_api.rs index 54e99a1..efd52a3 100644 --- a/src/device_handle/async_api.rs +++ b/src/device_handle/async_api.rs @@ -8,8 +8,9 @@ use std::convert::{TryFrom, TryInto}; use std::marker::{PhantomData, PhantomPinned}; use std::pin::Pin; use std::ptr::NonNull; +use std::time::Duration; -type CbResult<'a> = Result<&'a [u8], TransferError>; +pub type CbResult<'a> = Result<&'a [u8], TransferError>; #[derive(Error, Debug)] pub enum TransferError { @@ -94,7 +95,7 @@ impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d /// returned Err, or the callback has gotten an Err. // Step 3 of async API #[allow(unused)] - pub fn submit(&mut self) -> Result<(), TransferError> { + pub fn submit(self: &mut Pin>) -> Result<(), TransferError> { let errno = unsafe { ffi::libusb_submit_transfer(self.ptr.as_ptr()) }; use ffi::constants::*; match errno { @@ -189,3 +190,19 @@ impl Drop for AsyncTransfer<'_, '_, C, F> { Self::drop_helper(unsafe { Pin::new_unchecked(self) }); } } + +/// Polls for transfers and executes their callbacks. Will block until the +/// given timeout, or return immediately if timeout is zero. +pub fn poll_transfers(ctx: &impl UsbContext, timeout: Duration) { + let timeval = libc::timeval { + tv_sec: timeout.as_secs() as i64, + tv_usec: timeout.subsec_millis() as i64, + }; + unsafe { + ffi::libusb_handle_events_timeout_completed( + ctx.as_raw(), + std::ptr::addr_of!(timeval), + std::ptr::null_mut(), + ) + }; +} diff --git a/src/device_handle/mod.rs b/src/device_handle/mod.rs index e4102ac..b80873b 100644 --- a/src/device_handle/mod.rs +++ b/src/device_handle/mod.rs @@ -1,4 +1,4 @@ -mod async_api; +pub mod async_api; use std::{mem, ptr::NonNull, time::Duration, u8}; diff --git a/src/lib.rs b/src/lib.rs index 5dfc530..b0c54ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub use crate::{ context::{Context, GlobalContext, Hotplug, LogLevel, Registration, UsbContext}, device::Device, device_descriptor::DeviceDescriptor, + device_handle::async_api::{poll_transfers, AsyncTransfer, CbResult}, device_handle::DeviceHandle, device_list::{DeviceList, Devices}, endpoint_descriptor::EndpointDescriptor, @@ -106,6 +107,11 @@ pub fn open_device_with_vid_pid( if handle.is_null() { None } else { - Some(unsafe { DeviceHandle::from_libusb(GlobalContext::default(), std::ptr::NonNull::new_unchecked(handle)) }) + Some(unsafe { + DeviceHandle::from_libusb( + GlobalContext::default(), + std::ptr::NonNull::new_unchecked(handle), + ) + }) } } From 0181fa7b36cdfcb22fa7f753029f5afd91dbef42 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Sat, 15 May 2021 19:27:13 -0400 Subject: [PATCH 6/7] Made timeval cross platform --- src/device_handle/async_api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/device_handle/async_api.rs b/src/device_handle/async_api.rs index efd52a3..1e31aa0 100644 --- a/src/device_handle/async_api.rs +++ b/src/device_handle/async_api.rs @@ -195,8 +195,8 @@ impl Drop for AsyncTransfer<'_, '_, C, F> { /// given timeout, or return immediately if timeout is zero. pub fn poll_transfers(ctx: &impl UsbContext, timeout: Duration) { let timeval = libc::timeval { - tv_sec: timeout.as_secs() as i64, - tv_usec: timeout.subsec_millis() as i64, + tv_sec: timeout.as_secs().try_into().unwrap(), + tv_usec: timeout.subsec_millis().try_into().unwrap(), }; unsafe { ffi::libusb_handle_events_timeout_completed( From 0ab595aaed67238016023f0331bca0dbab5dbbfb Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Sat, 15 May 2021 22:41:50 -0400 Subject: [PATCH 7/7] Now handling polling error, and made buffer owned by AsyncTransfer --- examples/read_async.rs | 12 +++++++---- src/device.rs | 20 ++++-------------- src/device_handle/async_api.rs | 38 +++++++++++++++++++++------------- src/device_list.rs | 7 ++++++- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/examples/read_async.rs b/examples/read_async.rs index 82d92ed..1a923ea 100644 --- a/examples/read_async.rs +++ b/examples/read_async.rs @@ -22,12 +22,16 @@ fn main() { const NUM_TRANSFERS: usize = 32; const BUF_SIZE: usize = 1024; - let mut main_buffer = Box::new([0u8; BUF_SIZE * NUM_TRANSFERS]); let mut transfers = Vec::new(); - for buf in main_buffer.chunks_exact_mut(BUF_SIZE) { - let mut transfer = - AsyncTransfer::new_bulk(&device, endpoint, buf, callback, Duration::from_secs(10)); + for _ in 0..NUM_TRANSFERS { + let mut transfer = AsyncTransfer::new_bulk( + &device, + endpoint, + BUF_SIZE, + callback, + Duration::from_secs(10), + ); transfer.submit().expect("Could not submit transfer"); transfers.push(transfer); } diff --git a/src/device.rs b/src/device.rs index bf1a9c3..d703952 100644 --- a/src/device.rs +++ b/src/device.rs @@ -10,9 +10,9 @@ use crate::{ config_descriptor::{self, ConfigDescriptor}, device_descriptor::{self, DeviceDescriptor}, device_handle::DeviceHandle, + error, fields::{self, Speed}, Error, UsbContext, - error, }; /// A reference to a USB device. @@ -150,27 +150,16 @@ impl Device { pub fn get_parent(&self) -> Option { let device = unsafe { libusb_get_parent(self.device.as_ptr()) }; NonNull::new(device) - .map(|device| - unsafe { - Device::from_libusb( - self.context.clone(), - device, - ) - } - ) + .map(|device| unsafe { Device::from_libusb(self.context.clone(), device) }) } /// Get the list of all port numbers from root for the specified device pub fn port_numbers(&self) -> Result, Error> { // As per the USB 3.0 specs, the current maximum limit for the depth is 7. - let mut ports = [0;7]; + let mut ports = [0; 7]; let result = unsafe { - libusb_get_port_numbers( - self.device.as_ptr(), - ports.as_mut_ptr(), - ports.len() as i32 - ) + libusb_get_port_numbers(self.device.as_ptr(), ports.as_mut_ptr(), ports.len() as i32) }; let ports_number = if result < 0 { @@ -180,5 +169,4 @@ impl Device { }; Ok(ports[0..ports_number as usize].to_vec()) } - } diff --git a/src/device_handle/async_api.rs b/src/device_handle/async_api.rs index 1e31aa0..8df4f38 100644 --- a/src/device_handle/async_api.rs +++ b/src/device_handle/async_api.rs @@ -26,20 +26,19 @@ pub enum TransferError { Errno(&'static str, i32), } -pub struct AsyncTransfer<'d, 'b, C: UsbContext, F> { +pub struct AsyncTransfer<'d, C: UsbContext, F> { ptr: NonNull, closure: F, + buffer: Box<[u8]>, _pin: PhantomPinned, // `ptr` holds a ptr to `closure`, so mark !Unpin _device: PhantomData<&'d DeviceHandle>, - _buf: PhantomData<&'b mut [u8]>, } -// TODO: should CbResult lifetime be different from 'b? -impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d, 'b, C, F> { +impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d, C, F> { #[allow(unused)] pub fn new_bulk( device: &'d DeviceHandle, endpoint: u8, - buffer: &'b mut [u8], + buf_size: usize, callback: F, timeout: std::time::Duration, ) -> Pin> { @@ -57,9 +56,9 @@ impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d let result = Box::pin(Self { ptr, closure: callback, + buffer: vec![0u8; buf_size].into_boxed_slice(), _pin: PhantomPinned, _device: PhantomData, - _buf: PhantomData, }); unsafe { @@ -79,8 +78,8 @@ impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d ptr.as_ptr(), device.as_raw(), endpoint, - buffer.as_ptr() as *mut u8, - buffer.len().try_into().unwrap(), + result.buffer.as_ptr() as *mut u8, + result.buffer.len().try_into().unwrap(), Self::transfer_cb, closure_as_ptr.cast(), timeout, @@ -162,7 +161,7 @@ impl<'d, 'b, C: 'd + UsbContext, F: FnMut(CbResult<'b>) + Send> AsyncTransfer<'d } } } -impl AsyncTransfer<'_, '_, C, F> { +impl AsyncTransfer<'_, C, F> { /// Helper function for the Drop impl. fn drop_helper(self: Pin<&mut Self>) { // Actual drop code goes here. @@ -180,9 +179,9 @@ impl AsyncTransfer<'_, '_, C, F> { } } -impl Drop for AsyncTransfer<'_, '_, C, F> { +impl Drop for AsyncTransfer<'_, C, F> { fn drop(&mut self) { - // We call `drop_helper` because that function represents the actualsemantics + // We call `drop_helper` because that function represents the actual semantics // that `self` has when being dropped. // (see https://doc.rust-lang.org/std/pin/index.html#drop-implementation) // Safety: `new_unchecked` is okay because we know this value is never used @@ -193,16 +192,27 @@ impl Drop for AsyncTransfer<'_, '_, C, F> { /// Polls for transfers and executes their callbacks. Will block until the /// given timeout, or return immediately if timeout is zero. +/// Returns whether a transfer was completed pub fn poll_transfers(ctx: &impl UsbContext, timeout: Duration) { let timeval = libc::timeval { tv_sec: timeout.as_secs().try_into().unwrap(), tv_usec: timeout.subsec_millis().try_into().unwrap(), }; unsafe { - ffi::libusb_handle_events_timeout_completed( + let errno = ffi::libusb_handle_events_timeout_completed( ctx.as_raw(), std::ptr::addr_of!(timeval), std::ptr::null_mut(), - ) - }; + ); + use ffi::constants::*; + match errno { + 0 => (), + LIBUSB_ERROR_INVALID_PARAM => panic!("Provided timeout was unexpectedly invalid"), + _ => panic!( + "Error when polling transfers. ERRNO: {}, Message: {}", + errno, + std::ffi::CStr::from_ptr(ffi::libusb_strerror(errno)).to_string_lossy() + ), + } + } } diff --git a/src/device_list.rs b/src/device_list.rs index d07630b..3eaf757 100644 --- a/src/device_list.rs +++ b/src/device_list.rs @@ -102,7 +102,12 @@ impl<'a, T: UsbContext> Iterator for Devices<'a, T> { let device = self.devices[self.index]; self.index += 1; - Some(unsafe { device::Device::from_libusb(self.context.clone(), std::ptr::NonNull::new_unchecked(device)) }) + Some(unsafe { + device::Device::from_libusb( + self.context.clone(), + std::ptr::NonNull::new_unchecked(device), + ) + }) } else { None }