opentitanlib/transport/hyperdebug/
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 std::any::Any;
6use std::cell::{Cell, RefCell};
7use std::collections::HashMap;
8use std::collections::hash_map::Entry;
9use std::fs;
10use std::io::{Read, Write};
11use std::marker::PhantomData;
12use std::path::{Path, PathBuf};
13use std::rc::{Rc, Weak};
14use std::sync::LazyLock;
15use std::time::Duration;
16
17use anyhow::{Context, Result, bail, ensure};
18use regex::Regex;
19use serde_annotate::Annotate;
20use serialport::TTYPort;
21
22use crate::debug::openocd::OpenOcdJtagChain;
23use crate::io::gpio::{GpioBitbanging, GpioMonitoring, GpioPin};
24use crate::io::i2c::Bus;
25use crate::io::jtag::{JtagChain, JtagParams};
26use crate::io::spi::Target;
27use crate::io::uart::Uart;
28use crate::transport::MaintainConnection;
29use crate::transport::chip_whisperer::ChipWhisperer;
30use crate::transport::chip_whisperer::board::Board;
31use crate::transport::common::fpga::{ClearBitstream, FpgaProgram};
32use crate::transport::common::uart::flock_serial;
33use crate::transport::{
34    Capabilities, Capability, SetJtagPins, Transport, TransportError, TransportInterfaceType,
35    UpdateFirmware,
36};
37use crate::util::usb::UsbBackend;
38
39pub mod c2d2;
40pub mod dfu;
41pub mod gpio;
42pub mod i2c;
43pub mod servo_micro;
44pub mod spi;
45pub mod ti50;
46pub mod uart;
47
48pub use c2d2::C2d2Flavor;
49pub use dfu::HyperdebugDfu;
50pub use servo_micro::ServoMicroFlavor;
51pub use ti50::Ti50Flavor;
52
53/// Implementation of the Transport trait for HyperDebug based on the
54/// Nucleo-L552ZE-Q.
55pub struct Hyperdebug<T: Flavor> {
56    spi_interface: BulkInterface,
57    i2c_interface: Option<BulkInterface>,
58    cmsis_interface: Option<BulkInterface>,
59    uart_interfaces: HashMap<String, UartInterface>,
60    cached_io_interfaces: CachedIo,
61    inner: Rc<Inner>,
62    current_firmware_version: Option<String>,
63    cmsis_google_capabilities: Cell<Option<u16>>,
64    phantom: PhantomData<T>,
65}
66
67/// Trait allowing slightly different treatment of USB devices that work almost like a
68/// HyperDebug.  E.g. C2D2 and Servo micro.
69pub trait Flavor {
70    fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>>;
71    fn spi_index(_inner: &Rc<Inner>, instance: &str) -> Result<(u8, u8)> {
72        bail!(TransportError::InvalidInstance(
73            TransportInterfaceType::Spi,
74            instance.to_string()
75        ))
76    }
77    fn i2c_index(_inner: &Rc<Inner>, instance: &str) -> Result<(u8, i2c::Mode)> {
78        bail!(TransportError::InvalidInstance(
79            TransportInterfaceType::I2c,
80            instance.to_string()
81        ))
82    }
83    fn get_default_usb_vid() -> u16;
84    fn get_default_usb_pid() -> u16;
85    fn load_bitstream(_fpga_program: &FpgaProgram) -> Result<()> {
86        Err(TransportError::UnsupportedOperation.into())
87    }
88    fn clear_bitstream(_clear: &ClearBitstream) -> Result<()> {
89        Err(TransportError::UnsupportedOperation.into())
90    }
91    fn perform_initial_fw_check() -> bool {
92        true
93    }
94}
95
96pub const VID_GOOGLE: u16 = 0x18d1;
97pub const PID_HYPERDEBUG: u16 = 0x520e;
98
99/// Index of a single USB "interface", with its associated IN and OUT
100/// endpoints.  Used to instantiate e.g. SPI trait.
101#[derive(Copy, Clone)]
102pub struct BulkInterface {
103    interface: u8,
104    in_endpoint: u8,
105    out_endpoint: u8,
106}
107
108pub struct UartInterface {
109    interface: u8,
110    tty: PathBuf,
111}
112
113impl UartInterface {
114    pub fn new(interface: u8, tty: PathBuf) -> Self {
115        Self { interface, tty }
116    }
117}
118
119impl<T: Flavor> Hyperdebug<T> {
120    const USB_CLASS_VENDOR: u8 = 255;
121    const USB_SUBCLASS_UART: u8 = 80;
122    const USB_SUBCLASS_SPI: u8 = 81;
123    const USB_SUBCLASS_I2C: u8 = 82;
124    const USB_PROTOCOL_UART: u8 = 1;
125    const USB_PROTOCOL_SPI: u8 = 2;
126    const USB_PROTOCOL_I2C: u8 = 1;
127
128    /// CMSIS extension for HyperDebug.
129    const CMSIS_DAP_CUSTOM_COMMAND_GOOGLE_INFO: u8 = 0x80;
130
131    /// Sub-command for reading set of Google extension capabilities.
132    const GOOGLE_INFO_CAPABILITIES: u8 = 0x00;
133
134    // Values for capabilities bitfield
135    const GOOGLE_CAP_I2C: u16 = 0x0001;
136    const GOOGLE_CAP_I2C_DEVICE: u16 = 0x0002;
137    const GOOGLE_CAP_GPIO_MONITORING: u16 = 0x0004;
138    const GOOGLE_CAP_GPIO_BITBANGING: u16 = 0x0008;
139    const GOOGLE_CAP_UART_QUEUE_CLEAR: u16 = 0x0010;
140    const GOOGLE_CAP_TPM_POLL: u16 = 0x0020;
141
142    /// Establish connection with a particular HyperDebug.
143    pub fn open(
144        usb_vid: Option<u16>,
145        usb_pid: Option<u16>,
146        usb_serial: Option<&str>,
147    ) -> Result<Self> {
148        let mut device = UsbBackend::new(
149            usb_vid.unwrap_or_else(T::get_default_usb_vid),
150            usb_pid.unwrap_or_else(T::get_default_usb_pid),
151            usb_serial,
152        )?;
153
154        let path = PathBuf::from("/sys/bus/usb/devices");
155
156        let mut console_tty: Option<PathBuf> = None;
157        let mut spi_interface: Option<BulkInterface> = None;
158        let mut i2c_interface: Option<BulkInterface> = None;
159        let mut cmsis_interface: Option<BulkInterface> = None;
160        let mut uart_interfaces: HashMap<String, UartInterface> = HashMap::new();
161
162        let config_desc = device.active_config_descriptor()?;
163        let current_firmware_version = if let Some(idx) = config_desc.description_string_index()
164            && let Ok(current_firmware_version) = device.read_string_descriptor_ascii(idx)
165        {
166            if let Some(released_firmware_version) = dfu::official_firmware_version()?
167                && T::perform_initial_fw_check()
168                && current_firmware_version != released_firmware_version
169            {
170                log::warn!(
171                    "Current HyperDebug firmware version is {}, newest release is {}, Consider running `opentitantool transport update-firmware`",
172                    current_firmware_version,
173                    released_firmware_version,
174                );
175            }
176            Some(current_firmware_version)
177        } else {
178            None
179        };
180        // Iterate through each USB interface, discovering e.g. supported UARTs.
181        for interface in config_desc.interfaces() {
182            for interface_desc in interface.descriptors() {
183                let ports = device
184                    .port_numbers()?
185                    .iter()
186                    .map(|id| id.to_string())
187                    .collect::<Vec<String>>()
188                    .join(".");
189                let interface_path = path
190                    .join(format!("{}-{}", device.bus_number(), ports))
191                    .join(format!(
192                        "{}-{}:{}.{}",
193                        device.bus_number(),
194                        ports,
195                        config_desc.number(),
196                        interface.number()
197                    ));
198                // Check the class/subclass/protocol of this USB interface.
199                if interface_desc.class_code() == Self::USB_CLASS_VENDOR
200                    && interface_desc.sub_class_code() == Self::USB_SUBCLASS_UART
201                    && interface_desc.protocol_code() == Self::USB_PROTOCOL_UART
202                {
203                    // A serial console interface, use the ascii name to determine if it is the
204                    // HyperDebug Shell, or a UART forwarding interface.
205                    let idx = match interface_desc.description_string_index() {
206                        Some(idx) => idx,
207                        None => continue,
208                    };
209                    let interface_name = match device.read_string_descriptor_ascii(idx) {
210                        Ok(interface_name) => interface_name,
211                        _ => continue,
212                    };
213
214                    if !device.kernel_driver_active(interface.number())? {
215                        device.attach_kernel_driver(interface.number())?;
216                        // Wait for udev rules to apply proper permissions to new device.
217                        std::thread::sleep(Duration::from_millis(100));
218                    }
219
220                    if interface_name.ends_with("Shell") {
221                        // We found the "main" control interface of HyperDebug, allowing textual
222                        // commands to be sent, to e.g. manipulate GPIOs.
223                        console_tty = Some(Self::find_tty(&interface_path)?);
224                    } else {
225                        // We found an UART forwarding USB interface.
226                        let uart = UartInterface {
227                            interface: interface.number(),
228                            tty: Self::find_tty(&interface_path)?,
229                        };
230                        uart_interfaces.insert(interface_name.to_string(), uart);
231                    }
232                    continue;
233                }
234                if interface_desc.class_code() == Self::USB_CLASS_VENDOR
235                    && interface_desc.sub_class_code() == Self::USB_SUBCLASS_SPI
236                    && interface_desc.protocol_code() == Self::USB_PROTOCOL_SPI
237                {
238                    // We found the SPI forwarding USB interface (this one interface allows
239                    // multiplexing physical SPI ports.)
240                    Self::find_endpoints_for_interface(
241                        &mut spi_interface,
242                        &interface,
243                        &interface_desc,
244                    )?;
245                    continue;
246                }
247                if interface_desc.class_code() == Self::USB_CLASS_VENDOR
248                    && interface_desc.sub_class_code() == Self::USB_SUBCLASS_I2C
249                    && interface_desc.protocol_code() == Self::USB_PROTOCOL_I2C
250                {
251                    // We found the I2C forwarding USB interface (this one interface allows
252                    // multiplexing physical I2C ports.)
253                    Self::find_endpoints_for_interface(
254                        &mut i2c_interface,
255                        &interface,
256                        &interface_desc,
257                    )?;
258                    continue;
259                }
260                if interface_desc.class_code() == Self::USB_CLASS_VENDOR {
261                    // A serial console interface, use the ascii name to determine if it is the
262                    // HyperDebug Shell, or a UART forwarding interface.
263                    let idx = match interface_desc.description_string_index() {
264                        Some(idx) => idx,
265                        None => continue,
266                    };
267                    let interface_name = match device.read_string_descriptor_ascii(idx) {
268                        Ok(interface_name) => interface_name,
269                        _ => continue,
270                    };
271                    if interface_name.ends_with("CMSIS-DAP") {
272                        // We found the I2C forwarding USB interface (this one interface allows
273                        // multiplexing physical I2C ports.)
274                        Self::find_endpoints_for_interface(
275                            &mut cmsis_interface,
276                            &interface,
277                            &interface_desc,
278                        )?;
279                        continue;
280                    }
281                }
282            }
283        }
284        let result = Hyperdebug::<T> {
285            spi_interface: spi_interface.ok_or_else(|| {
286                TransportError::CommunicationError("Missing SPI interface".to_string())
287            })?,
288            i2c_interface,
289            cmsis_interface,
290            uart_interfaces,
291            cached_io_interfaces: CachedIo {
292                gpio: Default::default(),
293                spis: Default::default(),
294                i2cs_by_name: Default::default(),
295                i2cs_by_index: Default::default(),
296                uarts: Default::default(),
297            },
298            inner: Rc::new(Inner {
299                console_tty: console_tty.ok_or_else(|| {
300                    TransportError::CommunicationError("Missing console interface".to_string())
301                })?,
302                conn: RefCell::new(Weak::new()),
303                usb_device: RefCell::new(device),
304                selected_spi: Cell::new(0),
305            }),
306            current_firmware_version,
307            cmsis_google_capabilities: Cell::new(None),
308            phantom: PhantomData,
309        };
310        Ok(result)
311    }
312
313    /// Locates the /dev/ttyUSBn node corresponding to a given interface in the sys directory
314    /// tree, e.g. /sys/bus/usb/devices/1-4/1-4:1.0 .
315    fn find_tty(path: &Path) -> Result<PathBuf> {
316        for entry in fs::read_dir(path).context(format!("find TTY: read_dir({:?})", path))? {
317            let entry = entry.context(format!("find TTY: entity {:?}", path))?;
318            if let Ok(filename) = entry.file_name().into_string()
319                && filename.starts_with("tty")
320            {
321                return Ok(PathBuf::from("/dev").join(entry.file_name()));
322            }
323        }
324        Err(TransportError::CommunicationError("Did not find ttyUSBn device".to_string()).into())
325    }
326
327    fn find_endpoints_for_interface(
328        interface_variable_output: &mut Option<BulkInterface>,
329        interface: &rusb::Interface,
330        interface_desc: &rusb::InterfaceDescriptor,
331    ) -> Result<()> {
332        let mut in_endpoint: Option<u8> = None;
333        let mut out_endpoint: Option<u8> = None;
334        for endpoint_desc in interface_desc.endpoint_descriptors() {
335            if endpoint_desc.transfer_type() != rusb::TransferType::Bulk {
336                continue;
337            }
338            match endpoint_desc.direction() {
339                rusb::Direction::In => {
340                    ensure!(
341                        in_endpoint.is_none(),
342                        TransportError::CommunicationError("Multiple IN endpoints".to_string())
343                    );
344                    in_endpoint.replace(endpoint_desc.address());
345                }
346                rusb::Direction::Out => {
347                    ensure!(
348                        out_endpoint.is_none(),
349                        TransportError::CommunicationError("Multiple OUT endpoints".to_string())
350                    );
351                    out_endpoint.replace(endpoint_desc.address());
352                }
353            }
354        }
355        match (in_endpoint, out_endpoint) {
356            (Some(in_endpoint), Some(out_endpoint)) => {
357                ensure!(
358                    interface_variable_output.is_none(),
359                    TransportError::CommunicationError("Multiple identical interfaces".to_string())
360                );
361                interface_variable_output.replace(BulkInterface {
362                    interface: interface.number(),
363                    in_endpoint,
364                    out_endpoint,
365                });
366                Ok(())
367            }
368            _ => bail!(TransportError::CommunicationError(
369                "Missing one or more endpoints".to_string()
370            )),
371        }
372    }
373
374    fn get_cmsis_google_capabilities(&self) -> Result<u16> {
375        let Some(cmsis_interface) = self.cmsis_interface else {
376            // Since this debugger does not advertise any CMSIS USB interface at all, report no
377            // Google CMSIS extension capabilites.
378            return Ok(0);
379        };
380        if let Some(capabilities) = self.cmsis_google_capabilities.get() {
381            // Return cached value.
382            return Ok(capabilities);
383        }
384        self.inner
385            .usb_device
386            .borrow_mut()
387            .claim_interface(cmsis_interface.interface)?;
388        let cmd = [
389            Self::CMSIS_DAP_CUSTOM_COMMAND_GOOGLE_INFO,
390            Self::GOOGLE_INFO_CAPABILITIES,
391        ];
392        self.inner
393            .usb_device
394            .borrow()
395            .write_bulk(cmsis_interface.out_endpoint, &cmd)?;
396        let mut resp = [0u8; 64];
397        let bytecount = self
398            .inner
399            .usb_device
400            .borrow()
401            .read_bulk(cmsis_interface.in_endpoint, &mut resp)?;
402        let resp = &resp[..bytecount];
403        // First byte of response is echo of the request header, second byte indicates the number
404        // of data bytes to follow.
405        ensure!(
406            bytecount >= 4 && resp[0] == Self::CMSIS_DAP_CUSTOM_COMMAND_GOOGLE_INFO && resp[1] >= 2,
407            TransportError::CommunicationError("Unrecognized CMSIS-DAP response".to_string())
408        );
409        let capabilities = u16::from_le_bytes([resp[2], resp[3]]);
410        self.cmsis_google_capabilities.set(Some(capabilities));
411        self.inner
412            .usb_device
413            .borrow_mut()
414            .release_interface(cmsis_interface.interface)?;
415        Ok(capabilities)
416    }
417}
418
419/// Internal state of the Hyperdebug struct, this struct is reference counted such that Gpio,
420/// Spi and Uart sub-structs can all refer to this shared data, which is guaranteed to live on,
421/// even if the caller lets the outer Hyperdebug struct run out of scope.
422pub struct Inner {
423    console_tty: PathBuf,
424    conn: RefCell<Weak<Conn>>,
425    usb_device: RefCell<UsbBackend>,
426    selected_spi: Cell<u8>,
427}
428
429/// Holds cached IO communication instances(gpio, spi, i2c, uart) that the Hyperdebug struct generates.
430/// This way requests for the Hyperdebug Transport struct to create a previously generated instance
431/// will return the cached one instead of generating a completely new one.
432pub struct CachedIo {
433    gpio: RefCell<HashMap<String, Rc<dyn GpioPin>>>,
434    spis: RefCell<HashMap<u8, Rc<dyn Target>>>,
435    i2cs_by_name: RefCell<HashMap<String, Rc<dyn Bus>>>,
436    i2cs_by_index: RefCell<HashMap<u8, Rc<dyn Bus>>>,
437    uarts: RefCell<HashMap<PathBuf, Rc<dyn Uart>>>,
438}
439
440pub struct Conn {
441    console_port: RefCell<TTYPort>,
442    first_use: Cell<bool>,
443}
444
445// The way that the HyperDebug allows callers to request optimization for a sequence of operations
446// without other `opentitantool` processes meddling with the USB devices, is to let the caller
447// hold an `Rc`-reference to the `Conn` struct, thereby keeping the USB connection alive.
448impl MaintainConnection for Conn {}
449
450impl Inner {
451    /// General timeout for response on the HyperDebug text-based USB command console.
452    const COMMAND_TIMEOUT: Duration = Duration::from_millis(3000);
453
454    /// Establish connection with HyperDebug console USB interface.
455    pub fn connect(&self) -> Result<Rc<Conn>> {
456        if let Some(conn) = self.conn.borrow().upgrade() {
457            // The driver already has a connection, use it.
458            return Ok(conn);
459        }
460        // Establish a new connection.
461        let port_name = self
462            .console_tty
463            .to_str()
464            .ok_or(TransportError::UnicodePathError)?;
465        let port = TTYPort::open(
466            &serialport::new(port_name, 115_200)
467                .preserve_dtr_on_open()
468                .timeout(Self::COMMAND_TIMEOUT),
469        )
470        .context("Failed to open HyperDebug console")?;
471        flock_serial(&port, port_name)?;
472        let conn = Rc::new(Conn {
473            console_port: RefCell::new(port),
474            first_use: Cell::new(true),
475        });
476        // Return a (strong) reference to the newly opened connection, while keeping a weak
477        // reference to the same in this `Inner` object.  The result is that if the caller keeps
478        // the strong reference alive long enough, the next invocation of `connect()` will be able
479        // to re-use the same instance.  If on the other hand, the caller drops their reference,
480        // then the weak reference will not keep the instance alive, and next time a new
481        // connection will be made.
482        *self.conn.borrow_mut() = Rc::downgrade(&conn);
483        Ok(conn)
484    }
485
486    /// Send a command to HyperDebug firmware, expecting to receive no output.  Any output will be
487    /// reported through an `Err()` return.
488    pub fn cmd_no_output(&self, cmd: &str) -> Result<()> {
489        let mut unexpected_output: bool = false;
490        self.execute_command(cmd, |line| {
491            log::warn!("Unexpected HyperDebug output: {}", line);
492            unexpected_output = true;
493        })?;
494        if unexpected_output {
495            bail!(TransportError::CommunicationError(format!(
496                "Unexpected output to {}",
497                cmd
498            )));
499        }
500        Ok(())
501    }
502
503    /// Send a command to HyperDebug firmware, expecting to receive a single line of output.  Any
504    /// more or less output will be reported through an `Err()` return.
505    pub fn cmd_one_line_output(&self, cmd: &str) -> Result<String> {
506        let mut result: Option<String> = None;
507        let mut unexpected_output: bool = false;
508        self.execute_command(cmd, |line| {
509            if unexpected_output {
510                // Third or subsequent line, report it.
511                log::warn!("Unexpected HyperDebug output: {}", line);
512            } else if result.is_none() {
513                // First line, remember it.
514                result = Some(line.to_string());
515            } else {
516                // Second line, report the first as well as this one.
517                log::warn!("Unexpected HyperDebug output: {}", result.as_ref().unwrap());
518                log::warn!("Unexpected HyperDebug output: {}", line);
519                unexpected_output = true;
520            }
521        })?;
522        if unexpected_output {
523            bail!(TransportError::CommunicationError(
524                "Unexpected output".to_string()
525            ));
526        }
527        match result {
528            None => bail!(TransportError::CommunicationError(format!(
529                "No response to command {}",
530                cmd
531            ))),
532            Some(str) => Ok(str),
533        }
534    }
535
536    /// Send a command to HyperDebug firmware, expecting to receive a single line of output.  Any
537    /// more or less output will be reported through an `Err()` return.
538    pub fn cmd_one_line_output_match<'a>(
539        &self,
540        cmd: &str,
541        regex: &Regex,
542        buf: &'a mut String,
543    ) -> Result<regex::Captures<'a>> {
544        *buf = self.cmd_one_line_output(cmd)?;
545        let Some(captures) = regex.captures(buf) else {
546            log::warn!("Unexpected HyperDebug output: {}", buf);
547            bail!(TransportError::CommunicationError(
548                "Unexpected output".to_string()
549            ));
550        };
551        Ok(captures)
552    }
553
554    /// Send a command to HyperDebug firmware, with a callback to receive any output.
555    fn execute_command(&self, cmd: &str, callback: impl FnMut(&str)) -> Result<()> {
556        // Open console device, if not already open.
557        let conn = self.connect()?;
558        // Perform requested command, passing any output to callback.
559        conn.execute_command(cmd, callback)
560    }
561}
562
563impl Conn {
564    /// Send a command to HyperDebug firmware, with a callback to receive any output.
565    fn execute_command(&self, cmd: &str, mut callback: impl FnMut(&str)) -> Result<()> {
566        let port: &mut TTYPort = &mut self.console_port.borrow_mut();
567
568        if self.first_use.get() {
569            // Send Ctrl-C, followed by the command, then newline.  This will discard any previous
570            // partial input, before executing our command.
571            port.write(format!("\x03{}\n", cmd).as_bytes())
572                .context("writing to HyperDebug console")?;
573            self.first_use.set(false);
574        } else {
575            port.write(format!("{}\n", cmd).as_bytes())
576                .context("writing to HyperDebug console")?;
577        }
578
579        // Now process response from HyperDebug.  First we expect to see the echo of the command
580        // we just "typed". Then zero, one or more lines of useful output, which we want to pass
581        // to the callback, and then a prompt characters, indicating that the output is
582        // complete.
583        let mut buf = [0u8; 128];
584        let mut seen_echo = false;
585        let mut len: usize = 0;
586        loop {
587            // Read more data, appending to existing buffer.
588            match port.read(&mut buf[len..]) {
589                Ok(rc) => {
590                    len += rc;
591                    // See if we have one or more lines terminated with endline, if so, process
592                    // those and remove from the buffer by shifting the remaning data to the
593                    // front of the buffer.
594                    let mut line_start = 0;
595                    for i in 0..len {
596                        if buf[i] == b'\n' {
597                            // Found a complete line, process it
598                            let mut line_end = i;
599                            while line_end > line_start && buf[line_end - 1] == 13 {
600                                line_end -= 1;
601                            }
602                            let line = std::str::from_utf8(&buf[line_start..line_end])
603                                .context("utf8 decoding from HyperDebug console")?;
604                            if seen_echo {
605                                callback(line);
606                            } else if line.len() >= 2 && line[line.len() - 2..] == *"^C" {
607                                // Expected output from our sending of control character.
608                            } else if line.len() >= cmd.len()
609                                && line[line.len() - cmd.len()..] == *cmd
610                            {
611                                // A line ending with the command we sent, assume this is echo,
612                                // and that the actual command output will now follow.
613                                seen_echo = true;
614                            } else if !line.is_empty() {
615                                // Unexpected output before or instead of the echo of our command.
616                                log::info!("Unexpected output: {:?}", line)
617                            }
618                            line_start = i + 1;
619                        }
620                    }
621                    // If any lines were processed, remove from the buffer.
622                    if line_start > 0 {
623                        buf.rotate_left(line_start);
624                        len -= line_start;
625                    }
626                    if seen_echo && buf[0..len] == [b'>', b' '] {
627                        // We have seen echo of the command we sent, and now the last we got was a
628                        // command prompt, this is what we expect when the command has finished
629                        // successfully.
630                        return Ok(());
631                    }
632                }
633                Err(error) => return Err(error).context("reading from HyperDebug console"),
634            }
635        }
636    }
637}
638
639impl<T: Flavor> Transport for Hyperdebug<T> {
640    fn capabilities(&self) -> Result<Capabilities> {
641        Ok(Capabilities::new(
642            Capability::UART
643                | Capability::UART_NONBLOCKING
644                | Capability::GPIO
645                | Capability::GPIO_MONITORING
646                | Capability::GPIO_BITBANGING
647                | Capability::SPI
648                | Capability::SPI_DUAL
649                | Capability::SPI_QUAD
650                | Capability::I2C
651                | Capability::JTAG,
652        ))
653    }
654
655    fn apply_default_configuration(&self) -> Result<()> {
656        let mut error: Option<String> = None;
657        self.inner.execute_command("reinit", |line| {
658            log::warn!("Unexpected HyperDebug output: {}", line);
659            if line.starts_with("Error: ") {
660                error = Some(line.to_string());
661            }
662        })?;
663        if let Some(err) = error {
664            bail!(TransportError::CommunicationError(err));
665        }
666        Ok(())
667    }
668
669    // Create SPI Target instance, or return one from a cache of previously created instances.
670    fn spi(&self, instance: &str) -> Result<Rc<dyn Target>> {
671        let (enable_cmd, idx) = T::spi_index(&self.inner, instance)?;
672        if let Some(instance) = self.cached_io_interfaces.spis.borrow().get(&idx) {
673            return Ok(Rc::clone(instance));
674        }
675        let instance: Rc<dyn Target> = Rc::new(spi::HyperdebugSpiTarget::open(
676            &self.inner,
677            &self.spi_interface,
678            enable_cmd,
679            idx,
680            self.get_cmsis_google_capabilities()? & Self::GOOGLE_CAP_TPM_POLL != 0,
681        )?);
682        self.cached_io_interfaces
683            .spis
684            .borrow_mut()
685            .insert(idx, Rc::clone(&instance));
686        Ok(instance)
687    }
688
689    // Create I2C Target instance, or return one from a cache of previously created instances.
690    fn i2c(&self, name: &str) -> Result<Rc<dyn Bus>> {
691        if let Some(instance) = self.cached_io_interfaces.i2cs_by_name.borrow().get(name) {
692            return Ok(Rc::clone(instance));
693        }
694        let (idx, mode) = T::i2c_index(&self.inner, name)?;
695        if let Some(instance) = self.cached_io_interfaces.i2cs_by_index.borrow().get(&idx) {
696            self.cached_io_interfaces
697                .i2cs_by_name
698                .borrow_mut()
699                .insert(name.to_string(), Rc::clone(instance));
700            return Ok(Rc::clone(instance));
701        }
702        let cmsis_google_capabilities = self.get_cmsis_google_capabilities()?;
703        let instance: Rc<dyn Bus> = Rc::new(
704            match (
705                cmsis_google_capabilities & Self::GOOGLE_CAP_I2C != 0,
706                self.cmsis_interface.as_ref(),
707                self.i2c_interface.as_ref(),
708            ) {
709                (true, Some(cmsis_interface), _) => i2c::HyperdebugI2cBus::open(
710                    &self.inner,
711                    cmsis_interface,
712                    true, /* cmsis_encapsulation */
713                    cmsis_google_capabilities & Self::GOOGLE_CAP_I2C_DEVICE != 0,
714                    idx,
715                    mode,
716                )?,
717                (_, _, Some(i2c_interface)) => i2c::HyperdebugI2cBus::open(
718                    &self.inner,
719                    i2c_interface,
720                    false, /* cmsis_encapsulation */
721                    false, /* supports_i2c_device */
722                    idx,
723                    mode,
724                )?,
725                _ => bail!(TransportError::InvalidInstance(
726                    TransportInterfaceType::I2c,
727                    name.to_string()
728                )),
729            },
730        );
731        self.cached_io_interfaces
732            .i2cs_by_index
733            .borrow_mut()
734            .insert(idx, Rc::clone(&instance));
735        self.cached_io_interfaces
736            .i2cs_by_name
737            .borrow_mut()
738            .insert(name.to_string(), Rc::clone(&instance));
739        Ok(instance)
740    }
741
742    // Create Uart instance, or return one from a cache of previously created instances.
743    fn uart(&self, instance: &str) -> Result<Rc<dyn Uart>> {
744        match self.uart_interfaces.get(instance) {
745            Some(uart_interface) => {
746                if let Some(instance) = self
747                    .cached_io_interfaces
748                    .uarts
749                    .borrow()
750                    .get(&uart_interface.tty)
751                {
752                    return Ok(Rc::clone(instance));
753                }
754                let supports_clearing_queues =
755                    self.get_cmsis_google_capabilities()? & Self::GOOGLE_CAP_UART_QUEUE_CLEAR != 0;
756                let instance: Rc<dyn Uart> = Rc::new(uart::HyperdebugUart::open(
757                    &self.inner,
758                    uart_interface,
759                    supports_clearing_queues,
760                )?);
761                self.cached_io_interfaces
762                    .uarts
763                    .borrow_mut()
764                    .insert(uart_interface.tty.clone(), Rc::clone(&instance));
765                Ok(instance)
766            }
767            _ => Err(TransportError::InvalidInstance(
768                TransportInterfaceType::Uart,
769                instance.to_string(),
770            )
771            .into()),
772        }
773    }
774
775    // Create GpioPin instance, or return one from a cache of previously created instances.
776    fn gpio_pin(&self, pinname: &str) -> Result<Rc<dyn GpioPin>> {
777        Ok(
778            match self
779                .cached_io_interfaces
780                .gpio
781                .borrow_mut()
782                .entry(pinname.to_string())
783            {
784                Entry::Vacant(v) => {
785                    let u = v.insert(T::gpio_pin(&self.inner, pinname)?);
786                    Rc::clone(u)
787                }
788                Entry::Occupied(o) => Rc::clone(o.get()),
789            },
790        )
791    }
792
793    // Create GpioMonitoring instance.
794    fn gpio_monitoring(&self) -> Result<Rc<dyn GpioMonitoring>> {
795        // GpioMonitoring does not carry any state, so returning a new instance every time is
796        // harmless (save for some memory usage).
797        if self.get_cmsis_google_capabilities()? & Self::GOOGLE_CAP_GPIO_MONITORING != 0 {
798            Ok(Rc::new(gpio::HyperdebugGpioMonitoring::open(
799                &self.inner,
800                self.cmsis_interface,
801            )?))
802        } else {
803            // Older HyperDebug firmware does not support GPIO monitoring via binary CMSIS-DAP
804            // protocol.  Not passing the `cmsis_interface` below forces the code to use textual
805            // console protocol as fallback.
806            Ok(Rc::new(gpio::HyperdebugGpioMonitoring::open(
807                &self.inner,
808                None,
809            )?))
810        }
811    }
812
813    fn gpio_bitbanging(&self) -> Result<Rc<dyn GpioBitbanging>> {
814        ensure!(
815            self.get_cmsis_google_capabilities()? & Self::GOOGLE_CAP_GPIO_BITBANGING != 0,
816            TransportError::InvalidInterface(TransportInterfaceType::GpioBitbanging),
817        );
818        // GpioBitbanging does not carry any state, so returning a new instance every time is
819        // harmless (save for some memory usage).
820        let Some(cmsis_interface) = self.cmsis_interface else {
821            bail!(TransportError::InvalidInterface(
822                TransportInterfaceType::GpioBitbanging
823            ));
824        };
825        Ok(Rc::new(gpio::HyperdebugGpioBitbanging::open(
826            &self.inner,
827            cmsis_interface,
828        )?))
829    }
830
831    fn dispatch(&self, action: &dyn Any) -> Result<Option<Box<dyn Annotate>>> {
832        if let Some(update_firmware_action) = action.downcast_ref::<UpdateFirmware>() {
833            let usb_vid = self.inner.usb_device.borrow().get_vendor_id();
834            let usb_pid = self.inner.usb_device.borrow().get_product_id();
835            dfu::update_firmware(
836                &mut self.inner.usb_device.borrow_mut(),
837                self.current_firmware_version.as_deref(),
838                &update_firmware_action.firmware,
839                update_firmware_action.progress.as_ref(),
840                update_firmware_action.force,
841                usb_vid,
842                usb_pid,
843            )
844        } else if let Some(jtag_set_pins) = action.downcast_ref::<SetJtagPins>() {
845            match (
846                &jtag_set_pins.tclk,
847                &jtag_set_pins.tms,
848                &jtag_set_pins.tdi,
849                &jtag_set_pins.tdo,
850                &jtag_set_pins.trst,
851            ) {
852                (Some(tclk), Some(tms), Some(tdi), Some(tdo), Some(trst)) => {
853                    self.inner.cmd_no_output(&format!(
854                        "jtag set-pins {} {} {} {} {}",
855                        tclk.get_internal_pin_name()
856                            .ok_or(TransportError::InvalidOperation)?,
857                        tms.get_internal_pin_name()
858                            .ok_or(TransportError::InvalidOperation)?,
859                        tdi.get_internal_pin_name()
860                            .ok_or(TransportError::InvalidOperation)?,
861                        tdo.get_internal_pin_name()
862                            .ok_or(TransportError::InvalidOperation)?,
863                        trst.get_internal_pin_name()
864                            .ok_or(TransportError::InvalidOperation)?,
865                    ))?;
866                    Ok(None)
867                }
868                _ => Err(TransportError::UnsupportedOperation.into()),
869            }
870        } else if let Some(fpga_program) = action.downcast_ref::<FpgaProgram>() {
871            T::load_bitstream(fpga_program).map(|_| None)
872        } else if let Some(clear) = action.downcast_ref::<ClearBitstream>() {
873            T::clear_bitstream(clear).map(|_| None)
874        } else {
875            Err(TransportError::UnsupportedOperation.into())
876        }
877    }
878
879    fn jtag(&self, opts: &JtagParams) -> Result<Box<dyn JtagChain + '_>> {
880        ensure!(
881            self.cmsis_interface.is_some(),
882            TransportError::InvalidInterface(TransportInterfaceType::Jtag),
883        );
884        // Tell OpenOCD to use its CMSIS-DAP driver, and to connect to the same exact USB
885        // HyperDebug device that we are.
886        let usb_device = self.inner.usb_device.borrow();
887        let new_jtag = Box::new(OpenOcdJtagChain::new(
888            &format!(
889                "{}; cmsis_dap_vid_pid 0x{:04x} 0x{:04x}; adapter serial \"{}\";",
890                include_str!(env!("openocd_cmsis_dap_adapter_cfg")),
891                usb_device.get_vendor_id(),
892                usb_device.get_product_id(),
893                usb_device.get_serial_number(),
894            ),
895            opts,
896        )?);
897        Ok(new_jtag)
898    }
899
900    /// The way that the HyperDebug driver allows callers to request optimization for a sequence
901    /// of operations without other `opentitantool` processes meddling with the USB devices, is to
902    /// let the caller hold an `Rc`-reference to the `Conn` struct, thereby keeping the USB
903    /// connection alive.  Callers should only hold ond to the object as long as they can
904    /// guarantee that no other `opentitantool` processes simultaneously attempt to access the
905    /// same HyperDebug USB device.
906    fn maintain_connection(&self) -> Result<Rc<dyn MaintainConnection>> {
907        Ok(self.inner.connect()?)
908    }
909}
910
911/// A `StandardFlavor` is a plain Hyperdebug board.
912pub struct StandardFlavor;
913
914impl Flavor for StandardFlavor {
915    fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>> {
916        Ok(Rc::new(gpio::HyperdebugGpioPin::open(inner, pinname)?))
917    }
918
919    fn spi_index(inner: &Rc<Inner>, instance: &str) -> Result<(u8, u8)> {
920        match instance.parse() {
921            Err(_) => {
922                // Execute a "spi info" command to look up the numeric index corresponding to the
923                // given alphanumeric SPI instance name.
924                let mut buf = String::new();
925                let captures = inner
926                    .cmd_one_line_output_match(
927                        &format!("spi info {}", instance),
928                        &SPI_REGEX,
929                        &mut buf,
930                    )
931                    .map_err(|_| {
932                        TransportError::InvalidInstance(
933                            TransportInterfaceType::Spi,
934                            instance.to_string(),
935                        )
936                    })?;
937                Ok((
938                    spi::USB_SPI_REQ_ENABLE,
939                    captures.get(1).unwrap().as_str().parse().unwrap(),
940                ))
941            }
942            Ok(n) => Ok((spi::USB_SPI_REQ_ENABLE, n)),
943        }
944    }
945
946    fn i2c_index(inner: &Rc<Inner>, instance: &str) -> Result<(u8, i2c::Mode)> {
947        // Execute a "i2c info" command to look up the numeric index corresponding to the
948        // given alphanumeric I2C instance name.
949        let mut buf = String::new();
950        let captures = inner
951            .cmd_one_line_output_match(&format!("i2c info {}", instance), &SPI_REGEX, &mut buf)
952            .map_err(|_| {
953                TransportError::InvalidInstance(TransportInterfaceType::I2c, instance.to_string())
954            })?;
955        let mode = match captures.get(4) {
956            Some(c) if c.as_str().starts_with('d') => i2c::Mode::Device,
957            _ => i2c::Mode::Host,
958        };
959        Ok((captures.get(1).unwrap().as_str().parse().unwrap(), mode))
960    }
961
962    fn get_default_usb_vid() -> u16 {
963        VID_GOOGLE
964    }
965
966    fn get_default_usb_pid() -> u16 {
967        PID_HYPERDEBUG
968    }
969}
970
971/// A `ChipWhispererFlavor` is a Hyperdebug attached to a Chip Whisperer board.  Furthermore,
972/// both the Hyperdebug and Chip Whisperer board USB interfaces are attached to the host.
973/// Hyperdebug is used for all IO with the Chip Whisperer board except for bitstream
974/// programming.
975pub struct ChipWhispererFlavor<B: Board> {
976    _phantom: PhantomData<B>,
977}
978
979impl<B: Board> Flavor for ChipWhispererFlavor<B> {
980    fn gpio_pin(inner: &Rc<Inner>, pinname: &str) -> Result<Rc<dyn GpioPin>> {
981        StandardFlavor::gpio_pin(inner, pinname)
982    }
983    fn spi_index(inner: &Rc<Inner>, instance: &str) -> Result<(u8, u8)> {
984        StandardFlavor::spi_index(inner, instance)
985    }
986    fn i2c_index(inner: &Rc<Inner>, instance: &str) -> Result<(u8, i2c::Mode)> {
987        StandardFlavor::i2c_index(inner, instance)
988    }
989    fn get_default_usb_vid() -> u16 {
990        StandardFlavor::get_default_usb_vid()
991    }
992    fn get_default_usb_pid() -> u16 {
993        StandardFlavor::get_default_usb_pid()
994    }
995    fn load_bitstream(fpga_program: &FpgaProgram) -> Result<()> {
996        // First, try to establish a connection to the native Chip Whisperer interface
997        // which we will use for bitstream loading.
998        let board = ChipWhisperer::<B>::new(None, None, None, &[])?;
999
1000        // Program the FPGA bitstream.
1001        log::info!("Programming the FPGA bitstream.");
1002        let usb = board.device.borrow();
1003        usb.spi1_enable(false)?;
1004        usb.fpga_program(&fpga_program.bitstream, fpga_program.progress.as_ref())?;
1005        Ok(())
1006    }
1007    fn clear_bitstream(_clear: &ClearBitstream) -> Result<()> {
1008        let board = ChipWhisperer::<B>::new(None, None, None, &[])?;
1009        let usb = board.device.borrow();
1010        usb.spi1_enable(false)?;
1011        usb.clear_bitstream()?;
1012        Ok(())
1013    }
1014}
1015
1016static SPI_REGEX: LazyLock<Regex> =
1017    LazyLock::new(|| Regex::new("^ +([0-9]+) ([^ ]+) ([0-9]+) bps(?: ([hd])[^ ]*)?").unwrap());