|  | // 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) | 
|  | } | 
|  | } |