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