From 46cafd17ea720d1436dc2711163a23244b11a76e Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Thu, 12 Dec 2024 18:44:24 +0000 Subject: [PATCH] [opentitanlib] Add ftdi transport The new transport can be used to bootstrap the CW340 and the Voyager board via the FTDI chip. Signed-off-by: Douglas Reis --- sw/host/opentitanlib/BUILD | 8 + sw/host/opentitanlib/src/app/config/mod.rs | 1 + .../src/app/config/opentitan_ftdi.json | 82 +++++++++++ sw/host/opentitanlib/src/backend/ftdi.rs | 14 ++ sw/host/opentitanlib/src/backend/mod.rs | 6 + .../opentitanlib/src/transport/ftdi/chip.rs | 24 +++ .../opentitanlib/src/transport/ftdi/gpio.rs | 83 +++++++++++ .../opentitanlib/src/transport/ftdi/mod.rs | 139 ++++++++++++++++++ .../opentitanlib/src/transport/ftdi/spi.rs | 137 +++++++++++++++++ sw/host/opentitanlib/src/transport/mod.rs | 1 + 10 files changed, 495 insertions(+) create mode 100644 sw/host/opentitanlib/src/app/config/opentitan_ftdi.json create mode 100644 sw/host/opentitanlib/src/backend/ftdi.rs create mode 100644 sw/host/opentitanlib/src/transport/ftdi/chip.rs create mode 100644 sw/host/opentitanlib/src/transport/ftdi/gpio.rs create mode 100644 sw/host/opentitanlib/src/transport/ftdi/mod.rs create mode 100644 sw/host/opentitanlib/src/transport/ftdi/spi.rs diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD index 847fa1f34bcbc1..b5af0ef7b19ef6 100644 --- a/sw/host/opentitanlib/BUILD +++ b/sw/host/opentitanlib/BUILD @@ -65,6 +65,7 @@ rust_library( "src/app/mod.rs", "src/app/spi.rs", "src/backend/chip_whisperer.rs", + "src/backend/ftdi.rs", "src/backend/hyperdebug.rs", "src/backend/mod.rs", "src/backend/proxy.rs", @@ -174,6 +175,10 @@ rust_library( "src/transport/chip_whisperer/mod.rs", "src/transport/chip_whisperer/spi.rs", "src/transport/chip_whisperer/usb.rs", + "src/transport/ftdi/chip.rs", + "src/transport/ftdi/gpio.rs", + "src/transport/ftdi/mod.rs", + "src/transport/ftdi/spi.rs", "src/transport/common/fpga.rs", "src/transport/common/mod.rs", "src/transport/common/uart.rs", @@ -297,9 +302,12 @@ rust_library( "@crate_index//:deser-hjson", "@crate_index//:directories", "@crate_index//:ecdsa", + "@crate_index//:embedded-hal", "@crate_index//:env_logger", "@crate_index//:erased-serde", "@crate_index//:ftdi", + "@crate_index//:ftdi-embedded-hal", + "@crate_index//:ftdi-mpsse", "@crate_index//:hex", "@crate_index//:humantime", "@crate_index//:humantime-serde", diff --git a/sw/host/opentitanlib/src/app/config/mod.rs b/sw/host/opentitanlib/src/app/config/mod.rs index 43c4cba56566f3..293c8842f07f02 100644 --- a/sw/host/opentitanlib/src/app/config/mod.rs +++ b/sw/host/opentitanlib/src/app/config/mod.rs @@ -67,6 +67,7 @@ static BUILTINS: Lazy> = Lazy::new(|| { "/__builtin__/ti50emulator.json" => include_str!("ti50emulator.json"), "/__builtin__/opentitan_cw310.json" => include_str!("opentitan_cw310.json"), "/__builtin__/opentitan_cw340.json" => include_str!("opentitan_cw340.json"), + "/__builtin__/opentitan_ftdi.json" => include_str!("opentitan_ftdi.json"), "/__builtin__/opentitan.json" => include_str!("opentitan.json"), "/__builtin__/hyperdebug.json" => include_str!("hyperdebug.json"), "/__builtin__/hyperdebug_chipwhisperer.json" => include_str!("hyperdebug_chipwhisperer.json"), diff --git a/sw/host/opentitanlib/src/app/config/opentitan_ftdi.json b/sw/host/opentitanlib/src/app/config/opentitan_ftdi.json new file mode 100644 index 00000000000000..430c70be521a0b --- /dev/null +++ b/sw/host/opentitanlib/src/app/config/opentitan_ftdi.json @@ -0,0 +1,82 @@ +{ + "includes": ["/__builtin__/opentitan.json"], + "interface": "ftdi", + "pins": [ + { + "name": "RESET", + "mode": "PushPull", + "invert": false, + "alias_of": "adbus5", + "pull_mode": "None" + }, + { + "name": "TRST", + "mode": "PushPull", + "level": false, + "alias_of": "adbus4", + "pull_mode": "None" + }, + { + "name": "IOC0", + "alias_of": "bdbus4" + }, + { + "name": "IOC1", + "alias_of": "bdbus5" + }, + { + "name": "IOC2", + "alias_of": "bdbus6" + }, + { + "name": "MUX_FTDI", + "alias_of": "bdbus7" + }, + { + "name": "IOC8", + "alias_of": "adbus6" + }, + { + "name": "IOC5", + "alias_of": "adbus7" + } + ], + "spi": [ + { + "name": "BOOTSTRAP" + } + ], + "uarts": [ + { + "name": "console", + "alias_of": "0" + }, + { + "name": "dut", + "alias_of": "3" + } + ], + "strappings": [ + { + "name": "ROM_BOOTSTRAP", + "pins": [ + { + "name": "SW_STRAP0", + "level": true + }, + { + "name": "SW_STRAP1", + "level": true + }, + { + "name": "SW_STRAP2", + "level": true + }, + { + "name": "MUX_FTDI", + "level": true + } + ] + } + ] +} diff --git a/sw/host/opentitanlib/src/backend/ftdi.rs b/sw/host/opentitanlib/src/backend/ftdi.rs new file mode 100644 index 00000000000000..8327ef546b24c2 --- /dev/null +++ b/sw/host/opentitanlib/src/backend/ftdi.rs @@ -0,0 +1,14 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; + +use crate::backend::BackendOpts; +use crate::transport::ftdi::chip::Chip; +use crate::transport::ftdi::Ftdi; +use crate::transport::Transport; + +pub fn create(_args: &BackendOpts) -> Result> { + Ok(Box::new(Ftdi::::new()?)) +} diff --git a/sw/host/opentitanlib/src/backend/mod.rs b/sw/host/opentitanlib/src/backend/mod.rs index d6f1dcf8e4eff1..7db27f7e3d47ba 100644 --- a/sw/host/opentitanlib/src/backend/mod.rs +++ b/sw/host/opentitanlib/src/backend/mod.rs @@ -11,6 +11,7 @@ use crate::app::config::process_config_file; use crate::app::{TransportWrapper, TransportWrapperBuilder}; use crate::transport::chip_whisperer::board::{Cw310, Cw340}; use crate::transport::dediprog::Dediprog; +use crate::transport::ftdi::chip::Ft4232hq; use crate::transport::hyperdebug::{ C2d2Flavor, ChipWhispererFlavor, ServoMicroFlavor, StandardFlavor, Ti50Flavor, }; @@ -18,6 +19,7 @@ use crate::transport::{EmptyTransport, Transport}; use crate::util::parse_int::ParseInt; mod chip_whisperer; +mod ftdi; mod hyperdebug; mod proxy; mod ti50emulator; @@ -131,6 +133,10 @@ pub fn create(args: &BackendOpts) -> Result { chip_whisperer::create::(args)?, Some(Path::new("/__builtin__/opentitan_cw340.json")), ), + "ftdi" => ( + ftdi::create::(args)?, + Some(Path::new("/__builtin__/opentitan_ftdi.json")), + ), "dediprog" => { let dediprog: Box = Box::new(Dediprog::new( args.usb_vid, diff --git a/sw/host/opentitanlib/src/transport/ftdi/chip.rs b/sw/host/opentitanlib/src/transport/ftdi/chip.rs new file mode 100644 index 00000000000000..24530653f62807 --- /dev/null +++ b/sw/host/opentitanlib/src/transport/ftdi/chip.rs @@ -0,0 +1,24 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +pub trait Chip { + const VENDOR_ID: u16 = 0x0403; + const PRODUCT_ID: u16; + + const UART_BAUD: u32 = 115200; + + const INTERFACES: &'static [ftdi::Interface]; +} + +pub struct Ft4232hq {} + +impl Chip for Ft4232hq { + const PRODUCT_ID: u16 = 0x6011; + const INTERFACES: &'static [ftdi::Interface] = &[ + ftdi::Interface::A, + ftdi::Interface::B, + // ftdi::Interface::C, + ftdi::Interface::D, + ]; +} diff --git a/sw/host/opentitanlib/src/transport/ftdi/gpio.rs b/sw/host/opentitanlib/src/transport/ftdi/gpio.rs new file mode 100644 index 00000000000000..47acb3ca7b1c8b --- /dev/null +++ b/sw/host/opentitanlib/src/transport/ftdi/gpio.rs @@ -0,0 +1,83 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use embedded_hal::digital::OutputPin; +use ftdi_embedded_hal as ftdi_hal; + +use crate::io::gpio::{GpioError, GpioPin, PinMode, PullMode}; +use crate::transport::ftdi::Chip; + +pub struct Pin { + pin: Rc>>, + pinname: String, +} + +impl Pin { + pub fn open( + ftdi_interfaces: &HashMap>, + pinname: String, + ) -> Result { + let pinname = pinname.to_lowercase(); + let (interface, pin) = pinname.split_at(1); + let interface = match interface { + "a" => ftdi::Interface::A, + "b" => ftdi::Interface::B, + "c" => ftdi::Interface::C, + "d" => ftdi::Interface::D, + &_ => panic!("{}", pinname.clone()), + }; + + let hal = ftdi_interfaces + .get(&interface) + .ok_or_else(|| GpioError::InvalidPinName(pinname.clone()))?; + let pin = match pin { + "dbus0" => hal.ad0(), + "dbus1" => hal.ad1(), + "dbus2" => hal.ad2(), + "dbus3" => hal.ad3(), + "dbus4" => hal.ad4(), + "dbus5" => hal.ad5(), + "dbus6" => hal.ad6(), + "dbus7" => hal.ad7(), + _ => return Err(GpioError::InvalidPinName(pinname).into()), + }?; + + Ok(Self { + pin: Rc::new(RefCell::new(pin)), + pinname, + }) + } +} + +impl GpioPin for Pin { + fn read(&self) -> Result { + Ok(false) + } + + fn write(&self, value: bool) -> Result<()> { + if value { + self.pin.borrow_mut().set_high()?; + } else { + self.pin.borrow_mut().set_low()?; + } + Ok(()) + } + + fn set_mode(&self, _mode: PinMode) -> Result<()> { + Ok(()) + } + + fn set_pull_mode(&self, _mode: PullMode) -> Result<()> { + Ok(()) + } + + fn get_internal_pin_name(&self) -> Option<&str> { + Some(&self.pinname) + } +} diff --git a/sw/host/opentitanlib/src/transport/ftdi/mod.rs b/sw/host/opentitanlib/src/transport/ftdi/mod.rs new file mode 100644 index 00000000000000..bf45e33904e03d --- /dev/null +++ b/sw/host/opentitanlib/src/transport/ftdi/mod.rs @@ -0,0 +1,139 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use serde_annotate::Annotate; +use std::any::Any; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::io::gpio::GpioPin; +use crate::io::spi::Target; +use crate::io::uart::Uart; +use crate::io::uart::UartError; +use crate::transport::common::uart::SerialPortUart; +use crate::transport::{ + Capabilities, Capability, Transport, TransportError, TransportInterfaceType, +}; +use crate::util::parse_int::ParseInt; +use serialport::SerialPortType; + +use chip::Chip; +use ftdi_embedded_hal as ftdi_hal; + +pub mod chip; +pub mod gpio; +pub mod spi; + +#[derive(Default)] +struct Inner { + spi: Option>, + gpio: HashMap>, + uart: HashMap>, +} + +pub struct Ftdi { + pub(crate) ftdi_interfaces: HashMap>, + inner: RefCell, + phantom: std::marker::PhantomData, +} + +impl Ftdi { + pub fn new() -> anyhow::Result { + let mut ftdi_interfaces = HashMap::new(); + for interface in C::INTERFACES { + let device = ftdi::find_by_vid_pid(C::VENDOR_ID, C::PRODUCT_ID) + .interface(*interface) + .open()?; + ftdi_interfaces.insert(*interface, ftdi_hal::FtHal::init_freq(device, 8_000_000)?); + } + + let ftdi_dev = Ftdi { + ftdi_interfaces, + inner: RefCell::default(), + phantom: std::marker::PhantomData, + }; + Ok(ftdi_dev) + } + + fn open_uart(&self, instance: u32) -> Result { + let mut ports = serialport::available_ports() + .map_err(|e| UartError::EnumerationError(e.to_string()))?; + + ports.retain(|port| { + if let SerialPortType::UsbPort(info) = &port.port_type { + if info.vid == C::VENDOR_ID && info.pid == C::PRODUCT_ID { + return true; + } + } + false + }); + + let port = ports.get(instance as usize).ok_or_else(|| { + TransportError::InvalidInstance(TransportInterfaceType::Uart, instance.to_string()) + })?; + + SerialPortUart::open(&port.port_name, C::UART_BAUD) + } +} + +impl Transport for Ftdi { + fn capabilities(&self) -> Result { + Ok(Capabilities::new( + Capability::SPI | Capability::GPIO | Capability::UART | Capability::UART_NONBLOCKING, + )) + } + + fn uart(&self, instance: &str) -> Result> { + let mut inner = self.inner.borrow_mut(); + let instance = u32::from_str(instance).ok().ok_or_else(|| { + TransportError::InvalidInstance(TransportInterfaceType::Uart, instance.to_string()) + })?; + let uart = match inner.uart.entry(instance) { + Entry::Vacant(v) => { + let u = v.insert(Rc::new(self.open_uart(instance)?)); + Rc::clone(u) + } + Entry::Occupied(o) => Rc::clone(o.get()), + }; + Ok(uart) + } + + fn gpio_pin(&self, pinname: &str) -> Result> { + let mut inner = self.inner.borrow_mut(); + Ok(match inner.gpio.entry(pinname.to_string()) { + Entry::Vacant(v) => { + let u = v.insert(Rc::new(gpio::Pin::open::( + &self.ftdi_interfaces, + pinname.to_string(), + )?)); + Rc::clone(u) + } + Entry::Occupied(o) => Rc::clone(o.get()), + }) + } + + fn spi(&self, _instance: &str) -> Result> { + let mut inner = self.inner.borrow_mut(); + if inner.spi.is_none() { + inner.spi = Some(Rc::new(spi::Spi::open( + &self.ftdi_interfaces, + gpio::Pin::open::(&self.ftdi_interfaces, "bdbus3".to_string())?, + )?)); + } + Ok(Rc::clone(inner.spi.as_ref().unwrap())) + } + + fn dispatch(&self, _action: &dyn Any) -> Result>> { + Err(TransportError::UnsupportedOperation.into()) + } +} + +/// Command for Transport::dispatch(). +pub struct SetPll {} + +/// Command for Transport::dispatch(). Resets the Chip whisperer board's SAM3X chip. +pub struct ResetSam3x {} diff --git a/sw/host/opentitanlib/src/transport/ftdi/spi.rs b/sw/host/opentitanlib/src/transport/ftdi/spi.rs new file mode 100644 index 00000000000000..7c44fee42ea8f0 --- /dev/null +++ b/sw/host/opentitanlib/src/transport/ftdi/spi.rs @@ -0,0 +1,137 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use crate::io::spi::Transfer; +use anyhow::Result; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use embedded_hal::spi::SpiBus; + +use ftdi_embedded_hal as ftdi_hal; + +use crate::io::gpio; +use crate::io::spi::AssertChipSelect; +use crate::io::spi::MaxSizes; +use crate::io::spi::SpiError; +use crate::io::spi::TransferMode; +use crate::transport::Target; +use crate::transport::TransportError; + +pub struct Spi { + spi: Rc>>, + cs: T, +} + +impl Spi { + pub fn open( + ftdi_interfaces: &HashMap>, + cs: T, + ) -> Result { + let hal = ftdi_interfaces + .get(&ftdi::Interface::B) + .ok_or_else(|| SpiError::InvalidOption("spi interface".to_owned()))?; + let spi = hal.spi()?; + Ok(Spi { + spi: Rc::new(RefCell::new(spi)), + cs, + }) + } + + // Perform a SPI transaction. + fn spi_transaction(&self, transaction: &mut [Transfer]) -> Result<()> { + for transfer in transaction.iter_mut() { + match transfer { + Transfer::Read(buf) => self.spi.borrow_mut().read(buf)?, + Transfer::Write(buf) => self.spi.borrow_mut().write(buf)?, + Transfer::Both(wbuf, rbuf) => self.spi.borrow_mut().transfer(rbuf, wbuf)?, + Transfer::TpmPoll => (), + Transfer::GscReady => (), + } + } + Ok(()) + } +} + +impl Target for Spi { + fn get_transfer_mode(&self) -> Result { + Ok(TransferMode::Mode0) + } + fn set_transfer_mode(&self, mode: TransferMode) -> Result<()> { + log::warn!( + "set_transfer_mode to {:?}, but only Mode0 is supported on this device", + mode + ); + Ok(()) + } + + fn get_bits_per_word(&self) -> Result { + Ok(8) + } + fn set_bits_per_word(&self, bits_per_word: u32) -> Result<()> { + match bits_per_word { + 8 => Ok(()), + _ => Err(SpiError::InvalidWordSize(bits_per_word).into()), + } + } + + fn get_max_speed(&self) -> Result { + // FIXME: what is the speed of the SAM3U SPI interface on the Chip Whisperer board? + Ok(6_000_000) + } + fn set_max_speed(&self, frequency: u32) -> Result<()> { + log::warn!( + "set_max_speed to {:?}, but this device doesn't support changing speeds.", + frequency + ); + Ok(()) + } + + fn supports_bidirectional_transfer(&self) -> Result { + Ok(true) + } + + /// Indicates whether `Transfer::TpmPoll` is supported. + fn supports_tpm_poll(&self) -> Result { + Ok(false) + } + + fn set_pins( + &self, + _serial_clock: Option<&Rc>, + _host_out_device_in: Option<&Rc>, + _host_in_device_out: Option<&Rc>, + _chip_select: Option<&Rc>, + _gsc_ready: Option<&Rc>, + ) -> Result<()> { + Err(TransportError::UnsupportedOperation.into()) + } + + fn get_max_transfer_count(&self) -> Result { + // Arbitrary value: number of `Transfers` that can be in a single transaction. + Ok(42) + } + + fn get_max_transfer_sizes(&self) -> Result { + Ok(MaxSizes { + read: 1024, + write: 1024, + }) + } + + fn run_transaction(&self, transaction: &mut [Transfer]) -> Result<()> { + // Assert CS# (drive low). + self.cs.write(false)?; + // Translate SPI Read/Write Transactions into Chip Whisperer spi operations. + let result = self.spi_transaction(transaction); + // Release CS# (allow to float high). + self.cs.write(true)?; + result + } + + fn assert_cs(self: Rc) -> Result { + Err(TransportError::UnsupportedOperation.into()) + } +} diff --git a/sw/host/opentitanlib/src/transport/mod.rs b/sw/host/opentitanlib/src/transport/mod.rs index db0162ae8b4b9d..60abb239d33721 100644 --- a/sw/host/opentitanlib/src/transport/mod.rs +++ b/sw/host/opentitanlib/src/transport/mod.rs @@ -22,6 +22,7 @@ use crate::io::uart::Uart; pub mod chip_whisperer; pub mod common; pub mod dediprog; +pub mod ftdi; pub mod hyperdebug; pub mod ioexpander; pub mod proxy;