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