diff options
Diffstat (limited to 'usb_util/src/device.rs')
-rw-r--r-- | usb_util/src/device.rs | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/usb_util/src/device.rs b/usb_util/src/device.rs new file mode 100644 index 0000000..3fc77e2 --- /dev/null +++ b/usb_util/src/device.rs @@ -0,0 +1,458 @@ +// Copyright 2019 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 crate::{ + control_request_type, descriptor, ConfigDescriptorTree, + ControlRequestDataPhaseTransferDirection, ControlRequestRecipient, ControlRequestType, + DeviceDescriptor, DeviceDescriptorTree, Error, Result, StandardControlRequest, +}; +use libc::{EAGAIN, ENODEV, ENOENT}; +use std::convert::TryInto; +use std::fs::File; +use std::io::{Seek, SeekFrom}; +use std::mem::{size_of, size_of_val}; +use std::os::raw::{c_int, c_uint, c_ulong, c_void}; +use std::sync::Arc; +use sys_util::handle_eintr_errno; + +/// Device represents a USB device. +pub struct Device { + fd: Arc<File>, + device_descriptor_tree: DeviceDescriptorTree, +} + +/// Transfer contains the information necessary to submit a USB request +/// and, once it has been submitted and completed, contains the response. +pub struct Transfer { + // NOTE: This Vec is actually a single URB with a trailing + // variable-length field created by vec_with_array_field(). + urb: Vec<usb_sys::usbdevfs_urb>, + pub buffer: Vec<u8>, + callback: Option<Box<dyn Fn(Transfer) + Send + Sync>>, +} + +/// TransferHandle is a handle that allows cancellation of in-flight transfers +/// between submit_transfer() and get_completed_transfer(). +/// Attempting to cancel a transfer that has already completed is safe and will +/// return an error. +pub struct TransferHandle { + weak_transfer: std::sync::Weak<Transfer>, + fd: std::sync::Weak<File>, +} + +#[derive(PartialEq)] +pub enum TransferStatus { + Completed, + Error, + Cancelled, + NoDevice, +} + +impl Device { + /// Create a new `Device` from a file descriptor. + /// `fd` should be a file in usbdevfs (e.g. `/dev/bus/usb/001/002`). + pub fn new(mut fd: File) -> Result<Self> { + fd.seek(SeekFrom::Start(0)).map_err(Error::DescriptorRead)?; + let device_descriptor_tree = descriptor::parse_usbfs_descriptors(&mut fd)?; + + let device = Device { + fd: Arc::new(fd), + device_descriptor_tree, + }; + Ok(device) + } + + pub fn fd(&self) -> Arc<File> { + self.fd.clone() + } + + unsafe fn ioctl(&self, nr: c_ulong) -> Result<i32> { + let ret = handle_eintr_errno!(sys_util::ioctl(&*self.fd, nr)); + if ret < 0 { + return Err(Error::IoctlFailed(nr, sys_util::Error::last())); + } + Ok(ret) + } + + unsafe fn ioctl_with_ref<T>(&self, nr: c_ulong, arg: &T) -> Result<i32> { + let ret = handle_eintr_errno!(sys_util::ioctl_with_ref(&*self.fd, nr, arg)); + if ret < 0 { + return Err(Error::IoctlFailed(nr, sys_util::Error::last())); + } + Ok(ret) + } + + unsafe fn ioctl_with_mut_ref<T>(&self, nr: c_ulong, arg: &mut T) -> Result<i32> { + let ret = handle_eintr_errno!(sys_util::ioctl_with_mut_ref(&*self.fd, nr, arg)); + if ret < 0 { + return Err(Error::IoctlFailed(nr, sys_util::Error::last())); + } + Ok(ret) + } + + unsafe fn ioctl_with_mut_ptr<T>(&self, nr: c_ulong, arg: *mut T) -> Result<i32> { + let ret = handle_eintr_errno!(sys_util::ioctl_with_mut_ptr(&*self.fd, nr, arg)); + if ret < 0 { + return Err(Error::IoctlFailed(nr, sys_util::Error::last())); + } + Ok(ret) + } + + /// Submit a transfer to the device. + /// The transfer will be processed asynchronously by the device. + /// Call `poll_transfers()` on this device to check for completed transfers. + pub fn submit_transfer(&mut self, transfer: Transfer) -> Result<TransferHandle> { + let mut rc_transfer = Arc::new(transfer); + + // Technically, Arc::from_raw() should only be called on pointers returned + // from Arc::into_raw(). However, we need to stash this value inside the + // Arc<Transfer> itself, so we manually calculate the address that would be + // returned from Arc::into_raw() via Deref and then call Arc::into_raw() + // to forget the Arc without dropping its contents. + // Do not remove the into_raw() call! + let raw_transfer = (&*rc_transfer) as *const Transfer as usize; + match Arc::get_mut(&mut rc_transfer) { + Some(t) => t.urb_mut().usercontext = raw_transfer, + None => { + // This should never happen, since there is only one strong reference + // at this point. + return Err(Error::RcGetMutFailed); + } + } + let _ = Arc::into_raw(rc_transfer.clone()); + + let urb_ptr = rc_transfer.urb.as_ptr() as *mut usb_sys::usbdevfs_urb; + + // Safe because we control the lifetime of the URB via Arc::into_raw() and + // Arc::from_raw() in poll_transfers(). + unsafe { + self.ioctl_with_mut_ptr(usb_sys::USBDEVFS_SUBMITURB(), urb_ptr)?; + } + + let weak_transfer = Arc::downgrade(&rc_transfer); + + Ok(TransferHandle { + weak_transfer, + fd: Arc::downgrade(&self.fd), + }) + } + + /// Check for completed asynchronous transfers submitted via `submit_transfer()`. + /// The callback for each completed transfer will be called. + pub fn poll_transfers(&self) -> Result<()> { + // Reap completed transfers until we get EAGAIN. + loop { + let mut urb_ptr: *mut usb_sys::usbdevfs_urb = std::ptr::null_mut(); + // Safe because we provide a valid urb_ptr to be filled by the kernel. + let result = + unsafe { self.ioctl_with_mut_ref(usb_sys::USBDEVFS_REAPURBNDELAY(), &mut urb_ptr) }; + match result { + Err(Error::IoctlFailed(_nr, e)) => { + if e.errno() == EAGAIN { + // No more completed transfers right now. + break; + } + } + Err(e) => return Err(e), + Ok(_) => {} + } + + if urb_ptr.is_null() { + break; + } + + // Safe because the URB usercontext field is always set to the result of + // Arc::into_raw() in submit_transfer(). + let rc_transfer: Arc<Transfer> = + unsafe { Arc::from_raw((*urb_ptr).usercontext as *const Transfer) }; + + // There should always be exactly one strong reference to rc_transfer, + // so try_unwrap() should never fail. + let mut transfer = Arc::try_unwrap(rc_transfer).map_err(|_| Error::RcUnwrapFailed)?; + + if let Some(cb) = transfer.callback.take() { + cb(transfer); + } + } + + Ok(()) + } + + /// Perform a USB port reset to reinitialize a device. + pub fn reset(&self) -> Result<()> { + // Safe because self.fd is a valid usbdevfs file descriptor. + let result = unsafe { self.ioctl(usb_sys::USBDEVFS_RESET()) }; + + if let Err(Error::IoctlFailed(_nr, errno_err)) = result { + // The device may disappear after a reset if e.g. its firmware changed. + // Treat that as success. + if errno_err.errno() == libc::ENODEV { + return Ok(()); + } + } + + result?; + Ok(()) + } + + /// Claim an interface on this device. + pub fn claim_interface(&self, interface_number: u8) -> Result<()> { + let disconnect_claim = usb_sys::usbdevfs_disconnect_claim { + interface: interface_number.into(), + flags: 0, + driver: [0u8; 256], + }; + // Safe because self.fd is a valid usbdevfs file descriptor and we pass a valid + // pointer to a usbdevs_disconnect_claim structure. + unsafe { + self.ioctl_with_ref(usb_sys::USBDEVFS_DISCONNECT_CLAIM(), &disconnect_claim)?; + } + + Ok(()) + } + + /// Release an interface previously claimed with `claim_interface()`. + pub fn release_interface(&self, interface_number: u8) -> Result<()> { + let ifnum: c_uint = interface_number.into(); + // Safe because self.fd is a valid usbdevfs file descriptor and we pass a valid + // pointer to unsigned int. + unsafe { + self.ioctl_with_ref(usb_sys::USBDEVFS_RELEASEINTERFACE(), &ifnum)?; + } + + Ok(()) + } + + /// Activate an alternate setting for an interface. + pub fn set_interface_alt_setting( + &self, + interface_number: u8, + alternative_setting: u8, + ) -> Result<()> { + let setinterface = usb_sys::usbdevfs_setinterface { + interface: interface_number.into(), + altsetting: alternative_setting.into(), + }; + // Safe because self.fd is a valid usbdevfs file descriptor and we pass a valid + // pointer to a usbdevfs_setinterface structure. + unsafe { + self.ioctl_with_ref(usb_sys::USBDEVFS_SETINTERFACE(), &setinterface)?; + } + Ok(()) + } + + /// Set active configuration for this device. + pub fn set_active_configuration(&mut self, config: u8) -> Result<()> { + let config: c_int = config.into(); + // Safe because self.fd is a valid usbdevfs file descriptor and we pass a valid + // pointer to int. + unsafe { + self.ioctl_with_ref(usb_sys::USBDEVFS_SETCONFIGURATION(), &config)?; + } + + Ok(()) + } + + /// Get the device descriptor of this device. + pub fn get_device_descriptor(&self) -> Result<DeviceDescriptor> { + Ok(*self.device_descriptor_tree) + } + + /// Get active config descriptor of this device. + pub fn get_active_config_descriptor(&self) -> Result<ConfigDescriptorTree> { + let active_config = self.get_active_configuration()?; + match self + .device_descriptor_tree + .get_config_descriptor(active_config) + { + Some(config_descriptor) => Ok(config_descriptor.clone()), + None => Err(Error::NoSuchDescriptor), + } + } + + /// Get bConfigurationValue of the currently active configuration. + pub fn get_active_configuration(&self) -> Result<u8> { + // Send a synchronous control transfer to get the active configuration. + let mut active_config: u8 = 0; + let ctrl_transfer = usb_sys::usbdevfs_ctrltransfer { + bRequestType: control_request_type( + ControlRequestType::Standard, + ControlRequestDataPhaseTransferDirection::DeviceToHost, + ControlRequestRecipient::Device, + ), + bRequest: StandardControlRequest::GetConfiguration as u8, + wValue: 0, + wIndex: 0, + wLength: size_of_val(&active_config) as u16, + timeout: 5000, // milliseconds + data: &mut active_config as *mut u8 as *mut c_void, + }; + // Safe because self.fd is a valid usbdevfs file descriptor and we pass a valid + // pointer to a usbdevfs_ctrltransfer structure. + unsafe { + self.ioctl_with_ref(usb_sys::USBDEVFS_CONTROL(), &ctrl_transfer)?; + } + Ok(active_config) + } + + /// Clear the halt/stall condition for an endpoint. + pub fn clear_halt(&self, ep_addr: u8) -> Result<()> { + let endpoint: c_uint = ep_addr.into(); + // Safe because self.fd is a valid usbdevfs file descriptor and we pass a valid + // pointer to unsigned int. + unsafe { + self.ioctl_with_ref(usb_sys::USBDEVFS_CLEAR_HALT(), &endpoint)?; + } + + Ok(()) + } +} + +// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`. +fn vec_with_size_in_bytes<T: Default>(size_in_bytes: usize) -> Vec<T> { + let rounded_size = (size_in_bytes + size_of::<T>() - 1) / size_of::<T>(); + let mut v = Vec::with_capacity(rounded_size); + for _ in 0..rounded_size { + v.push(T::default()) + } + v +} + +// This function has been borrowed from kvm - see the doc comment there for details. +fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> { + let element_space = count * size_of::<F>(); + let vec_size_bytes = size_of::<T>() + element_space; + vec_with_size_in_bytes(vec_size_bytes) +} + +impl Transfer { + fn urb(&self) -> &usb_sys::usbdevfs_urb { + // self.urb is a Vec created with `vec_with_array_field`; the first entry is + // the URB itself. + &self.urb[0] + } + + fn urb_mut(&mut self) -> &mut usb_sys::usbdevfs_urb { + &mut self.urb[0] + } + + fn new( + transfer_type: u8, + endpoint: u8, + buffer: Vec<u8>, + iso_packets: &[usb_sys::usbdevfs_iso_packet_desc], + ) -> Result<Transfer> { + let mut transfer = Transfer { + urb: vec_with_array_field::<usb_sys::usbdevfs_urb, usb_sys::usbdevfs_iso_packet_desc>( + iso_packets.len(), + ), + buffer, + callback: None, + }; + + transfer.urb_mut().urb_type = transfer_type; + transfer.urb_mut().endpoint = endpoint; + transfer.urb_mut().buffer = transfer.buffer.as_mut_ptr() as *mut c_void; + transfer.urb_mut().buffer_length = transfer + .buffer + .len() + .try_into() + .map_err(Error::InvalidBufferLength)?; + + // Safe because we ensured there is enough space in transfer.urb to hold the number of + // isochronous frames required. + let iso_frame_desc = unsafe { + transfer + .urb_mut() + .iso_frame_desc + .as_mut_slice(iso_packets.len()) + }; + iso_frame_desc.copy_from_slice(iso_packets); + + Ok(transfer) + } + + /// Create a control transfer. + pub fn new_control(buffer: Vec<u8>) -> Result<Transfer> { + let endpoint = 0; + Self::new(usb_sys::USBDEVFS_URB_TYPE_CONTROL, endpoint, buffer, &[]) + } + + /// Create an interrupt transfer. + pub fn new_interrupt(endpoint: u8, buffer: Vec<u8>) -> Result<Transfer> { + Self::new(usb_sys::USBDEVFS_URB_TYPE_INTERRUPT, endpoint, buffer, &[]) + } + + /// Create a bulk transfer. + pub fn new_bulk(endpoint: u8, buffer: Vec<u8>) -> Result<Transfer> { + Self::new(usb_sys::USBDEVFS_URB_TYPE_BULK, endpoint, buffer, &[]) + } + + /// Create an isochronous transfer. + pub fn new_isochronous(endpoint: u8, buffer: Vec<u8>) -> Result<Transfer> { + // TODO(dverkamp): allow user to specify iso descriptors + Self::new(usb_sys::USBDEVFS_URB_TYPE_ISO, endpoint, buffer, &[]) + } + + /// Get the status of a completed transfer. + pub fn status(&self) -> TransferStatus { + let status = self.urb().status; + if status == 0 { + TransferStatus::Completed + } else if status == -ENODEV { + TransferStatus::NoDevice + } else if status == -ENOENT { + TransferStatus::Cancelled + } else { + TransferStatus::Error + } + } + + /// Get the actual amount of data transferred, which may be less than + /// the original length. + pub fn actual_length(&self) -> usize { + self.urb().actual_length as usize + } + + /// Set callback function for transfer completion. + pub fn set_callback<C: 'static + Fn(Transfer) + Send + Sync>(&mut self, cb: C) { + self.callback = Some(Box::new(cb)); + } +} + +impl TransferHandle { + /// Attempt to cancel the transfer associated with this `TransferHandle`. + /// Safe to call even if the transfer has already completed; + /// `Error::TransferAlreadyCompleted` will be returned in this case. + pub fn cancel(self) -> Result<()> { + let rc_transfer = match self.weak_transfer.upgrade() { + None => return Err(Error::TransferAlreadyCompleted), + Some(rc_transfer) => rc_transfer, + }; + + let urb_ptr = rc_transfer.urb.as_ptr() as *mut usb_sys::usbdevfs_urb; + let fd = match self.fd.upgrade() { + None => return Err(Error::NoDevice), + Some(fd) => fd, + }; + + // Safe because fd is a valid usbdevfs file descriptor and we pass a valid + // pointer to a usbdevfs_urb structure. + if unsafe { + handle_eintr_errno!(sys_util::ioctl_with_mut_ptr( + &*fd, + usb_sys::USBDEVFS_DISCARDURB(), + urb_ptr + )) + } < 0 + { + return Err(Error::IoctlFailed( + usb_sys::USBDEVFS_DISCARDURB(), + sys_util::Error::last(), + )); + } + + Ok(()) + } +} |