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 serial_str = if let Some(s) = usb_serial {
129            format!(" (serial={})", s)
130        } else {
131            String::new()
132        };
133        let mut devices = UsbBackend::scan(Some((usb_vid, usb_pid)), None, usb_serial)?;
134        if devices.is_empty() {
135            return Err(TransportError::NoDevice(format!(
136                "vid:pid=0x{:04x}:0x{:04x}{}",
137                usb_vid, usb_pid, serial_str
138            ))
139            .into());
140        }
141        if devices.len() > 1 {
142            return Err(TransportError::MultipleDevices(
143                format!("{:?}", devices),
144                format!("vid:pid=0x{:04x}:0x{:04x}{}", usb_vid, usb_pid, serial_str),
145            )
146            .into());
147        }
148
149        let (device, serial_number) = devices.remove(0);
150        Ok(UsbBackend {
151            handle: device.open().context("USB open error")?,
152            device,
153            serial_number,
154            timeout: Duration::from_millis(500),
155        })
156    }
157
158    pub fn from_interface(
159        class: u8,
160        subclass: u8,
161        protocol: u8,
162        usb_serial: Option<&str>,
163    ) -> Result<Self> {
164        Self::from_interface_with_timeout(class, subclass, protocol, usb_serial, Duration::ZERO)
165    }
166
167    pub fn from_interface_with_timeout(
168        class: u8,
169        subclass: u8,
170        protocol: u8,
171        usb_serial: Option<&str>,
172        timeout: Duration,
173    ) -> Result<Self> {
174        let deadline = Instant::now() + timeout;
175        let serial_str = if let Some(s) = usb_serial {
176            format!(" (serial={})", s)
177        } else {
178            String::new()
179        };
180        loop {
181            let mut devices =
182                UsbBackend::scan(None, Some((class, subclass, protocol)), usb_serial)?;
183            if devices.is_empty() {
184                if Instant::now() < deadline {
185                    std::thread::sleep(Duration::from_millis(100));
186                    continue;
187                } else {
188                    return Err(TransportError::NoDevice(format!(
189                        "class:subclass:protocol=0x{:02x}:0x{:02x}:0x{:02x}{}",
190                        class, subclass, protocol, serial_str
191                    ))
192                    .into());
193                }
194            }
195            if devices.len() > 1 {
196                return Err(TransportError::MultipleDevices(
197                    format!("{:?}", devices),
198                    format!(
199                        "class:subclass:protocol=0x{:02x}:0x{:02x}:0x{:02x}{}",
200                        class, subclass, protocol, serial_str
201                    ),
202                )
203                .into());
204            }
205
206            let (device, serial_number) = devices.remove(0);
207            return Ok(UsbBackend {
208                handle: device.open().context("USB open error")?,
209                device,
210                serial_number,
211                timeout: Duration::from_millis(500),
212            });
213        }
214    }
215
216    pub fn handle(&self) -> &rusb::DeviceHandle<rusb::Context> {
217        &self.handle
218    }
219
220    pub fn device(&self) -> &rusb::Device<rusb::Context> {
221        &self.device
222    }
223
224    pub fn get_vendor_id(&self) -> u16 {
225        self.device.device_descriptor().unwrap().vendor_id()
226    }
227
228    pub fn get_product_id(&self) -> u16 {
229        self.device.device_descriptor().unwrap().product_id()
230    }
231
232    /// Gets the usb serial number of the device.
233    pub fn get_serial_number(&self) -> &str {
234        self.serial_number.as_str()
235    }
236
237    pub fn set_active_configuration(&self, config: u8) -> Result<()> {
238        self.handle
239            .set_active_configuration(config)
240            .context("USB error")
241    }
242
243    pub fn claim_interface(&self, iface: u8) -> Result<()> {
244        self.handle.claim_interface(iface).context("USB error")
245    }
246
247    pub fn release_interface(&self, iface: u8) -> Result<()> {
248        self.handle.release_interface(iface).context("USB error")
249    }
250
251    pub fn set_alternate_setting(&self, iface: u8, setting: u8) -> Result<()> {
252        self.handle
253            .set_alternate_setting(iface, setting)
254            .context("USB error")
255    }
256
257    pub fn kernel_driver_active(&self, iface: u8) -> Result<bool> {
258        self.handle.kernel_driver_active(iface).context("USB error")
259    }
260
261    pub fn detach_kernel_driver(&self, iface: u8) -> Result<()> {
262        self.handle.detach_kernel_driver(iface).context("USB error")
263    }
264
265    pub fn attach_kernel_driver(&self, iface: u8) -> Result<()> {
266        self.handle.attach_kernel_driver(iface).context("USB error")
267    }
268
269    //
270    // Enumerating interfaces of the USB device.  The methods below leak rusb data structures,
271    // and may have to be refactored, when we convert UsbDevice into a trait, and want to
272    // support mocked implementations.
273    //
274
275    pub fn active_config_descriptor(&self) -> Result<rusb::ConfigDescriptor> {
276        self.device.active_config_descriptor().context("USB error")
277    }
278
279    pub fn bus_number(&self) -> u8 {
280        self.device.bus_number()
281    }
282
283    pub fn port_numbers(&self) -> Result<Vec<u8>> {
284        self.device.port_numbers().context("USB error")
285    }
286
287    pub fn read_string_descriptor_ascii(&self, idx: u8) -> Result<String> {
288        self.handle
289            .read_string_descriptor_ascii(idx)
290            .context("USB error")
291    }
292
293    pub fn reset(&self) -> Result<()> {
294        self.handle.reset().context("USB Error")
295    }
296
297    //
298    // Sending and receiving data, the below methods provide a nice interface.
299    //
300
301    /// Issue a USB control request with optional host-to-device data.
302    pub fn write_control(
303        &self,
304        request_type: u8,
305        request: u8,
306        value: u16,
307        index: u16,
308        buf: &[u8],
309    ) -> Result<usize> {
310        self.handle
311            .write_control(request_type, request, value, index, buf, self.timeout)
312            .context("USB error")
313    }
314
315    /// Issue a USB control request with optional device-to-host data.
316    pub fn read_control(
317        &self,
318        request_type: u8,
319        request: u8,
320        value: u16,
321        index: u16,
322        buf: &mut [u8],
323    ) -> Result<usize> {
324        self.handle
325            .read_control(request_type, request, value, index, buf, self.timeout)
326            .context("USB error")
327    }
328
329    /// Read bulk data bytes to given USB endpoint.
330    pub fn read_bulk(&self, endpoint: u8, data: &mut [u8]) -> Result<usize> {
331        let len = self
332            .handle
333            .read_bulk(endpoint, data, self.timeout)
334            .context("USB error")?;
335        Ok(len)
336    }
337
338    /// Read bulk data bytes to given USB endpoint.
339    pub fn read_bulk_timeout(
340        &self,
341        endpoint: u8,
342        data: &mut [u8],
343        timeout: Duration,
344    ) -> Result<usize> {
345        let len = self
346            .handle
347            .read_bulk(endpoint, data, timeout)
348            .context("USB error")?;
349        Ok(len)
350    }
351
352    /// Write bulk data bytes to given USB endpoint.
353    pub fn write_bulk(&self, endpoint: u8, data: &[u8]) -> Result<usize> {
354        let len = self
355            .handle
356            .write_bulk(endpoint, data, self.timeout)
357            .context("USB error")?;
358        Ok(len)
359    }
360}
361
362// Structure representing a USB hub. The device needs to have sufficient permission
363// to be opened.
364pub struct UsbHub {
365    handle: rusb::DeviceHandle<rusb::Context>,
366}
367
368// USB hub operation.
369pub enum UsbHubOp {
370    // Power-off a specific port.
371    PowerOff,
372    // Power-on a specific port.
373    PowerOn,
374    // Suspend a specific port.
375    Suspend,
376    // Suspend a specific port.
377    Resume,
378    // Reset a specific port.
379    Reset,
380}
381
382const PORT_SUSPEND: u16 = 2;
383const PORT_RESET: u16 = 4;
384const PORT_POWER: u16 = 8;
385
386impl UsbHub {
387    // Construct a hub from a device.
388    pub fn from_device(dev: &rusb::Device<rusb::Context>) -> Result<UsbHub> {
389        // Make sure the device is a hub.
390        let dev_desc = dev.device_descriptor()?;
391        // Assume that if the device has the HUB class then Linux will already enforce
392        // that it follows the specification.
393        ensure!(
394            dev_desc.class_code() == rusb::constants::LIBUSB_CLASS_HUB,
395            "device is not a hub"
396        );
397        Ok(UsbHub {
398            handle: dev.open().with_context(|| {
399                format!(
400                    "Cannot access USB hub on bus {bus}, address {addr}\n\
401                If this test requires access to the HUB, you need to make sure that \
402                the program has sufficient permissions to access the hub\n\
403                See sw/host/tests/chip/usb/README.md for more information\n\
404                The following command may fix the issue:\n\
405                sudo chmod 0666 /dev/bus/usb/{bus:03}/{addr:03}",
406                    bus = dev.bus_number(),
407                    addr = dev.address(),
408                )
409            })?,
410        })
411    }
412
413    pub fn device(&self) -> rusb::Device<rusb::Context> {
414        self.handle.device()
415    }
416
417    // Report the status of a port (only returns the port status, not the port change).
418    fn port_status(&self, port: u8, timeout: Duration) -> Result<u16> {
419        let req_type = rusb::constants::LIBUSB_RECIPIENT_OTHER
420            | rusb::constants::LIBUSB_REQUEST_TYPE_CLASS
421            | rusb::constants::LIBUSB_ENDPOINT_IN;
422        let mut status = [0u8; 4];
423        let _ = self.handle.read_control(
424            req_type,
425            rusb::constants::LIBUSB_REQUEST_GET_STATUS,
426            0,
427            port as u16,
428            &mut status,
429            timeout,
430        )?;
431        Ok(status[0] as u16 | (status[1] as u16) << 8)
432    }
433
434    // Perform an operation.
435    pub fn op(&self, op: UsbHubOp, port: u8, timeout: Duration, check_status: bool) -> Result<()> {
436        let (feature_index, set_feature, human_op) = match op {
437            UsbHubOp::Suspend => (PORT_SUSPEND, true, "suspend"),
438            UsbHubOp::Resume => (PORT_SUSPEND, false, "resume"),
439            UsbHubOp::Reset => (PORT_RESET, true, "reset"),
440            UsbHubOp::PowerOn => (PORT_POWER, true, "power on"),
441            UsbHubOp::PowerOff => (PORT_POWER, false, "power off"),
442        };
443        let req = if set_feature {
444            rusb::constants::LIBUSB_REQUEST_SET_FEATURE
445        } else {
446            rusb::constants::LIBUSB_REQUEST_CLEAR_FEATURE
447        };
448        let req_type = rusb::constants::LIBUSB_RECIPIENT_OTHER
449            | rusb::constants::LIBUSB_REQUEST_TYPE_CLASS
450            | rusb::constants::LIBUSB_ENDPOINT_OUT;
451        // Expected port status after the operation.
452        let port_status_mask = 1u16 << feature_index;
453        let port_status_after = if set_feature { port_status_mask } else { 0u16 };
454
455        // Perform operation.
456        let _ =
457            self.handle
458                .write_control(req_type, req, feature_index, port as u16, &[], timeout)?;
459        // Wait until port has changed status.
460        if !check_status {
461            return Ok(());
462        }
463        let start = Instant::now();
464        loop {
465            let port_status = self.port_status(port, timeout)?;
466            if port_status & port_status_mask == port_status_after {
467                break;
468            }
469            if start.elapsed() >= timeout {
470                anyhow::bail!(
471                    "Trying to {} port {} but port did not change status (last status was {:x})",
472                    human_op,
473                    port,
474                    port_status
475                );
476            }
477        }
478        log::info!("{} performed in {:#?}", human_op, start.elapsed());
479
480        Ok(())
481    }
482}