[spi_passthru] Test flash erase opcodes

1. Add a ujson struct for communication information about uploaded
   commands.
2. Test the chip_erase opcode.

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 fae8627..8980089 100644
--- a/sw/device/lib/testing/json/command.h
+++ b/sw/device/lib/testing/json/command.h
@@ -17,6 +17,7 @@
     value(_, PinmuxConfig) \
     value(_, SpiConfigureJedecId) \
     value(_, SpiReadStatus) \
+    value(_, SpiWaitForUpload) \
     value(_, SpiWriteStatus) \
     value(_, SpiWriteSfdp) \
     value(_, SwStrapRead)
diff --git a/sw/device/lib/testing/json/spi_passthru.h b/sw/device/lib/testing/json/spi_passthru.h
index d2d5582..84159a6 100644
--- a/sw/device/lib/testing/json/spi_passthru.h
+++ b/sw/device/lib/testing/json/spi_passthru.h
@@ -26,6 +26,16 @@
     field(data, uint8_t, 256)
 UJSON_SERDE_STRUCT(SfdpData, sfdp_data_t, STRUCT_SFDP_DATA);
 
+#define STRUCT_UPLOAD_INFO(field, string) \
+    field(opcode, uint8_t) \
+    field(has_address, bool) \
+    field(addr_4b, bool) \
+    field(data_len, uint16_t) \
+    field(flash_status, uint32_t) \
+    field(address, uint32_t) \
+    field(data, uint8_t, 256)
+UJSON_SERDE_STRUCT(UploadInfo, upload_info_t, STRUCT_UPLOAD_INFO);
+
 // clang-format on
 #ifdef __cplusplus
 }
diff --git a/sw/device/tests/spi_passthru_test.c b/sw/device/tests/spi_passthru_test.c
index 3ab0ebe..6217498 100644
--- a/sw/device/tests/spi_passthru_test.c
+++ b/sw/device/tests/spi_passthru_test.c
@@ -64,6 +64,62 @@
   return RESP_OK_STATUS(uj);
 }
 
+static status_t wait_for_upload(ujson_t *uj, dif_spi_device_handle_t *spid) {
+  // Wait for a SPI transaction cause an upload.
+  bool upload_pending;
+  do {
+    // The UploadCmdfifoNotEmpty interrupt status is updated after the SPI
+    // transaction completes.
+    TRY(dif_spi_device_irq_is_pending(
+        &spid->dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, &upload_pending));
+  } while (!upload_pending);
+
+  upload_info_t info = {0};
+  uint8_t occupancy;
+
+  // Get the SPI opcode.
+  TRY(dif_spi_device_get_flash_command_fifo_occupancy(spid, &occupancy));
+  if (occupancy != 1) {
+    // Cannot have an uploaded command without an opcode.
+    return INTERNAL();
+  }
+  TRY(dif_spi_device_pop_flash_command_fifo(spid, &info.opcode));
+  // Get the flash_status register.
+  TRY(dif_spi_device_get_flash_status_registers(spid, &info.flash_status));
+
+  // Get the SPI address (if available).
+  TRY(dif_spi_device_get_flash_address_fifo_occupancy(spid, &occupancy));
+  if (occupancy) {
+    dif_toggle_t addr_4b;
+    TRY(dif_spi_device_get_4b_address_mode(spid, &addr_4b));
+    info.addr_4b = addr_4b;
+    TRY(dif_spi_device_pop_flash_address_fifo(spid, &info.address));
+    info.has_address = true;
+  }
+
+  // Get the SPI data payload (if available).
+  uint32_t start;
+  TRY(dif_spi_device_get_flash_payload_fifo_occupancy(spid, &info.data_len,
+                                                      &start));
+  if (info.data_len) {
+    if (info.data_len > sizeof(info.data)) {
+      // We aren't expecting more than 256 bytes of data.
+      return INVALID_ARGUMENT();
+    }
+    TRY(dif_spi_device_read_flash_buffer(spid,
+                                         kDifSpiDeviceFlashBufferTypePayload,
+                                         start, info.data_len, info.data));
+  }
+
+  // Finished: ack the IRQ and clear the busy bit (and all other bits)
+  // in flash_status.
+  TRY(dif_spi_device_irq_acknowledge(&spid->dev,
+                                     kDifSpiDeviceIrqUploadCmdfifoNotEmpty));
+  TRY(dif_spi_device_set_flash_status_registers(spid, 0));
+  RESP_OK(ujson_serialize_upload_info_t, uj, &info);
+  return OK_STATUS();
+}
+
 status_t command_processor(ujson_t *uj) {
   while (true) {
     test_command_t command;
@@ -81,6 +137,9 @@
       case kTestCommandSpiWriteSfdp:
         RESP_ERR(uj, write_sfdp_data(uj, &spid));
         break;
+      case kTestCommandSpiWaitForUpload:
+        RESP_ERR(uj, wait_for_upload(uj, &spid));
+        break;
 
       default:
         LOG_ERROR("Unrecognized command: %d", command);
diff --git a/sw/host/opentitanlib/src/test_utils/spi_passthru.rs b/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
index d30ce2c..a44814e 100644
--- a/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
+++ b/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
@@ -25,7 +25,7 @@
 impl StatusRegister {
     pub fn read(uart: &dyn Uart) -> Result<Self> {
         TestCommand::SpiReadStatus.send(&*uart)?;
-        StatusRegister::recv(uart, Duration::from_secs(300), false)
+        Self::recv(uart, Duration::from_secs(300), false)
     }
 
     pub fn write(&self, uart: &dyn Uart) -> Result<()> {
@@ -44,3 +44,14 @@
         Ok(())
     }
 }
+
+impl UploadInfo {
+    pub fn execute<F>(uart: &dyn Uart, f: F) -> Result<Self>
+    where
+        F: FnOnce() -> Result<()>,
+    {
+        TestCommand::SpiWaitForUpload.send(&*uart)?;
+        f()?;
+        Self::recv(uart, Duration::from_secs(300), false)
+    }
+}
diff --git a/sw/host/tests/chip/spi_passthru/src/main.rs b/sw/host/tests/chip/spi_passthru/src/main.rs
index 186d3c6..cb552dd 100644
--- a/sw/host/tests/chip/spi_passthru/src/main.rs
+++ b/sw/host/tests/chip/spi_passthru/src/main.rs
@@ -11,11 +11,12 @@
 use opentitanlib::io::spi::{Target, Transfer};
 use opentitanlib::spiflash::SpiFlash;
 use opentitanlib::test_utils::init::InitializeTest;
-use opentitanlib::test_utils::spi_passthru::{ConfigJedecId, SfdpData, StatusRegister};
+use opentitanlib::test_utils::spi_passthru::{ConfigJedecId, SfdpData, StatusRegister, UploadInfo};
 use opentitanlib::uart::console::UartConsole;
 
-//const FLASH_STATUS_WIP: u32 = 0x01;
+const FLASH_STATUS_WIP: u32 = 0x01;
 const FLASH_STATUS_WEL: u32 = 0x02;
+const FLASH_STATUS_STD_BITS: u32 = FLASH_STATUS_WEL | FLASH_STATUS_WIP;
 
 #[derive(Debug, StructOpt)]
 struct Opts {
@@ -175,6 +176,26 @@
     Ok(())
 }
 
+fn test_chip_erase(opts: &Opts, transport: &TransportWrapper) -> Result<()> {
+    let uart = transport.uart("console")?;
+    let spi = transport.spi(&opts.spi)?;
+    let flash = SpiFlash::default();
+
+    let info = UploadInfo::execute(&*uart, || {
+        flash.chip_erase(&*spi)?;
+        Ok(())
+    })?;
+
+    assert_eq!(info.opcode, SpiFlash::CHIP_ERASE);
+    assert_eq!(info.has_address, false);
+    assert_eq!(info.data_len, 0);
+    assert_eq!(
+        info.flash_status & FLASH_STATUS_STD_BITS,
+        FLASH_STATUS_WEL | FLASH_STATUS_WIP
+    );
+    Ok(())
+}
+
 fn main() -> Result<()> {
     let opts = Opts::from_args();
     opts.init.init_logging();
@@ -190,5 +211,6 @@
     execute_test!(test_write_enable_disable, &opts, &transport);
     execute_test!(test_read_status_extended, &opts, &transport);
     execute_test!(test_read_sfdp, &opts, &transport);
+    execute_test!(test_chip_erase, &opts, &transport);
     Ok(())
 }