| // 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/mmio.h" |
| #include "sw/device/lib/dif/dif_flash_ctrl.h" |
| #include "sw/device/lib/dif/dif_rv_plic.h" |
| #include "sw/device/lib/runtime/irq.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/testing/flash_ctrl_testutils.h" |
| #include "sw/device/lib/testing/rv_plic_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" |
| #include "sw/device/lib/testing/autogen/isr_testutils.h" |
| |
| #define FLASH_CTRL_NUM_IRQS 5 |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| static dif_rv_plic_t plic0; |
| static dif_flash_ctrl_state_t flash_state; |
| static dif_flash_ctrl_t flash_ctrl; |
| |
| static plic_isr_ctx_t plic_ctx = { |
| .rv_plic = &plic0, |
| .hart_id = kTopEarlgreyPlicTargetIbex0, |
| }; |
| |
| static flash_ctrl_isr_ctx_t flash_ctx = { |
| .flash_ctrl = &flash_ctrl, |
| .plic_flash_ctrl_start_irq_id = kTopEarlgreyPlicIrqIdFlashCtrlProgEmpty, |
| .is_only_irq = false, |
| }; |
| |
| enum { |
| kFlashInfoPageIdCreatorSecret = 1, |
| kFlashInfoPageIdOwnerSecret = 2, |
| kFlashInfoPageIdIsoPart = 3, |
| kFlashInfoBank = 0, |
| kRegionBaseBank0Page0Index = 0, |
| kRegionBaseBank1Page0Index = 256, |
| kRegionBaseBank1Page255Index = 511, |
| kFlashBank0DataRegion = 0, |
| kFlashBank1DataRegion = 1, |
| kPartitionId = 0, |
| kRegionSize = 1, |
| kInfoSize = 16, |
| kDataSize = 32, |
| kPageSize = 2048, |
| }; |
| |
| const uint32_t kRandomData1[kInfoSize] = { |
| 0xb295d21b, 0xecdfbdcd, 0x67e7ab2d, 0x6f660b08, 0x273bf65c, 0xe80f1695, |
| 0x586b80db, 0xc3dba27e, 0xdc124c5d, 0xb01ccd52, 0x815713e1, 0x31a141b2, |
| 0x2124be3b, 0x299a6f2a, 0x1f2a4741, 0x1a073cc0, |
| }; |
| |
| const uint32_t kRandomData2[kInfoSize] = { |
| 0x69e705a0, 0x65c2ec6b, 0x04b0b634, 0x59313526, 0x1858aee1, 0xd49f3ba9, |
| 0x230bcd38, 0xc1eb6b3e, 0x68c15e3b, 0x024d02a9, 0x0b062ae4, 0x334dd155, |
| 0x53fdbf8a, 0x3792f1e2, 0xee317161, 0x33b19bf3, |
| }; |
| |
| const uint32_t kRandomData3[kInfoSize] = { |
| 0x2b78dbf5, 0x3e6e5a00, 0xbf82c6d5, 0x68d8e33f, 0x9c524bbc, 0xac5beeef, |
| 0x1287ca5a, 0x12b61419, 0x872e709f, 0xf91b7c0c, 0x18312a1f, 0x325cef9a, |
| 0x19fefa95, 0x4ceb421b, 0xa57d74c4, 0xaf1d723d, |
| }; |
| |
| const uint32_t kRandomData4[kDataSize] = { |
| 0x0f5b84a3, 0xfa0330c3, 0xe125d174, 0x959d9779, 0xe10da3ba, 0x739e804d, |
| 0xf8f8c317, 0xf236e75f, 0xa2118c37, 0x2d12fa9d, 0xa6fd72cd, 0x4b21d3dc, |
| 0x6d36ca93, 0xbac514a6, 0x5f5695f8, 0xe7fdbe07, 0xde77eac9, 0x5ee7432f, |
| 0xc7d26081, 0xae1d7262, 0x47d46715, 0x9da2de97, 0xa41e639d, 0x34470ce0, |
| 0x8ac69175, 0x1dbcd910, 0x8193d43e, 0xe1538689, 0x166599e1, 0x0d5cc465, |
| 0x86298854, 0x93121b13, |
| }; |
| |
| const uint32_t kRandomData5[kDataSize] = { |
| 0xe5214227, 0x8473a570, 0xc6fc9728, 0x6110fbbe, 0xa2b4cdc8, 0x0156836a, |
| 0xa0c90954, 0x23e66c9b, 0x607c9e7c, 0x40f993b6, 0x253dfc7d, 0xe0c70727, |
| 0xa7b974ea, 0x0e8561c8, 0xfe8858a9, 0x36bf06bc, 0x2a734e91, 0xf0aca1e6, |
| 0x6e22f4c5, 0x469cb0a2, 0x0f6bbc43, 0xc719f5cd, 0x0a129d7d, 0x9a6c171e, |
| 0x1b39ff3a, 0x9644ab82, 0x5209d14c, 0x46a7e380, 0x575b1e0b, 0x4af5e8c3, |
| 0xfcbbfa64, 0xe3afddf2, |
| }; |
| |
| static volatile bool expected_irqs[FLASH_CTRL_NUM_IRQS]; |
| static volatile bool fired_irqs[FLASH_CTRL_NUM_IRQS]; |
| |
| /** |
| * Provides external IRQ handling for this test. |
| * |
| * This function overrides the default OTTF external ISR. |
| */ |
| void ottf_external_isr(void) { |
| top_earlgrey_plic_peripheral_t peripheral_serviced; |
| dif_flash_ctrl_irq_t irq_serviced; |
| isr_testutils_flash_ctrl_isr(plic_ctx, flash_ctx, &peripheral_serviced, |
| &irq_serviced); |
| CHECK(peripheral_serviced == kTopEarlgreyPlicPeripheralFlashCtrl, |
| "Interurpt from unexpected peripheral: %d", peripheral_serviced); |
| fired_irqs[irq_serviced] = true; |
| } |
| |
| /** |
| * Clear the volatile IRQ variables. |
| */ |
| static void clear_irq_variables(void) { |
| for (int i = 0; i < FLASH_CTRL_NUM_IRQS; ++i) { |
| expected_irqs[i] = false; |
| fired_irqs[i] = false; |
| } |
| } |
| |
| /** |
| * Initializes FLASH_CTRL and enables the relevant interrupts. |
| */ |
| static void flash_ctrl_init_with_irqs(mmio_region_t base_addr, |
| dif_flash_ctrl_state_t *flash_state, |
| dif_flash_ctrl_t *flash_ctrl) { |
| CHECK_DIF_OK(dif_flash_ctrl_init(base_addr, flash_ctrl)); |
| CHECK_DIF_OK(dif_flash_ctrl_init_state(flash_state, base_addr)); |
| |
| for (int i = 0; i < FLASH_CTRL_NUM_IRQS; ++i) { |
| CHECK_DIF_OK(dif_flash_ctrl_irq_set_enabled( |
| flash_ctrl, kDifFlashCtrlIrqProgEmpty + i, kDifToggleEnabled)); |
| } |
| clear_irq_variables(); |
| } |
| |
| /** |
| * Compares the expected and fired IRQs and clears both. |
| */ |
| static void compare_and_clear_irq_variables(void) { |
| for (int i = 0; i < FLASH_CTRL_NUM_IRQS; ++i) { |
| CHECK(expected_irqs[i] == fired_irqs[i], "expected IRQ mismatch = %d", i); |
| } |
| clear_irq_variables(); |
| } |
| |
| /** |
| * Check data read from host interface against known data. |
| */ |
| static void read_and_check_host_if(uint32_t addr, const uint32_t *check_data) { |
| mmio_region_t flash_addr = |
| mmio_region_from_addr(TOP_EARLGREY_EFLASH_BASE_ADDR + addr); |
| uint32_t host_data[kDataSize]; |
| for (int i = 0; i < kDataSize; ++i) { |
| host_data[i] = mmio_region_read32(flash_addr, i * sizeof(uint32_t)); |
| } |
| CHECK_ARRAYS_EQ(host_data, check_data, kDataSize); |
| } |
| |
| /** |
| * Tests the interrupts for erase, write and |
| * read of the specified information partition. |
| * Confirms that the written data is read back correctly. |
| */ |
| static void do_info_partition_test(uint32_t partition_number, |
| const uint32_t *test_data) { |
| uint32_t address = flash_ctrl_testutils_info_region_setup( |
| &flash_state, partition_number, kFlashInfoBank, kPartitionId); |
| |
| CHECK_DIF_OK(dif_flash_ctrl_set_prog_fifo_watermark(&flash_state, 0)); |
| CHECK_DIF_OK(dif_flash_ctrl_set_read_fifo_watermark(&flash_state, 8)); |
| |
| clear_irq_variables(); |
| |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| CHECK(flash_ctrl_testutils_erase_page(&flash_state, address, kPartitionId, |
| kDifFlashCtrlPartitionTypeInfo)); |
| compare_and_clear_irq_variables(); |
| |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| expected_irqs[kDifFlashCtrlIrqProgEmpty] = true; |
| expected_irqs[kDifFlashCtrlIrqProgLvl] = true; |
| CHECK(flash_ctrl_testutils_write(&flash_state, address, kPartitionId, |
| test_data, kDifFlashCtrlPartitionTypeInfo, |
| kInfoSize)); |
| |
| compare_and_clear_irq_variables(); |
| |
| uint32_t readback_data[kInfoSize]; |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| expected_irqs[kDifFlashCtrlIrqRdLvl] = true; |
| expected_irqs[kDifFlashCtrlIrqRdFull] = true; |
| CHECK(flash_ctrl_testutils_read(&flash_state, address, kPartitionId, |
| readback_data, kDifFlashCtrlPartitionTypeInfo, |
| kInfoSize, 1)); |
| |
| compare_and_clear_irq_variables(); |
| |
| CHECK_ARRAYS_EQ(readback_data, test_data, kInfoSize); |
| } |
| |
| /** |
| * Tests the interrupts for read of bank0 data partition. |
| * Only read is tested as this partition contains the program |
| * code so should not be erased or written. |
| * The data read via the flash_ctrl interface is checked against the |
| * data read via the host interface. |
| */ |
| static void do_bank0_data_partition_test(void) { |
| uint32_t address = flash_ctrl_testutils_data_region_setup( |
| &flash_state, kRegionBaseBank0Page0Index, kFlashBank0DataRegion, |
| kRegionSize); |
| |
| CHECK_DIF_OK(dif_flash_ctrl_set_read_fifo_watermark(&flash_state, 8)); |
| |
| clear_irq_variables(); |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| expected_irqs[kDifFlashCtrlIrqRdLvl] = true; |
| expected_irqs[kDifFlashCtrlIrqRdFull] = true; |
| |
| uint32_t readback_data[kDataSize]; |
| CHECK(flash_ctrl_testutils_read(&flash_state, address, kPartitionId, |
| readback_data, kDifFlashCtrlPartitionTypeData, |
| kDataSize, 1)); |
| |
| compare_and_clear_irq_variables(); |
| read_and_check_host_if(0, readback_data); |
| } |
| |
| /** |
| * Tests the interrupts for erase, write and read of |
| * the lowest and highest page of bank 1 data partition. |
| * Confirms that the written data is read back correctly. |
| * The whole bank is then erased and the interrupt is checked |
| * followed by confirmation that the previously written data |
| * has been wiped. |
| */ |
| static void do_bank1_data_partition_test(void) { |
| uint32_t address; |
| |
| CHECK_DIF_OK(dif_flash_ctrl_set_prog_fifo_watermark(&flash_state, 0)); |
| CHECK_DIF_OK(dif_flash_ctrl_set_read_fifo_watermark(&flash_state, 8)); |
| |
| // Loop for low and high page erase, write and read. |
| for (int i = 0; i < 2; ++i) { |
| uint32_t page_index = |
| (i == 0) ? kRegionBaseBank1Page0Index : kRegionBaseBank1Page255Index; |
| const uint32_t *test_data = (i == 0) ? kRandomData4 : kRandomData5; |
| |
| address = flash_ctrl_testutils_data_region_setup( |
| &flash_state, page_index, kFlashBank1DataRegion, kRegionSize); |
| |
| clear_irq_variables(); |
| |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| CHECK(flash_ctrl_testutils_erase_page(&flash_state, address, kPartitionId, |
| kDifFlashCtrlPartitionTypeData)); |
| |
| compare_and_clear_irq_variables(); |
| |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| expected_irqs[kDifFlashCtrlIrqProgEmpty] = true; |
| expected_irqs[kDifFlashCtrlIrqProgLvl] = true; |
| CHECK(flash_ctrl_testutils_write(&flash_state, address, kPartitionId, |
| test_data, kDifFlashCtrlPartitionTypeData, |
| kDataSize)); |
| |
| compare_and_clear_irq_variables(); |
| |
| uint32_t readback_data[kDataSize]; |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| expected_irqs[kDifFlashCtrlIrqRdLvl] = true; |
| expected_irqs[kDifFlashCtrlIrqRdFull] = true; |
| CHECK(flash_ctrl_testutils_read( |
| &flash_state, address, kPartitionId, readback_data, |
| kDifFlashCtrlPartitionTypeData, kDataSize, 1)); |
| |
| compare_and_clear_irq_variables(); |
| |
| read_and_check_host_if(kPageSize * page_index, test_data); |
| CHECK_ARRAYS_EQ(readback_data, test_data, kDataSize); |
| } |
| |
| // Erasing the whole of bank 1. |
| CHECK_DIF_OK(dif_flash_ctrl_set_bank_erase_enablement(&flash_state, 1, |
| kDifToggleEnabled)); |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| |
| address = flash_ctrl_testutils_data_region_setup( |
| &flash_state, kRegionBaseBank1Page0Index, kFlashBank1DataRegion, |
| kRegionSize); |
| dif_flash_ctrl_transaction_t transaction = { |
| .byte_address = address, |
| .op = kDifFlashCtrlOpBankErase, |
| .partition_type = kDifFlashCtrlPartitionTypeData, |
| .partition_id = 0x0, |
| .word_count = 0x0}; |
| CHECK_DIF_OK(dif_flash_ctrl_start(&flash_state, transaction)); |
| CHECK(flash_ctrl_testutils_wait_transaction_end(&flash_state)); |
| |
| compare_and_clear_irq_variables(); |
| |
| // Loop for low and high page read back after bank erase. |
| for (int i = 0; i < 2; ++i) { |
| uint32_t page_index = |
| (i == 0) ? kRegionBaseBank1Page0Index : kRegionBaseBank1Page255Index; |
| |
| address = flash_ctrl_testutils_data_region_setup( |
| &flash_state, page_index, kFlashBank1DataRegion, kRegionSize); |
| |
| uint32_t readback_data[kDataSize]; |
| expected_irqs[kDifFlashCtrlIrqOpDone] = true; |
| expected_irqs[kDifFlashCtrlIrqRdLvl] = true; |
| expected_irqs[kDifFlashCtrlIrqRdFull] = true; |
| CHECK(flash_ctrl_testutils_read( |
| &flash_state, address, kPartitionId, readback_data, |
| kDifFlashCtrlPartitionTypeData, kDataSize, 1)); |
| |
| compare_and_clear_irq_variables(); |
| |
| uint32_t expected_data[kDataSize]; |
| memset(expected_data, 0xff, sizeof(expected_data)); |
| |
| read_and_check_host_if(kPageSize * page_index, expected_data); |
| CHECK_ARRAYS_EQ(readback_data, expected_data, kDataSize); |
| } |
| } |
| |
| bool test_main(void) { |
| CHECK_DIF_OK(dif_rv_plic_init( |
| mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &plic0)); |
| |
| flash_ctrl_init_with_irqs( |
| mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR), |
| &flash_state, &flash_ctrl); |
| rv_plic_testutils_irq_range_enable(&plic0, plic_ctx.hart_id, |
| kTopEarlgreyPlicIrqIdFlashCtrlProgEmpty, |
| kTopEarlgreyPlicIrqIdFlashCtrlOpDone); |
| |
| // Enable the external IRQ at Ibex. |
| irq_global_ctrl(true); |
| irq_external_ctrl(true); |
| |
| do_info_partition_test(kFlashInfoPageIdCreatorSecret, kRandomData1); |
| do_info_partition_test(kFlashInfoPageIdOwnerSecret, kRandomData2); |
| do_info_partition_test(kFlashInfoPageIdIsoPart, kRandomData3); |
| do_bank0_data_partition_test(); |
| do_bank1_data_partition_test(); |
| |
| return true; |
| } |