From 166cf0b6ed99767b0f1756ed14f2d3139d80215e Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Sun, 26 Jul 2026 15:10:24 -0700 Subject: [PATCH 1/4] earlgrey: fix verilator test setup We need to include the environment's `test_args` for host-based test harnesses. --- target/earlgrey/tooling/opentitan_runner.bzl | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/target/earlgrey/tooling/opentitan_runner.bzl b/target/earlgrey/tooling/opentitan_runner.bzl index b3b92192..12217b0f 100644 --- a/target/earlgrey/tooling/opentitan_runner.bzl +++ b/target/earlgrey/tooling/opentitan_runner.bzl @@ -144,22 +144,20 @@ exec {runner} {args} if not is_custom_harness and not test_cmd_str: test_cmd_str = "console --non-interactive --exit-success='{}' --exit-failure='{}'".format(exit_success, exit_failure) + if env.test_args: + formatted_test_args = [arg.format(firmware = bin_file.short_path) for arg in env.test_args] + test_args = " ".join(formatted_test_args) + else: + test_args = "--interface={interface}".format(interface = env.interface) + if is_custom_harness: test_exec = test_harness.short_path - test_args = "--interface={interface}".format(interface = env.interface) - if test_cmd_str: - test_args += " " + test_cmd_str else: test_exec = opentitantool.short_path - if env.test_args: - formatted_test_args = [arg.format(firmware = bin_file.short_path) for arg in env.test_args] - test_args = " ".join(formatted_test_args) - else: - test_args = "--interface={interface}".format(interface = env.interface) - test_args = "--rcfile= " + test_args - if test_cmd_str: - test_args += " " + test_cmd_str + + if test_cmd_str: + test_args += " " + test_cmd_str script_content = "#!/bin/bash\nset -e\n" if setup_args_str: From 24ee8aa94632e4f672141f9f2672cc709310e7a0 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Sat, 25 Jul 2026 21:15:17 -0700 Subject: [PATCH 2/4] earlgrey: relax `target_compatible_with` Relax `target_compatible_with` to facilitate host-based testing Signed-off-by: Chris Frantz --- target/earlgrey/drivers/BUILD.bazel | 3 --- target/earlgrey/util/error/BUILD.bazel | 2 -- 2 files changed, 5 deletions(-) diff --git a/target/earlgrey/drivers/BUILD.bazel b/target/earlgrey/drivers/BUILD.bazel index 6d73375c..92c80c76 100644 --- a/target/earlgrey/drivers/BUILD.bazel +++ b/target/earlgrey/drivers/BUILD.bazel @@ -2,14 +2,12 @@ # SPDX-License-Identifier: Apache-2.0 load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") -load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") rust_library( name = "gpio", srcs = ["gpio.rs"], crate_name = "earlgrey_gpio", edition = "2024", - target_compatible_with = TARGET_COMPATIBLE_WITH, visibility = ["//visibility:public"], deps = [ ":pinmux", @@ -26,7 +24,6 @@ rust_library( srcs = ["pinmux.rs"], crate_name = "earlgrey_pinmux", edition = "2024", - target_compatible_with = TARGET_COMPATIBLE_WITH, visibility = ["//visibility:public"], deps = [ "//target/earlgrey/registers", diff --git a/target/earlgrey/util/error/BUILD.bazel b/target/earlgrey/util/error/BUILD.bazel index 854cbfe4..b102afdd 100644 --- a/target/earlgrey/util/error/BUILD.bazel +++ b/target/earlgrey/util/error/BUILD.bazel @@ -2,7 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 load("@rules_rust//rust:defs.bzl", "rust_library") -load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") rust_library( name = "error", @@ -13,7 +12,6 @@ rust_library( ], crate_name = "earlgrey_util_error", edition = "2024", - target_compatible_with = TARGET_COMPATIBLE_WITH, visibility = ["//visibility:public"], deps = [ "//hal/blocking", From 59b770da1491516e877be67cc01062d41452d31c Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Sun, 26 Jul 2026 14:27:34 -0700 Subject: [PATCH 3/4] earlgrey: set up pinmux Signed-off-by: Chris Frantz --- target/earlgrey/pinout/BUILD.bazel | 32 +++++ target/earlgrey/pinout/config.rs | 169 +++++++++++++++++++++++++++ target/earlgrey/pinout/dualsbs.rs | 62 ++++++++++ target/earlgrey/pinout/lib.rs | 27 +++++ target/earlgrey/pinout/swstraps.rs | 28 +++++ target/earlgrey/pinout/target.rs | 158 +++++++++++++++++++++++++ target/earlgrey/util/error/pinmux.rs | 3 + 7 files changed, 479 insertions(+) create mode 100644 target/earlgrey/pinout/BUILD.bazel create mode 100644 target/earlgrey/pinout/config.rs create mode 100644 target/earlgrey/pinout/dualsbs.rs create mode 100644 target/earlgrey/pinout/lib.rs create mode 100644 target/earlgrey/pinout/swstraps.rs create mode 100644 target/earlgrey/pinout/target.rs diff --git a/target/earlgrey/pinout/BUILD.bazel b/target/earlgrey/pinout/BUILD.bazel new file mode 100644 index 00000000..edd588f8 --- /dev/null +++ b/target/earlgrey/pinout/BUILD.bazel @@ -0,0 +1,32 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") + +rust_library( + name = "pinout", + srcs = [ + "config.rs", + "dualsbs.rs", + "lib.rs", + "swstraps.rs", + "target.rs", + ], + crate_name = "earlgrey_pinout", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking", + "//target/earlgrey/drivers:gpio", + "//target/earlgrey/drivers:pinmux", + "//target/earlgrey/registers:top_earlgrey", + "//target/earlgrey/util/error", + "//util/error", + ] + select({ + "@platforms//os:none": [ + "@pigweed//pw_kernel/userspace", + ], + "//conditions:default": [ + ], + }), +) diff --git a/target/earlgrey/pinout/config.rs b/target/earlgrey/pinout/config.rs new file mode 100644 index 00000000..e9be32e5 --- /dev/null +++ b/target/earlgrey/pinout/config.rs @@ -0,0 +1,169 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use earlgrey_gpio::GpioPin; +use earlgrey_pinmux::{Pad, PadConfig, Pull}; +use top_earlgrey::{PinmuxOutsel as Outsel, PinmuxPeripheralIn as PeriphIn}; + +pub enum Config { + Input { + periph: PeriphIn, + pad: Pad, + pad_config: PadConfig, + }, + Output { + periph: Outsel, + pad: Pad, + pad_config: PadConfig, + }, + Io { + periph: PeriphIn, + pad: Pad, + pad_config: PadConfig, + }, +} + +impl Config { + pub fn is_input(&self) -> bool { + matches!(self, Config::Input { .. } | Config::Io { .. }) + } + + pub fn is_output(&self) -> bool { + matches!(self, Config::Output { .. } | Config::Io { .. }) + } +} + +pub struct PinoutConfig { + pub name: &'static str, + pub padname: &'static str, + pub pin: Option, + pub config: Config, +} + +impl PinoutConfig { + pub const fn gpio_in( + name: &'static str, + padname: &'static str, + pin: GpioPin, + pad: Pad, + pad_config: PadConfig, + ) -> PinoutConfig { + PinoutConfig { + name, + padname, + pin: Some(pin), + config: Config::Input { + periph: pin.as_periph(), + pad, + pad_config, + }, + } + } + + pub const fn gpio_out( + name: &'static str, + padname: &'static str, + pin: GpioPin, + pad: Pad, + pad_config: PadConfig, + ) -> PinoutConfig { + PinoutConfig { + name, + padname, + pin: Some(pin), + config: Config::Output { + periph: pin.as_outsel(), + pad, + pad_config, + }, + } + } + + pub const fn func_in( + name: &'static str, + padname: &'static str, + periph: PeriphIn, + pad: Pad, + pad_config: PadConfig, + ) -> PinoutConfig { + PinoutConfig { + name, + padname, + pin: None, + config: Config::Input { + periph, + pad, + pad_config, + }, + } + } + + pub const fn func_out( + name: &'static str, + padname: &'static str, + periph: Outsel, + pad: Pad, + pad_config: PadConfig, + ) -> PinoutConfig { + PinoutConfig { + name, + padname, + pin: None, + config: Config::Output { + periph, + pad, + pad_config, + }, + } + } + + pub const fn func_io( + name: &'static str, + padname: &'static str, + periph: PeriphIn, + pad: Pad, + pad_config: PadConfig, + ) -> PinoutConfig { + PinoutConfig { + name, + padname, + pin: None, + config: Config::Io { + periph, + pad, + pad_config, + }, + } + } +} + +pub const IN_PULL_NONE: PadConfig = PadConfig { + pull: Pull::None, + open_drain: false, + invert: false, +}; +pub const IN_PULL_UP: PadConfig = PadConfig { + pull: Pull::Up, + open_drain: false, + invert: false, +}; +pub const IN_PULL_DOWN: PadConfig = PadConfig { + pull: Pull::Down, + open_drain: false, + invert: false, +}; +pub const OUT_PUSH_PULL: PadConfig = PadConfig { + pull: Pull::None, + open_drain: false, + invert: false, +}; +pub const OUT_PULL_UP: PadConfig = PadConfig { + pull: Pull::Up, + open_drain: true, + invert: false, +}; +pub const OUT_PULL_DOWN: PadConfig = PadConfig { + pull: Pull::Down, + open_drain: true, + invert: false, +}; diff --git a/target/earlgrey/pinout/dualsbs.rs b/target/earlgrey/pinout/dualsbs.rs new file mode 100644 index 00000000..221c7446 --- /dev/null +++ b/target/earlgrey/pinout/dualsbs.rs @@ -0,0 +1,62 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use earlgrey_gpio::GpioPin; +use earlgrey_pinmux::Pad; +use top_earlgrey::{PinmuxOutsel as Outsel, PinmuxPeripheralIn as PeriphIn}; + +use crate::config::*; +use crate::Pinout; + +type PC = PinoutConfig; + +pub struct DualSideBySide; +impl DualSideBySide { + // Outputs use Gpio [0..15]. + pub const RST_CTRL0_N: GpioPin = GpioPin::Pin0; + pub const RST_CTRL1_N: GpioPin = GpioPin::Pin1; + pub const SPI_RESET_N: GpioPin = GpioPin::Pin2; + pub const SPI_MUX_EN_N: GpioPin = GpioPin::Pin3; + pub const SPI_MUX_CTRL: GpioPin = GpioPin::Pin4; + pub const SPI_HOST0_WP_N: GpioPin = GpioPin::Pin5; + pub const SPI_HOST1_WP_N: GpioPin = GpioPin::Pin6; + pub const USB_MUX_CTRL: GpioPin = GpioPin::Pin7; + pub const EXT_DEBUG_N: GpioPin = GpioPin::Pin8; + + // Inputs use Gpio [16..31]. + pub const USB_PRESENCE_N: GpioPin = GpioPin::Pin16; + pub const RST_MON0_N: GpioPin = GpioPin::Pin17; + pub const RST_MON1_N: GpioPin = GpioPin::Pin18; + pub const SW_STRAP0: GpioPin = GpioPin::Pin22; + pub const SW_STRAP1: GpioPin = GpioPin::Pin23; + pub const SW_STRAP2: GpioPin = GpioPin::Pin24; +} + +impl Pinout for DualSideBySide { + #[rustfmt::skip] + const PINOUT: &[PinoutConfig] = &[ + PC::func_in( "UART0_RX", "IOC3", PeriphIn::Uart0Rx, Pad::IOC3, IN_PULL_UP), + PC::func_out("UART0_TX", "IOC4", Outsel::Uart0Tx, Pad::IOC4, OUT_PULL_UP), + PC::func_in( "USBDEV_SENSE", "none", PeriphIn::UsbdevSense, Pad::ConstantOne, IN_PULL_NONE), + PC::gpio_in( "USB_PRESENCE_N", "IOR11", Self::USB_PRESENCE_N, Pad::IOR11, IN_PULL_UP), + PC::gpio_out("USB_MUX_CTRL", "IOC6", Self::USB_MUX_CTRL, Pad::IOC6, OUT_PUSH_PULL), + PC::gpio_out("RST_CTRL0_N", "IOA0", Self::RST_CTRL0_N, Pad::IOA0, OUT_PUSH_PULL), + PC::gpio_out("RST_CTRL1_N", "IOA1", Self::RST_CTRL1_N, Pad::IOA1, OUT_PUSH_PULL), + PC::gpio_in( "RST_MON0_N", "IOA2", Self::RST_MON0_N, Pad::IOA2, IN_PULL_NONE), + PC::gpio_in( "RST_MON1_N", "IOA5", Self::RST_MON1_N, Pad::IOA5, IN_PULL_NONE), + PC::gpio_out("SPI_RESET_N", "IOA7", Self::SPI_RESET_N, Pad::IOA7, OUT_PULL_UP), + PC::func_in( "SPI_DEV_CS1_L", "IOA4", PeriphIn::SpiDeviceTpmCsb, Pad::IOA4, IN_PULL_UP), + PC::gpio_out("SPI_MUX_EN_N", "IOB7", Self::SPI_MUX_EN_N, Pad::IOB7, OUT_PULL_UP), + PC::gpio_out("SPI_MUX_CTRL", "IOB8", Self::SPI_MUX_CTRL, Pad::IOB8, OUT_PULL_UP), + PC::gpio_out("SPI_HOST0_WP_N", "IOA3", Self::SPI_HOST0_WP_N, Pad::IOA3, OUT_PULL_UP), + PC::gpio_out("SPI_HOST1_WP_N", "IOA6", Self::SPI_HOST1_WP_N, Pad::IOA6, OUT_PULL_UP), + PC::gpio_out("EXT_DEBUG_N", "IOC9", Self::EXT_DEBUG_N, Pad::IOC9, OUT_PULL_UP), + PC::func_out("SPI_HOST1_CLK", "IOB0", Outsel::SpiHost1Sck, Pad::IOB0, OUT_PULL_UP), + PC::func_out("SPI_HOST1_CS_L", "IOB3", Outsel::SpiHost1Csb, Pad::IOB3, OUT_PULL_UP), + PC::func_io( "SPI_HOST1_D0", "IOB1", PeriphIn::SpiHost1Sd0, Pad::IOB1, IN_PULL_UP), + PC::func_io( "SPI_HOST1_D1", "IOB2", PeriphIn::SpiHost1Sd1, Pad::IOB2, IN_PULL_UP), + PC::gpio_in( "SW_STRAP2", "IOC2", Self::SW_STRAP2, Pad::IOC2, IN_PULL_NONE), + PC::gpio_in( "SW_STRAP1", "IOC1", Self::SW_STRAP1, Pad::IOC1, IN_PULL_NONE), + PC::gpio_in( "SW_STRAP0", "IOC0", Self::SW_STRAP0, Pad::IOC0, IN_PULL_NONE), + ]; +} diff --git a/target/earlgrey/pinout/lib.rs b/target/earlgrey/pinout/lib.rs new file mode 100644 index 00000000..03691810 --- /dev/null +++ b/target/earlgrey/pinout/lib.rs @@ -0,0 +1,27 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use earlgrey_gpio::EarlGreyGpio; +use util_error::ErrorCode; + +pub mod config; +pub mod dualsbs; +pub mod swstraps; +#[cfg(target_os = "none")] +mod target; + +pub trait Pinout { + const PINOUT: &[config::PinoutConfig]; + + #[cfg(target_os = "none")] + fn configure(gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + config::PinoutConfig::configure(Self::PINOUT, gpio) + } + + #[cfg(not(target_os = "none"))] + fn configure(_gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + unimplemented!() + } +} diff --git a/target/earlgrey/pinout/swstraps.rs b/target/earlgrey/pinout/swstraps.rs new file mode 100644 index 00000000..6f95896c --- /dev/null +++ b/target/earlgrey/pinout/swstraps.rs @@ -0,0 +1,28 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use earlgrey_gpio::GpioPin; +use earlgrey_pinmux::Pad; + +use crate::config::*; +use crate::Pinout; + +type PC = PinoutConfig; + +pub struct SwStraps; +impl SwStraps { + pub const SW_STRAP0: GpioPin = GpioPin::Pin22; + pub const SW_STRAP1: GpioPin = GpioPin::Pin23; + pub const SW_STRAP2: GpioPin = GpioPin::Pin24; +} + +impl Pinout for SwStraps { + #[rustfmt::skip] + const PINOUT: &[PinoutConfig] = &[ + // Note: to correctly read the strap value, the pinout must be ordered + // from MSB to LSB. + PC::gpio_in("SW_STRAP2", "IOC2", Self::SW_STRAP2, Pad::IOC2, IN_PULL_NONE), + PC::gpio_in("SW_STRAP1", "IOC1", Self::SW_STRAP1, Pad::IOC1, IN_PULL_NONE), + PC::gpio_in("SW_STRAP0", "IOC0", Self::SW_STRAP0, Pad::IOC0, IN_PULL_NONE), + ]; +} diff --git a/target/earlgrey/pinout/target.rs b/target/earlgrey/pinout/target.rs new file mode 100644 index 00000000..6907e6ed --- /dev/null +++ b/target/earlgrey/pinout/target.rs @@ -0,0 +1,158 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use earlgrey_gpio::{EarlGreyGpio, EarlGreyPinConfig, GpioMask}; +use earlgrey_pinmux::{EarlGreyPinmux, Pull}; +use earlgrey_util_error::EG_PINMUX_INVALID_STRAP_CONFIG; +use openprot_hal_blocking::gpio_port::{GpioPort, PinMask}; +use top_earlgrey::PinmuxOutsel as Outsel; +use userspace::time::{sleep_until, Clock, Duration, SystemClock}; +use util_error::ErrorCode; + +use crate::config::{Config, PinoutConfig}; +use crate::swstraps::SwStraps; +use crate::Pinout; + +impl Config { + pub fn apply(&self, pinmux: &mut EarlGreyPinmux) -> Result<(), ErrorCode> { + match self { + Self::Input { + periph, + pad, + pad_config, + } => { + pinmux.configure_pad(*pad, pad_config)?; + pinmux.connect_input(*periph, *pad)?; + } + Self::Output { + periph, + pad, + pad_config, + } => { + pinmux.configure_pad(*pad, pad_config)?; + pinmux.connect_output(*pad, *periph)?; + } + Self::Io { + periph, + pad, + pad_config, + } => { + pinmux.configure_pad(*pad, pad_config)?; + pinmux.connect_input(*periph, *pad)?; + // For the range of peripherals that are valid as input and output, + // (Gpio0 to SpiHost1Sd3), the offset between PeriphIn and Outsel + // is 3. + // TODO: should we check this and return an error? + let outsel = Outsel::try_from(*periph as u32 + 3).unwrap(); + pinmux.connect_output(*pad, outsel)?; + } + } + Ok(()) + } +} + +impl PinoutConfig { + pub fn apply(&self, gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + self.config.apply(&mut gpio.pinmux)?; + if let Some(gpio_pin) = self.pin { + gpio.configure( + gpio_pin.into(), + EarlGreyPinConfig { + is_input: self.config.is_input(), + is_output: self.config.is_output(), + input_filter: false, + pad: None, + pull: Pull::None, + }, + ) + .map_err(ErrorCode::from)?; + } + Ok(()) + } + + pub fn configure(pinout: &[Self], gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + for pin in pinout { + pin.apply(gpio)?; + } + Ok(()) + } +} + +impl SwStraps { + const PAD_DELAY: Duration = Duration::from_micros(50); + + fn read_strap(pin: &PinoutConfig, gpio: &mut EarlGreyGpio) -> Result { + let Config::Input { pad, .. } = &pin.config else { + return Err(EG_PINMUX_INVALID_STRAP_CONFIG); + }; + let Some(gpio_pin) = pin.pin else { + return Err(EG_PINMUX_INVALID_STRAP_CONFIG); + }; + let pin_mask = GpioMask::from(gpio_pin); + // 1. Configure for no pull. + gpio.configure( + pin_mask, + EarlGreyPinConfig { + is_input: pin.config.is_input(), + is_output: pin.config.is_output(), + input_filter: false, + pad: Some(*pad), + pull: Pull::None, + }, + ) + .map_err(ErrorCode::from)?; + + // 2. Delay 50us + let _ = sleep_until(SystemClock::now() + Self::PAD_DELAY); + + // 3. Read the high bit of the strap. + let val1 = if gpio + .read_input() + .map_err(ErrorCode::from)? + .contains(pin_mask) + { + 2 + } else { + 0 + }; + + // 4. Configure pull opposite to val1 + let pull = if val1 == 0 { Pull::Up } else { Pull::Down }; + gpio.configure( + pin_mask, + EarlGreyPinConfig { + is_input: pin.config.is_input(), + is_output: pin.config.is_output(), + input_filter: false, + pad: Some(*pad), + pull, + }, + ) + .map_err(ErrorCode::from)?; + + // 5. Delay 50us + let _ = sleep_until(SystemClock::now() + Self::PAD_DELAY); + + // 6. Read the low bit of the strap. + let val2 = if gpio + .read_input() + .map_err(ErrorCode::from)? + .contains(pin_mask) + { + 1 + } else { + 0 + }; + + Ok(val1 | val2) + } + + pub fn read_straps(gpio: &mut EarlGreyGpio) -> Result { + let mut result = 0; + for pin in Self::PINOUT { + result <<= 2; + result |= Self::read_strap(pin, gpio)?; + } + Ok(result) + } +} diff --git a/target/earlgrey/util/error/pinmux.rs b/target/earlgrey/util/error/pinmux.rs index 690a85bd..d7a39e8f 100644 --- a/target/earlgrey/util/error/pinmux.rs +++ b/target/earlgrey/util/error/pinmux.rs @@ -17,3 +17,6 @@ pub const EG_PINMUX_INVALID_OUTPUT: ErrorCode = EG_PINMUX.from_pw(2, Error::Inva /// The requested pad is invalid or does not support configuration. pub const EG_PINMUX_INVALID_PAD: ErrorCode = EG_PINMUX.from_pw(3, Error::InvalidArgument); + +/// The requested pad is invalid or does not support configuration. +pub const EG_PINMUX_INVALID_STRAP_CONFIG: ErrorCode = EG_PINMUX.from_pw(4, Error::InvalidArgument); From 7eefa1c3c21e101c7d586e268d8ed4fabec4b65c Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Sun, 26 Jul 2026 15:10:06 -0700 Subject: [PATCH 4/4] earlgrey: test reading swstraps Signed-off-by: Chris Frantz --- target/earlgrey/tests/README.md | 1 + target/earlgrey/tests/swstraps/BUILD.bazel | 166 ++++++++++++++++++ target/earlgrey/tests/swstraps/README.md | 71 ++++++++ .../tests/swstraps/host_swstraps_check.rs | 165 +++++++++++++++++ target/earlgrey/tests/swstraps/system.json5 | 49 ++++++ target/earlgrey/tests/swstraps/target.rs | 20 +++ .../earlgrey/tests/swstraps/test_swstraps.rs | 61 +++++++ 7 files changed, 533 insertions(+) create mode 100644 target/earlgrey/tests/swstraps/BUILD.bazel create mode 100644 target/earlgrey/tests/swstraps/README.md create mode 100644 target/earlgrey/tests/swstraps/host_swstraps_check.rs create mode 100644 target/earlgrey/tests/swstraps/system.json5 create mode 100644 target/earlgrey/tests/swstraps/target.rs create mode 100644 target/earlgrey/tests/swstraps/test_swstraps.rs diff --git a/target/earlgrey/tests/README.md b/target/earlgrey/tests/README.md index 3037d6db..f4be282d 100644 --- a/target/earlgrey/tests/README.md +++ b/target/earlgrey/tests/README.md @@ -10,6 +10,7 @@ Each subdirectory here represents a test suite or a specific test target: * **[`eflash`](eflash)**: Tests the embedded flash driver operations (erase, program, read) using a secure `flash_server` userspace process. * **[`ipc/user`](ipc/user)**: Tests userspace IPC channel communication between different processes on the microkernel. * **[`logging`](logging)**: Tests the microkernel logging subsystems and `pw_log` routing. +* **[`swstraps`](swstraps)**: Tests software straps pinmux configuration, reading, and stabilization across hardware/simulator targets. * **[`threads/kernel`](threads/kernel)**: Tests kernel-level threading, context switching, and scheduling invariants. * **[`uart`](uart)**: Tests UART driver initialization and loopback communication (using interrupt-driven RX/TX). * **[`unittest_runner`](unittest_runner)**: Runs the upstream Pigweed kernel unit and integration test suite on the target hardware. diff --git a/target/earlgrey/tests/swstraps/BUILD.bazel b/target/earlgrey/tests/swstraps/BUILD.bazel new file mode 100644 index 00000000..a961f184 --- /dev/null +++ b/target/earlgrey/tests/swstraps/BUILD.bazel @@ -0,0 +1,166 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") +load("//third_party/lowrisc_opentitan:defs.bzl", "opentitan_rust_binary") + +rust_app( + name = "test_swstraps", + srcs = [ + "test_swstraps.rs", + ], + codegen_crate_name = "test_swstraps_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//target/earlgrey/drivers:gpio", + "//target/earlgrey/pinout", + "//util/panic", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +system_image( + name = "swstraps", + apps = [ + ":test_swstraps", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +rust_binary_no_panics_test( + name = "no_panics", + apps = [ + "test_swstraps", + ], + binary = ":swstraps", + tags = ["no_panics"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_rust_binary( + name = "host_swstraps_check", + srcs = ["host_swstraps_check.rs"], + edition = "2024", + rustc_flags = [ + "-C", + "link-arg=-Wl,--allow-shlib-undefined", + ], + deps = [ + "//third_party/lowrisc_opentitan:opentitanlib", + "@ot_crate_index//:anyhow", + "@ot_crate_index//:clap", + "@ot_crate_index//:humantime", + "@ot_crate_index//:log", + ], +) + +opentitan_test( + name = "swstraps_verilator_test", + timeout = "eternal", + environment = "//target/earlgrey/env:verilator", + interface = "verilator", + tags = [ + # This test can take 20-40m to execute under verilator, so its marked manual. + "manual", + "verilator", + ], + target = ":swstraps", + test_cmd = "--logging=info", + test_harness = ":host_swstraps_check", +) + +opentitan_test( + name = "swstraps_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + environment = "//target/earlgrey/env:hyper310", + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":swstraps", + test_cmd = "--logging=info", + test_harness = ":host_swstraps_check", +) + +opentitan_test( + name = "swstraps_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + environment = "//target/earlgrey/env:hyper340", + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":swstraps", + test_cmd = "--logging=info", + test_harness = ":host_swstraps_check", +) + +opentitan_test( + name = "swstraps_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + environment = "//target/earlgrey/env:teacup", + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":swstraps", + test_cmd = "--logging=info", + test_harness = ":host_swstraps_check", +) diff --git a/target/earlgrey/tests/swstraps/README.md b/target/earlgrey/tests/swstraps/README.md new file mode 100644 index 00000000..42f14d6a --- /dev/null +++ b/target/earlgrey/tests/swstraps/README.md @@ -0,0 +1,71 @@ +# Software Straps Test (`swstraps`) + +This integration test verifies software straps pinmux configuration, GPIO reading, and value reassembly on the OpenTitan Earlgrey target. It ensures that the firmware can correctly read the three two-bit `SW_STRAPn` GPIO pins (`SW_STRAP0`, `SW_STRAP1`, and `SW_STRAP2`) and reassemble the 6-bit strap value (`0x00` to `0x3f`). + +## Test Components + +The test consists of two main components: + +1. **Firmware (`test_swstraps.rs`)**: Runs on the Pigweed Maize microkernel on the OpenTitan Earlgrey target. + * Configures the GPIO and pinmux using `SwStraps::configure` from the `earlgrey_pinout` crate. + * Logs the running banner `🔄 RUNNING SWSTRAPS TEST` to the console. + * In an infinite loop, reads the strap value using `SwStraps::read_straps(&mut gpio)`. + * When a change from the last reported value is detected, enters a 10ms debounce and stabilization loop (`sleep_until(SystemClock::now() + Duration::from_millis(10))`). + * Once two consecutive 10ms readings match, prints the stable hex value `SW_STRAP = 0xXX` to the UART console via `pw_log::info!`. + +2. **Host Harness (`host_swstraps_check.rs`)**: Runs on the host machine controlling the test across hardware and simulator environments. + * Resets the target board and waits for the initial `RUNNING` banner and initial stable strap value (`SW_STRAP = 0x00`) left by board initialization. + * Iterates through all test strapping patterns (`1..64`, followed by `0x00` at the end): + * **Verilator / Silicon (`teacup`)**: Tests all 64 possible 2-bit strap combinations (strong zero, weak zero, weak one, and strong one). + * **FPGA (`hyper310` / `hyper340`)**: FPGAs do not support weak strapping values. The harness dynamically filters out any pattern containing weak bits (`1` or `2`) and tests only the 8 strong-only patterns (`0x03`, `0x0c`, `0x0f`, `0x30`, `0x33`, `0x3c`, `0x3f`, and `0x00`). + * Drives the target's strap pins using `PinMode::PushPull` (for strong 0/1) or `PinMode::Input` with pull-up/pull-down (to simulate weak 0/1 on Verilator). + * Monitors the UART console using `UartConsole::wait_for` until the target detects the transition, stabilizes, and logs the expected `SW_STRAP = 0xXX` pattern. + * Resets the strap pins back to all zeros (`0x00`) after the test completes. + +## Strap Bit Encoding & Pin Mapping + +Each of the three software straps (`SW_STRAP0`, `SW_STRAP1`, `SW_STRAP2`) encodes a 2-bit value determined by high and low pull-direction testing: + +| Value | Meaning | Description | +| :---: | :--- | :--- | +| `0` (`00`) | **Strong Zero** | Actively driven low (`PushPull`, low) | +| `1` (`01`) | **Weak Zero** | Pulled low via pull-down resistor (`Input`, pull-down) | +| `2` (`10`) | **Weak One** | Pulled high via pull-up resistor (`Input`, pull-up) | +| `3` (`11`) | **Strong One** | Actively driven high (`PushPull`, high) | + +* **Silicon (`teacup`)**: Uses dedicated HyperDebug strong/weak GPIO pin pairs (`SW_STRAP0`/`SW_STRAP0_WEAK`, etc.). +* **Verilator & FPGA**: Directly drives `IOC0` (`SW_STRAP0`), `IOC1` (`SW_STRAP1`), and `IOC2` (`SW_STRAP2`). + +## Running the Test + +### On CW310 (FPGA) Hardware + +To run the test on a connected CW310 board: + +```bash +bazelisk test --test_output=all --cache_test_results=no //target/earlgrey/tests/swstraps:swstraps_hyper310_test +``` + +### On CW340 (FPGA) Hardware + +To run the test on a connected CW340 board: + +```bash +bazelisk test --test_output=all --cache_test_results=no //target/earlgrey/tests/swstraps:swstraps_hyper340_test +``` + +### On Verilator Simulator + +To run the full 64-pattern test suite in Verilator simulation: + +```bash +bazelisk test --test_output=all --cache_test_results=no //target/earlgrey/tests/swstraps:swstraps_verilator_test +``` + +### On Silicon (`teacup`) Hardware + +To run the full 64-pattern test suite on Teacup silicon: + +```bash +bazelisk test --test_output=all --cache_test_results=no //target/earlgrey/tests/swstraps:swstraps_silicon_test +``` diff --git a/target/earlgrey/tests/swstraps/host_swstraps_check.rs b/target/earlgrey/tests/swstraps/host_swstraps_check.rs new file mode 100644 index 00000000..973b1662 --- /dev/null +++ b/target/earlgrey/tests/swstraps/host_swstraps_check.rs @@ -0,0 +1,165 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{bail, Result}; +use clap::Parser; +use std::time::Duration; + +use opentitanlib::app::TransportWrapper; +use opentitanlib::io::gpio::{PinMode, PullMode}; +use opentitanlib::test_utils::init::InitializeTest; +use opentitanlib::uart::console::UartConsole; + +#[derive(Debug, Parser)] +struct Opts { + #[command(flatten)] + init: InitializeTest, + + /// Console receive timeout. + #[arg(long, value_parser = humantime::parse_duration, default_value = "180s")] + timeout: Duration, +} + +fn sw_strap_set_teacup(transport: &TransportWrapper, value: u8) -> Result<()> { + // The teacup board has HyperDebug GPIOs dedicated to driving the weak values. + let pin_pairs = [ + ( + transport.gpio_pin("SW_STRAP0")?, + transport.gpio_pin("SW_STRAP0_WEAK")?, + ), + ( + transport.gpio_pin("SW_STRAP1")?, + transport.gpio_pin("SW_STRAP1_WEAK")?, + ), + ( + transport.gpio_pin("SW_STRAP2")?, + transport.gpio_pin("SW_STRAP2_WEAK")?, + ), + ]; + for (i, (strong, weak)) in pin_pairs.iter().enumerate() { + let shift = 2usize.checked_mul(i).unwrap_or(0); + let pinval = ((value >> shift) & 3) as usize; + if pinval == 0 || pinval == 3 { + strong.set(Some(PinMode::PushPull), Some(pinval == 3), None, None)?; + weak.set(Some(PinMode::Input), None, None, None)?; + } else { + weak.set(Some(PinMode::PushPull), Some(pinval == 2), None, None)?; + strong.set(Some(PinMode::Input), None, None, None)?; + } + } + Ok(()) +} + +fn sw_strap_set_verilator(transport: &TransportWrapper, value: u8) -> Result<()> { + let dont_care = false; + let settings = [ + (PinMode::PushPull, false, PullMode::None), + (PinMode::Input, dont_care, PullMode::PullDown), + (PinMode::Input, dont_care, PullMode::PullUp), + (PinMode::PushPull, true, PullMode::None), + ]; + let pins = [ + transport.gpio_pin("IOC0")?, + transport.gpio_pin("IOC1")?, + transport.gpio_pin("IOC2")?, + ]; + for (i, pin) in pins.iter().enumerate() { + let shift = 2usize.checked_mul(i).unwrap_or(0); + let pinval = ((value >> shift) & 3) as usize; + let Some(&(mode, val, pull)) = settings.get(pinval) else { + bail!("Invalid pinval index: {}", pinval); + }; + pin.set(Some(mode), Some(val), Some(pull), None)?; + } + Ok(()) +} + +fn set_strap(transport: &TransportWrapper, interface: &str, value: u8) -> Result<()> { + match interface { + "teacup" => sw_strap_set_teacup(transport, value), + "verilator" | "hyper310" | "hyper340" => sw_strap_set_verilator(transport, value), + intf => bail!("Unsupported interface for SWStraps test: {}", intf), + } +} + +fn is_strong_only(value: u8) -> bool { + for i in 0..3 { + let shift = 2usize.checked_mul(i).unwrap_or(0); + let v = (value >> shift) & 3; + if v == 1 || v == 2 { + return false; + } + } + true +} + +fn strap_pattern(value: u8) -> String { + let bits = [ + b's', // Strong zero. + b'w', // Weak zero. + b'W', // Weak one. + b'S', // Strong one. + ]; + let mut buf = [b'X'; 3]; + for i in 0..3 { + let shift = 2usize.checked_mul(i).unwrap_or(0); + let v = ((value >> shift) & 3) as usize; + if let (Some(idx), Some(&ch)) = (2usize.checked_sub(i), bits.get(v)) { + if let Some(slot) = buf.get_mut(idx) { + *slot = ch; + } + } + } + std::str::from_utf8(&buf).unwrap_or("???").into() +} + +fn main() -> Result<()> { + let opts = Opts::parse(); + opts.init.init_logging(); + + let transport = opts.init.init_target()?; + let interface = opts.init.backend_opts.interface.as_str(); + + log::info!("Resetting target..."); + transport.reset(opentitanlib::app::UartRx::Clear)?; + + let uart = transport.uart("console")?; + log::info!("Waiting for RUNNING banner on console..."); + UartConsole::wait_for(&*uart, r"RUNNING", opts.timeout)?; + + log::info!("Waiting for initial strap value 0x00 after boot..."); + UartConsole::wait_for(&*uart, r"SW_STRAP = 0x00", opts.timeout)?; + log::info!("Verified initial strap value 0x00"); + + // Test remaining strap patterns 1..64, then test 0x00 again at the end. + let test_values = (1..64).chain(std::iter::once(0)); + + for value in test_values { + if (interface == "hyper310" || interface == "hyper340") && !is_strong_only(value) { + log::info!( + "Skipping weak strapping value {:#04x} (pattern: {}) on FPGA", + value, + strap_pattern(value) + ); + continue; + } + + log::info!( + "Testing strap value {:#04x} (pattern: {})", + value, + strap_pattern(value) + ); + set_strap(&transport, interface, value)?; + + let expected_pattern = format!(r"SW_STRAP = {:#04x}", value); + log::info!("Waiting for console output: '{}'...", expected_pattern); + UartConsole::wait_for(&*uart, &expected_pattern, opts.timeout)?; + log::info!("Verified strap value {:#04x}", value); + } + + // Reset straps to all zeros at the end of the test. + set_strap(&transport, interface, 0)?; + + log::info!("✅ All SWStraps tests passed!"); + Ok(()) +} diff --git a/target/earlgrey/tests/swstraps/system.json5 b/target/earlgrey/tests/swstraps/system.json5 new file mode 100644 index 00000000..6da38810 --- /dev/null +++ b/target/earlgrey/tests/swstraps/system.json5 @@ -0,0 +1,49 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + { + name: "test_swstraps", + flash_size_bytes: 16384, + processes: [ + { + name: "test_swstraps", + ram_size_bytes: 4096, + objects: [ + { + type: "thread", + name: "main_thread", + kernel_stack_size_bytes: 2048, + }, + ], + memory_mappings: [ + { + name: "gpio", + type: "device", + start_address: 0x40040000, + size_bytes: 0x40, + }, + { + name: "pinmux", + type: "device", + start_address: 0x40460000, + size_bytes: 0x1000, + }, + ], + }, + ], + }, + ], +} diff --git a/target/earlgrey/tests/swstraps/target.rs b/target/earlgrey/tests/swstraps/target.rs new file mode 100644 index 00000000..01bb3730 --- /dev/null +++ b/target/earlgrey/tests/swstraps/target.rs @@ -0,0 +1,20 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey SWStraps Test"; + + fn main() -> ! { + codegen::start(); + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/tests/swstraps/test_swstraps.rs b/target/earlgrey/tests/swstraps/test_swstraps.rs new file mode 100644 index 00000000..cbc73d2e --- /dev/null +++ b/target/earlgrey/tests/swstraps/test_swstraps.rs @@ -0,0 +1,61 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +use earlgrey_gpio::EarlGreyGpio; +use earlgrey_pinout::swstraps::SwStraps; +use earlgrey_pinout::Pinout; +use pw_status::Result; +use userspace::entry; +use userspace::time::{sleep_until, Clock, Duration, SystemClock}; +use util_panic as _; + +const DELAY_10MS: Duration = Duration::from_millis(10); + +fn sleep_10ms() { + let _ = sleep_until(SystemClock::now() + DELAY_10MS); +} + +fn run_swstraps_test() -> Result<()> { + // SAFETY: EarlGreyGpio::new() initializes MMIO access to the GPIO and Pinmux peripherals; + // safe in this single-threaded test environment. + let mut gpio = unsafe { EarlGreyGpio::new() }; + SwStraps::configure(&mut gpio).map_err(|_| pw_status::Error::Internal)?; + + pw_log::info!("🔄 RUNNING SWSTRAPS TEST"); + + let mut last_reported: Option = None; + + loop { + let mut current = + SwStraps::read_straps(&mut gpio).map_err(|_| pw_status::Error::Internal)?; + + if Some(current) != last_reported { + // A change is observed (or initial read after boot). + // Sleep 10ms and read again in a loop until the value is stable. + loop { + sleep_10ms(); + let next = + SwStraps::read_straps(&mut gpio).map_err(|_| pw_status::Error::Internal)?; + if next == current { + break; + } + current = next; + } + + if Some(current) != last_reported { + pw_log::info!("SW_STRAP = {:#04x}", current); + last_reported = Some(current); + } + } + + sleep_10ms(); + } +} + +#[entry] +fn entry() -> Result<()> { + run_swstraps_test() +}