opentitanlib/io/usb/
mod.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
5pub mod desc;
6
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::time::{Duration, Instant};
10use thiserror::Error;
11
12use crate::impl_serializable_error;
13use crate::transport::TransportError;
14
15/// Errors related to the GPIO interface.
16#[derive(Debug, Error, Serialize, Deserialize)]
17pub enum UsbError {
18    #[error("Generic error: {0}")]
19    Generic(String),
20}
21impl_serializable_error!(UsbError);
22
23/// A trait which represents a USB device.
24pub trait UsbDevice {
25    /// Return the VID of the device.
26    fn get_vendor_id(&self) -> u16;
27
28    /// Return the PID of the device.
29    fn get_product_id(&self) -> u16;
30
31    /// Gets the serial number of the device.
32    fn get_serial_number(&self) -> Option<&str>;
33
34    /// Try to get the parent of this device (or None if root).
35    fn get_parent(&self) -> Result<Box<dyn UsbDevice>>;
36
37    /// Set the active configuration.
38    fn set_active_configuration(&self, config: u8) -> Result<()>;
39
40    /// Claim an interface for use with the kernel.
41    fn claim_interface(&self, iface: u8) -> Result<()>;
42
43    /// Release a previously claimed interface to the kernel.
44    fn release_interface(&self, iface: u8) -> Result<()>;
45
46    /// Set an interface alternate setting.
47    fn set_alternate_setting(&self, iface: u8, setting: u8) -> Result<()>;
48
49    /// Check whether a kernel driver currentl controls the device.
50    fn kernel_driver_active(&self, iface: u8) -> Result<bool>;
51
52    /// Detach the kernel driver from the device.
53    fn detach_kernel_driver(&self, iface: u8) -> Result<()>;
54
55    /// Attach the kernel driver to the device.
56    fn attach_kernel_driver(&self, iface: u8) -> Result<()>;
57
58    /// Return the device's descriptor.
59    fn device_descriptor(&self) -> desc::Device<'_>;
60
61    /// Return the currently active configuration's descriptor.
62    fn active_configuration(&self) -> Result<desc::Configuration<'_>>;
63
64    /// Return the device's bus number.
65    fn bus_number(&self) -> u8;
66
67    /// Return the device's address.
68    fn address(&self) -> u8;
69
70    /// Return the sequence of port numbers from the root down to the device.
71    fn port_numbers(&self) -> Result<Vec<u8>>;
72
73    /// Return a string descriptor in ASCII.
74    fn read_string_descriptor_ascii(&self, idx: u8) -> Result<String>;
75
76    /// Reset the device.
77    ///
78    /// Note that this UsbDevice handle will most likely become invalid
79    /// after resetting the device and a new one has to be obtained.
80    fn reset(&self) -> Result<()>;
81
82    /// Get the default timeout for operations.
83    fn get_timeout(&self) -> Duration;
84
85    /// Issue a USB control request with optional host-to-device data.
86    fn write_control_timeout(
87        &self,
88        request_type: u8,
89        request: u8,
90        value: u16,
91        index: u16,
92        buf: &[u8],
93        timeout: Duration,
94    ) -> Result<usize>;
95
96    /// Issue a USB control request with optional host-to-device data.
97    ///
98    /// This function uses the default timeout set up by the context.
99    fn write_control(
100        &self,
101        request_type: u8,
102        request: u8,
103        value: u16,
104        index: u16,
105        buf: &[u8],
106    ) -> Result<usize> {
107        self.write_control_timeout(request_type, request, value, index, buf, self.get_timeout())
108    }
109
110    /// Issue a USB control request with optional device-to-host data.
111    fn read_control_timeout(
112        &self,
113        request_type: u8,
114        request: u8,
115        value: u16,
116        index: u16,
117        buf: &mut [u8],
118        timeout: Duration,
119    ) -> Result<usize>;
120
121    /// Issue a USB control request with optional device-to-host data.
122    ///
123    /// This function uses the default timeout set up by the context.
124    fn read_control(
125        &self,
126        request_type: u8,
127        request: u8,
128        value: u16,
129        index: u16,
130        buf: &mut [u8],
131    ) -> Result<usize> {
132        self.read_control_timeout(request_type, request, value, index, buf, self.get_timeout())
133    }
134
135    /// Read bulk data bytes to given USB endpoint.
136    fn read_bulk_timeout(&self, endpoint: u8, data: &mut [u8], timeout: Duration) -> Result<usize>;
137
138    /// Read bulk data bytes to given USB endpoint.
139    ///
140    /// This function uses the default timeout set up by the context.
141    fn read_bulk(&self, endpoint: u8, data: &mut [u8]) -> Result<usize> {
142        self.read_bulk_timeout(endpoint, data, self.get_timeout())
143    }
144
145    /// Write bulk data bytes to given USB endpoint.
146    fn write_bulk_timeout(&self, endpoint: u8, data: &[u8], timeout: Duration) -> Result<usize>;
147
148    /// Write bulk data bytes to given USB endpoint.
149    ///
150    /// This function uses the default timeout set up by the context.
151    fn write_bulk(&self, endpoint: u8, data: &[u8]) -> Result<usize> {
152        self.write_bulk_timeout(endpoint, data, self.get_timeout())
153    }
154
155    /// Test whether two instances refer to the same underlying USB device.
156    fn eq(&self, other: &dyn UsbDevice) -> bool {
157        self.bus_number() == other.bus_number() && self.address() == other.address()
158    }
159}
160
161impl std::fmt::Debug for dyn UsbDevice {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
163        write!(
164            f,
165            "{}-{} [vid={:x},pid={:x},address={},serial={}]",
166            self.bus_number(),
167            self.port_numbers()
168                .map(|ports| ports
169                    .iter()
170                    .map(|x| x.to_string())
171                    .collect::<Vec<_>>()
172                    .join("."))
173                .unwrap_or("<ports unavailable>".into()),
174            self.get_vendor_id(),
175            self.get_product_id(),
176            self.address(),
177            self.get_serial_number().unwrap_or("<not available>"),
178        )
179    }
180}
181
182/// Defaut poll interval for timeout-based functions in the USB context.
183const DEVICE_POLL_INTERVAL_MILLIS: u64 = 100;
184
185fn look_for_device<T: UsbContext + ?Sized>(
186    ctx: &T,
187    usb_vid: Option<u16>,
188    usb_pid: Option<u16>,
189    usb_protocol: Option<(u8, u8, u8)>,
190    usb_serial: Option<&str>,
191    timeout: Duration,
192    search_criterion: &str,
193) -> Result<Box<dyn UsbDevice>> {
194    let deadline = Instant::now() + timeout;
195    let search_criterion = if let Some(s) = usb_serial {
196        format!("{} (serial={})", search_criterion, s)
197    } else {
198        search_criterion.to_string()
199    };
200    loop {
201        let mut devices = ctx.scan(usb_vid, usb_pid, usb_protocol, usb_serial)?;
202        if devices.is_empty() {
203            if Instant::now() < deadline {
204                std::thread::sleep(Duration::from_millis(DEVICE_POLL_INTERVAL_MILLIS));
205                continue;
206            } else {
207                return Err(TransportError::NoDevice(search_criterion).into());
208            }
209        }
210        if devices.len() > 1 {
211            return Err(TransportError::MultipleDevices(
212                format!("{:?}", devices),
213                search_criterion,
214            )
215            .into());
216        }
217
218        return Ok(devices.remove(0));
219    }
220}
221
222/// A trait which represents a USB context.
223pub trait UsbContext {
224    /// Scan the USB bus for devices matching VID/PID, and optionally also matching a serial
225    /// number. This method always returns immediately with the list of currently plugged
226    /// devices matching the requested criteria.
227    fn scan(
228        &self,
229        usb_vid: Option<u16>,
230        usb_pid: Option<u16>,
231        usb_protocol: Option<(u8, u8, u8)>,
232        usb_serial: Option<&str>,
233    ) -> Result<Vec<Box<dyn UsbDevice>>>;
234
235    /// Find a device by VID:PID, and optionally disambiguate by serial number.
236    ///
237    /// If no device matches, this function returns immediately and does not wait.
238    fn device_by_id(
239        &self,
240        usb_vid: u16,
241        usb_pid: u16,
242        usb_serial: Option<&str>,
243    ) -> Result<Box<dyn UsbDevice>> {
244        self.device_by_id_with_timeout(usb_vid, usb_pid, usb_serial, Duration::ZERO)
245    }
246
247    /// Find a device by VID:PID, and optionally disambiguate by serial number.
248    ///
249    /// If no device matches, this function will keep trying until the provided timeout expires.
250    fn device_by_id_with_timeout(
251        &self,
252        usb_vid: u16,
253        usb_pid: u16,
254        usb_serial: Option<&str>,
255        timeout: Duration,
256    ) -> Result<Box<dyn UsbDevice>> {
257        look_for_device(
258            self,
259            Some(usb_vid),
260            Some(usb_pid),
261            None,
262            usb_serial,
263            timeout,
264            &format!("vid:pid=0x{:04x}:0x{:04x}", usb_vid, usb_pid),
265        )
266    }
267
268    /// Find a device with a specific interface, and optionally disambiguate by serial number.
269    ///
270    /// If no device matches, this function returns immediately and does not wait.
271    fn device_by_interface(
272        &self,
273        class: u8,
274        subclass: u8,
275        protocol: u8,
276        usb_serial: Option<&str>,
277    ) -> Result<Box<dyn UsbDevice>> {
278        self.device_by_interface_with_timeout(class, subclass, protocol, usb_serial, Duration::ZERO)
279    }
280
281    /// Find a device with a specific interface, and optionally disambiguate by serial number.
282    ///
283    /// If no device matches, this function will keep trying until the provided timeout expires.
284    fn device_by_interface_with_timeout(
285        &self,
286        class: u8,
287        subclass: u8,
288        protocol: u8,
289        usb_serial: Option<&str>,
290        timeout: Duration,
291    ) -> Result<Box<dyn UsbDevice>> {
292        look_for_device(
293            self,
294            None,
295            None,
296            Some((class, subclass, protocol)),
297            usb_serial,
298            timeout,
299            &format!(
300                "class:subclass:protocol=0x{:02x}:0x{:02x}:0x{:02x}",
301                class, subclass, protocol
302            ),
303        )
304    }
305}