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