usb_util: implement usb_transfer

Wrap libusb_transfer and callback.

BUG=chromium:831850
TEST=local build

Change-Id: I1bc84e68cb36796e919f647e3a072f1599f80a4a
Reviewed-on: https://chromium-review.googlesource.com/1138643
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Jingkui Wang <jkwang@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
Jingkui Wang 2018-07-16 10:00:30 -07:00 committed by chrome-bot
parent 2bac1e7a9c
commit 200fd78ff1
3 changed files with 401 additions and 0 deletions

View file

@ -8,6 +8,7 @@ use std::sync::Arc;
use bindings;
use error::{Error, Result};
use libusb_context::LibUsbContextInner;
use usb_transfer::{UsbTransfer, UsbTransferBuffer};
/// DeviceHandle wraps libusb_device_handle.
pub struct DeviceHandle {
@ -126,4 +127,16 @@ impl DeviceHandle {
try_libusb!(unsafe { bindings::libusb_clear_halt(self.handle, endpoint) });
Ok(())
}
/// Libusb asynchronous I/O interface has a 5 step process. It gives lots of
/// flexibility but makes it hard to manage object life cycle and easy to
/// write unsafe code. We wrap this interface to a simple "transfer" and "cancel"
/// interface. Resubmission is not supported and deallocation is handled safely
/// here.
pub fn submit_async_transfer<T: UsbTransferBuffer>(
&self,
transfer: UsbTransfer<T>,
) -> Result<()> {
unsafe { transfer.submit(self.handle) }
}
}

View file

@ -23,3 +23,4 @@ pub mod interface_descriptor;
pub mod libusb_context;
pub mod libusb_device;
pub mod types;
pub mod usb_transfer;

View file

@ -0,0 +1,387 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::mem::size_of;
use std::os::raw::c_void;
use std::sync::{Arc, Weak};
use bindings::{
libusb_alloc_transfer, libusb_cancel_transfer, libusb_device_handle, libusb_free_transfer,
libusb_submit_transfer, libusb_transfer, libusb_transfer_status, LIBUSB_TRANSFER_CANCELLED,
LIBUSB_TRANSFER_COMPLETED, LIBUSB_TRANSFER_ERROR, LIBUSB_TRANSFER_NO_DEVICE,
LIBUSB_TRANSFER_OVERFLOW, LIBUSB_TRANSFER_STALL, LIBUSB_TRANSFER_TIMED_OUT,
LIBUSB_TRANSFER_TYPE_BULK, LIBUSB_TRANSFER_TYPE_CONTROL, LIBUSB_TRANSFER_TYPE_INTERRUPT,
};
use error::{Error, Result};
use types::UsbRequestSetup;
/// Status of transfer.
#[derive(PartialEq)]
pub enum TransferStatus {
Completed,
Error,
TimedOut,
Cancelled,
Stall,
NoDevice,
Overflow,
}
impl From<libusb_transfer_status> for TransferStatus {
fn from(s: libusb_transfer_status) -> Self {
match s {
LIBUSB_TRANSFER_COMPLETED => TransferStatus::Completed,
LIBUSB_TRANSFER_ERROR => TransferStatus::Error,
LIBUSB_TRANSFER_TIMED_OUT => TransferStatus::TimedOut,
LIBUSB_TRANSFER_CANCELLED => TransferStatus::Cancelled,
LIBUSB_TRANSFER_STALL => TransferStatus::Stall,
LIBUSB_TRANSFER_NO_DEVICE => TransferStatus::NoDevice,
LIBUSB_TRANSFER_OVERFLOW => TransferStatus::Overflow,
_ => TransferStatus::Error,
}
}
}
/// Trait for usb transfer buffer.
pub trait UsbTransferBuffer: Send {
fn as_ptr(&mut self) -> *mut u8;
fn len(&self) -> i32;
}
/// Default buffer size for control data transfer.
const CONTROL_DATA_BUFFER_SIZE: usize = 1024;
/// Buffer type for control transfer. The first 8-bytes is a UsbRequestSetup struct.
#[repr(C, packed)]
pub struct ControlTransferBuffer {
pub setup_buffer: UsbRequestSetup,
pub data_buffer: [u8; CONTROL_DATA_BUFFER_SIZE],
}
impl ControlTransferBuffer {
fn new() -> ControlTransferBuffer {
ControlTransferBuffer {
setup_buffer: UsbRequestSetup {
request_type: 0,
request: 0,
value: 0,
index: 0,
length: 0,
},
data_buffer: [0; CONTROL_DATA_BUFFER_SIZE],
}
}
/// Set request setup for this control buffer.
pub fn set_request_setup(&mut self, request_setup: &UsbRequestSetup) {
self.setup_buffer = request_setup.clone();
}
}
impl UsbTransferBuffer for ControlTransferBuffer {
fn as_ptr(&mut self) -> *mut u8 {
self as *mut ControlTransferBuffer as *mut u8
}
fn len(&self) -> i32 {
if self.setup_buffer.length as usize > CONTROL_DATA_BUFFER_SIZE {
panic!("Setup packet has an oversize length");
}
self.setup_buffer.length as i32 + size_of::<UsbRequestSetup>() as i32
}
}
/// Buffer type for Bulk transfer.
pub struct BulkTransferBuffer {
buffer: Vec<u8>,
}
impl BulkTransferBuffer {
fn with_size(buffer_size: usize) -> Self {
BulkTransferBuffer {
buffer: vec![0; buffer_size],
}
}
/// Get mutable interal slice of this buffer.
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.buffer
}
/// Get interal slice of this buffer.
pub fn as_slice(&self) -> &[u8] {
&self.buffer
}
}
impl UsbTransferBuffer for BulkTransferBuffer {
fn as_ptr(&mut self) -> *mut u8 {
if self.buffer.len() == 0 {
// Vec::as_mut_ptr() won't give 0x0 even if len() is 0.
std::ptr::null_mut()
} else {
self.buffer.as_mut_ptr()
}
}
fn len(&self) -> i32 {
self.buffer.len() as i32
}
}
type UsbTransferCompletionCallback<T> = Fn(UsbTransfer<T>) + Send + 'static;
// This wraps libusb_transfer pointer.
struct LibUsbTransfer {
ptr: *mut libusb_transfer,
}
impl Drop for LibUsbTransfer {
fn drop(&mut self) {
// Safe because 'self.ptr' is allocated by libusb_alloc_transfer.
unsafe {
libusb_free_transfer(self.ptr);
}
}
}
// It is safe to invoke libusb functions from multiple threads.
// We cannot modify libusb_transfer safely from multiple threads. All the modifications happens
// in construct (UsbTransfer::new) or consume (UsbTransfer::into_raw), we can consider this thread
// safe.
unsafe impl Send for LibUsbTransfer {}
unsafe impl Sync for LibUsbTransfer {}
/// TransferCanceller can cancel the transfer.
pub struct TransferCanceller {
transfer: Weak<LibUsbTransfer>,
}
impl TransferCanceller {
/// Return false if fail to cancel.
pub fn try_cancel(&self) -> bool {
match self.transfer.upgrade() {
Some(t) => {
// Safe because self.transfer has ownership of the raw pointer.
let r = unsafe { libusb_cancel_transfer(t.ptr) };
if r == 0 {
true
} else {
false
}
}
None => false,
}
}
}
struct UsbTransferInner<T: UsbTransferBuffer> {
transfer: Arc<LibUsbTransfer>,
callback: Option<Box<UsbTransferCompletionCallback<T>>>,
buffer: T,
}
/// UsbTransfer owns a LibUsbTransfer, it's buffer and callback.
pub struct UsbTransfer<T: UsbTransferBuffer> {
inner: Box<UsbTransferInner<T>>,
}
/// Build a control transfer.
pub fn control_transfer(timeout: u32) -> UsbTransfer<ControlTransferBuffer> {
UsbTransfer::<ControlTransferBuffer>::new(
0,
LIBUSB_TRANSFER_TYPE_CONTROL as u8,
timeout,
ControlTransferBuffer::new(),
)
}
/// Build a data transfer.
pub fn bulk_transfer(endpoint: u8, timeout: u32, size: usize) -> UsbTransfer<BulkTransferBuffer> {
UsbTransfer::<BulkTransferBuffer>::new(
endpoint,
LIBUSB_TRANSFER_TYPE_BULK as u8,
timeout,
BulkTransferBuffer::with_size(size),
)
}
/// Build a data transfer.
pub fn interrupt_transfer(
endpoint: u8,
timeout: u32,
size: usize,
) -> UsbTransfer<BulkTransferBuffer> {
UsbTransfer::<BulkTransferBuffer>::new(
endpoint,
LIBUSB_TRANSFER_TYPE_INTERRUPT as u8,
timeout,
BulkTransferBuffer::with_size(size),
)
}
impl<T: UsbTransferBuffer> UsbTransfer<T> {
fn new(endpoint: u8, type_: u8, timeout: u32, buffer: T) -> Self {
// Safe because alloc is safe.
let transfer: *mut libusb_transfer = unsafe { libusb_alloc_transfer(0) };
// Just panic on OOM.
assert!(!transfer.is_null());
let inner = Box::new(UsbTransferInner {
transfer: Arc::new(LibUsbTransfer { ptr: transfer }),
callback: None,
buffer,
});
// Safe because we inited transfer.
let raw_transfer: &mut libusb_transfer = unsafe { &mut *(inner.transfer.ptr) };
raw_transfer.endpoint = endpoint;
raw_transfer.type_ = type_;
raw_transfer.timeout = timeout;
raw_transfer.callback = Some(UsbTransfer::<T>::on_transfer_completed);
UsbTransfer { inner }
}
/// Get canceller of this transfer.
pub fn get_canceller(&self) -> TransferCanceller {
let weak_transfer = Arc::downgrade(&self.inner.transfer);
TransferCanceller {
transfer: weak_transfer,
}
}
/// Set callback function for transfer completion.
pub fn set_callback<C: 'static + Fn(UsbTransfer<T>) + Send>(&mut self, cb: C) {
self.inner.callback = Some(Box::new(cb));
}
/// Get a reference to the buffer.
pub fn buffer(&self) -> &T {
&self.inner.buffer
}
/// Get a mutable reference to the buffer.
pub fn buffer_mut(&mut self) -> &mut T {
&mut self.inner.buffer
}
/// Get actual length of data that was transferred.
pub fn actual_length(&self) -> i32 {
let transfer = self.inner.transfer.ptr;
// Safe because inner.ptr is always allocated by libusb_alloc_transfer.
unsafe { (*transfer).actual_length }
}
/// Get the transfer status of this transfer.
pub fn status(&self) -> TransferStatus {
let transfer = self.inner.transfer.ptr;
// Safe because inner.ptr is always allocated by libusb_alloc_transfer.
unsafe { TransferStatus::from((*transfer).status) }
}
/// Submit this transfer to device handle. 'self' is consumed. On success, the memory will be
/// 'leaked' (and stored in user_data) and sent to libusb, when the async operation is done,
/// on_transfer_completed will recreate 'self' and deliver it to callback/free 'self'. On
/// faliure, 'self' is returned with an error.
///
/// # Safety
///
/// Assumes libusb_device_handle is an handled opened by libusb, self.inner.transfer.ptr is
/// initialized with correct buffer and length.
pub unsafe fn submit(self, handle: *mut libusb_device_handle) -> Result<()> {
let transfer = self.into_raw();
(*transfer).dev_handle = handle;
match Error::from(libusb_submit_transfer(transfer)) {
Error::Success(_e) => Ok(()),
err => {
UsbTransfer::<T>::from_raw(transfer);
Err(err)
}
}
}
/// Invoke callback when transfer is completed.
///
/// # Safety
///
/// Assumes libusb_tranfser is finished. This function is called by libusb, don't call it
/// manually.
unsafe extern "C" fn on_transfer_completed(transfer: *mut libusb_transfer) {
let mut transfer = UsbTransfer::<T>::from_raw(transfer);
// Callback is reset to None.
if let Some(cb) = transfer.inner.callback.take() {
cb(transfer);
}
}
fn into_raw(mut self) -> *mut libusb_transfer {
let transfer: *mut libusb_transfer = self.inner.transfer.ptr;
// Safe because transfer is allocated by libusb_alloc_transfer.
unsafe {
(*transfer).buffer = self.buffer_mut().as_ptr();
(*transfer).length = self.buffer_mut().len();
(*transfer).user_data = Box::into_raw(self.inner) as *mut c_void;
}
transfer
}
unsafe fn from_raw(transfer: *mut libusb_transfer) -> Self {
UsbTransfer {
inner: Box::<UsbTransferInner<T>>::from_raw(
(*transfer).user_data as *mut UsbTransferInner<T>,
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
pub fn fake_submit_transfer<T: UsbTransferBuffer>(transfer: UsbTransfer<T>) {
let transfer = transfer.into_raw();
unsafe {
match (*transfer).callback {
Some(cb) => cb(transfer),
// Although no callback is invoked, we still need on_transfer_completed to
// free memory.
None => panic!("Memory leak!"),
};
}
}
#[test]
fn check_control_buffer_size() {
assert_eq!(
size_of::<ControlTransferBuffer>(),
size_of::<UsbRequestSetup>() + CONTROL_DATA_BUFFER_SIZE
);
}
#[test]
fn submit_transfer_no_callback_test() {
let t = control_transfer(0);
fake_submit_transfer(t);
let t = bulk_transfer(0, 0, 1);
fake_submit_transfer(t);
}
struct FakeTransferController {
data: Mutex<u8>,
}
#[test]
fn submit_transfer_with_callback() {
let c = Arc::new(FakeTransferController {
data: Mutex::new(0),
});
let c1 = Arc::downgrade(&c);
let mut t = control_transfer(0);
t.set_callback(move |_t| {
let c = c1.upgrade().unwrap();
*c.data.lock().unwrap() = 3;
});
fake_submit_transfer(t);
assert_eq!(*c.data.lock().unwrap(), 3);
}
}