blob: da8d84fd18de154befea551269cb7d3628baee88 [file] [log] [blame]
// 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);
}