[uj] Add commands to interact with SPI flash

Add ujson commands to read the SFDP, read the JEDEC ID, erase sectors or
the whole flash array, and program the flash array of the SPI flash chip
connected to OT's spi_host.

Signed-off-by: Alexander Williams <awill@opentitan.org>
diff --git a/sw/device/lib/testing/json/command.h b/sw/device/lib/testing/json/command.h
index 8980089..a0886f8 100644
--- a/sw/device/lib/testing/json/command.h
+++ b/sw/device/lib/testing/json/command.h
@@ -20,6 +20,10 @@
     value(_, SpiWaitForUpload) \
     value(_, SpiWriteStatus) \
     value(_, SpiWriteSfdp) \
+    value(_, SpiFlashReadId) \
+    value(_, SpiFlashReadSfdp) \
+    value(_, SpiFlashEraseSector) \
+    value(_, SpiFlashWrite) \
     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 84159a6..c9bcafa 100644
--- a/sw/device/lib/testing/json/spi_passthru.h
+++ b/sw/device/lib/testing/json/spi_passthru.h
@@ -36,6 +36,31 @@
     field(data, uint8_t, 256)
 UJSON_SERDE_STRUCT(UploadInfo, upload_info_t, STRUCT_UPLOAD_INFO);
 
+#define STRUCT_SPI_FLASH_READ_ID(field, string) \
+    field(device_id, uint16_t) \
+    field(manufacturer_id, uint8_t) \
+    field(continuation_len, uint8_t)
+UJSON_SERDE_STRUCT(SpiFlashReadId, spi_flash_read_id_t,
+    STRUCT_SPI_FLASH_READ_ID);
+
+#define STRUCT_SPI_FLASH_READ_SFDP(field, string) \
+    field(address, uint32_t) \
+    field(length, uint16_t)
+UJSON_SERDE_STRUCT(SpiFlashReadSfdp, spi_flash_read_sfdp_t,
+    STRUCT_SPI_FLASH_READ_SFDP);
+
+#define STRUCT_SPI_FLASH_ERASE_SECTOR(field, string) \
+    field(address, uint32_t) \
+    field(addr4b, bool)
+UJSON_SERDE_STRUCT(SpiFlashEraseSector, spi_flash_erase_sector_t, STRUCT_SPI_FLASH_ERASE_SECTOR);
+
+#define STRUCT_SPI_FLASH_WRITE(field, string) \
+    field(address, uint32_t) \
+    field(addr4b, bool) \
+    field(data, uint8_t, 256) \
+    field(length, uint16_t)
+UJSON_SERDE_STRUCT(SpiFlashWrite, spi_flash_write_t, STRUCT_SPI_FLASH_WRITE);
+
 // clang-format on
 #ifdef __cplusplus
 }
diff --git a/sw/host/opentitanlib/src/test_utils/spi_passthru.rs b/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
index a44814e..ae2e131 100644
--- a/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
+++ b/sw/host/opentitanlib/src/test_utils/spi_passthru.rs
@@ -55,3 +55,38 @@
         Self::recv(uart, Duration::from_secs(300), false)
     }
 }
+
+impl SpiFlashReadId {
+    pub fn execute(uart: &dyn Uart) -> Result<Self> {
+        TestCommand::SpiFlashReadId.send(uart)?;
+        let data = SpiFlashReadId::recv(uart, Duration::from_secs(300), false)?;
+        Ok(data)
+    }
+}
+
+impl SpiFlashReadSfdp {
+    pub fn execute(&self, uart: &dyn Uart) -> Result<SfdpData> {
+        TestCommand::SpiFlashReadSfdp.send(uart)?;
+        self.send(uart)?;
+        let sfdp = SfdpData::recv(uart, Duration::from_secs(300), false)?;
+        Ok(sfdp)
+    }
+}
+
+impl SpiFlashEraseSector {
+    pub fn execute(&self, uart: &dyn Uart) -> Result<()> {
+        TestCommand::SpiFlashEraseSector.send(uart)?;
+        self.send(uart)?;
+        Status::recv(uart, Duration::from_secs(300), false)?;
+        Ok(())
+    }
+}
+
+impl SpiFlashWrite {
+    pub fn execute(&self, uart: &dyn Uart) -> Result<()> {
+        TestCommand::SpiFlashWrite.send(uart)?;
+        self.send(uart)?;
+        Status::recv(uart, Duration::from_secs(300), false)?;
+        Ok(())
+    }
+}