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::{self, UsbContext};
7use std::time::{Duration, Instant};
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::Context>,
14    handle: rusb::DeviceHandle<rusb::Context>,
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_pid: Option<(u16, u16)>,
24        usb_protocol: Option<(u8, u8, u8)>,
25        usb_serial: Option<&str>,
26    ) -> Result<Vec<(rusb::Device<rusb::Context>, String)>> {
27        let mut devices = Vec::new();
28        let mut deferred_log_messages = Vec::new();
29        for device in rusb::Context::new()?.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
43            if let Some((vid, pid)) = usb_vid_pid {
44                if descriptor.vendor_id() != vid {
45                    continue;
46                }
47                if descriptor.product_id() != pid {
48                    continue;
49                }
50            }
51            if let Some((class, subclass, protocol)) = usb_protocol {
52                let config = match device.active_config_descriptor() {
53                    Ok(desc) => desc,
54                    Err(e) => {
55                        deferred_log_messages.push(format!(
56                            "Could not read config descriptor for device at bus={} address={}: {}",
57                            device.bus_number(),
58                            device.address(),
59                            e,
60                        ));
61                        continue;
62                    }
63                };
64                let mut found = false;
65                for intf in config.interfaces() {
66                    for desc in intf.descriptors() {
67                        if desc.class_code() == class
68                            && desc.sub_class_code() == subclass
69                            && desc.protocol_code() == protocol
70                        {
71                            found = true;
72                        }
73                    }
74                }
75                if !found {
76                    continue;
77                }
78            }
79            let handle = match device.open() {
80                Ok(handle) => handle,
81                Err(e) => {
82                    deferred_log_messages.push(format!(
83                        "Could not open device at bus={} address={}: {}",
84                        device.bus_number(),
85                        device.address(),
86                        e,
87                    ));
88                    continue;
89                }
90            };
91
92            let serial_number = match handle.read_serial_number_string_ascii(&descriptor) {
93                Ok(sn) => sn,
94                Err(e) => {
95                    deferred_log_messages.push(format!(
96                        "Could not read serial number from device at bus={} address={}: {}",
97                        device.bus_number(),
98                        device.address(),
99                        e,
100                    ));
101                    continue;
102                }
103            };
104            if let Some(sn) = &usb_serial
105                && &serial_number != sn
106            {
107                continue;
108            }
109            devices.push((device, serial_number));
110        }
111
112        // We expect to find exactly one matching device. If that happens, the
113        // deferred log messages are unimportant. Otherwise, one of the messages
114        // may yield some insight into what went wrong, so they should be logged
115        // at a higher priority.
116        let severity = match devices.len() {
117            1 => log::Level::Info,
118            _ => log::Level::Error,
119        };
120        for s in deferred_log_messages {
121            log::log!(severity, "{}", s);
122        }
123        Ok(devices)
124    }
125
126    /// Create a new UsbBackend.
127    pub fn new(usb_vid: u16, usb_pid: u16, usb_serial: Option<&str>) -> Result<Self> {
128        let mut devices = UsbBackend::scan(Some((usb_vid, usb_pid)), None, usb_serial)?;
129        ensure!(!devices.is_empty(), TransportError::NoDevice);
130        ensure!(devices.len() == 1, TransportError::MultipleDevices);
131
132        let (device, serial_number) = devices.remove(0);
133        Ok(UsbBackend {
134            handle: device.open().context("USB open error")?,
135            device,
136            serial_number,
137            timeout: Duration::from_millis(500),
138        })
139    }
140
141    pub fn from_interface(
142        class: u8,
143        subclass: u8,
144        protocol: u8,
145        usb_serial: Option<&str>,
146    ) -> Result<Self> {
147        Self::from_interface_with_timeout(class, subclass, protocol, usb_serial, Duration::ZERO)
148    }
149
150    pub fn from_interface_with_timeout(
151        class: u8,
152        subclass: u8,
153        protocol: u8,
154        usb_serial: Option<&str>,
155        timeout: Duration,
156    ) -> Result<Self> {
157        let deadline = Instant::now() + timeout;
158        loop {
159            let mut devices =
160                UsbBackend::scan(None, Some((class, subclass, protocol)), usb_serial)?;
161            if devices.is_empty() {
162                if Instant::now() < deadline {
163                    std::thread::sleep(Duration::from_millis(100));
164                    continue;
165                } else {
166                    return Err(TransportError::NoDevice.into());
167                }
168            }
169            ensure!(devices.len() == 1, TransportError::MultipleDevices);
170
171            let (device, serial_number) = devices.remove(0);
172            return Ok(UsbBackend {
173                handle: device.open().context("USB open error")?,
174                device,
175                serial_number,
176                timeout: Duration::from_millis(500),
177            });
178        }
179    }
180
181    pub fn handle(&self) -> &rusb::DeviceHandle<rusb::Context> {
182        &self.handle
183    }
184
185    pub fn device(&self) -> &rusb::Device<rusb::Context> {
186        &self.device
187    }
188
189    pub fn get_vendor_id(&self) -> u16 {
190        self.device.device_descriptor().unwrap().vendor_id()
191    }
192
193    pub fn get_product_id(&self) -> u16 {
194        self.device.device_descriptor().unwrap().product_id()
195    }
196
197    /// Gets the usb serial number of the device.
198    pub fn get_serial_number(&self) -> &str {
199        self.serial_number.as_str()
200    }
201
202    pub fn set_active_configuration(&self, config: u8) -> Result<()> {
203        self.handle
204            .set_active_configuration(config)
205            .context("USB error")
206    }
207
208    pub fn claim_interface(&self, iface: u8) -> Result<()> {
209        self.handle.claim_interface(iface).context("USB error")
210    }
211
212    pub fn release_interface(&self, iface: u8) -> Result<()> {
213        self.handle.release_interface(iface).context("USB error")
214    }
215
216    pub fn set_alternate_setting(&self, iface: u8, setting: u8) -> Result<()> {
217        self.handle
218            .set_alternate_setting(iface, setting)
219            .context("USB error")
220    }
221
222    pub fn kernel_driver_active(&self, iface: u8) -> Result<bool> {
223        self.handle.kernel_driver_active(iface).context("USB error")
224    }
225
226    pub fn detach_kernel_driver(&self, iface: u8) -> Result<()> {
227        self.handle.detach_kernel_driver(iface).context("USB error")
228    }
229
230    pub fn attach_kernel_driver(&self, iface: u8) -> Result<()> {
231        self.handle.attach_kernel_driver(iface).context("USB error")
232    }
233
234    //
235    // Enumerating interfaces of the USB device.  The methods below leak rusb data structures,
236    // and may have to be refactored, when we convert UsbDevice into a trait, and want to
237    // support mocked implementations.
238    //
239
240    pub fn active_config_descriptor(&self) -> Result<rusb::ConfigDescriptor> {
241        self.device.active_config_descriptor().context("USB error")
242    }
243
244    pub fn bus_number(&self) -> u8 {
245        self.device.bus_number()
246    }
247
248    pub fn port_numbers(&self) -> Result<Vec<u8>> {
249        self.device.port_numbers().context("USB error")
250    }
251
252    pub fn read_string_descriptor_ascii(&self, idx: u8) -> Result<String> {
253        self.handle
254            .read_string_descriptor_ascii(idx)
255            .context("USB error")
256    }
257
258    pub fn reset(&self) -> Result<()> {
259        self.handle.reset().context("USB Error")
260    }
261
262    //
263    // Sending and receiving data, the below methods provide a nice interface.
264    //
265
266    /// Issue a USB control request with optional host-to-device data.
267    pub fn write_control(
268        &self,
269        request_type: u8,
270        request: u8,
271        value: u16,
272        index: u16,
273        buf: &[u8],
274    ) -> Result<usize> {
275        self.handle
276            .write_control(request_type, request, value, index, buf, self.timeout)
277            .context("USB error")
278    }
279
280    /// Issue a USB control request with optional device-to-host data.
281    pub fn read_control(
282        &self,
283        request_type: u8,
284        request: u8,
285        value: u16,
286        index: u16,
287        buf: &mut [u8],
288    ) -> Result<usize> {
289        self.handle
290            .read_control(request_type, request, value, index, buf, self.timeout)
291            .context("USB error")
292    }
293
294    /// Read bulk data bytes to given USB endpoint.
295    pub fn read_bulk(&self, endpoint: u8, data: &mut [u8]) -> Result<usize> {
296        let len = self
297            .handle
298            .read_bulk(endpoint, data, self.timeout)
299            .context("USB error")?;
300        Ok(len)
301    }
302
303    /// Read bulk data bytes to given USB endpoint.
304    pub fn read_bulk_timeout(
305        &self,
306        endpoint: u8,
307        data: &mut [u8],
308        timeout: Duration,
309    ) -> Result<usize> {
310        let len = self
311            .handle
312            .read_bulk(endpoint, data, timeout)
313            .context("USB error")?;
314        Ok(len)
315    }
316
317    /// Write bulk data bytes to given USB endpoint.
318    pub fn write_bulk(&self, endpoint: u8, data: &[u8]) -> Result<usize> {
319        let len = self
320            .handle
321            .write_bulk(endpoint, data, self.timeout)
322            .context("USB error")?;
323        Ok(len)
324    }
325}
326
327// Structure representing a USB hub. The device needs to have sufficient permission
328// to be opened.
329pub struct UsbHub {
330    handle: rusb::DeviceHandle<rusb::Context>,
331}
332
333// USB hub operation.
334pub enum UsbHubOp {
335    // Power-off a specific port.
336    PowerOff,
337    // Power-on a specific port.
338    PowerOn,
339    // Suspend a specific port.
340    Suspend,
341    // Suspend a specific port.
342    Resume,
343    // Reset a specific port.
344    Reset,
345}
346
347const PORT_SUSPEND: u16 = 2;
348const PORT_RESET: u16 = 4;
349const PORT_POWER: u16 = 8;
350
351impl UsbHub {
352    // Construct a hub from a device.
353    pub fn from_device(dev: &rusb::Device<rusb::Context>) -> Result<UsbHub> {
354        // Make sure the device is a hub.
355        let dev_desc = dev.device_descriptor()?;
356        // Assume that if the device has the HUB class then Linux will already enforce
357        // that it follows the specification.
358        ensure!(
359            dev_desc.class_code() == rusb::constants::LIBUSB_CLASS_HUB,
360            "device is not a hub"
361        );
362        Ok(UsbHub {
363            handle: dev.open().context("cannot open hub")?,
364        })
365    }
366
367    // Perform an operation.
368    pub fn op(&self, op: UsbHubOp, port: u8, timeout: Duration) -> Result<()> {
369        let (value, set_feature) = match op {
370            UsbHubOp::Suspend => (PORT_SUSPEND, true),
371            UsbHubOp::Resume => (PORT_SUSPEND, false),
372            UsbHubOp::Reset => (PORT_RESET, true),
373            UsbHubOp::PowerOn => (PORT_POWER, true),
374            UsbHubOp::PowerOff => (PORT_POWER, false),
375        };
376        let req = if set_feature {
377            rusb::constants::LIBUSB_REQUEST_SET_FEATURE
378        } else {
379            rusb::constants::LIBUSB_REQUEST_CLEAR_FEATURE
380        };
381        let req_type = rusb::constants::LIBUSB_RECIPIENT_OTHER
382            | rusb::constants::LIBUSB_REQUEST_TYPE_CLASS
383            | rusb::constants::LIBUSB_ENDPOINT_OUT;
384        let _ = self
385            .handle
386            .write_control(req_type, req, value, port as u16, &[], timeout)?;
387        Ok(())
388    }
389}