| // 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; | |
| } |