| // 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/base/macros.h" |
| #include "sw/device/lib/base/memory.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/dif/dif_base.h" |
| #include "sw/device/lib/dif/dif_flash_ctrl.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/testing/flash_ctrl_testutils.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| #include "sw/device/lib/testing/test_framework/ottf_main.h" |
| |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| |
| #define CHECK_EQZ(x) CHECK((x) == 0) |
| #define CHECK_NEZ(x) CHECK((x) != 0) |
| |
| static dif_flash_ctrl_device_info_t flash_info; |
| #define FLASH_WORD_SZ flash_info.bytes_per_word |
| #define FLASH_PAGE_SZ flash_info.bytes_per_page |
| #define FLASH_UINT32_WORDS_PER_PAGE \ |
| (FLASH_PAGE_SZ / FLASH_WORD_SZ) * (FLASH_WORD_SZ / sizeof(uint32_t)) |
| #define FLASH_PAGES_PER_BANK flash_info.data_pages |
| #define FLASH_BANK_SZ (flash_info.data_pages * flash_info.bytes_per_page) |
| |
| static dif_flash_ctrl_state_t flash; |
| |
| /* |
| * Basic test of page erase / program / read functions. Tests pages from both |
| * the data and info partitions. |
| */ |
| static void test_basic_io(void) { |
| // The info partitions have no default access. Specifically set up a region. |
| dif_flash_ctrl_info_region_t info_region = { |
| .bank = 1, .partition_id = 0, .page = 0}; |
| dif_flash_ctrl_region_properties_t region_properties = { |
| .rd_en = kMultiBitBool4True, |
| .prog_en = kMultiBitBool4True, |
| .erase_en = kMultiBitBool4True, |
| .scramble_en = kMultiBitBool4True, |
| .ecc_en = kMultiBitBool4True, |
| .high_endurance_en = kMultiBitBool4False}; |
| CHECK_DIF_OK(dif_flash_ctrl_set_info_region_properties(&flash, info_region, |
| region_properties)); |
| CHECK_DIF_OK(dif_flash_ctrl_set_info_region_enablement(&flash, info_region, |
| kDifToggleEnabled)); |
| |
| // Also set up data region to enable scrambling. |
| region_properties.rd_en = kMultiBitBool4True; |
| region_properties.prog_en = kMultiBitBool4True; |
| region_properties.erase_en = kMultiBitBool4True; |
| |
| dif_flash_ctrl_data_region_properties_t data_region = { |
| .base = FLASH_PAGES_PER_BANK, |
| .size = 0x1, |
| .properties = region_properties}; |
| |
| CHECK_DIF_OK( |
| dif_flash_ctrl_set_data_region_properties(&flash, 0, data_region)); |
| CHECK_DIF_OK( |
| dif_flash_ctrl_set_data_region_enablement(&flash, 0, kDifToggleEnabled)); |
| |
| ptrdiff_t flash_bank_1_addr = FLASH_BANK_SZ; |
| mmio_region_t flash_bank_1 = mmio_region_from_addr( |
| TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR + flash_bank_1_addr); |
| |
| // Test erasing flash data partition; this should turn the whole bank to all |
| // ones. |
| CHECK(flash_ctrl_testutils_erase_page(&flash, flash_bank_1_addr, |
| /*partition_id=*/0, |
| kDifFlashCtrlPartitionTypeData)); |
| for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; ++i) { |
| CHECK_EQZ(~mmio_region_read32(flash_bank_1, i * sizeof(uint32_t))); |
| } |
| |
| // Erasing flash info partition 0; this should turn one page to all ones. |
| CHECK(flash_ctrl_testutils_erase_page(&flash, flash_bank_1_addr, |
| /*partition_id=*/0, |
| kDifFlashCtrlPartitionTypeInfo)); |
| |
| // Prepare an entire page of non-trivial data to program into flash. |
| uint32_t input_page[FLASH_UINT32_WORDS_PER_PAGE]; |
| uint32_t output_page[FLASH_UINT32_WORDS_PER_PAGE]; |
| CHECK(sizeof(input_page) == flash_info.bytes_per_page, |
| "Unexpected buffer size. got: %d, want: %d", sizeof(input_page), |
| flash_info.bytes_per_page); |
| CHECK(sizeof(output_page) == flash_info.bytes_per_page, |
| "Unexpected buffer size. got: %d, want: %d", sizeof(input_page), |
| flash_info.bytes_per_page); |
| memset(input_page, 0xa5, FLASH_UINT32_WORDS_PER_PAGE * sizeof(uint32_t)); |
| for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; i += 2) { |
| input_page[i] ^= 0xffffffff; |
| } |
| |
| // Attempt to live-program an entire page, where the overall payload is much |
| // larger than the internal flash FIFO. |
| CHECK(flash_ctrl_testutils_erase_and_write_page( |
| &flash, flash_bank_1_addr, /*partition_id=*/0, input_page, |
| kDifFlashCtrlPartitionTypeData, FLASH_UINT32_WORDS_PER_PAGE)); |
| CHECK(flash_ctrl_testutils_read(&flash, flash_bank_1_addr, /*partition_id=*/0, |
| output_page, kDifFlashCtrlPartitionTypeData, |
| FLASH_UINT32_WORDS_PER_PAGE, /*delay=*/0)); |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE); |
| |
| // Check from host side also. |
| for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; i++) { |
| output_page[i] = mmio_region_read32(flash_bank_1, i * sizeof(uint32_t)); |
| } |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE); |
| |
| // Similar check for info page. |
| CHECK(flash_ctrl_testutils_erase_and_write_page( |
| &flash, flash_bank_1_addr, /*partition_id=*/0, input_page, |
| kDifFlashCtrlPartitionTypeInfo, FLASH_UINT32_WORDS_PER_PAGE)); |
| CHECK(flash_ctrl_testutils_read(&flash, flash_bank_1_addr, /*partition_id=*/0, |
| output_page, kDifFlashCtrlPartitionTypeInfo, |
| FLASH_UINT32_WORDS_PER_PAGE, /*delay=*/0)); |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE); |
| |
| // Set up default access for data partitions. |
| flash_ctrl_testutils_default_region_access( |
| &flash, /*rd_en=*/true, /*prog_en=*/true, /*erase_en=*/true, |
| /*scramble_en=*/false, /*ecc_en=*/false, /*high_endurance_en=*/false); |
| |
| // Perform similar test on the last page of the first bank. |
| ptrdiff_t flash_bank_0_last_page_addr = flash_bank_1_addr - FLASH_PAGE_SZ; |
| mmio_region_t flash_bank_0_last_page = mmio_region_from_addr( |
| TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR + flash_bank_0_last_page_addr); |
| CHECK(flash_ctrl_testutils_erase_page(&flash, flash_bank_0_last_page_addr, |
| /*partition_id=*/0, |
| kDifFlashCtrlPartitionTypeData)); |
| for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; ++i) { |
| CHECK_EQZ( |
| ~mmio_region_read32(flash_bank_0_last_page, i * sizeof(uint32_t))); |
| } |
| |
| CHECK(flash_ctrl_testutils_write( |
| &flash, flash_bank_0_last_page_addr, /*partition_id=*/0, input_page, |
| kDifFlashCtrlPartitionTypeData, FLASH_UINT32_WORDS_PER_PAGE)); |
| CHECK(flash_ctrl_testutils_read(&flash, flash_bank_0_last_page_addr, |
| /*partition_id=*/0, output_page, |
| kDifFlashCtrlPartitionTypeData, |
| FLASH_UINT32_WORDS_PER_PAGE, /*delay=*/0)); |
| |
| CHECK_ARRAYS_EQ(output_page, input_page, FLASH_UINT32_WORDS_PER_PAGE); |
| } |
| |
| static void test_memory_protection(void) { |
| dif_flash_ctrl_state_t flash; |
| CHECK_DIF_OK(dif_flash_ctrl_init_state( |
| &flash, mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR))); |
| |
| // Set up default access for data partitions. |
| flash_ctrl_testutils_default_region_access( |
| &flash, /*rd_en=*/true, /*prog_en=*/true, /*erase_en=*/true, |
| /*scramble_en=*/false, /*ecc_en=*/false, /*high_endurance_en=*/false); |
| |
| // A memory protection region representing the first page of the second bank. |
| dif_flash_ctrl_region_properties_t protected_properties = { |
| .rd_en = kMultiBitBool4True, |
| .prog_en = kMultiBitBool4True, |
| .erase_en = kMultiBitBool4True, |
| .scramble_en = kMultiBitBool4False, |
| .ecc_en = kMultiBitBool4True, |
| .high_endurance_en = kMultiBitBool4False}; |
| |
| dif_flash_ctrl_data_region_properties_t protected_region = { |
| .base = FLASH_PAGES_PER_BANK, |
| .size = 0x1, |
| .properties = protected_properties}; |
| |
| uintptr_t ok_region_start = TOP_EARLGREY_FLASH_CTRL_MEM_BASE_ADDR + |
| (protected_region.base * FLASH_PAGE_SZ); |
| uintptr_t ok_region_end = |
| ok_region_start + (protected_region.size * FLASH_PAGE_SZ); |
| mmio_region_t ok_region = mmio_region_from_addr(ok_region_start); |
| |
| uintptr_t bad_region_start = ok_region_end; |
| |
| // Erase good and bad regions. |
| CHECK(flash_ctrl_testutils_erase_page(&flash, ok_region_start, |
| /*partition_id=*/0, |
| kDifFlashCtrlPartitionTypeData)); |
| CHECK(flash_ctrl_testutils_erase_page(&flash, bad_region_start, |
| /*partition_id=*/0, |
| kDifFlashCtrlPartitionTypeData)); |
| |
| // Turn off flash access by default. |
| flash_ctrl_testutils_default_region_access( |
| &flash, /*rd_en=*/false, /*prog_en=*/false, /*erase_en=*/false, |
| /*scramble_en=*/false, /*ecc_en=*/false, /*high_endurance_en=*/false); |
| |
| // Enable protected region for access. |
| CHECK_DIF_OK( |
| dif_flash_ctrl_set_data_region_properties(&flash, 0, protected_region)); |
| |
| // Attempt to perform a write. |
| uintptr_t region_boundary_start = bad_region_start - (FLASH_WORD_SZ * 2); |
| mmio_region_t region_boundary = mmio_region_from_addr(region_boundary_start); |
| |
| // Place half the words in the good region and half in the bad. |
| uint32_t words[(FLASH_WORD_SZ * 2 * 2) / sizeof(uint32_t)]; |
| memset(words, 0xa5, ARRAYSIZE(words) * sizeof(uint32_t)); |
| for (int i = 0; i < ARRAYSIZE(words); ++i) { |
| words[i] += i; |
| } |
| |
| // Perform a partial write. |
| CHECK(!flash_ctrl_testutils_write( |
| &flash, region_boundary_start, /*partition_id=*/0, words, |
| kDifFlashCtrlPartitionTypeData, ARRAYSIZE(words))); |
| // Words in the good region should still match, while words in the bad region |
| // should be all-ones, since we erased. |
| for (int i = 0; i < ARRAYSIZE(words); ++i) { |
| uint32_t expected = 0xffffffff; |
| if (i < ARRAYSIZE(words) / 2) { |
| expected = words[i]; |
| } |
| CHECK(mmio_region_read32(region_boundary, i * sizeof(uint32_t)) == |
| expected); |
| } |
| |
| // Attempt to erase bad page, which should fail. |
| CHECK(!flash_ctrl_testutils_erase_page(&flash, bad_region_start, |
| /*partition_id=*/0, |
| kDifFlashCtrlPartitionTypeData)); |
| |
| // Attempt to erase the good page, which should succeed. |
| CHECK(flash_ctrl_testutils_erase_page(&flash, ok_region_start, |
| /*partition_id=*/0, |
| kDifFlashCtrlPartitionTypeData)); |
| for (int i = 0; i < FLASH_UINT32_WORDS_PER_PAGE; i++) { |
| CHECK_EQZ(~mmio_region_read32(ok_region, i * sizeof(uint32_t))); |
| } |
| } |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| bool test_main(void) { |
| flash_info = dif_flash_ctrl_get_device_info(); |
| CHECK_DIF_OK(dif_flash_ctrl_init_state( |
| &flash, mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR))); |
| flash_ctrl_testutils_wait_for_init(&flash); |
| |
| LOG_INFO("flash test!"); |
| |
| CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/0, |
| kDifToggleEnabled)); |
| CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/1, |
| kDifToggleEnabled)); |
| |
| test_basic_io(); |
| test_memory_protection(); |
| |
| CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/0, |
| kDifToggleDisabled)); |
| CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash, /*bank=*/1, |
| kDifToggleDisabled)); |
| |
| return true; |
| } |