[testutils] Add SPI flash command utils
Add some utility functions to send SPI flash command sequences to a chip
connected to a spi_host (at chip select 0).
Rebase the DV passthrough test on these functions.
Signed-off-by: Alexander Williams <awill@opentitan.org>
diff --git a/sw/device/lib/testing/BUILD b/sw/device/lib/testing/BUILD
index 1df3d48..bd711f4 100644
--- a/sw/device/lib/testing/BUILD
+++ b/sw/device/lib/testing/BUILD
@@ -361,6 +361,19 @@
)
cc_library(
+ name = "spi_flash_testutils",
+ srcs = ["spi_flash_testutils.c"],
+ hdrs = ["spi_flash_testutils.h"],
+ target_compatible_with = [OPENTITAN_CPU],
+ deps = [
+ ":spi_device_testutils",
+ "//sw/device/lib/base:macros",
+ "//sw/device/lib/dif:spi_host",
+ "//sw/device/lib/testing/test_framework:check",
+ ],
+)
+
+cc_library(
name = "usb_testutils",
srcs = [
"usb_testutils.c",
diff --git a/sw/device/lib/testing/spi_flash_testutils.c b/sw/device/lib/testing/spi_flash_testutils.c
new file mode 100644
index 0000000..3090058
--- /dev/null
+++ b/sw/device/lib/testing/spi_flash_testutils.c
@@ -0,0 +1,207 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/testing/spi_flash_testutils.h"
+
+#include "sw/device/lib/base/macros.h"
+#include "sw/device/lib/dif/dif_spi_host.h"
+#include "sw/device/lib/testing/spi_device_testutils.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+
+void spi_flash_testutils_read_id(dif_spi_host_t *spih,
+ spi_flash_testutils_jedec_id_t *id) {
+ CHECK(spih != NULL);
+ CHECK(id != NULL);
+
+ uint8_t buffer[32];
+ dif_spi_host_segment_t transaction[] = {
+ {
+ .type = kDifSpiHostSegmentTypeOpcode,
+ .opcode = kSpiDeviceFlashOpReadJedec,
+ },
+ {
+ .type = kDifSpiHostSegmentTypeRx,
+ .rx =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .buf = buffer,
+ .length = sizeof(buffer),
+ },
+ },
+ };
+ CHECK_DIF_OK(dif_spi_host_transaction(spih, /*csid=*/0, transaction,
+ ARRAYSIZE(transaction)));
+
+ size_t page = 0;
+ while ((page < sizeof(buffer)) && (buffer[page] == 0x7fu)) {
+ ++page;
+ }
+ CHECK(page + 3 <= sizeof(buffer));
+ id->continuation_len = page;
+ id->manufacturer_id = buffer[page];
+ id->device_id = buffer[page + 1];
+ id->device_id |= (uint16_t)buffer[page + 2] << 8;
+}
+
+void spi_flash_testutils_read_sfdp(dif_spi_host_t *spih, uint32_t address,
+ uint8_t *buffer, size_t length) {
+ CHECK(spih != NULL);
+ CHECK(buffer != NULL);
+
+ dif_spi_host_segment_t transaction[] = {
+ {
+ .type = kDifSpiHostSegmentTypeOpcode,
+ .opcode = kSpiDeviceFlashOpReadSfdp,
+ },
+ {
+ .type = kDifSpiHostSegmentTypeAddress,
+ .address =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .mode = kDifSpiHostAddrMode3b,
+ .address = address,
+ },
+ },
+ {
+ .type = kDifSpiHostSegmentTypeDummy,
+ .dummy =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .length = 8,
+ },
+ },
+ {
+ .type = kDifSpiHostSegmentTypeRx,
+ .rx =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .buf = buffer,
+ .length = length,
+ },
+ },
+ };
+ CHECK_DIF_OK(dif_spi_host_transaction(spih, /*csid=*/0, transaction,
+ ARRAYSIZE(transaction)));
+}
+
+void spi_flash_testutils_wait_until_not_busy(dif_spi_host_t *spih) {
+ CHECK(spih != NULL);
+ uint8_t status;
+
+ do {
+ dif_spi_host_segment_t transaction[] = {
+ {
+ .type = kDifSpiHostSegmentTypeOpcode,
+ .opcode = kSpiDeviceFlashOpReadStatus1,
+ },
+ {
+ .type = kDifSpiHostSegmentTypeRx,
+ .rx =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .buf = &status,
+ .length = 1,
+ },
+ },
+ };
+ CHECK_DIF_OK(dif_spi_host_transaction(spih, /*csid=*/0, transaction,
+ ARRAYSIZE(transaction)));
+ } while (status & kSpiFlashStatusBitWip);
+}
+
+void spi_flash_testutils_issue_write_enable(dif_spi_host_t *spih) {
+ CHECK(spih != NULL);
+ dif_spi_host_segment_t transaction[] = {
+ {
+ .type = kDifSpiHostSegmentTypeOpcode,
+ .opcode = kSpiDeviceFlashOpWriteEnable,
+ },
+ };
+ CHECK_DIF_OK(dif_spi_host_transaction(spih, /*csid=*/0, transaction,
+ ARRAYSIZE(transaction)));
+}
+
+void spi_flash_testutils_erase_chip(dif_spi_host_t *spih) {
+ CHECK(spih != NULL);
+ spi_flash_testutils_issue_write_enable(spih);
+
+ dif_spi_host_segment_t transaction[] = {
+ {
+ .type = kDifSpiHostSegmentTypeOpcode,
+ .opcode = kSpiDeviceFlashOpChipErase,
+ },
+ };
+ CHECK_DIF_OK(dif_spi_host_transaction(spih, /*csid=*/0, transaction,
+ ARRAYSIZE(transaction)));
+ spi_flash_testutils_wait_until_not_busy(spih);
+}
+
+void spi_flash_testutils_erase_sector(dif_spi_host_t *spih, uint32_t address,
+ bool addr_is_4b) {
+ CHECK(spih != NULL);
+ spi_flash_testutils_issue_write_enable(spih);
+
+ dif_spi_host_addr_mode_t addr_mode =
+ addr_is_4b ? kDifSpiHostAddrMode4b : kDifSpiHostAddrMode3b;
+ dif_spi_host_segment_t transaction[] = {
+ {
+ .type = kDifSpiHostSegmentTypeOpcode,
+ .opcode = kSpiDeviceFlashOpSectorErase,
+ },
+ {
+ .type = kDifSpiHostSegmentTypeAddress,
+ .address =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .mode = addr_mode,
+ .address = address,
+ },
+ },
+ };
+ CHECK_DIF_OK(dif_spi_host_transaction(spih, /*csid=*/0, transaction,
+ ARRAYSIZE(transaction)));
+
+ spi_flash_testutils_wait_until_not_busy(spih);
+}
+
+void spi_flash_testutils_program_page(dif_spi_host_t *spih, uint8_t *payload,
+ size_t length, uint32_t address,
+ bool addr_is_4b) {
+ CHECK(spih != NULL);
+ CHECK(payload != NULL);
+ CHECK(length <= 256); // Length must be less than a page size.
+
+ spi_flash_testutils_issue_write_enable(spih);
+
+ dif_spi_host_addr_mode_t addr_mode =
+ addr_is_4b ? kDifSpiHostAddrMode4b : kDifSpiHostAddrMode3b;
+ dif_spi_host_segment_t transaction[] = {
+ {
+ .type = kDifSpiHostSegmentTypeOpcode,
+ .opcode = kSpiDeviceFlashOpPageProgram,
+ },
+ {
+ .type = kDifSpiHostSegmentTypeAddress,
+ .address =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .mode = addr_mode,
+ .address = address,
+ },
+ },
+ {
+ .type = kDifSpiHostSegmentTypeTx,
+ .tx =
+ {
+ .width = kDifSpiHostWidthStandard,
+ .buf = payload,
+ .length = length,
+ },
+ },
+ };
+ CHECK_DIF_OK(dif_spi_host_transaction(spih, /*csid=*/0, transaction,
+ ARRAYSIZE(transaction)));
+
+ spi_flash_testutils_wait_until_not_busy(spih);
+}
diff --git a/sw/device/lib/testing/spi_flash_testutils.h b/sw/device/lib/testing/spi_flash_testutils.h
new file mode 100644
index 0000000..04b8c87
--- /dev/null
+++ b/sw/device/lib/testing/spi_flash_testutils.h
@@ -0,0 +1,99 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_SPI_FLASH_TESTUTILS_H_
+#define OPENTITAN_SW_DEVICE_LIB_TESTING_SPI_FLASH_TESTUTILS_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "sw/device/lib/dif/dif_spi_host.h"
+
+typedef struct spi_flash_testutils_jedec_id {
+ uint16_t device_id;
+ uint8_t manufacturer_id;
+ uint8_t continuation_len;
+} spi_flash_testutils_jedec_id_t;
+
+/**
+ * Read out the JEDEC ID from the SPI flash.
+ *
+ * @param spih A SPI host handle.
+ * @param[out] id A pointer to where to store the ID.
+ */
+void spi_flash_testutils_read_id(dif_spi_host_t *spih,
+ spi_flash_testutils_jedec_id_t *id);
+
+/**
+ * Read out the SFDP from the indicated address and place the table contents
+ * into the buffer.
+ *
+ * @param spih A SPI host handle.
+ * @param buffer A pointer to a buffer that will hold the SFDP contents.
+ * @param length The number of bytes to write into `buffer`.
+ */
+void spi_flash_testutils_read_sfdp(dif_spi_host_t *spih, uint32_t address,
+ uint8_t *buffer, size_t length);
+
+typedef enum spi_flash_status_bit {
+ kSpiFlashStatusBitWip = 0x1,
+ kSpiFlashStatusBitWel = 0x2,
+} spi_flash_status_bit_t;
+
+/**
+ * Spin wait until a Read Status command shows the downstream SPI flash is no
+ * longer busy.
+ */
+void spi_flash_testutils_wait_until_not_busy(dif_spi_host_t *spih);
+
+/**
+ * Issue the Write Enable command to the downstream SPI flash.
+ */
+void spi_flash_testutils_issue_write_enable(dif_spi_host_t *spih);
+
+/**
+ * Perform full Chip Erase sequence, including the Write Enable and Chip Erase
+ * commands, and poll the status registers in a loop until the WIP bit clears.
+ *
+ * Does not return until the erase completes.
+ *
+ * @param spih A SPI host handle.
+ */
+void spi_flash_testutils_erase_chip(dif_spi_host_t *spih);
+
+/**
+ * Perform full Sector Erase sequence, including the Write Enable and Sector
+ * Erase commands, and poll the status registers in a loop until the WIP bit
+ * clears.
+ *
+ * Does not return until the erase completes.
+ *
+ * @param spih A SPI host handle.
+ * @param address An address contained within the desired sector.
+ * @param addr_is_4b True if `address` is 4 bytes long, else 3 bytes.
+ */
+void spi_flash_testutils_erase_sector(dif_spi_host_t *spih, uint32_t address,
+ bool addr_is_4b);
+
+/**
+ * Perform full Page Program sequence, including the Write Enable and Page
+ * Program commands, and poll the status registers in a loop until the WIP bit
+ * clears.
+ *
+ * Does not return until the programming operation completes.
+ *
+ * @param spih A SPI host handle.
+ * @param payload A pointer to the payload to be written to the page.
+ * @param length Number of bytes in the payload. Must be less than or equal to
+ * 256 bytes.
+ * @param address The start address where the payload programming should begin.
+ * Note that an address + length that crosses a page boundary may
+ * wrap around to the start of the page.
+ * @param addr_is_4b True if `address` is 4 bytes long, else 3 bytes.
+ */
+void spi_flash_testutils_program_page(dif_spi_host_t *spih, uint8_t *payload,
+ size_t length, uint32_t address,
+ bool addr_is_4b);
+
+#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_SPI_FLASH_TESTUTILS_H_
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD
index 0be24d4..9bbed52 100644
--- a/sw/device/tests/sim_dv/BUILD
+++ b/sw/device/tests/sim_dv/BUILD
@@ -831,6 +831,7 @@
"//sw/device/lib/runtime:log",
"//sw/device/lib/testing:pinmux_testutils",
"//sw/device/lib/testing:spi_device_testutils",
+ "//sw/device/lib/testing:spi_flash_testutils",
"//sw/device/lib/testing/test_framework:ottf_main",
],
)
diff --git a/sw/device/tests/sim_dv/spi_passthrough_test.c b/sw/device/tests/sim_dv/spi_passthrough_test.c
index 2e4c7a6..266d98b 100644
--- a/sw/device/tests/sim_dv/spi_passthrough_test.c
+++ b/sw/device/tests/sim_dv/spi_passthrough_test.c
@@ -18,6 +18,7 @@
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/pinmux_testutils.h"
#include "sw/device/lib/testing/spi_device_testutils.h"
+#include "sw/device/lib/testing/spi_flash_testutils.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
@@ -135,11 +136,6 @@
// },
};
-enum spi_flash_status_bit {
- kSpiFlashStatusBitWip = 0x1,
- kSpiFlashStatusBitWel = 0x2,
-};
-
/**
* Initialize the provided SPI host. For the most part, the values provided are
* filler, as spi_host0 will be in passthrough mode and spi_host1 will remain
@@ -162,48 +158,6 @@
}
/**
- * Spin wait until a Read Status command shows the downstream SPI flash is no
- * longer busy.
- */
-void wait_until_not_busy(void) {
- uint8_t status;
-
- do {
- dif_spi_host_segment_t transaction[] = {
- {
- .type = kDifSpiHostSegmentTypeOpcode,
- .opcode = kSpiDeviceFlashOpReadStatus1,
- },
- {
- .type = kDifSpiHostSegmentTypeRx,
- .rx =
- {
- .width = kDifSpiHostWidthStandard,
- .buf = &status,
- .length = 1,
- },
- },
- };
- CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
- ARRAYSIZE(transaction)));
- } while (status & kSpiFlashStatusBitWip);
-}
-
-/**
- * Issue the Write Enable command to the downstream SPI flash.
- */
-void issue_write_enable(void) {
- dif_spi_host_segment_t transaction[] = {
- {
- .type = kDifSpiHostSegmentTypeOpcode,
- .opcode = kSpiDeviceFlashOpWriteEnable,
- },
- };
- CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
- ARRAYSIZE(transaction)));
-}
-
-/**
* Handle an incoming Write Status command.
*
* Modifies the internal status register and relays the command out to the
@@ -229,7 +183,7 @@
status |= (payload << offset);
CHECK_DIF_OK(dif_spi_device_set_flash_status_registers(&spi_device, status));
- issue_write_enable();
+ spi_flash_testutils_issue_write_enable(&spi_host0);
dif_spi_host_segment_t transaction[] = {
{
@@ -248,7 +202,7 @@
};
CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
ARRAYSIZE(transaction)));
- wait_until_not_busy();
+ spi_flash_testutils_wait_until_not_busy(&spi_host0);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}
@@ -258,17 +212,7 @@
* Relays the command out to the downstream SPI flash.
*/
void handle_chip_erase(void) {
- issue_write_enable();
-
- dif_spi_host_segment_t transaction[] = {
- {
- .type = kDifSpiHostSegmentTypeOpcode,
- .opcode = kSpiDeviceFlashOpChipErase,
- },
- };
- CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
- ARRAYSIZE(transaction)));
- wait_until_not_busy();
+ spi_flash_testutils_erase_chip(&spi_host0);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}
@@ -290,30 +234,8 @@
CHECK_DIF_OK(
dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
- issue_write_enable();
-
- dif_spi_host_addr_mode_t addr_mode = dif_toggle_to_bool(addr4b_enabled)
- ? kDifSpiHostAddrMode4b
- : kDifSpiHostAddrMode3b;
- dif_spi_host_segment_t transaction[] = {
- {
- .type = kDifSpiHostSegmentTypeOpcode,
- .opcode = kSpiDeviceFlashOpSectorErase,
- },
- {
- .type = kDifSpiHostSegmentTypeAddress,
- .address =
- {
- .width = kDifSpiHostWidthStandard,
- .mode = addr_mode,
- .address = address,
- },
- },
- };
- CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
- ARRAYSIZE(transaction)));
-
- wait_until_not_busy();
+ bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
+ spi_flash_testutils_erase_sector(&spi_host0, address, addr_is_4b);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}
@@ -346,39 +268,9 @@
CHECK_DIF_OK(
dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
- issue_write_enable();
-
- dif_spi_host_addr_mode_t addr_mode = dif_toggle_to_bool(addr4b_enabled)
- ? kDifSpiHostAddrMode4b
- : kDifSpiHostAddrMode3b;
- dif_spi_host_segment_t transaction[] = {
- {
- .type = kDifSpiHostSegmentTypeOpcode,
- .opcode = kSpiDeviceFlashOpPageProgram,
- },
- {
- .type = kDifSpiHostSegmentTypeAddress,
- .address =
- {
- .width = kDifSpiHostWidthStandard,
- .mode = addr_mode,
- .address = address,
- },
- },
- {
- .type = kDifSpiHostSegmentTypeTx,
- .tx =
- {
- .width = kDifSpiHostWidthStandard,
- .buf = payload,
- .length = payload_occupancy,
- },
- },
- };
- CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
- ARRAYSIZE(transaction)));
-
- wait_until_not_busy();
+ bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
+ spi_flash_testutils_program_page(&spi_host0, payload, payload_occupancy,
+ address, addr_is_4b);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}