opentitanlib/transport/common/
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, bail, ensure};
6use rusb::{self, UsbContext};
7use std::fs;
8use std::time::{Duration, Instant};
9
10use crate::io::usb::{UsbContext as OtUsbContext, UsbDevice, desc};
11use crate::transport::TransportError;
12
13/// Represents a device provided by the `rusb` crate.
14pub struct RusbDevice {
15    handle: rusb::DeviceHandle<rusb::Context>,
16    serial_number: Option<String>,
17    timeout: Duration,
18    device_desc: Vec<u8>,
19    configurations: Vec<Vec<u8>>,
20}
21
22/// Represents a backend using the `rusb` crate.
23#[derive(Default)]
24pub struct RusbContext {}
25
26impl RusbContext {
27    pub fn new() -> Self {
28        RusbContext::default()
29    }
30
31    /// Scan the USB bus for a device matching VID/PID, and optionally also matching a serial
32    /// number.
33    fn scan(
34        usb_vid_pid: Option<(u16, u16)>,
35        usb_protocol: Option<(u8, u8, u8)>,
36        usb_serial: Option<&str>,
37    ) -> Result<Vec<(rusb::Device<rusb::Context>, Option<String>)>> {
38        let mut devices = Vec::new();
39        let mut deferred_log_messages = Vec::new();
40        // The global context sometimes fails to detect new devices which causes some
41        // really puzzling errors. Here we create a new context for every scan.
42        // Although less efficient, this works around most of the issues with hotplug.
43        for device in rusb::Context::new()?.devices().context("USB error")?.iter() {
44            let descriptor = match device.device_descriptor() {
45                Ok(desc) => desc,
46                Err(e) => {
47                    deferred_log_messages.push(format!(
48                        "Could not read device descriptor for device at bus={} address={}: {}",
49                        device.bus_number(),
50                        device.address(),
51                        e,
52                    ));
53                    continue;
54                }
55            };
56
57            if let Some((vid, pid)) = usb_vid_pid {
58                if descriptor.vendor_id() != vid {
59                    continue;
60                }
61                if descriptor.product_id() != pid {
62                    continue;
63                }
64            }
65            if let Some((class, subclass, protocol)) = usb_protocol {
66                let config = match device.active_config_descriptor() {
67                    Ok(desc) => desc,
68                    Err(e) => {
69                        deferred_log_messages.push(format!(
70                            "Could not read config descriptor for device at bus={} address={}: {}",
71                            device.bus_number(),
72                            device.address(),
73                            e,
74                        ));
75                        continue;
76                    }
77                };
78                let mut found = false;
79                for intf in config.interfaces() {
80                    for desc in intf.descriptors() {
81                        if desc.class_code() == class
82                            && desc.sub_class_code() == subclass
83                            && desc.protocol_code() == protocol
84                        {
85                            found = true;
86                        }
87                    }
88                }
89                if !found {
90                    continue;
91                }
92            }
93            let handle = match device.open() {
94                Ok(handle) => handle,
95                Err(e) => {
96                    deferred_log_messages.push(format!(
97                        "Could not open device at bus={} address={}: {}",
98                        device.bus_number(),
99                        device.address(),
100                        e,
101                    ));
102                    continue;
103                }
104            };
105
106            let serial_number = if descriptor.serial_number_string_index().is_some() {
107                match handle.read_serial_number_string_ascii(&descriptor) {
108                    Ok(sn) => Some(sn),
109                    Err(e) => {
110                        deferred_log_messages.push(format!(
111                            "Could not read serial number from device at bus={} address={}: {}",
112                            device.bus_number(),
113                            device.address(),
114                            e,
115                        ));
116                        continue;
117                    }
118                }
119            } else {
120                None
121            };
122            if usb_serial.is_some() && serial_number.as_deref() != usb_serial {
123                continue;
124            }
125            devices.push((device, serial_number));
126        }
127
128        // We expect to find exactly one matching device. If that happens, the
129        // deferred log messages are unimportant. Otherwise, one of the messages
130        // may yield some insight into what went wrong, so they should be logged
131        // at a higher priority.
132        let severity = match devices.len() {
133            1 => log::Level::Info,
134            _ => log::Level::Error,
135        };
136        for s in deferred_log_messages {
137            log::log!(severity, "{}", s);
138        }
139        Ok(devices)
140    }
141}
142
143impl OtUsbContext for RusbContext {
144    fn device_by_id_with_timeout(
145        &self,
146        usb_vid: u16,
147        usb_pid: u16,
148        usb_serial: Option<&str>,
149        timeout: Duration,
150    ) -> Result<Box<dyn UsbDevice>> {
151        let deadline = Instant::now() + timeout;
152        let serial_str = if let Some(s) = usb_serial {
153            format!(" (serial={})", s)
154        } else {
155            String::new()
156        };
157        loop {
158            let mut devices = RusbContext::scan(Some((usb_vid, usb_pid)), None, usb_serial)?;
159            if devices.is_empty() {
160                if Instant::now() < deadline {
161                    std::thread::sleep(Duration::from_millis(100));
162                    continue;
163                } else {
164                    return Err(TransportError::NoDevice(format!(
165                        "vid:pid=0x{:04x}:0x{:04x}{}",
166                        usb_vid, usb_pid, serial_str
167                    ))
168                    .into());
169                }
170            }
171            if devices.len() > 1 {
172                return Err(TransportError::MultipleDevices(
173                    format!("{:?}", devices),
174                    format!("vid:pid=0x{:04x}:0x{:04x}{}", usb_vid, usb_pid, serial_str),
175                )
176                .into());
177            }
178
179            let (device, serial_number) = devices.remove(0);
180            return Ok(Box::new(RusbDevice::new(
181                device
182                    .open()
183                    .with_context(|| format!("Cannot open device {device:?}"))?,
184                serial_number,
185                Duration::from_millis(500),
186            )?));
187        }
188    }
189
190    fn device_by_interface_with_timeout(
191        &self,
192        class: u8,
193        subclass: u8,
194        protocol: u8,
195        usb_serial: Option<&str>,
196        timeout: Duration,
197    ) -> Result<Box<dyn UsbDevice>> {
198        let deadline = Instant::now() + timeout;
199        let serial_str = if let Some(s) = usb_serial {
200            format!(" (serial={})", s)
201        } else {
202            String::new()
203        };
204        loop {
205            let mut devices =
206                RusbContext::scan(None, Some((class, subclass, protocol)), usb_serial)?;
207            if devices.is_empty() {
208                if Instant::now() < deadline {
209                    std::thread::sleep(Duration::from_millis(100));
210                    continue;
211                } else {
212                    return Err(TransportError::NoDevice(format!(
213                        "class:subclass:protocol=0x{:02x}:0x{:02x}:0x{:02x}{}",
214                        class, subclass, protocol, serial_str
215                    ))
216                    .into());
217                }
218            }
219            if devices.len() > 1 {
220                return Err(TransportError::MultipleDevices(
221                    format!("{:?}", devices),
222                    format!(
223                        "class:subclass:protocol=0x{:02x}:0x{:02x}:0x{:02x}{}",
224                        class, subclass, protocol, serial_str
225                    ),
226                )
227                .into());
228            }
229
230            let (device, serial_number) = devices.remove(0);
231            return Ok(Box::new(RusbDevice::new(
232                device
233                    .open()
234                    .with_context(|| format!("Cannot open device {device:?}"))?,
235                serial_number,
236                Duration::from_millis(500),
237            )?));
238        }
239    }
240}
241
242impl RusbDevice {
243    pub fn new(
244        handle: rusb::DeviceHandle<rusb::Context>,
245        serial_number: Option<String>,
246        timeout: Duration,
247    ) -> Result<Self> {
248        let mut configurations = Vec::new();
249
250        // Unfortunately, rusb simply wraps around libusb which does not
251        // give access to the raw configuration descriptor so we must
252        // get it directly from the device.
253        let dev_desc_size = handle.device().device_descriptor()?.length() as usize;
254        let mut device_desc = vec![0u8; dev_desc_size];
255        let size = handle
256            .read_control(
257                0x80,   // Standard, device, IN
258                6,      // GET_DESCRIPTOR
259                1 << 8, // DEVICE
260                0,
261                &mut device_desc,
262                timeout,
263            )
264            .context("could not retrieve device descriptor")?;
265        ensure!(
266            size == dev_desc_size,
267            "Device did not return the full device descriptor"
268        );
269
270        let nr_config = handle
271            .device()
272            .device_descriptor()
273            .context("could not retrieve device descriptor")?
274            .num_configurations();
275        for config_idx in 0..nr_config {
276            let tot_len = handle
277                .device()
278                .config_descriptor(config_idx)
279                .context("could not retrieve config descriptor")?
280                .total_length() as usize;
281            let mut desc = vec![0u8; tot_len];
282            let size = handle
283                .read_control(
284                    0x80,                       // Standard, device, IN
285                    6,                          // GET_DESCRIPTOR
286                    2 << 8 | config_idx as u16, // CONFIGURATION
287                    0,
288                    &mut desc,
289                    timeout,
290                )
291                .context("could not retrieve config descriptor")?;
292            ensure!(
293                size == tot_len,
294                "Device did not return the full configuration descriptor"
295            );
296            configurations.push(desc)
297        }
298
299        Ok(RusbDevice {
300            handle,
301            serial_number,
302            timeout,
303            device_desc,
304            configurations,
305        })
306    }
307}
308
309impl UsbDevice for RusbDevice {
310    fn get_timeout(&self) -> Duration {
311        self.timeout
312    }
313
314    fn get_parent(&self) -> Result<Box<dyn UsbDevice>> {
315        let device = self
316            .handle
317            .device()
318            .get_parent()
319            .context("Unable to get parent USB device")?;
320        let handle = device.open().context(format!(
321            "Could not open device at bus={} address={}",
322            device.bus_number(),
323            device.address(),
324        ))?;
325        // We do not try to read the serial number of the parent because hubs generally do not have
326        // unique serial numbers.
327        Ok(Box::new(RusbDevice::new(handle, None, self.get_timeout())?))
328    }
329
330    fn get_vendor_id(&self) -> u16 {
331        self.handle
332            .device()
333            .device_descriptor()
334            .unwrap()
335            .vendor_id()
336    }
337
338    fn get_product_id(&self) -> u16 {
339        self.handle
340            .device()
341            .device_descriptor()
342            .unwrap()
343            .product_id()
344    }
345
346    /// Gets the usb serial number of the device.
347    fn get_serial_number(&self) -> Option<&str> {
348        self.serial_number.as_deref()
349    }
350
351    fn set_active_configuration(&self, config: u8) -> Result<()> {
352        self.handle
353            .set_active_configuration(config)
354            .context("USB error")
355    }
356
357    fn claim_interface(&self, iface: u8) -> Result<()> {
358        self.handle.claim_interface(iface).context("USB error")
359    }
360
361    fn release_interface(&self, iface: u8) -> Result<()> {
362        self.handle.release_interface(iface).context("USB error")
363    }
364
365    fn set_alternate_setting(&self, iface: u8, setting: u8) -> Result<()> {
366        self.handle
367            .set_alternate_setting(iface, setting)
368            .context("USB error")
369    }
370
371    fn kernel_driver_active(&self, iface: u8) -> Result<bool> {
372        self.handle.kernel_driver_active(iface).context("USB error")
373    }
374
375    fn detach_kernel_driver(&self, iface: u8) -> Result<()> {
376        self.handle.detach_kernel_driver(iface).context("USB error")
377    }
378
379    fn attach_kernel_driver(&self, iface: u8) -> Result<()> {
380        self.handle.attach_kernel_driver(iface).context("USB error")
381    }
382
383    fn device_descriptor(&self) -> desc::Device<'_> {
384        desc::Device::new(&self.device_desc)
385    }
386
387    fn active_configuration(&self) -> Result<desc::Configuration<'_>> {
388        let active_cfg_val = self
389            .handle
390            .active_configuration()
391            .context("Cannot retrieve active configuration value")?;
392        // Find the configuration matching the currently active one.
393        for cfg in self.configurations.iter() {
394            let cfg = desc::Configuration::new(cfg);
395            if let Ok(desc) = cfg.descriptor()
396                && desc.config_val == active_cfg_val
397            {
398                return Ok(cfg);
399            }
400        }
401        anyhow::bail!("No configuration corresponds to the configuration value {active_cfg_val:?}")
402    }
403
404    fn bus_number(&self) -> u8 {
405        self.handle.device().bus_number()
406    }
407
408    fn address(&self) -> u8 {
409        self.handle.device().address()
410    }
411
412    fn port_numbers(&self) -> Result<Vec<u8>> {
413        self.handle.device().port_numbers().context("USB error")
414    }
415
416    fn read_string_descriptor_ascii(&self, idx: u8) -> Result<String> {
417        self.handle
418            .read_string_descriptor_ascii(idx)
419            .context("USB error")
420    }
421
422    fn reset(&self) -> Result<()> {
423        self.handle.reset().context("USB Error")
424    }
425
426    fn write_control_timeout(
427        &self,
428        request_type: u8,
429        request: u8,
430        value: u16,
431        index: u16,
432        buf: &[u8],
433        timeout: Duration,
434    ) -> Result<usize> {
435        self.handle
436            .write_control(request_type, request, value, index, buf, timeout)
437            .context("USB error")
438    }
439
440    fn read_control_timeout(
441        &self,
442        request_type: u8,
443        request: u8,
444        value: u16,
445        index: u16,
446        buf: &mut [u8],
447        timeout: Duration,
448    ) -> Result<usize> {
449        self.handle
450            .read_control(request_type, request, value, index, buf, timeout)
451            .context("USB error")
452    }
453
454    fn read_bulk_timeout(&self, endpoint: u8, data: &mut [u8], timeout: Duration) -> Result<usize> {
455        let len = self
456            .handle
457            .read_bulk(endpoint, data, timeout)
458            .context("USB error")?;
459        Ok(len)
460    }
461
462    fn write_bulk_timeout(&self, endpoint: u8, data: &[u8], timeout: Duration) -> Result<usize> {
463        let len = self
464            .handle
465            .write_bulk(endpoint, data, timeout)
466            .context("USB error")?;
467        Ok(len)
468    }
469}
470
471// Structure representing a USB hub. The device needs to have sufficient permission
472// to be opened.
473pub struct UsbHub {
474    handle: Box<dyn UsbDevice>,
475}
476
477// USB hub operation.
478#[derive(Debug, Copy, Clone)]
479pub enum UsbHubOp {
480    // Power-off a specific port.
481    PowerOff,
482    // Power-on a specific port.
483    PowerOn,
484    // Suspend a specific port.
485    Suspend,
486    // Suspend a specific port.
487    Resume,
488    // Reset a specific port.
489    Reset,
490}
491
492const PORT_SUSPEND: u16 = 2;
493const PORT_RESET: u16 = 4;
494const PORT_POWER: u16 = 8;
495
496impl UsbHub {
497    // Construct a hub from the parent of a device.
498    pub fn from_parent_device(dev: &dyn UsbDevice) -> Result<UsbHub> {
499        let handle = dev.get_parent().with_context(|| {
500            format!(
501                "Cannot access USB parent hub of device on bus {bus}, address {addr}\n\
502                If this test requires access to the HUB, you need to make sure that \
503                the program has sufficient permissions to access the hub\n\
504                See sw/host/tests/chip/usb/README.md for more information\n\
505                The following command may fix the issue:\n\
506                sudo chmod 0666 /dev/bus/usb/{bus:03}/ADDR\n\
507                where ADDR is the address of the hub",
508                bus = dev.bus_number(),
509                addr = dev.address(),
510            )
511        })?;
512        UsbHub::from_device(handle)
513    }
514
515    // Construct a hub from a device.
516    pub fn from_device(dev: Box<dyn UsbDevice>) -> Result<UsbHub> {
517        // Make sure the device is a hub.
518        let dev_desc = dev.device_descriptor().descriptor()?;
519        // Assume that if the device has the HUB class then Linux will already enforce
520        // that it follows the specification.
521        ensure!(
522            dev_desc.class == rusb::constants::LIBUSB_CLASS_HUB,
523            "device is not a hub"
524        );
525        Ok(UsbHub { handle: dev })
526    }
527
528    pub fn device(&self) -> &dyn UsbDevice {
529        &*self.handle
530    }
531
532    // Report the status of a port (only returns the port status, not the port change).
533    fn port_status(&self, port: u8, timeout: Duration) -> Result<u16> {
534        let req_type = rusb::constants::LIBUSB_RECIPIENT_OTHER
535            | rusb::constants::LIBUSB_REQUEST_TYPE_CLASS
536            | rusb::constants::LIBUSB_ENDPOINT_IN;
537        let mut status = [0u8; 4];
538        let _ = self.handle.read_control_timeout(
539            req_type,
540            rusb::constants::LIBUSB_REQUEST_GET_STATUS,
541            0,
542            port as u16,
543            &mut status,
544            timeout,
545        )?;
546        Ok(status[0] as u16 | (status[1] as u16) << 8)
547    }
548
549    fn try_sysfs_op(&self, op: UsbHubOp, port: u8) -> Result<()> {
550        let disable_content = match op {
551            UsbHubOp::PowerOn => b"0",
552            UsbHubOp::PowerOff => b"1",
553            _ => bail!("operation not supported by the kernel"),
554        };
555        // The device location string is <bus>-<port1>.<port2>...
556        let hub_ports = self
557            .handle
558            .port_numbers()?
559            .iter()
560            .map(|x| x.to_string())
561            .collect::<Vec<_>>()
562            .join(".");
563        let dev_loc = format!("{}-{}", self.handle.bus_number(), hub_ports);
564        let cfg = self.handle.active_configuration()?.descriptor()?.config_val;
565        let disable_file_path =
566            format!("/sys/bus/usb/devices/{dev_loc}:{cfg}.0/{dev_loc}-port{port}/disable");
567        ensure!(
568            fs::exists(&disable_file_path).unwrap_or(false),
569            "sysfs file {} not found, are you using a very old kernel?",
570            disable_file_path
571        );
572        fs::write(&disable_file_path, disable_content)
573            .with_context(|| format!("Unable to write {}", disable_file_path))?;
574        log::info!("Hub operation {op:?} performed using the sysfs interface");
575        Ok(())
576    }
577
578    // Perform an operation.
579    pub fn op(&self, op: UsbHubOp, port: u8, timeout: Duration, check_status: bool) -> Result<()> {
580        // For power-off/on operations, it is much better to go through the kernel interface
581        // if possible, otherwise the kernel might not notice that the device was connected/disconnected.
582        match self.try_sysfs_op(op, port) {
583            Ok(()) => return Ok(()),
584            Err(err) => log::error!(
585                "Could not perform hub operation {op:?} using sysfs, falling back to direct hub operations: {err:#}"
586            ),
587        }
588
589        let (feature_index, set_feature) = match op {
590            UsbHubOp::Suspend => (PORT_SUSPEND, true),
591            UsbHubOp::Resume => (PORT_SUSPEND, false),
592            UsbHubOp::Reset => (PORT_RESET, true),
593            UsbHubOp::PowerOn => (PORT_POWER, true),
594            UsbHubOp::PowerOff => (PORT_POWER, false),
595        };
596        let req = if set_feature {
597            rusb::constants::LIBUSB_REQUEST_SET_FEATURE
598        } else {
599            rusb::constants::LIBUSB_REQUEST_CLEAR_FEATURE
600        };
601        let req_type = rusb::constants::LIBUSB_RECIPIENT_OTHER
602            | rusb::constants::LIBUSB_REQUEST_TYPE_CLASS
603            | rusb::constants::LIBUSB_ENDPOINT_OUT;
604        // Expected port status after the operation.
605        let port_status_mask = 1u16 << feature_index;
606        let port_status_after = if set_feature { port_status_mask } else { 0u16 };
607
608        // Perform operation.
609        let _ = self.handle.write_control_timeout(
610            req_type,
611            req,
612            feature_index,
613            port as u16,
614            &[],
615            timeout,
616        )?;
617        // Wait until port has changed status.
618        if !check_status {
619            return Ok(());
620        }
621        let start = Instant::now();
622        loop {
623            let port_status = self.port_status(port, timeout)?;
624            if port_status & port_status_mask == port_status_after {
625                break;
626            }
627            ensure!(
628                start.elapsed() <= timeout,
629                "Trying to {op:?} port {port} but port did not change status (last status was {port_status:x})",
630            );
631        }
632        log::info!("Hub operation {op:?} performed in {:#?}", start.elapsed());
633
634        Ok(())
635    }
636}