Skip to content

Commit 23b338e

Browse files
committed
[opentitanlib] Introduce abstract over the USB backend
Currently there is a UsbBackend struct which wraps around rusb and provides some utility functions. In the future, we may want some transports to implement USB in a different way (e.g. QEMU implements USB over a virtual channel). To do, the first step is to create an abstraction over the existing `rusb` implementation. This PR split UsbBackend into two: a context trait and a device trait. The `rusb` implementation becomes just a particular implementation of those traits. Signed-off-by: Amaury Pouly <[email protected]>
1 parent 8d1c28f commit 23b338e

File tree

18 files changed

+449
-248
lines changed

18 files changed

+449
-248
lines changed

sw/host/opentitanlib/BUILD

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ rust_library(
120120
"src/io/nonblocking_help.rs",
121121
"src/io/spi.rs",
122122
"src/io/uart.rs",
123+
"src/io/usb.rs",
123124
"src/lib.rs",
124125
"src/otp/alert_handler.rs",
125126
"src/otp/alert_handler_regs.rs",
@@ -189,6 +190,7 @@ rust_library(
189190
"src/transport/common/fpga.rs",
190191
"src/transport/common/mod.rs",
191192
"src/transport/common/uart.rs",
193+
"src/transport/common/usb.rs",
192194
"src/transport/dediprog/gpio.rs",
193195
"src/transport/dediprog/mod.rs",
194196
"src/transport/dediprog/spi.rs",
@@ -255,7 +257,6 @@ rust_library(
255257
"src/util/status.rs",
256258
"src/util/testing.rs",
257259
"src/util/unknown.rs",
258-
"src/util/usb.rs",
259260
"src/util/usr_access.rs",
260261
"src/util/vcd.rs",
261262
"src/util/vmem/mod.rs",

sw/host/opentitanlib/src/app/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::io::jtag::{JtagChain, JtagParams};
1919
use crate::io::nonblocking_help::NonblockingHelp;
2020
use crate::io::spi::{Target, TransferMode};
2121
use crate::io::uart::Uart;
22+
use crate::io::usb::UsbContext;
2223
use crate::transport::{
2324
Capability, ProgressIndicator, ProxyOps, Transport, TransportError, TransportInterfaceType,
2425
ioexpander,
@@ -823,6 +824,11 @@ impl TransportWrapper {
823824
self.transport.uart(map_name(&self.uart_map, name).as_str())
824825
}
825826

827+
/// Returns a [`UsbContext`] implementation.
828+
pub fn usb(&self) -> Result<Rc<dyn UsbContext>> {
829+
self.transport.usb()
830+
}
831+
826832
/// Returns a [`GpioPin`] implementation.
827833
pub fn gpio_pin(&self, name: &str) -> Result<Rc<dyn GpioPin>> {
828834
let resolved_pin_name = map_name(&self.pin_map, name);

sw/host/opentitanlib/src/io/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ pub mod jtag;
1212
pub mod nonblocking_help;
1313
pub mod spi;
1414
pub mod uart;
15+
pub mod usb;

sw/host/opentitanlib/src/io/usb.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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+
5+
use anyhow::Result;
6+
use serde::{Deserialize, Serialize};
7+
use std::rc::Rc;
8+
use std::time::Duration;
9+
use thiserror::Error;
10+
11+
use crate::impl_serializable_error;
12+
13+
/// Errors related to the GPIO interface.
14+
#[derive(Debug, Error, Serialize, Deserialize)]
15+
pub enum UsbError {
16+
#[error("Generic error: {0}")]
17+
Generic(String),
18+
}
19+
impl_serializable_error!(UsbError);
20+
21+
/// A trait which represents a USB device.
22+
///
23+
/// Note: some of the methods use `rusb`'s datatypes to avoid redefining
24+
/// all USB structure but otherwise does not require rusb to be implemented.
25+
pub trait UsbDevice {
26+
/// Return the VID of the device.
27+
fn get_vendor_id(&self) -> u16;
28+
29+
/// Return the PID of the device.
30+
fn get_product_id(&self) -> u16;
31+
32+
/// Gets the serial number of the device.
33+
fn get_serial_number(&self) -> &str;
34+
35+
/// Set the active configuration.
36+
fn set_active_configuration(&self, config: u8) -> Result<()>;
37+
38+
/// Claim an interface for use with the kernel.
39+
fn claim_interface(&self, iface: u8) -> Result<()>;
40+
41+
/// Release a previously claimed interface to the kernel.
42+
fn release_interface(&self, iface: u8) -> Result<()>;
43+
44+
/// Set an interface alternate setting.
45+
fn set_alternate_setting(&self, iface: u8, setting: u8) -> Result<()>;
46+
47+
/// Check whether a kernel driver currentl controls the device.
48+
fn kernel_driver_active(&self, iface: u8) -> Result<bool>;
49+
50+
/// Detach the kernel driver from the device.
51+
fn detach_kernel_driver(&self, iface: u8) -> Result<()>;
52+
53+
/// Attach the kernel driver to the device.
54+
fn attach_kernel_driver(&self, iface: u8) -> Result<()>;
55+
56+
/// Return the currently active configuration's descriptor.
57+
fn active_config_descriptor(&self) -> Result<rusb::ConfigDescriptor>;
58+
59+
/// Return the device's bus number.
60+
fn bus_number(&self) -> u8;
61+
62+
/// Return the sequence of port numbers from the root down to the device.
63+
fn port_numbers(&self) -> Result<Vec<u8>>;
64+
65+
/// Return a string descriptor in ASCII.
66+
fn read_string_descriptor_ascii(&self, idx: u8) -> Result<String>;
67+
68+
/// Reset the device.
69+
///
70+
/// Note that this UsbDevice handle will most likely become invalid
71+
/// after resetting the device and a new one has to be obtained.
72+
fn reset(&self) -> Result<()>;
73+
74+
/// Get the default timeout for operations.
75+
fn get_timeout(&self) -> Duration;
76+
77+
/// Issue a USB control request with optional host-to-device data.
78+
fn write_control_timeout(
79+
&self,
80+
request_type: u8,
81+
request: u8,
82+
value: u16,
83+
index: u16,
84+
buf: &[u8],
85+
timeout: Duration,
86+
) -> Result<usize>;
87+
88+
/// Issue a USB control request with optional host-to-device data.
89+
///
90+
/// This function uses the default timeout set up by the context.
91+
fn write_control(
92+
&self,
93+
request_type: u8,
94+
request: u8,
95+
value: u16,
96+
index: u16,
97+
buf: &[u8],
98+
) -> Result<usize> {
99+
self.write_control_timeout(request_type, request, value, index, buf, self.get_timeout())
100+
}
101+
102+
/// Issue a USB control request with optional device-to-host data.
103+
fn read_control_timeout(
104+
&self,
105+
request_type: u8,
106+
request: u8,
107+
value: u16,
108+
index: u16,
109+
buf: &mut [u8],
110+
timeout: Duration,
111+
) -> Result<usize>;
112+
113+
/// Issue a USB control request with optional device-to-host data.
114+
///
115+
/// This function uses the default timeout set up by the context.
116+
fn read_control(
117+
&self,
118+
request_type: u8,
119+
request: u8,
120+
value: u16,
121+
index: u16,
122+
buf: &mut [u8],
123+
) -> Result<usize> {
124+
self.read_control_timeout(request_type, request, value, index, buf, self.get_timeout())
125+
}
126+
127+
/// Read bulk data bytes to given USB endpoint.
128+
fn read_bulk_timeout(&self, endpoint: u8, data: &mut [u8], timeout: Duration) -> Result<usize>;
129+
130+
/// Read bulk data bytes to given USB endpoint.
131+
///
132+
/// This function uses the default timeout set up by the context.
133+
fn read_bulk(&self, endpoint: u8, data: &mut [u8]) -> Result<usize> {
134+
self.read_bulk_timeout(endpoint, data, self.get_timeout())
135+
}
136+
137+
/// Write bulk data bytes to given USB endpoint.
138+
fn write_bulk_timeout(&self, endpoint: u8, data: &[u8], timeout: Duration) -> Result<usize>;
139+
140+
/// Write bulk data bytes to given USB endpoint.
141+
///
142+
/// This function uses the default timeout set up by the context.
143+
fn write_bulk(&self, endpoint: u8, data: &[u8]) -> Result<usize> {
144+
self.write_bulk_timeout(endpoint, data, self.get_timeout())
145+
}
146+
}
147+
148+
/// A trait which represents a USB context.
149+
pub trait UsbContext {
150+
/// Find a device by VID:PID, and optionally disambiguate by serial number.
151+
///
152+
/// If no device matches, this function returns immediately and does not wait.
153+
fn device_by_id(
154+
&self,
155+
usb_vid: u16,
156+
usb_pid: u16,
157+
usb_serial: Option<&str>,
158+
) -> Result<Rc<dyn UsbDevice>> {
159+
self.device_by_id_with_timeout(usb_vid, usb_pid, usb_serial, Duration::ZERO)
160+
}
161+
162+
/// Find a device by VID:PID, and optionally disambiguate by serial number.
163+
fn device_by_id_with_timeout(
164+
&self,
165+
usb_vid: u16,
166+
usb_pid: u16,
167+
usb_serial: Option<&str>,
168+
timeout: Duration,
169+
) -> Result<Rc<dyn UsbDevice>>;
170+
171+
/// Find a device with a specific interface, and optionally disambiguate by serial number.
172+
///
173+
/// If no device matches, this function returns immediately and does not wait.
174+
fn device_by_interface(
175+
&self,
176+
class: u8,
177+
subclass: u8,
178+
protocol: u8,
179+
usb_serial: Option<&str>,
180+
) -> Result<Rc<dyn UsbDevice>> {
181+
self.device_by_interface_with_timeout(class, subclass, protocol, usb_serial, Duration::ZERO)
182+
}
183+
184+
fn device_by_interface_with_timeout(
185+
&self,
186+
class: u8,
187+
subclass: u8,
188+
protocol: u8,
189+
usb_serial: Option<&str>,
190+
timeout: Duration,
191+
) -> Result<Rc<dyn UsbDevice>>;
192+
}

sw/host/opentitanlib/src/rescue/usbdfu.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44

55
use anyhow::{Result, bail};
66
use std::cell::{Cell, Ref, RefCell};
7+
use std::rc::Rc;
78
use std::time::Duration;
89

910
use crate::app::{TransportWrapper, UartRx};
1011
use crate::rescue::dfu::*;
1112
use crate::rescue::{EntryMode, Rescue, RescueError, RescueMode, RescueParams};
12-
use crate::util::usb::UsbBackend;
13+
use crate::io::usb::UsbDevice;
1314

1415
pub struct UsbDfu {
15-
usb: RefCell<Option<UsbBackend>>,
16+
usb: RefCell<Option<Rc<dyn UsbDevice>>>,
1617
interface: Cell<u8>,
1718
params: RescueParams,
1819
reset_delay: Duration,
@@ -33,9 +34,9 @@ impl UsbDfu {
3334
}
3435
}
3536

36-
fn device(&self) -> Ref<'_, UsbBackend> {
37-
let device = self.usb.borrow();
38-
Ref::map(device, |d| d.as_ref().expect("device handle"))
37+
fn device(&self) -> Ref<'_, dyn UsbDevice> {
38+
let usb = self.usb.borrow();
39+
Ref::map(usb, |d| &**d.as_ref().expect("device handle"))
3940
}
4041
}
4142

@@ -54,7 +55,7 @@ impl Rescue for UsbDfu {
5455
EntryMode::None => {}
5556
}
5657

57-
let device = UsbBackend::from_interface_with_timeout(
58+
let device = transport.usb()?.device_by_interface_with_timeout(
5859
Self::CLASS,
5960
Self::SUBCLASS,
6061
Self::PROTOCOL,

sw/host/opentitanlib/src/transport/chip_whisperer/usb.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ use std::convert::TryFrom;
1010
use std::convert::TryInto;
1111
use std::marker::PhantomData;
1212
use std::time::Duration;
13+
use std::rc::Rc;
1314

1415
use super::board::Board;
1516
use crate::collection;
1617
use crate::io::gpio::GpioError;
1718
use crate::io::spi::SpiError;
19+
use crate::io::usb::{UsbContext, UsbDevice};
1820
use crate::transport::{ProgressIndicator, TransportError, TransportInterfaceType};
21+
use crate::transport::common::usb::RusbContext;
1922
use crate::util::parse_int::ParseInt;
20-
use crate::util::usb::UsbBackend;
2123

2224
/// The `Backend` struct provides high-level access to the Chip Whisperer board.
2325
pub struct Backend<B: Board> {
24-
usb: UsbBackend,
26+
usb: Rc<dyn UsbDevice>,
2527
_marker: PhantomData<B>,
2628
}
2729

@@ -108,8 +110,9 @@ impl<B: Board> Backend<B> {
108110
usb_pid: Option<u16>,
109111
usb_serial: Option<&str>,
110112
) -> Result<Self> {
113+
let usb_context = RusbContext::new();
111114
Ok(Backend {
112-
usb: UsbBackend::new(
115+
usb: usb_context.device_by_id(
113116
usb_vid.unwrap_or(B::VENDOR_ID),
114117
usb_pid.unwrap_or(B::PRODUCT_ID),
115118
usb_serial,

sw/host/opentitanlib/src/transport/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
pub mod fpga;
66
pub mod uart;
7+
pub mod usb;

0 commit comments

Comments
 (0)