| // 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/flash_ctrl_testutils.h" | |
| #include <assert.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include "sw/device/lib/base/abs_mmio.h" | |
| #include "sw/device/lib/base/mmio.h" | |
| #include "sw/device/lib/dif/dif_flash_ctrl.h" | |
| #include "sw/device/lib/runtime/hart.h" | |
| #include "sw/device/lib/runtime/ibex.h" | |
| #include "sw/device/lib/testing/test_framework/check.h" | |
| #include "flash_ctrl_regs.h" | |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" | |
| void flash_ctrl_testutils_wait_for_init(dif_flash_ctrl_state_t *flash_state) { | |
| dif_flash_ctrl_status_t status; | |
| do { | |
| CHECK_DIF_OK(dif_flash_ctrl_get_status(flash_state, &status)); | |
| } while (status.controller_init_wip); | |
| } | |
| bool flash_ctrl_testutils_wait_transaction_end( | |
| dif_flash_ctrl_state_t *flash_state) { | |
| dif_flash_ctrl_output_t output; | |
| while (true) { | |
| dif_result_t dif_result = dif_flash_ctrl_end(flash_state, &output); | |
| CHECK(dif_result != kDifBadArg); | |
| CHECK(dif_result != kDifError); | |
| if (dif_result == kDifOk) { | |
| break; | |
| } | |
| } | |
| if (output.operation_error) { | |
| dif_flash_ctrl_error_codes_t codes = output.error_code.codes; | |
| uint32_t error_reg = 0; | |
| error_reg = bitfield_bit32_write(error_reg, FLASH_CTRL_ERR_CODE_MP_ERR_BIT, | |
| codes.memory_properties_error); | |
| error_reg = bitfield_bit32_write(error_reg, FLASH_CTRL_ERR_CODE_RD_ERR_BIT, | |
| codes.read_error); | |
| error_reg = | |
| bitfield_bit32_write(error_reg, FLASH_CTRL_ERR_CODE_PROG_WIN_ERR_BIT, | |
| codes.prog_window_error); | |
| error_reg = | |
| bitfield_bit32_write(error_reg, FLASH_CTRL_ERR_CODE_PROG_TYPE_ERR_BIT, | |
| codes.prog_type_error); | |
| error_reg = | |
| bitfield_bit32_write(error_reg, FLASH_CTRL_ERR_CODE_UPDATE_ERR_BIT, | |
| codes.shadow_register_error); | |
| } | |
| CHECK_DIF_OK( | |
| dif_flash_ctrl_clear_error_codes(flash_state, output.error_code.codes)); | |
| return output.operation_error == 0; | |
| } | |
| uint32_t flash_ctrl_testutils_data_region_setup_properties( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t base_page_index, | |
| uint32_t data_region, uint32_t region_size, | |
| dif_flash_ctrl_region_properties_t region_properties) { | |
| dif_flash_ctrl_data_region_properties_t data_region_properties = { | |
| .base = base_page_index, | |
| .properties = region_properties, | |
| .size = region_size}; | |
| CHECK_DIF_OK(dif_flash_ctrl_set_data_region_properties( | |
| flash_state, data_region, data_region_properties)); | |
| CHECK_DIF_OK(dif_flash_ctrl_set_data_region_enablement( | |
| flash_state, data_region, kDifToggleEnabled)); | |
| dif_flash_ctrl_device_info_t device_info = dif_flash_ctrl_get_device_info(); | |
| return (base_page_index * device_info.bytes_per_page); | |
| } | |
| uint32_t flash_ctrl_testutils_data_region_setup( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t base_page_index, | |
| uint32_t data_region, uint32_t region_size) { | |
| dif_flash_ctrl_region_properties_t region_properties = { | |
| .ecc_en = kMultiBitBool4True, | |
| .high_endurance_en = kMultiBitBool4False, | |
| .erase_en = kMultiBitBool4True, | |
| .prog_en = kMultiBitBool4True, | |
| .rd_en = kMultiBitBool4True, | |
| .scramble_en = kMultiBitBool4False}; | |
| return flash_ctrl_testutils_data_region_setup_properties( | |
| flash_state, base_page_index, data_region, region_size, | |
| region_properties); | |
| } | |
| uint32_t flash_ctrl_testutils_data_region_scrambled_setup( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t base_page_index, | |
| uint32_t data_region, uint32_t region_size) { | |
| dif_flash_ctrl_region_properties_t region_properties = { | |
| .ecc_en = kMultiBitBool4True, | |
| .high_endurance_en = kMultiBitBool4False, | |
| .erase_en = kMultiBitBool4True, | |
| .prog_en = kMultiBitBool4True, | |
| .rd_en = kMultiBitBool4True, | |
| .scramble_en = kMultiBitBool4True}; | |
| return flash_ctrl_testutils_data_region_setup_properties( | |
| flash_state, base_page_index, data_region, region_size, | |
| region_properties); | |
| } | |
| uint32_t flash_ctrl_testutils_info_region_setup_properties( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t page_id, uint32_t bank, | |
| uint32_t partition_id, | |
| dif_flash_ctrl_region_properties_t region_properties) { | |
| dif_flash_ctrl_info_region_t info_region = { | |
| .bank = bank, .page = page_id, .partition_id = partition_id}; | |
| CHECK_DIF_OK(dif_flash_ctrl_set_info_region_properties( | |
| flash_state, info_region, region_properties)); | |
| CHECK_DIF_OK(dif_flash_ctrl_set_info_region_enablement( | |
| flash_state, info_region, kDifToggleEnabled)); | |
| dif_flash_ctrl_device_info_t device_info = dif_flash_ctrl_get_device_info(); | |
| return (page_id * device_info.bytes_per_page); | |
| } | |
| uint32_t flash_ctrl_testutils_info_region_setup( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t page_id, uint32_t bank, | |
| uint32_t partition_id) { | |
| dif_flash_ctrl_region_properties_t region_properties = { | |
| .ecc_en = kMultiBitBool4True, | |
| .high_endurance_en = kMultiBitBool4False, | |
| .erase_en = kMultiBitBool4True, | |
| .prog_en = kMultiBitBool4True, | |
| .rd_en = kMultiBitBool4True, | |
| .scramble_en = kMultiBitBool4False}; | |
| return flash_ctrl_testutils_info_region_setup_properties( | |
| flash_state, page_id, bank, partition_id, region_properties); | |
| } | |
| uint32_t flash_ctrl_testutils_info_region_scrambled_setup( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t page_id, uint32_t bank, | |
| uint32_t partition_id) { | |
| dif_flash_ctrl_region_properties_t region_properties = { | |
| .ecc_en = kMultiBitBool4True, | |
| .high_endurance_en = kMultiBitBool4False, | |
| .erase_en = kMultiBitBool4True, | |
| .prog_en = kMultiBitBool4True, | |
| .rd_en = kMultiBitBool4True, | |
| .scramble_en = kMultiBitBool4True}; | |
| return flash_ctrl_testutils_info_region_setup_properties( | |
| flash_state, page_id, bank, partition_id, region_properties); | |
| } | |
| bool flash_ctrl_testutils_erase_page( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t byte_address, | |
| uint32_t partition_id, dif_flash_ctrl_partition_type_t partition_type) { | |
| dif_flash_ctrl_transaction_t transaction = {.byte_address = byte_address, | |
| .op = kDifFlashCtrlOpPageErase, | |
| .partition_type = partition_type, | |
| .partition_id = partition_id, | |
| .word_count = 0x0}; | |
| CHECK_DIF_OK(dif_flash_ctrl_start(flash_state, transaction)); | |
| return flash_ctrl_testutils_wait_transaction_end(flash_state); | |
| } | |
| bool flash_ctrl_testutils_write(dif_flash_ctrl_state_t *flash_state, | |
| uint32_t byte_address, uint32_t partition_id, | |
| const uint32_t *data, | |
| dif_flash_ctrl_partition_type_t partition_type, | |
| uint32_t word_count) { | |
| // Cannot program partial words. | |
| // TODO: #13773 for more details. | |
| // When 13773 is supported, programs to non-scrambled or ecc enabled | |
| // pages can support byte writes. While programs to scrambled or ecc | |
| // enabled pages can support only flash item writes. | |
| if (byte_address & (sizeof(uint32_t) - 1)) { | |
| LOG_ERROR("Unaligned address 0x%x", byte_address); | |
| return false; | |
| } | |
| dif_flash_ctrl_transaction_t transaction = {.byte_address = byte_address, | |
| .op = kDifFlashCtrlOpProgram, | |
| .partition_type = partition_type, | |
| .partition_id = partition_id, | |
| .word_count = 0x0}; | |
| static_assert(FLASH_CTRL_PARAM_BYTES_PER_WORD >= sizeof(uint32_t), | |
| "flash_ctrl_testutils_write() assumes the flash word width is" | |
| "greater than or equal to the bus word width."); | |
| uint32_t words_written = 0; | |
| uint32_t word_address = byte_address / sizeof(uint32_t); | |
| const uint32_t prog_window_size = | |
| (uint32_t)FLASH_CTRL_PARAM_REG_BUS_PGM_RES_BYTES / sizeof(uint32_t); | |
| const uint32_t prog_window_mask = ~(prog_window_size - 1); | |
| bool retval = true; | |
| while (words_written < word_count) { | |
| // Writes must not cross programming resolution window boundaries, which | |
| // occur at every prog_window_size words. | |
| uint32_t window_limit = | |
| ((word_address + prog_window_size) & prog_window_mask) - word_address; | |
| uint32_t words_remaining = word_count - words_written; | |
| uint32_t words_to_write = | |
| (words_remaining < window_limit) ? words_remaining : window_limit; | |
| transaction.byte_address = word_address * sizeof(uint32_t); | |
| transaction.word_count = words_to_write; | |
| CHECK_DIF_OK(dif_flash_ctrl_start(flash_state, transaction)); | |
| CHECK_DIF_OK(dif_flash_ctrl_prog_fifo_push(flash_state, words_to_write, | |
| data + words_written)); | |
| retval &= flash_ctrl_testutils_wait_transaction_end(flash_state); | |
| word_address += words_to_write; | |
| words_written += words_to_write; | |
| } | |
| return retval; | |
| } | |
| bool flash_ctrl_testutils_erase_and_write_page( | |
| dif_flash_ctrl_state_t *flash_state, uint32_t byte_address, | |
| uint32_t partition_id, const uint32_t *data, | |
| dif_flash_ctrl_partition_type_t partition_type, uint32_t word_count) { | |
| bool retval = flash_ctrl_testutils_erase_page(flash_state, byte_address, | |
| partition_id, partition_type); | |
| retval &= flash_ctrl_testutils_write(flash_state, byte_address, partition_id, | |
| data, partition_type, word_count); | |
| return retval; | |
| } | |
| bool flash_ctrl_testutils_read(dif_flash_ctrl_state_t *flash_state, | |
| uint32_t byte_address, uint32_t partition_id, | |
| uint32_t *data_out, | |
| dif_flash_ctrl_partition_type_t partition_type, | |
| uint32_t word_count, uint32_t delay) { | |
| dif_flash_ctrl_transaction_t transaction = {.byte_address = byte_address, | |
| .op = kDifFlashCtrlOpRead, | |
| .partition_type = partition_type, | |
| .partition_id = partition_id, | |
| .word_count = word_count}; | |
| // Read Page. | |
| CHECK_DIF_OK(dif_flash_ctrl_start(flash_state, transaction)); | |
| // Optional delay to allow for read fifo fill testing. | |
| busy_spin_micros(delay); | |
| CHECK_DIF_OK(dif_flash_ctrl_read_fifo_pop(flash_state, word_count, data_out)); | |
| return flash_ctrl_testutils_wait_transaction_end(flash_state); | |
| } | |
| void flash_ctrl_testutils_default_region_access( | |
| dif_flash_ctrl_state_t *flash_state, bool rd_en, bool prog_en, | |
| bool erase_en, bool scramble_en, bool ecc_en, bool high_endurance_en) { | |
| dif_flash_ctrl_region_properties_t default_properties = { | |
| .rd_en = rd_en ? kMultiBitBool4True : kMultiBitBool4False, | |
| .prog_en = prog_en ? kMultiBitBool4True : kMultiBitBool4False, | |
| .erase_en = erase_en ? kMultiBitBool4True : kMultiBitBool4False, | |
| .scramble_en = scramble_en ? kMultiBitBool4True : kMultiBitBool4False, | |
| .ecc_en = ecc_en ? kMultiBitBool4True : kMultiBitBool4False, | |
| .high_endurance_en = | |
| high_endurance_en ? kMultiBitBool4True : kMultiBitBool4False}; | |
| CHECK_DIF_OK(dif_flash_ctrl_set_default_region_properties( | |
| flash_state, default_properties)); | |
| } | |
| bool flash_ctrl_testutils_bank_erase(dif_flash_ctrl_state_t *flash_state, | |
| uint32_t bank, bool data_only) { | |
| dif_toggle_t bank_erase_enabled; | |
| CHECK_DIF_OK(dif_flash_ctrl_get_bank_erase_enablement(flash_state, bank, | |
| &bank_erase_enabled)); | |
| if (bank_erase_enabled == kDifToggleDisabled) { | |
| CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(flash_state, bank, | |
| kDifToggleEnabled)); | |
| } | |
| dif_flash_ctrl_device_info_t flash_info = dif_flash_ctrl_get_device_info(); | |
| uint32_t byte_address = | |
| bank * flash_info.bytes_per_page * flash_info.data_pages; | |
| dif_flash_ctrl_partition_type_t partition_type = | |
| data_only ? kDifFlashCtrlPartitionTypeData | |
| : kDifFlashCtrlPartitionTypeInfo; | |
| dif_flash_ctrl_transaction_t transaction = {.byte_address = byte_address, | |
| .op = kDifFlashCtrlOpBankErase, | |
| .partition_type = partition_type, | |
| .partition_id = 0, | |
| .word_count = 0x0}; | |
| CHECK_DIF_OK(dif_flash_ctrl_start(flash_state, transaction)); | |
| bool retval = flash_ctrl_testutils_wait_transaction_end(flash_state); | |
| CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(flash_state, bank, | |
| bank_erase_enabled)); | |
| return retval; | |
| } | |
| enum { | |
| kNonVolatileCounterFlashWords = 256, | |
| }; | |
| static_assert(kNonVolatileCounterFlashWords == | |
| kFlashCtrlTestUtilsCounterMaxCount, | |
| "Word count must be equal to max count."); | |
| static_assert( | |
| FLASH_CTRL_PARAM_BYTES_PER_WORD == sizeof(uint64_t), | |
| "Elements of the counter array must be the same size as a flash word"); | |
| extern char _non_volatile_counter_flash_words[]; | |
| OT_SECTION(".non_volatile_counter_0") | |
| uint64_t nv_counter_0[kNonVolatileCounterFlashWords]; | |
| OT_SECTION(".non_volatile_counter_1") | |
| uint64_t nv_counter_1[kNonVolatileCounterFlashWords]; | |
| OT_SECTION(".non_volatile_counter_2") | |
| uint64_t nv_counter_2[kNonVolatileCounterFlashWords]; | |
| OT_SECTION(".non_volatile_counter_3") | |
| uint64_t nv_counter_3[kNonVolatileCounterFlashWords]; | |
| static uint64_t *const kNvCounters[] = { | |
| nv_counter_0, | |
| nv_counter_1, | |
| nv_counter_2, | |
| nv_counter_3, | |
| }; | |
| uint32_t flash_ctrl_testutils_counter_get(size_t counter) { | |
| CHECK(counter < ARRAYSIZE(kNvCounters)); | |
| CHECK((uint32_t)&_non_volatile_counter_flash_words == | |
| kNonVolatileCounterFlashWords); | |
| // Use a reverse loop since `flash_ctrl_testutils_counter_set_at_least()` can | |
| // introduce gaps. | |
| size_t i = kNonVolatileCounterFlashWords - 1; | |
| for (; i < kNonVolatileCounterFlashWords; --i) { | |
| if (kNvCounters[counter][i] == 0) { | |
| break; | |
| } | |
| } | |
| return i + 1; | |
| } | |
| void flash_ctrl_testutils_counter_increment(dif_flash_ctrl_state_t *flash_state, | |
| size_t counter) { | |
| size_t i = flash_ctrl_testutils_counter_get(counter); | |
| CHECK(i < kNonVolatileCounterFlashWords, | |
| "Non-volatile counter %u is at its maximum", counter); | |
| flash_ctrl_testutils_counter_set_at_least(flash_state, counter, i + 1); | |
| CHECK(flash_ctrl_testutils_counter_get(counter) == i + 1, | |
| "Counter increment failed"); | |
| } | |
| void flash_ctrl_testutils_counter_set_at_least( | |
| dif_flash_ctrl_state_t *flash_state, size_t counter, uint32_t val) { | |
| CHECK(val <= kNonVolatileCounterFlashWords, | |
| "Non-volatile counter %u new value %u > max value %u", counter, val, | |
| kNonVolatileCounterFlashWords); | |
| if (val == 0) { | |
| return; | |
| } | |
| uint32_t new_val[FLASH_CTRL_PARAM_BYTES_PER_WORD / sizeof(uint32_t)] = {0, 0}; | |
| CHECK(flash_ctrl_testutils_write(flash_state, | |
| (uint32_t)&kNvCounters[counter][val - 1] - | |
| TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR, | |
| 0, new_val, kDifFlashCtrlPartitionTypeData, | |
| ARRAYSIZE(new_val)), | |
| "Flash write failed"); | |
| } | |
| // At the beginning of the simulation (Verilator, VCS,etc.), | |
| // the content of the flash might be all-zeros, and thus, | |
| // the NVM counter's inital value might be 256. | |
| // In that case, flash_ctrl_testutils_counter_set_at_least() will not increment | |
| // This function can be used to initialize a NVM counter to zero by filling | |
| // its flash region with non-zero values. | |
| void flash_ctrl_testutils_counter_init_zero(dif_flash_ctrl_state_t *flash_state, | |
| size_t counter) { | |
| uint32_t new_val[FLASH_CTRL_PARAM_BYTES_PER_WORD / sizeof(uint32_t)] = {0xaa, | |
| 0xbb}; | |
| for (int ii = 0; ii < kNonVolatileCounterFlashWords; ii++) { | |
| CHECK(flash_ctrl_testutils_erase_and_write_page( | |
| flash_state, | |
| (uint32_t)&kNvCounters[counter][ii] - | |
| TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR, | |
| 0, new_val, kDifFlashCtrlPartitionTypeData, ARRAYSIZE(new_val)), | |
| "Flash write failed"); | |
| } | |
| } | |
| void flash_ctrl_testutils_backdoor_init(dif_flash_ctrl_state_t *flash_state) { | |
| CHECK_DIF_OK(dif_flash_ctrl_init_state( | |
| flash_state, | |
| mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR))); | |
| flash_ctrl_testutils_default_region_access(flash_state, | |
| /*rd_en*/ true, | |
| /*prog_en*/ true, | |
| /*erase_en*/ true, | |
| /*scramble_en*/ false, | |
| /*ecc_en*/ false, | |
| /*he_en*/ false); | |
| } | |
| void flash_ctrl_testutils_backdoor_wait_update( | |
| dif_flash_ctrl_state_t *flash_state, uintptr_t addr, size_t timeout) { | |
| static uint32_t data = UINT32_MAX; | |
| CHECK(flash_ctrl_testutils_write( | |
| flash_state, (uint32_t)addr - TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR, 0, | |
| &data, kDifFlashCtrlPartitionTypeData, 1)); | |
| IBEX_SPIN_FOR(UINT32_MAX != *(uint32_t *)addr, timeout); | |
| } |