| // Copyright 2020 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::collections::VecDeque; |
| use std::fmt; |
| use std::io::{self, Read, Write}; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::sync::Arc; |
| use std::thread; |
| use std::time::{Duration, Instant}; |
| use std::vec::Vec; |
| |
| use rusb::{Direction, GlobalContext, Registration, TransferType, UsbContext}; |
| use sync::{Condvar, Mutex}; |
| use sys_util::{debug, error, info, EventFd, PollContext}; |
| |
| const USB_TRANSFER_TIMEOUT: Duration = Duration::from_secs(60); |
| const USB_CLEANUP_TIMEOUT: Duration = Duration::from_secs(2); |
| |
| #[derive(Debug)] |
| pub enum Error { |
| ClaimInterface(u8, rusb::Error), |
| ReleaseInterface(u8, rusb::Error), |
| DetachDrivers(rusb::Error), |
| DeviceList(rusb::Error), |
| OpenDevice(rusb::Error), |
| CleanupThread(io::Error), |
| ReadConfigDescriptor(rusb::Error), |
| ReadDeviceDescriptor(rusb::Error), |
| RegisterCallback(rusb::Error), |
| SetActiveConfig(rusb::Error), |
| SetAlternateSetting(u8, rusb::Error), |
| NoDevice, |
| NoFreeInterface, |
| NotIppUsb, |
| Poll(sys_util::Error), |
| } |
| |
| impl std::error::Error for Error {} |
| |
| impl fmt::Display for Error { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| use Error::*; |
| match self { |
| ClaimInterface(i, err) => write!(f, "Failed to claim interface {}: {}", i, err), |
| ReleaseInterface(i, err) => write!(f, "Failed to release interface {}: {}", i, err), |
| DetachDrivers(err) => write!(f, "Failed to detach kernel drivers: {}", err), |
| DeviceList(err) => write!(f, "Failed to read device list: {}", err), |
| OpenDevice(err) => write!(f, "Failed to open device: {}", err), |
| CleanupThread(err) => write!(f, "Failed to start cleanup thread: {}", err), |
| ReadConfigDescriptor(err) => write!(f, "Failed to read config descriptor: {}", err), |
| ReadDeviceDescriptor(err) => write!(f, "Failed to read device descriptor: {}", err), |
| RegisterCallback(err) => write!(f, "Failed to register for hotplug callback: {}", err), |
| SetActiveConfig(err) => write!(f, "Failed to set active config: {}", err), |
| SetAlternateSetting(i, err) => write!( |
| f, |
| "Failed to set interface {} alternate setting: {}", |
| i, err |
| ), |
| NoDevice => write!(f, "No valid IPP USB device found."), |
| NoFreeInterface => write!(f, "There is no free IPP USB interface to claim."), |
| NotIppUsb => write!(f, "The specified device is not an IPP USB device."), |
| Poll(err) => write!(f, "Error polling shutdown fd: {}", err), |
| } |
| } |
| } |
| |
| type Result<T> = std::result::Result<T, Error>; |
| |
| fn is_ippusb_interface(descriptor: &rusb::InterfaceDescriptor) -> bool { |
| descriptor.class_code() == 0x07 |
| && descriptor.sub_class_code() == 0x01 |
| && descriptor.protocol_code() == 0x04 |
| } |
| |
| /// The information for an interface descriptor that supports IPPUSB. |
| /// Bulk transfers can be read/written to the in/out endpoints, respectively. |
| #[derive(Copy, Clone)] |
| struct IppusbDescriptor { |
| interface_number: u8, |
| alternate_setting: u8, |
| in_endpoint: u8, |
| out_endpoint: u8, |
| } |
| |
| /// The configuration and descriptors that support IPPUSB for a USB device. |
| /// A valid IppusbDevice will have at least two interfaces. |
| struct IppusbDevice { |
| config: u8, |
| interfaces: Vec<IppusbDescriptor>, |
| } |
| |
| /// Given a libusb Device, searches through the device's configurations to see if there is a |
| /// particular configuration that supports IPP over USB. If such a configuration is found, returns |
| /// an IppusbDevice struct, which specifies the configuration as well as the IPPUSB interfaces |
| /// within that configuration. |
| /// |
| /// If the given device does not support IPP over USB, returns None. |
| /// |
| /// A device is considered to support IPP over USB if it has a configuration with at least two |
| /// IPPUSB interfaces. |
| /// |
| /// An interface is considered an IPPUSB interface if it has the proper class, sub-class, and |
| /// protocol, and if it has a bulk-in and bulk-out endpoint. |
| fn read_ippusb_device_info<T: UsbContext>( |
| device: &rusb::Device<T>, |
| ) -> Result<Option<IppusbDevice>> { |
| let desc = device |
| .device_descriptor() |
| .map_err(Error::ReadDeviceDescriptor)?; |
| for i in 0..desc.num_configurations() { |
| let config = device |
| .config_descriptor(i) |
| .map_err(Error::ReadConfigDescriptor)?; |
| |
| let mut interfaces = Vec::new(); |
| for interface in config.interfaces() { |
| 'alternates: for alternate in interface.descriptors() { |
| if !is_ippusb_interface(&alternate) { |
| continue; |
| } |
| info!( |
| "Device {}:{} - Found IPPUSB interface. config {}, interface {}, alternate {}", |
| device.bus_number(), |
| device.address(), |
| config.number(), |
| interface.number(), |
| alternate.setting_number() |
| ); |
| |
| // Find the bulk in and out endpoints for this interface. |
| let mut in_endpoint: Option<u8> = None; |
| let mut out_endpoint: Option<u8> = None; |
| for endpoint in alternate.endpoint_descriptors() { |
| match (endpoint.direction(), endpoint.transfer_type()) { |
| (Direction::In, TransferType::Bulk) => { |
| in_endpoint.get_or_insert(endpoint.address()); |
| } |
| (Direction::Out, TransferType::Bulk) => { |
| out_endpoint.get_or_insert(endpoint.address()); |
| } |
| _ => {} |
| }; |
| |
| if in_endpoint.is_some() && out_endpoint.is_some() { |
| break; |
| } |
| } |
| |
| if let (Some(in_endpoint), Some(out_endpoint)) = (in_endpoint, out_endpoint) { |
| interfaces.push(IppusbDescriptor { |
| interface_number: interface.number(), |
| alternate_setting: alternate.setting_number(), |
| in_endpoint, |
| out_endpoint, |
| }); |
| // We must consider at most one alternate setting when detecting IPPUSB |
| // interfaces. |
| break 'alternates; |
| } |
| } |
| } |
| |
| // A device must have at least two IPPUSB interfaces in order to be considered an IPPUSB device. |
| if interfaces.len() >= 2 { |
| return Ok(Some(IppusbDevice { |
| config: config.number(), |
| interfaces, |
| })); |
| } |
| } |
| |
| Ok(None) |
| } |
| |
| struct ClaimedInterface { |
| handle: rusb::DeviceHandle<GlobalContext>, |
| descriptor: IppusbDescriptor, |
| } |
| |
| impl ClaimedInterface { |
| /// Send a USB claim for our interface and switch it to the correct alternate setting. |
| fn claim(&mut self) -> Result<()> { |
| self.handle |
| .claim_interface(self.descriptor.interface_number) |
| .map_err(|e| Error::ClaimInterface(self.descriptor.interface_number, e))?; |
| self.handle |
| .set_alternate_setting( |
| self.descriptor.interface_number, |
| self.descriptor.alternate_setting, |
| ) |
| .map_err(|e| Error::SetAlternateSetting(self.descriptor.interface_number, e)) |
| } |
| |
| /// Send a USB release for our interface. |
| fn release(&mut self) -> Result<()> { |
| self.handle |
| .release_interface(self.descriptor.interface_number) |
| .map_err(|e| Error::ReleaseInterface(self.descriptor.interface_number, e)) |
| } |
| } |
| |
| /// InterfaceManagerState contains the internal state of InterfaceManager. It is intended to |
| /// be shared across InterfaceManager instances and protected by a mutex. |
| struct InterfaceManagerState { |
| interfaces: VecDeque<ClaimedInterface>, |
| handle: rusb::DeviceHandle<GlobalContext>, |
| usb_config: u8, |
| active: usize, |
| pending_cleanup: bool, |
| next_cleanup: Instant, |
| } |
| |
| impl InterfaceManagerState { |
| fn claim_all(&mut self) -> Result<()> { |
| // Detach any outstanding kernel drivers for the current config before attempting |
| // to switch to our desired config. |
| let config = self |
| .handle |
| .device() |
| .active_config_descriptor() |
| .map_err(Error::ReadConfigDescriptor)?; |
| |
| for interface in config.interfaces() { |
| match self.handle.detach_kernel_driver(interface.number()) { |
| Err(e) if e != rusb::Error::NotFound => return Err(Error::DetachDrivers(e)), |
| _ => {} |
| } |
| } |
| |
| self.handle |
| .set_active_configuration(self.usb_config) |
| .map_err(Error::SetActiveConfig)?; |
| |
| for interface in &mut self.interfaces { |
| if let Err(e) = interface.claim() { |
| // We don't bother to free any successfully claimed interfaces because |
| // it's not an error to try to claim them again when the next connection |
| // arrives. |
| error!( |
| "Failed to reclaim interface {}: {}", |
| interface.descriptor.interface_number, e |
| ); |
| return Err(e); |
| } |
| } |
| Ok(()) |
| } |
| |
| fn release_all(&mut self) -> Result<()> { |
| for interface in &mut self.interfaces { |
| interface.release()?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// InterfaceManager is responsible for managing a pool of claimed USB interfaces. |
| /// At construction, it is provided with a set of interfaces, and then clients |
| /// can use its member functions in order to request and free interfaces. |
| /// |
| /// If no interfaces are currently available, requesting an interface will block |
| /// until an interface is freed by another thread. |
| /// |
| /// InterfaceManager maintains the invariant that interfaces are claimed when |
| /// handed out. It expects newly-inserted interfaces to be claimed by libusb and |
| /// it ensures that they are still claimed when retrieved. Internally it releases |
| /// and claims free interfaces to allow sharing with other programs that might need |
| /// to access the USB interfaces. |
| #[derive(Clone)] |
| pub struct InterfaceManager { |
| interface_available: Arc<Condvar>, |
| state: Arc<Mutex<InterfaceManagerState>>, |
| } |
| |
| impl InterfaceManager { |
| fn new( |
| handle: rusb::DeviceHandle<GlobalContext>, |
| usb_config: u8, |
| interfaces: Vec<ClaimedInterface>, |
| ) -> Self { |
| let mut deque: VecDeque<ClaimedInterface> = interfaces.into(); |
| for interface in &mut deque { |
| interface.release().unwrap_or_else(|e| { |
| error!( |
| "Failed to release interface {}: {}", |
| interface.descriptor.interface_number, e |
| ); |
| }); |
| } |
| Self { |
| interface_available: Arc::new(Condvar::new()), |
| state: Arc::new(Mutex::new(InterfaceManagerState { |
| interfaces: deque, |
| handle, |
| usb_config, |
| active: 0, |
| pending_cleanup: false, |
| next_cleanup: Instant::now(), |
| })), |
| } |
| } |
| |
| /// Start a separate thread to release interfaces. Interfaces are released once |
| /// USB_CLEANUP_TIMEOUT elapses with no activity after all interfaces are internally |
| /// returned. |
| fn start_cleanup_thread(&mut self) -> Result<std::thread::JoinHandle<()>> { |
| let manager = self.clone(); |
| |
| let handle = thread::Builder::new() |
| .name("interface cleanup".into()) |
| .spawn(move || { |
| debug!("Cleanup thread starting"); |
| let mut state = manager.state.lock(); |
| |
| // We wait in two phases: |
| // 1. As long as no cleanup is pending or there is an active interface, we need to |
| // wait indefinitely. Each interface sets pending_cleanup when it is returned, |
| // so this will eventually drop to 0 active interfaces with a cleanup pending. |
| // 2. Once cleanup is needed and there are no active interfaces, we wait for a |
| // timeout. If another connection comes during the timeout, we go back to the |
| // previous phase. |
| 'outer: loop { |
| // Phase 1: Wait for cleanup to be needed and all active interfaces to be |
| // returned. |
| state = manager |
| .interface_available |
| .wait_while(state, |t| t.active > 0 || !t.pending_cleanup); |
| |
| // Phase 2: Wait for the cleanup time to arrive. |
| // 1. If an interface is claimed and returned during the timeout, we will |
| // detect this because the timeout will be extended. We can simply wait |
| // more if this happens. |
| // 2. If an interface is claimed and is still active after our timeout, there's |
| // no need to keep waking up. Go back to phase 1 and wait for the active |
| // interfaces to be returned. |
| // 3. Once everything remains the same for the whole timeout period, we can |
| // exit the while loop and release all the interfaces. |
| while state.next_cleanup > Instant::now() { |
| let wait = state.next_cleanup - Instant::now(); |
| let result = |
| manager |
| .interface_available |
| .wait_timeout_while(state, wait, |t| t.active == 0); |
| state = result.0; // Throw away the timed out part of the result. |
| if state.active > 0 { |
| continue 'outer; |
| } |
| } |
| |
| // Now we know that there must be no active clients and the cleanup time must |
| // have arrived: |
| // 1. If there were an active client, the check inside the while loop above |
| // would have gone back to the outer loop. |
| // 2. If the cleanup time hadn't arrived, the inner while loop wouldn't have |
| // exited. |
| // This means we can safely release all the interfaces without affecting any |
| // active connections. |
| assert_eq!(state.active, 0, "Active interfaces not expected"); |
| assert!( |
| Instant::now() >= state.next_cleanup, |
| "Cleanup time not arrived" |
| ); |
| debug!("Releasing all USB interfaces"); |
| match state.release_all() { |
| Ok(()) => {} |
| |
| // If the device was unplugged, don't bother to print an error. We're |
| // about to exit anyway. |
| Err(Error::ReleaseInterface(_, rusb::Error::NoDevice)) => {} |
| |
| // If this failed for some other reason, we're in some bad state. There |
| // are no active interfaces, so restarting won't interrupt an active |
| // connection. We should just quit and let upstart reset us back to a |
| // known good state. |
| Err(e) => panic!("Failed to release interfaces: {}", e), |
| }; |
| state.pending_cleanup = false; |
| } |
| }) |
| .map_err(Error::CleanupThread)?; |
| |
| Ok(handle) |
| } |
| |
| /// Get an interface from the pool of tracked interfaces. |
| /// Will block until an interface is available. |
| /// If interfaces are currently not claimed, will first set the active device |
| /// configuration and re-claim USB interfaces. |
| fn request_interface(&mut self) -> Result<ClaimedInterface> { |
| let mut state = self.state.lock(); |
| |
| if state.active == 0 && !state.pending_cleanup { |
| debug!("Claiming all interfaces"); |
| state.claim_all()?; |
| state.pending_cleanup = true; |
| } |
| state.active += 1; |
| |
| loop { |
| if let Some(interface) = state.interfaces.pop_front() { |
| debug!( |
| "* Using interface {}", |
| interface.descriptor.interface_number |
| ); |
| return Ok(interface); |
| } |
| |
| state = self.interface_available.wait(state); |
| } |
| } |
| |
| /// Return an interface to the pool of interfaces. |
| fn free_interface(&mut self, interface: ClaimedInterface) { |
| debug!( |
| "* Returning interface {}", |
| interface.descriptor.interface_number |
| ); |
| let mut state = self.state.lock(); |
| state.interfaces.push_back(interface); |
| state.next_cleanup = Instant::now() + USB_CLEANUP_TIMEOUT; |
| state.pending_cleanup = true; |
| state.active -= 1; |
| |
| // Use notify_all instead of notify_one because the cleanup thread may also be |
| // waiting on this condition variable. |
| self.interface_available.notify_all(); |
| } |
| } |
| |
| pub struct UnplugDetector { |
| registration: Registration, |
| event_thread_run: Arc<AtomicBool>, |
| // This is always Some until the destructor runs. |
| event_thread: Option<std::thread::JoinHandle<()>>, |
| } |
| |
| impl UnplugDetector { |
| pub fn new( |
| device: rusb::Device<GlobalContext>, |
| shutdown_fd: EventFd, |
| shutdown: &'static AtomicBool, |
| delay_shutdown: bool, |
| ) -> Result<Self> { |
| let handler = CallbackHandler::new(device, shutdown_fd, shutdown, delay_shutdown); |
| let context = GlobalContext::default(); |
| let registration = context |
| .register_callback(None, None, None, Box::new(handler)) |
| .map_err(Error::RegisterCallback)?; |
| |
| // Spawn thread to handle triggering the plug/unplug events. |
| // While this is technically busy looping, the thread wakes up |
| // only once every 60 seconds unless an event is detected. |
| // When the callback is unregistered in Drop, an unplug event will |
| // be triggered so we will wake up immediately. |
| let run = Arc::new(AtomicBool::new(true)); |
| let thread_run = run.clone(); |
| let event_thread = std::thread::spawn(move || { |
| while thread_run.load(Ordering::Relaxed) { |
| let result = context.handle_events(None); |
| if let Err(e) = result { |
| error!("Failed to handle libusb events: {}", e); |
| } |
| } |
| info!("Shutting down libusb event thread."); |
| }); |
| |
| Ok(Self { |
| registration, |
| event_thread_run: run, |
| event_thread: Some(event_thread), |
| }) |
| } |
| } |
| |
| impl Drop for UnplugDetector { |
| fn drop(&mut self) { |
| self.event_thread_run.store(false, Ordering::Relaxed); |
| let context = GlobalContext::default(); |
| context.unregister_callback(self.registration); |
| |
| // Calling unregister_callback wakes the event thread, so this should complete quickly. |
| // Unwrap is safe because event_thread only becomes None at drop. |
| let _ = self.event_thread.take().unwrap().join(); |
| } |
| } |
| |
| struct CallbackHandler { |
| device: rusb::Device<GlobalContext>, |
| shutdown_fd: EventFd, |
| shutdown_requested: &'static AtomicBool, |
| delay_shutdown: bool, |
| } |
| |
| impl CallbackHandler { |
| fn new( |
| device: rusb::Device<GlobalContext>, |
| shutdown_fd: EventFd, |
| shutdown_requested: &'static AtomicBool, |
| delay_shutdown: bool, |
| ) -> Self { |
| Self { |
| device, |
| shutdown_fd, |
| shutdown_requested, |
| delay_shutdown, |
| } |
| } |
| |
| // If delayed shutdown is requested, wait for another shutdown event for up to 2s. |
| // This gives upstart time to send the process a signal after a USB devices is unplugged. |
| fn wait_for_shutdown(&mut self) -> Result<()> { |
| if !self.delay_shutdown { |
| return Ok(()); |
| } |
| |
| if self.shutdown_requested.load(Ordering::Relaxed) { |
| // No need to wait if shutdown has already been requested from another source. |
| return Ok(()); |
| } |
| |
| info!("Waiting for shutdown signal"); |
| let timeout = Duration::from_secs(2); |
| let ctx: PollContext<u32> = PollContext::new().map_err(Error::Poll)?; |
| ctx.add(&self.shutdown_fd, 1).map_err(Error::Poll)?; |
| ctx.wait_timeout(timeout).map_err(Error::Poll)?; |
| Ok(()) |
| } |
| } |
| |
| impl rusb::Hotplug<GlobalContext> for CallbackHandler { |
| fn device_arrived(&mut self, _device: rusb::Device<GlobalContext>) { |
| // Do nothing. |
| } |
| |
| fn device_left(&mut self, device: rusb::Device<GlobalContext>) { |
| if device == self.device { |
| info!("Device was unplugged, shutting down"); |
| |
| if let Err(e) = self.wait_for_shutdown() { |
| error!("Failed to wait for signal: {}", e); |
| } |
| |
| self.shutdown_requested.store(true, Ordering::Relaxed); |
| if let Err(e) = self.shutdown_fd.write(1) { |
| error!("Failed to trigger shutdown: {}", e); |
| } |
| } |
| } |
| } |
| |
| /// A UsbConnector represents an active connection to an IPPUSB device. |
| /// Users can temporarily request a UsbConnection from the UsbConnector using |
| /// get_connection(), and use that UsbConnection to perform I/O to the device. |
| #[derive(Clone)] |
| pub struct UsbConnector { |
| verbose_log: bool, |
| handle: Arc<rusb::DeviceHandle<GlobalContext>>, |
| manager: InterfaceManager, |
| } |
| |
| impl UsbConnector { |
| pub fn new(verbose_log: bool, bus_device: Option<(u8, u8)>) -> Result<UsbConnector> { |
| let device_list = rusb::DeviceList::new().map_err(Error::DeviceList)?; |
| |
| let (device, info) = match bus_device { |
| Some((bus, address)) => { |
| let device = device_list |
| .iter() |
| .find(|d| d.bus_number() == bus && d.address() == address) |
| .ok_or(Error::NoDevice)?; |
| |
| let info = read_ippusb_device_info(&device)?.ok_or(Error::NotIppUsb)?; |
| (device, info) |
| } |
| None => { |
| let mut selected_device: Option<(rusb::Device<GlobalContext>, IppusbDevice)> = None; |
| for device in device_list.iter() { |
| if let Some(info) = read_ippusb_device_info(&device)? { |
| selected_device = Some((device, info)); |
| break; |
| } |
| } |
| selected_device.ok_or(Error::NoDevice)? |
| } |
| }; |
| |
| info!( |
| "Selected device {}:{}", |
| device.bus_number(), |
| device.address() |
| ); |
| let mut handle = device.open().map_err(Error::OpenDevice)?; |
| handle |
| .set_auto_detach_kernel_driver(true) |
| .map_err(Error::DetachDrivers)?; |
| |
| // Detach any outstanding kernel drivers for the current config. |
| let config = device |
| .active_config_descriptor() |
| .map_err(Error::ReadConfigDescriptor)?; |
| |
| for interface in config.interfaces() { |
| match handle.detach_kernel_driver(interface.number()) { |
| Err(e) if e != rusb::Error::NotFound => return Err(Error::DetachDrivers(e)), |
| _ => {} |
| } |
| } |
| |
| handle |
| .set_active_configuration(info.config) |
| .map_err(Error::SetActiveConfig)?; |
| |
| // Open the IPPUSB interfaces. |
| let mut connections = Vec::new(); |
| for descriptor in info.interfaces { |
| let mut interface_handle = device.open().map_err(Error::OpenDevice)?; |
| interface_handle |
| .claim_interface(descriptor.interface_number) |
| .map_err(|e| Error::ClaimInterface(descriptor.interface_number, e))?; |
| interface_handle |
| .set_alternate_setting(descriptor.interface_number, descriptor.alternate_setting) |
| .map_err(|e| Error::SetAlternateSetting(descriptor.interface_number, e))?; |
| connections.push(ClaimedInterface { |
| handle: interface_handle, |
| descriptor, |
| }); |
| } |
| |
| let mgr_handle = device.open().map_err(Error::OpenDevice)?; |
| let mut manager = InterfaceManager::new(mgr_handle, info.config, connections); |
| manager.start_cleanup_thread()?; |
| |
| Ok(UsbConnector { |
| verbose_log, |
| handle: Arc::new(handle), |
| manager, |
| }) |
| } |
| |
| pub fn device(&self) -> rusb::Device<GlobalContext> { |
| self.handle.device() |
| } |
| |
| pub fn get_connection(&mut self) -> Result<UsbConnection> { |
| let interface = self.manager.request_interface()?; |
| Ok(UsbConnection::new( |
| self.verbose_log, |
| self.manager.clone(), |
| interface, |
| )) |
| } |
| } |
| |
| /// A struct representing a claimed IPPUSB interface. The owner of this struct |
| /// can communicate with the IPPUSB device via the Read and Write. |
| pub struct UsbConnection { |
| verbose_log: bool, |
| manager: InterfaceManager, |
| // `interface` is never None until the UsbConnection is dropped, at which point the |
| // ClaimedInterface is returned to the pool of connections in InterfaceManager. |
| interface: Option<ClaimedInterface>, |
| } |
| |
| impl UsbConnection { |
| fn new(verbose_log: bool, manager: InterfaceManager, interface: ClaimedInterface) -> Self { |
| Self { |
| verbose_log, |
| manager, |
| interface: Some(interface), |
| } |
| } |
| } |
| |
| impl Drop for UsbConnection { |
| fn drop(&mut self) { |
| // Unwrap because interface only becomes None at drop. |
| let interface = self.interface.take().unwrap(); |
| self.manager.free_interface(interface); |
| } |
| } |
| |
| fn to_io_error(err: rusb::Error) -> io::Error { |
| let kind = match err { |
| rusb::Error::InvalidParam => io::ErrorKind::InvalidInput, |
| rusb::Error::NotFound => io::ErrorKind::NotFound, |
| rusb::Error::Timeout => io::ErrorKind::TimedOut, |
| rusb::Error::Pipe => io::ErrorKind::BrokenPipe, |
| rusb::Error::Interrupted => io::ErrorKind::Interrupted, |
| _ => io::ErrorKind::Other, |
| }; |
| io::Error::new(kind, err) |
| } |
| |
| impl Write for &UsbConnection { |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| // Unwrap because interface only becomes None at drop. |
| let interface = self.interface.as_ref().unwrap(); |
| let endpoint = interface.descriptor.out_endpoint; |
| let written = interface |
| .handle |
| .write_bulk(endpoint, buf, USB_TRANSFER_TIMEOUT) |
| .map_err(to_io_error)?; |
| |
| if self.verbose_log { |
| let mut output = String::new(); |
| for byte in buf[..written].iter() { |
| let c = *byte as char; |
| if c == '\n' { |
| output.push(c); |
| } else { |
| for v in c.escape_default() { |
| output.push(v); |
| } |
| } |
| } |
| debug!("USB write:\n{}", output); |
| } |
| |
| Ok(written) |
| } |
| |
| fn flush(&mut self) -> io::Result<()> { |
| Ok(()) |
| } |
| } |
| |
| impl Read for &UsbConnection { |
| fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
| // Unwrap because interface only becomes None at drop. |
| let interface = self.interface.as_ref().unwrap(); |
| let endpoint = interface.descriptor.in_endpoint; |
| let start = Instant::now(); |
| let mut result = interface |
| .handle |
| .read_bulk(endpoint, buf, USB_TRANSFER_TIMEOUT) |
| .map_err(to_io_error); |
| let mut zero_reads = 0; |
| |
| // USB reads cannot hit EOF. We will retry after a short delay so that higher-level |
| // readers can pretend this doesn't exist. |
| while let Ok(0) = result { |
| zero_reads += 1; |
| if start.elapsed() > USB_TRANSFER_TIMEOUT { |
| result = Err(io::Error::new( |
| io::ErrorKind::TimedOut, |
| "Timed out waiting for non-zero USB read", |
| )); |
| break; |
| } |
| thread::sleep(Duration::from_millis(10)); |
| result = interface |
| .handle |
| .read_bulk(endpoint, buf, USB_TRANSFER_TIMEOUT) |
| .map_err(to_io_error); |
| } |
| |
| if zero_reads > 0 { |
| debug!( |
| "Spent {}ms waiting for {} 0-byte USB reads", |
| start.elapsed().as_millis(), |
| zero_reads |
| ); |
| } |
| result |
| } |
| } |