[spi_passthru] Test status-modifying opcodes
1. Test EN4B, EX4B, WREN and WRDI.
2. Test reading the extended status register.
Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/device/lib/testing/json/command.h b/sw/device/lib/testing/json/command.h
index d7c3fb9..c34ecb3 100644
--- a/sw/device/lib/testing/json/command.h
+++ b/sw/device/lib/testing/json/command.h
@@ -16,6 +16,8 @@
value(_, GpioGet) \
value(_, PinmuxConfig) \
value(_, SpiConfigureJedecId) \
+ value(_, SpiReadStatus) \
+ value(_, SpiWriteStatus) \
value(_, SwStrapRead)
UJSON_SERDE_ENUM(TestCommand, test_command_t, ENUM_TEST_COMMAND);
diff --git a/sw/device/lib/testing/json/spi_passthru.h b/sw/device/lib/testing/json/spi_passthru.h
index 6efe820..9ebe145 100644
--- a/sw/device/lib/testing/json/spi_passthru.h
+++ b/sw/device/lib/testing/json/spi_passthru.h
@@ -17,6 +17,11 @@
field(continuation_len, uint8_t)
UJSON_SERDE_STRUCT(ConfigJedecId, config_jedec_id_t, STRUCT_CONFIG_JEDEC_ID);
+#define STRUCT_STATUS_REGISTER(field, string) \
+ field(status, uint32_t) \
+ field(addr_4b, bool)
+UJSON_SERDE_STRUCT(StatusRegister, status_register_t, STRUCT_STATUS_REGISTER);
+
// clang-format on
#ifdef __cplusplus
}
diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD
index 6e09e85..7824a1d 100644
--- a/sw/device/tests/BUILD
+++ b/sw/device/tests/BUILD
@@ -2195,7 +2195,9 @@
opentitan_functest(
name = "spi_passthru_test",
- srcs = ["spi_passthru_test.c"],
+ srcs = [
+ "spi_passthru_test.c",
+ ],
cw310 = cw310_params(
# This test will need hyperdebug once we start using multi-lane
# SPI modes.
@@ -2214,6 +2216,7 @@
deps = [
"//sw/device/lib/dif:gpio",
"//sw/device/lib/dif:spi_device",
+ "//sw/device/lib/testing:spi_device_testutils",
"//sw/device/lib/testing/json:command",
"//sw/device/lib/testing/json:spi_passthru",
"//sw/device/lib/testing/test_framework:ottf_flow_control",
diff --git a/sw/device/tests/spi_passthru_test.c b/sw/device/tests/spi_passthru_test.c
index d2eb4c5..0d4d068 100644
--- a/sw/device/tests/spi_passthru_test.c
+++ b/sw/device/tests/spi_passthru_test.c
@@ -11,6 +11,7 @@
#include "sw/device/lib/dif/dif_spi_device.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/json/command.h"
+#include "sw/device/lib/testing/spi_device_testutils.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_flow_control.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
@@ -23,7 +24,7 @@
static dif_spi_device_handle_t spid;
-status_t configure_jedec_id(ujson_t *uj, dif_spi_device_handle_t *spid) {
+static status_t configure_jedec_id(ujson_t *uj, dif_spi_device_handle_t *spid) {
config_jedec_id_t config;
TRY(ujson_deserialize_config_jedec_id_t(uj, &config));
dif_spi_device_flash_id_t id = {
@@ -33,23 +34,28 @@
.num_continuation_code = config.continuation_len,
};
TRY(dif_spi_device_set_flash_id(spid, id));
-
- dif_spi_device_flash_command_t read_id = {
- .opcode = 0x9F,
- .address_type = kDifSpiDeviceFlashAddrDisabled,
- .dummy_cycles = 0,
- .payload_io_type = kDifSpiDevicePayloadIoSingle,
- .passthrough_swap_address = false,
- .payload_dir_to_host = true,
- .payload_swap_enable = false,
- .upload = false,
- .set_busy_status = false,
- };
- TRY(dif_spi_device_set_flash_command_slot(spid, 3, kDifToggleEnabled,
- read_id));
return RESP_OK_STATUS(uj);
}
+static status_t write_status_register(ujson_t *uj,
+ dif_spi_device_handle_t *spid) {
+ status_register_t sr;
+ TRY(ujson_deserialize_status_register_t(uj, &sr));
+ TRY(dif_spi_device_set_flash_status_registers(spid, sr.status));
+ return RESP_OK_STATUS(uj);
+}
+
+static status_t read_status_register(ujson_t *uj,
+ dif_spi_device_handle_t *spid) {
+ status_register_t sr;
+ dif_toggle_t addr_4b;
+ TRY(dif_spi_device_get_flash_status_registers(spid, &sr.status));
+ TRY(dif_spi_device_get_4b_address_mode(spid, &addr_4b));
+ sr.addr_4b = addr_4b;
+ RESP_OK(ujson_serialize_status_register_t, uj, &sr);
+ return OK_STATUS();
+}
+
status_t command_processor(ujson_t *uj) {
while (true) {
test_command_t command;
@@ -58,6 +64,12 @@
case kTestCommandSpiConfigureJedecId:
RESP_ERR(uj, configure_jedec_id(uj, &spid));
break;
+ case kTestCommandSpiReadStatus:
+ RESP_ERR(uj, read_status_register(uj, &spid));
+ break;
+ case kTestCommandSpiWriteStatus:
+ RESP_ERR(uj, write_status_register(uj, &spid));
+ break;
default:
LOG_ERROR("Unrecognized command: %d", command);
RESP_ERR(uj, INVALID_ARGUMENT());
@@ -71,14 +83,12 @@
CHECK_DIF_OK(dif_spi_device_init_handle(
mmio_region_from_addr(TOP_EARLGREY_SPI_DEVICE_BASE_ADDR), &spid));
- dif_spi_device_config_t dev_cfg = {
- .clock_polarity = kDifSpiDeviceEdgePositive,
- .data_phase = kDifSpiDeviceEdgeNegative,
- .tx_order = kDifSpiDeviceBitOrderMsbToLsb,
- .rx_order = kDifSpiDeviceBitOrderMsbToLsb,
- .device_mode = kDifSpiDeviceModePassthrough,
- };
- CHECK_DIF_OK(dif_spi_device_configure(&spid, dev_cfg));
+ // We want to block passthru of the first 5 read commands, corresponding to
+ // ReadStatus{1,2,3}, ReadJedecID and ReadSfdp.
+ // We also block all write commands.
+ spi_device_testutils_configure_passthrough(&spid,
+ /*filters=*/0x1F,
+ /*upload_write_commands=*/true);
dif_spi_device_passthrough_intercept_config_t passthru_cfg = {
.status = true,
diff --git a/sw/host/opentitanlib/src/spiflash/flash.rs b/sw/host/opentitanlib/src/spiflash/flash.rs
index ba2848e..1f1ff08 100644
--- a/sw/host/opentitanlib/src/spiflash/flash.rs
+++ b/sw/host/opentitanlib/src/spiflash/flash.rs
@@ -16,6 +16,8 @@
BadEraseAddress(u32, u32),
#[error("erase length {0} not a multiple of {1} bytes")]
BadEraseLength(u32, u32),
+ #[error("bad sequence length: {0}")]
+ BadSequenceLength(usize),
}
#[derive(Debug, PartialEq, Eq)]
@@ -70,6 +72,9 @@
pub const WRITE_ENABLE: u8 = 0x06;
pub const WRITE_DISABLE: u8 = 0x04;
pub const READ_STATUS: u8 = 0x05;
+ // Winbond parts use 0x35 and 0x15 for extended status reads.
+ pub const READ_STATUS2: u8 = 0x35;
+ pub const READ_STATUS3: u8 = 0x15;
pub const READ_ID: u8 = 0x9f;
pub const ENTER_4B: u8 = 0xb7;
pub const EXIT_4B: u8 = 0xe9;
@@ -123,6 +128,23 @@
Ok(buf[0])
}
+ /// Read the extended status register from the `spi` target.
+ pub fn read_status_ex(spi: &dyn Target, seq: Option<&[u8]>) -> Result<u32> {
+ let seq = seq.unwrap_or(&[Self::READ_STATUS, Self::READ_STATUS2, Self::READ_STATUS3]);
+ ensure!(
+ seq.len() > 0 && seq.len() <= 3,
+ Error::BadSequenceLength(seq.len())
+ );
+ let mut buf = [0u8; 4];
+ for (op, byte) in seq.iter().zip(buf.iter_mut()) {
+ spi.run_transaction(&mut [
+ Transfer::Write(std::slice::from_ref(op)),
+ Transfer::Read(std::slice::from_mut(byte)),
+ ])?;
+ }
+ Ok(u32::from_le_bytes(buf))
+ }
+
/// Poll the status register waiting for the busy bit to clear.
pub fn wait_for_busy_clear(spi: &dyn Target) -> Result<()> {
while SpiFlash::read_status(spi)? & SpiFlash::STATUS_WIP != 0 {
diff --git a/sw/host/opentitanlib/src/test_utils/spi_passthru.rs b/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
index de18a89..f64af10 100644
--- a/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
+++ b/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
@@ -21,3 +21,17 @@
Ok(())
}
}
+
+impl StatusRegister {
+ pub fn read(uart: &dyn Uart) -> Result<Self> {
+ TestCommand::SpiReadStatus.send(&*uart)?;
+ StatusRegister::recv(uart, Duration::from_secs(300), false)
+ }
+
+ pub fn write(&self, uart: &dyn Uart) -> Result<()> {
+ TestCommand::SpiWriteStatus.send(&*uart)?;
+ self.send(uart)?;
+ Status::recv(uart, Duration::from_secs(300), false)?;
+ Ok(())
+ }
+}
diff --git a/sw/host/tests/chip/spi_passthru/src/main.rs b/sw/host/tests/chip/spi_passthru/src/main.rs
index 71cdf4e..bfa2408 100644
--- a/sw/host/tests/chip/spi_passthru/src/main.rs
+++ b/sw/host/tests/chip/spi_passthru/src/main.rs
@@ -8,11 +8,15 @@
use opentitanlib::app::TransportWrapper;
use opentitanlib::execute_test;
+use opentitanlib::io::spi::Transfer;
use opentitanlib::spiflash::SpiFlash;
use opentitanlib::test_utils::init::InitializeTest;
-use opentitanlib::test_utils::spi_passthru::ConfigJedecId;
+use opentitanlib::test_utils::spi_passthru::{ConfigJedecId, StatusRegister};
use opentitanlib::uart::console::UartConsole;
+//const FLASH_STATUS_WIP: u32 = 0x01;
+const FLASH_STATUS_WEL: u32 = 0x02;
+
#[derive(Debug, StructOpt)]
struct Opts {
#[structopt(flatten)]
@@ -60,6 +64,76 @@
Ok(())
}
+fn test_enter_exit_4b_mode(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
+ let uart = transport.uart("console")?;
+ let spi = transport.spi(&opts.spi)?;
+
+ log::info!("Entering 4B address mode");
+ spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::ENTER_4B])])?;
+ let sr = StatusRegister::read(&*uart)?;
+ assert!(sr.addr_4b, "expected to be in 4b mode");
+
+ log::info!("Exiting 4B address mode");
+ spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::EXIT_4B])])?;
+ let sr = StatusRegister::read(&*uart)?;
+ assert!(!sr.addr_4b, "expected to be in 3b mode");
+ Ok(())
+}
+
+fn test_write_enable_disable(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
+ let uart = transport.uart("console")?;
+ let spi = transport.spi(&opts.spi)?;
+
+ log::info!("Sending WRITE_ENABLE");
+ spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::WRITE_ENABLE])])?;
+ let status = SpiFlash::read_status(&*spi)?;
+ let sr = StatusRegister::read(&*uart)?;
+ assert!(
+ status as u32 & FLASH_STATUS_WEL != 0,
+ "expected WEL set via read_status"
+ );
+ assert!(
+ sr.status & FLASH_STATUS_WEL != 0,
+ "expected WEL set on the device"
+ );
+
+ log::info!("Sending WRITE_DISABLE");
+ spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::WRITE_DISABLE])])?;
+ let status = SpiFlash::read_status(&*spi)?;
+ let sr = StatusRegister::read(&*uart)?;
+ assert!(
+ status as u32 & FLASH_STATUS_WEL == 0,
+ "expected WEL clear via read_status"
+ );
+ assert!(
+ sr.status & FLASH_STATUS_WEL == 0,
+ "expected WEL clear on the device"
+ );
+ Ok(())
+}
+
+fn test_read_status_extended(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
+ let uart = transport.uart("console")?;
+ let spi = transport.spi(&opts.spi)?;
+
+ let sr = StatusRegister {
+ status: 0x5A55AA,
+ addr_4b: false,
+ };
+ sr.write(&*uart)?;
+ // Note: because we're programming the flash_status register in firmware,
+ // we require one CS low-to-high transition to latch the values from the
+ // CSR into the spi device. We'd normally expect this type of register
+ // setup to be done at init time and that the first SPI transaction will
+ // be a READ_ID or READ_SFDP, thus latching the flash_status contents.
+ //
+ // In this test program, we simply issue a NOP transaction to the device.
+ spi.run_transaction(&mut [Transfer::Write(&[SpiFlash::NOP])])?;
+ let value = SpiFlash::read_status_ex(&*spi, None)?;
+ assert_eq!(value, sr.status);
+ Ok(())
+}
+
fn main() -> Result<()> {
let opts = Opts::from_args();
opts.init.init_logging();
@@ -71,5 +145,8 @@
uart.clear_rx_buffer()?;
execute_test!(test_jedec_id, &opts, &transport);
+ execute_test!(test_enter_exit_4b_mode, &opts, &transport);
+ execute_test!(test_write_enable_disable, &opts, &transport);
+ execute_test!(test_read_status_extended, &opts, &transport);
Ok(())
}