| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use anyhow::{ensure, Context, Result}; |
| use rusb; |
| use std::time::Duration; |
| |
| use crate::transport::TransportError; |
| |
| /// The `UsbBackend` provides low-level USB access to debugging devices. |
| pub struct UsbBackend { |
| device: rusb::Device<rusb::GlobalContext>, |
| handle: rusb::DeviceHandle<rusb::GlobalContext>, |
| serial_number: String, |
| timeout: Duration, |
| } |
| |
| impl UsbBackend { |
| /// Scan the USB bus for a device matching VID/PID, and optionally also matching a serial |
| /// number. |
| pub fn scan( |
| usb_vid: u16, |
| usb_pid: u16, |
| usb_serial: Option<&str>, |
| ) -> Result<Vec<(rusb::Device<rusb::GlobalContext>, String)>> { |
| let mut devices = Vec::new(); |
| let mut deferred_log_messages = Vec::new(); |
| for device in rusb::devices().context("USB error")?.iter() { |
| let descriptor = match device.device_descriptor() { |
| Ok(desc) => desc, |
| Err(e) => { |
| deferred_log_messages.push(format!( |
| "Could not read device descriptor for device at bus={} address={}: {}", |
| device.bus_number(), |
| device.address(), |
| e, |
| )); |
| continue; |
| } |
| }; |
| if descriptor.vendor_id() != usb_vid { |
| continue; |
| } |
| if descriptor.product_id() != usb_pid { |
| continue; |
| } |
| let handle = match device.open() { |
| Ok(handle) => handle, |
| Err(e) => { |
| deferred_log_messages.push(format!( |
| "Could not open device at bus={} address={}: {}", |
| device.bus_number(), |
| device.address(), |
| e, |
| )); |
| continue; |
| } |
| }; |
| let serial_number = match handle.read_serial_number_string_ascii(&descriptor) { |
| Ok(sn) => sn, |
| Err(e) => { |
| deferred_log_messages.push(format!( |
| "Could not read serial number from device at bus={} address={}: {}", |
| device.bus_number(), |
| device.address(), |
| e, |
| )); |
| continue; |
| } |
| }; |
| if let Some(sn) = &usb_serial { |
| if &serial_number != sn { |
| continue; |
| } |
| } |
| devices.push((device, serial_number)); |
| } |
| |
| // We expect to find exactly one matching device. If that happens, the |
| // deferred log messages are unimportant. Otherwise, one of the messages |
| // may yield some insight into what went wrong, so they should be logged |
| // at a higher priority. |
| let severity = match devices.len() { |
| 1 => log::Level::Info, |
| _ => log::Level::Error, |
| }; |
| for s in deferred_log_messages { |
| log::log!(severity, "{}", s); |
| } |
| |
| Ok(devices) |
| } |
| |
| /// Create a new UsbBackend. |
| pub fn new(usb_vid: u16, usb_pid: u16, usb_serial: Option<&str>) -> Result<Self> { |
| let mut devices = UsbBackend::scan(usb_vid, usb_pid, usb_serial)?; |
| ensure!(!devices.is_empty(), TransportError::NoDevice); |
| ensure!(devices.len() == 1, TransportError::MultipleDevices); |
| |
| let (device, serial_number) = devices.remove(0); |
| Ok(UsbBackend { |
| handle: device.open().context("USB open error")?, |
| device, |
| serial_number, |
| timeout: Duration::from_millis(500), |
| }) |
| } |
| |
| pub fn get_vendor_id(&self) -> u16 { |
| self.device.device_descriptor().unwrap().vendor_id() |
| } |
| |
| pub fn get_product_id(&self) -> u16 { |
| self.device.device_descriptor().unwrap().product_id() |
| } |
| |
| /// Gets the usb serial number of the device. |
| pub fn get_serial_number(&self) -> &str { |
| self.serial_number.as_str() |
| } |
| |
| // |
| // Enumerating interfaces of the USB device. The methods below leak rusb data structures, |
| // and may have to be refactored, when we convert UsbDevice into a trait, and want to |
| // support mocked implementations. |
| // |
| |
| pub fn claim_interface(&mut self, iface: u8) -> Result<()> { |
| self.handle.claim_interface(iface).context("USB error") |
| } |
| |
| pub fn active_config_descriptor(&self) -> Result<rusb::ConfigDescriptor> { |
| self.device.active_config_descriptor().context("USB error") |
| } |
| |
| pub fn bus_number(&self) -> u8 { |
| self.device.bus_number() |
| } |
| |
| pub fn port_numbers(&self) -> Result<Vec<u8>> { |
| self.device.port_numbers().context("USB error") |
| } |
| |
| pub fn read_string_descriptor_ascii(&self, idx: u8) -> Result<String> { |
| self.handle |
| .read_string_descriptor_ascii(idx) |
| .context("USB error") |
| } |
| |
| // |
| // Sending and receiving data, the below methods provide a nice interface. |
| // |
| |
| /// Issue a USB control request with optional host-to-device data. |
| pub fn write_control( |
| &self, |
| request_type: u8, |
| request: u8, |
| value: u16, |
| index: u16, |
| buf: &[u8], |
| ) -> Result<usize> { |
| self.handle |
| .write_control(request_type, request, value, index, buf, self.timeout) |
| .context("USB error") |
| } |
| |
| /// Issue a USB control request with optional device-to-host data. |
| pub fn read_control( |
| &self, |
| request_type: u8, |
| request: u8, |
| value: u16, |
| index: u16, |
| buf: &mut [u8], |
| ) -> Result<usize> { |
| self.handle |
| .read_control(request_type, request, value, index, buf, self.timeout) |
| .context("USB error") |
| } |
| |
| /// Read bulk data bytes to given USB endpoint. |
| pub fn read_bulk(&self, endpoint: u8, data: &mut [u8]) -> Result<usize> { |
| let len = self |
| .handle |
| .read_bulk(endpoint, data, self.timeout) |
| .context("USB error")?; |
| Ok(len) |
| } |
| |
| /// Write bulk data bytes to given USB endpoint. |
| pub fn write_bulk(&self, endpoint: u8, data: &[u8]) -> Result<usize> { |
| let len = self |
| .handle |
| .write_bulk(endpoint, data, self.timeout) |
| .context("USB error")?; |
| Ok(len) |
| } |
| } |