[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(()) }