opentitanlib/util/
usb.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Context, Result, ensure};
6use rusb;
7use std::time::Duration;
8
9use crate::transport::TransportError;
10
11/// The `UsbBackend` provides low-level USB access to debugging devices.
12pub struct UsbBackend {
13    device: rusb::Device<rusb::GlobalContext>,
14    handle: rusb::DeviceHandle<rusb::GlobalContext>,
15    serial_number: String,
16    timeout: Duration,
17}
18
19impl UsbBackend {
20    /// Scan the USB bus for a device matching VID/PID, and optionally also matching a serial
21    /// number.
22    pub fn scan(
23        usb_vid: u16,
24        usb_pid: u16,
25        usb_serial: Option<&str>,
26    ) -> Result<Vec<(rusb::Device<rusb::GlobalContext>, String)>> {
27        let mut devices = Vec::new();
28        let mut deferred_log_messages = Vec::new();
29        for device in rusb::devices().context("USB error")?.iter() {
30            let descriptor = match device.device_descriptor() {
31                Ok(desc) => desc,
32                Err(e) => {
33                    deferred_log_messages.push(format!(
34                        "Could not read device descriptor for device at bus={} address={}: {}",
35                        device.bus_number(),
36                        device.address(),
37                        e,
38                    ));
39                    continue;
40                }
41            };
42            if descriptor.vendor_id() != usb_vid {
43                continue;
44            }
45            if descriptor.product_id() != usb_pid {
46                continue;
47            }
48            let handle = match device.open() {
49                Ok(handle) => handle,
50                Err(e) => {
51                    deferred_log_messages.push(format!(
52                        "Could not open device at bus={} address={}: {}",
53                        device.bus_number(),
54                        device.address(),
55                        e,
56                    ));
57                    continue;
58                }
59            };
60            let serial_number = match handle.read_serial_number_string_ascii(&descriptor) {
61                Ok(sn) => sn,
62                Err(e) => {
63                    deferred_log_messages.push(format!(
64                        "Could not read serial number from device at bus={} address={}: {}",
65                        device.bus_number(),
66                        device.address(),
67                        e,
68                    ));
69                    continue;
70                }
71            };
72            if let Some(sn) = &usb_serial
73                && &serial_number != sn
74            {
75                continue;
76            }
77            devices.push((device, serial_number));
78        }
79
80        // We expect to find exactly one matching device. If that happens, the
81        // deferred log messages are unimportant. Otherwise, one of the messages
82        // may yield some insight into what went wrong, so they should be logged
83        // at a higher priority.
84        let severity = match devices.len() {
85            1 => log::Level::Info,
86            _ => log::Level::Error,
87        };
88        for s in deferred_log_messages {
89            log::log!(severity, "{}", s);
90        }
91
92        Ok(devices)
93    }
94
95    /// Create a new UsbBackend.
96    pub fn new(usb_vid: u16, usb_pid: u16, usb_serial: Option<&str>) -> Result<Self> {
97        let mut devices = UsbBackend::scan(usb_vid, usb_pid, usb_serial)?;
98        ensure!(!devices.is_empty(), TransportError::NoDevice);
99        ensure!(devices.len() == 1, TransportError::MultipleDevices);
100
101        let (device, serial_number) = devices.remove(0);
102        Ok(UsbBackend {
103            handle: device.open().context("USB open error")?,
104            device,
105            serial_number,
106            timeout: Duration::from_millis(500),
107        })
108    }
109
110    pub fn get_vendor_id(&self) -> u16 {
111        self.device.device_descriptor().unwrap().vendor_id()
112    }
113
114    pub fn get_product_id(&self) -> u16 {
115        self.device.device_descriptor().unwrap().product_id()
116    }
117
118    /// Gets the usb serial number of the device.
119    pub fn get_serial_number(&self) -> &str {
120        self.serial_number.as_str()
121    }
122
123    pub fn set_active_configuration(&mut self, config: u8) -> Result<()> {
124        self.handle
125            .set_active_configuration(config)
126            .context("USB error")
127    }
128
129    pub fn claim_interface(&mut self, iface: u8) -> Result<()> {
130        self.handle.claim_interface(iface).context("USB error")
131    }
132
133    pub fn release_interface(&mut self, iface: u8) -> Result<()> {
134        self.handle.release_interface(iface).context("USB error")
135    }
136
137    pub fn kernel_driver_active(&self, iface: u8) -> Result<bool> {
138        self.handle.kernel_driver_active(iface).context("USB error")
139    }
140
141    pub fn detach_kernel_driver(&mut self, iface: u8) -> Result<()> {
142        self.handle.detach_kernel_driver(iface).context("USB error")
143    }
144
145    pub fn attach_kernel_driver(&mut self, iface: u8) -> Result<()> {
146        self.handle.attach_kernel_driver(iface).context("USB error")
147    }
148
149    //
150    // Enumerating interfaces of the USB device.  The methods below leak rusb data structures,
151    // and may have to be refactored, when we convert UsbDevice into a trait, and want to
152    // support mocked implementations.
153    //
154
155    pub fn active_config_descriptor(&self) -> Result<rusb::ConfigDescriptor> {
156        self.device.active_config_descriptor().context("USB error")
157    }
158
159    pub fn bus_number(&self) -> u8 {
160        self.device.bus_number()
161    }
162
163    pub fn port_numbers(&self) -> Result<Vec<u8>> {
164        self.device.port_numbers().context("USB error")
165    }
166
167    pub fn read_string_descriptor_ascii(&self, idx: u8) -> Result<String> {
168        self.handle
169            .read_string_descriptor_ascii(idx)
170            .context("USB error")
171    }
172
173    //
174    // Sending and receiving data, the below methods provide a nice interface.
175    //
176
177    /// Issue a USB control request with optional host-to-device data.
178    pub fn write_control(
179        &self,
180        request_type: u8,
181        request: u8,
182        value: u16,
183        index: u16,
184        buf: &[u8],
185    ) -> Result<usize> {
186        self.handle
187            .write_control(request_type, request, value, index, buf, self.timeout)
188            .context("USB error")
189    }
190
191    /// Issue a USB control request with optional device-to-host data.
192    pub fn read_control(
193        &self,
194        request_type: u8,
195        request: u8,
196        value: u16,
197        index: u16,
198        buf: &mut [u8],
199    ) -> Result<usize> {
200        self.handle
201            .read_control(request_type, request, value, index, buf, self.timeout)
202            .context("USB error")
203    }
204
205    /// Read bulk data bytes to given USB endpoint.
206    pub fn read_bulk(&self, endpoint: u8, data: &mut [u8]) -> Result<usize> {
207        let len = self
208            .handle
209            .read_bulk(endpoint, data, self.timeout)
210            .context("USB error")?;
211        Ok(len)
212    }
213
214    /// Read bulk data bytes to given USB endpoint.
215    pub fn read_bulk_timeout(
216        &self,
217        endpoint: u8,
218        data: &mut [u8],
219        timeout: Duration,
220    ) -> Result<usize> {
221        let len = self
222            .handle
223            .read_bulk(endpoint, data, timeout)
224            .context("USB error")?;
225        Ok(len)
226    }
227
228    /// Write bulk data bytes to given USB endpoint.
229    pub fn write_bulk(&self, endpoint: u8, data: &[u8]) -> Result<usize> {
230        let len = self
231            .handle
232            .write_bulk(endpoint, data, self.timeout)
233            .context("USB error")?;
234        Ok(len)
235    }
236}