opentitanlib/backend/
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
5use anyhow::Result;
6use clap::{Args, FromArgMatches};
7use std::path::{Path, PathBuf};
8use thiserror::Error;
9
10use crate::app::config::process_config_file;
11use crate::app::{TransportWrapper, TransportWrapperBuilder};
12use crate::transport::{EmptyTransport, Transport};
13use crate::util::parse_int::ParseInt;
14
15pub trait Backend {
16    /// Additional backend-specific arguments in addition to `BackendOpts`.
17    type Opts: Args;
18
19    /// Create a transport with the provided arguments.
20    fn create_transport(common: &BackendOpts, opts: &Self::Opts) -> Result<Box<dyn Transport>>;
21}
22
23#[doc(hidden)]
24pub use inventory::submit;
25
26#[doc(hidden)]
27pub struct InterfaceRegistration {
28    pub name: &'static str,
29    pub augment_args: fn(clap::Command) -> clap::Command,
30    pub create_transport: fn(&BackendOpts, &clap::ArgMatches) -> Result<Box<dyn Transport>>,
31    pub config: Option<&'static str>,
32}
33
34impl InterfaceRegistration {
35    pub const fn of<T: Backend>(name: &'static str, config: Option<&'static str>) -> Self {
36        Self {
37            name,
38            augment_args: |command| {
39                let id = T::Opts::group_id();
40
41                // Filter duplicates with id, in case backend is registered multiple times.
42                if command
43                    .get_groups()
44                    .any(|x| Some(x.get_id()) == id.as_ref())
45                {
46                    return command;
47                }
48
49                T::Opts::augment_args(command)
50            },
51            create_transport: |common, arg_matches| {
52                let opts = T::Opts::from_arg_matches(arg_matches)?;
53                T::create_transport(common, &opts)
54            },
55            config,
56        }
57    }
58}
59
60inventory::collect!(InterfaceRegistration);
61
62#[macro_export]
63macro_rules! define_interface {
64    ($name: literal, $backend: ty) => {
65        $crate::backend::define_interface!($name, $backend, None);
66    };
67    ($name: literal, $backend: ty, $config: literal) => {
68        $crate::backend::define_interface!($name, $backend, Some($config));
69    };
70    ($name: literal, $backend: ty, $config: expr) => {
71        $crate::backend::submit!($crate::backend::InterfaceRegistration::of::<$backend>(
72            $name, $config
73        ));
74    };
75}
76pub use crate::define_interface;
77
78struct TransportSpecificOpts {
79    matches: clap::ArgMatches,
80}
81
82impl std::fmt::Debug for TransportSpecificOpts {
83    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        fmt.debug_struct("TransportSpecificOpts").finish()
85    }
86}
87
88impl clap::FromArgMatches for TransportSpecificOpts {
89    fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
90        Ok(Self {
91            matches: matches.clone(),
92        })
93    }
94
95    fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> {
96        unreachable!()
97    }
98}
99
100impl Args for TransportSpecificOpts {
101    fn augment_args(mut cmd: clap::Command) -> clap::Command {
102        for reg in inventory::iter::<InterfaceRegistration> {
103            cmd = (reg.augment_args)(cmd);
104        }
105        cmd
106    }
107
108    fn augment_args_for_update(_: clap::Command) -> clap::Command {
109        unreachable!()
110    }
111}
112
113#[derive(Debug, Args)]
114pub struct BackendOpts {
115    /// Name of the debug interface.
116    #[arg(long, default_value = "")]
117    pub interface: String,
118
119    /// Whether to disable DFT with a strapping config during reset. Only required in TestUnlocked*
120    /// LC states activate the JTAG RV TAP. This is required since the DFT straps share the console
121    /// UART pins, that hyperdebug tries to pull high. In mission mode states this should be set to
122    /// false, as DFT straps are not sampled here.
123    #[arg(long)]
124    pub disable_dft_on_reset: bool,
125
126    /// USB Vendor ID of the interface.
127    #[arg(long, value_parser = u16::from_str)]
128    pub usb_vid: Option<u16>,
129    /// USB Product ID of the interface.
130    #[arg(long, value_parser = u16::from_str)]
131    pub usb_pid: Option<u16>,
132    /// USB serial number of the interface.
133    #[arg(long)]
134    pub usb_serial: Option<String>,
135
136    #[command(flatten)]
137    transport_specific_opts: TransportSpecificOpts,
138
139    /// Configuration files.
140    #[arg(long, num_args = 1)]
141    pub conf: Vec<PathBuf>,
142
143    /// Path to OpenOCD JTAG adapter config file to use (usually Olimex, but could be another
144    /// adapter).  If unspecified, will use native support of the backend transport.  (Currently
145    /// only the HyperDebug transport has such native JTAG support, and for any other transport,
146    /// this argument must be specified if using JTAG.)
147    #[arg(long)]
148    pub openocd_adapter_config: Option<PathBuf>,
149}
150
151#[derive(Error, Debug)]
152pub enum Error {
153    #[error("Unknown interface {0}")]
154    UnknownInterface(String),
155}
156
157/// Creates the requested backend interface according to [`BackendOpts`].
158pub fn create(args: &BackendOpts) -> Result<TransportWrapper> {
159    let interface = args.interface.as_str();
160    let mut env = TransportWrapperBuilder::new(interface.to_string(), args.disable_dft_on_reset);
161
162    for conf_file in &args.conf {
163        process_config_file(&mut env, conf_file.as_ref())?
164    }
165
166    let interface = env.get_interface();
167
168    let (backend, default_conf) = 'done: {
169        for reg in inventory::iter::<InterfaceRegistration> {
170            if interface == reg.name {
171                let transport =
172                    (reg.create_transport)(args, &args.transport_specific_opts.matches)?;
173                break 'done (transport, reg.config.map(Path::new));
174            }
175        }
176
177        Err(Error::UnknownInterface(interface.to_string()))?
178    };
179
180    if args.conf.is_empty()
181        && let Some(conf_file) = default_conf
182    {
183        process_config_file(&mut env, conf_file)?
184    }
185    env.set_openocd_adapter_config(&args.openocd_adapter_config);
186    env.build(backend)
187}
188
189pub fn create_empty_transport() -> Result<Box<dyn Transport>> {
190    Ok(Box::new(EmptyTransport))
191}
192
193struct EmptyBackend;
194
195impl Backend for EmptyBackend {
196    type Opts = ();
197
198    fn create_transport(_: &BackendOpts, _: &()) -> Result<Box<dyn Transport>> {
199        Ok(Box::new(EmptyTransport))
200    }
201}
202
203define_interface!("", EmptyBackend);