| // 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/arch/device.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/dif/dif_flash_ctrl.h" |
| #include "sw/device/lib/dif/dif_uart.h" |
| #include "sw/device/lib/runtime/hart.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/runtime/print.h" |
| #include "sw/device/lib/testing/flash_ctrl_testutils.h" |
| #include "sw/device/lib/testing/pinmux_testutils.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| #include "sw/device/lib/testing/test_framework/status.h" |
| |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" // Generated. |
| |
| static dif_uart_t uart0; |
| static dif_flash_ctrl_state_t flash_state; |
| |
| enum { |
| kFlashInfoPageIdCreatorSecret = 1, |
| kFlashInfoPageIdOwnerSecret = 2, |
| kFlashInfoPageIdIsoPart = 3, |
| kFlashInfoBank = 0, |
| kFlashDataRegionZero = 0, |
| kFlashDataRegionOne = 1, |
| kRegionBaseBank0Page0Index = 0, |
| kRegionBaseBank1Page0Index = 256, |
| kPartitionId = 0, |
| kRegionSize = 1, |
| kPageSize = 2048, |
| kNumTestWords = 16, |
| kNumTestBytes = kNumTestWords * sizeof(uint32_t), |
| }; |
| |
| enum { |
| kTestPhaseCheckUnscrambledInit0 = 0, |
| kTestPhaseCheckUnscrambledInit1 = 1, |
| kTestPhaseCheckUnscrambledInit2 = 2, |
| kTestPhaseCheckScrambledInit0 = 3, |
| kTestPhaseCheckScrambledInit1 = 4, |
| kTestPhaseCheckBackdoor0 = 5, |
| kTestPhaseCheckBackdoor1 = 6, |
| kTestPhaseKeymgrPrep = 7, |
| kTestPhaseKeymgrTest0 = 8, |
| kTestPhaseKeymgrTest1 = 9, |
| }; |
| |
| enum { |
| kNumRegions = 5, |
| kAddressBank0Page0Data = 0, |
| kAddressBank1Page0Data = 1, |
| kAddressCreatorSecret = 2, |
| kAddressOwnerSecret = 3, |
| kAddressIsoPart = 4, |
| }; |
| |
| enum { |
| kCreatorSecretDataRetSramAddress = 0, |
| kOwnerSecretDataRetSramAddress = |
| kCreatorSecretDataRetSramAddress + (kNumTestWords * sizeof(uint32_t)), |
| kIsoPartDataRetSramAddress = |
| kOwnerSecretDataRetSramAddress + (kNumTestWords * sizeof(uint32_t)), |
| kBank0Page0DataRetSramAddress = |
| kIsoPartDataRetSramAddress + (kNumTestWords * sizeof(uint32_t)), |
| kBank1Page0DataRetSramAddress = |
| kBank0Page0DataRetSramAddress + (kNumTestWords * sizeof(uint32_t)), |
| }; |
| |
| // The test phase is updated by the testbench with a symbol backdoor overwrite. |
| static volatile const uint8_t kTestPhase = 0; |
| |
| // The test data is updated by the testbench by filling the retention SRAM |
| // which can then be copied and used locally. |
| static uint32_t kCreatorSecretData[kNumTestWords]; |
| static uint32_t kOwnerSecretData[kNumTestWords]; |
| static uint32_t kIsoPartData[kNumTestWords]; |
| static uint32_t kBank0Page0Data[kNumTestWords]; |
| static uint32_t kBank1Page0Data[kNumTestWords]; |
| |
| static uint32_t region_addresses[kNumRegions]; |
| |
| static void setup_unscrambled_regions(void) { |
| region_addresses[kAddressBank0Page0Data] = |
| flash_ctrl_testutils_data_region_setup(&flash_state, |
| kRegionBaseBank0Page0Index, |
| kFlashDataRegionZero, kRegionSize); |
| region_addresses[kAddressBank1Page0Data] = |
| flash_ctrl_testutils_data_region_setup(&flash_state, |
| kRegionBaseBank1Page0Index, |
| kFlashDataRegionOne, kRegionSize); |
| region_addresses[kAddressCreatorSecret] = |
| flash_ctrl_testutils_info_region_setup(&flash_state, |
| kFlashInfoPageIdCreatorSecret, |
| kFlashInfoBank, kPartitionId); |
| region_addresses[kAddressOwnerSecret] = |
| flash_ctrl_testutils_info_region_setup(&flash_state, |
| kFlashInfoPageIdOwnerSecret, |
| kFlashInfoBank, kPartitionId); |
| region_addresses[kAddressIsoPart] = flash_ctrl_testutils_info_region_setup( |
| &flash_state, kFlashInfoPageIdIsoPart, kFlashInfoBank, kPartitionId); |
| } |
| |
| static void setup_scrambled_regions(void) { |
| region_addresses[kAddressBank0Page0Data] = |
| flash_ctrl_testutils_data_region_scrambled_setup( |
| &flash_state, kRegionBaseBank0Page0Index, kFlashDataRegionZero, |
| kRegionSize); |
| region_addresses[kAddressBank1Page0Data] = |
| flash_ctrl_testutils_data_region_scrambled_setup( |
| &flash_state, kRegionBaseBank1Page0Index, kFlashDataRegionOne, |
| kRegionSize); |
| region_addresses[kAddressCreatorSecret] = |
| flash_ctrl_testutils_info_region_scrambled_setup( |
| &flash_state, kFlashInfoPageIdCreatorSecret, kFlashInfoBank, |
| kPartitionId); |
| region_addresses[kAddressOwnerSecret] = |
| flash_ctrl_testutils_info_region_scrambled_setup( |
| &flash_state, kFlashInfoPageIdOwnerSecret, kFlashInfoBank, |
| kPartitionId); |
| region_addresses[kAddressIsoPart] = |
| flash_ctrl_testutils_info_region_scrambled_setup( |
| &flash_state, kFlashInfoPageIdIsoPart, kFlashInfoBank, kPartitionId); |
| } |
| |
| static void erase_and_write_regions(void) { |
| CHECK(flash_ctrl_testutils_erase_and_write_page( |
| &flash_state, region_addresses[kAddressBank0Page0Data], kPartitionId, |
| kBank0Page0Data, kDifFlashCtrlPartitionTypeData, kNumTestWords)); |
| CHECK(flash_ctrl_testutils_erase_and_write_page( |
| &flash_state, region_addresses[kAddressBank1Page0Data], kPartitionId, |
| kBank1Page0Data, kDifFlashCtrlPartitionTypeData, kNumTestWords)); |
| CHECK(flash_ctrl_testutils_erase_and_write_page( |
| &flash_state, region_addresses[kAddressCreatorSecret], kPartitionId, |
| kCreatorSecretData, kDifFlashCtrlPartitionTypeInfo, kNumTestWords)); |
| CHECK(flash_ctrl_testutils_erase_and_write_page( |
| &flash_state, region_addresses[kAddressOwnerSecret], kPartitionId, |
| kOwnerSecretData, kDifFlashCtrlPartitionTypeInfo, kNumTestWords)); |
| CHECK(flash_ctrl_testutils_erase_and_write_page( |
| &flash_state, region_addresses[kAddressIsoPart], kPartitionId, |
| kIsoPartData, kDifFlashCtrlPartitionTypeInfo, kNumTestWords)); |
| } |
| |
| static void read_and_check_host_if(uint32_t addr, const uint32_t *check_data) { |
| uint32_t host_data[kNumTestWords]; |
| mmio_region_memcpy_from_mmio32( |
| mmio_region_from_addr(TOP_EARLGREY_EFLASH_BASE_ADDR), addr, &host_data, |
| kNumTestBytes); |
| CHECK_ARRAYS_EQ(host_data, check_data, kNumTestWords); |
| } |
| |
| static void check_readback_data_match(void) { |
| uint32_t readback_data[kNumTestWords]; |
| CHECK(flash_ctrl_testutils_read( |
| &flash_state, region_addresses[kAddressBank0Page0Data], kPartitionId, |
| readback_data, kDifFlashCtrlPartitionTypeData, kNumTestWords, 0)); |
| CHECK_ARRAYS_EQ(readback_data, kBank0Page0Data, kNumTestWords); |
| CHECK(flash_ctrl_testutils_read( |
| &flash_state, region_addresses[kAddressBank1Page0Data], kPartitionId, |
| readback_data, kDifFlashCtrlPartitionTypeData, kNumTestWords, 0)); |
| CHECK_ARRAYS_EQ(readback_data, kBank1Page0Data, kNumTestWords); |
| CHECK(flash_ctrl_testutils_read( |
| &flash_state, region_addresses[kAddressCreatorSecret], kPartitionId, |
| readback_data, kDifFlashCtrlPartitionTypeInfo, kNumTestWords, 0)); |
| CHECK_ARRAYS_EQ(readback_data, kCreatorSecretData, kNumTestWords); |
| CHECK(flash_ctrl_testutils_read( |
| &flash_state, region_addresses[kAddressOwnerSecret], kPartitionId, |
| readback_data, kDifFlashCtrlPartitionTypeInfo, kNumTestWords, 0)); |
| CHECK_ARRAYS_EQ(readback_data, kOwnerSecretData, kNumTestWords); |
| CHECK(flash_ctrl_testutils_read( |
| &flash_state, region_addresses[kAddressIsoPart], kPartitionId, |
| readback_data, kDifFlashCtrlPartitionTypeInfo, kNumTestWords, 0)); |
| CHECK_ARRAYS_EQ(readback_data, kIsoPartData, kNumTestWords); |
| |
| read_and_check_host_if(kRegionBaseBank0Page0Index, kBank0Page0Data); |
| read_and_check_host_if(kPageSize * kRegionBaseBank1Page0Index, |
| kBank1Page0Data); |
| } |
| |
| static bool transaction_end_check_read_error(void) { |
| 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; |
| } |
| } |
| bool is_read_error = !output.error_code.codes.memory_properties_error & |
| output.error_code.codes.read_error & |
| !output.error_code.codes.prog_window_error & |
| !output.error_code.codes.prog_type_error & |
| !output.error_code.codes.shadow_register_error; |
| CHECK_DIF_OK( |
| dif_flash_ctrl_clear_error_codes(&flash_state, output.error_code.codes)); |
| return is_read_error; |
| } |
| |
| static void check_readback_fail(void) { |
| uint32_t readback_data[kNumTestWords]; |
| |
| dif_flash_ctrl_transaction_t transaction = { |
| .byte_address = region_addresses[kAddressBank0Page0Data], |
| .op = kDifFlashCtrlOpRead, |
| .partition_type = kDifFlashCtrlPartitionTypeData, |
| .partition_id = kPartitionId, |
| .word_count = kNumTestWords}; |
| CHECK_DIF_OK(dif_flash_ctrl_start(&flash_state, transaction)); |
| CHECK_DIF_OK( |
| dif_flash_ctrl_read_fifo_pop(&flash_state, kNumTestWords, readback_data)); |
| CHECK(transaction_end_check_read_error()); |
| |
| transaction.byte_address = region_addresses[kAddressBank1Page0Data]; |
| CHECK_DIF_OK(dif_flash_ctrl_start(&flash_state, transaction)); |
| CHECK_DIF_OK( |
| dif_flash_ctrl_read_fifo_pop(&flash_state, kNumTestWords, readback_data)); |
| CHECK(transaction_end_check_read_error()); |
| |
| transaction.partition_type = kDifFlashCtrlPartitionTypeInfo; |
| transaction.byte_address = region_addresses[kAddressCreatorSecret]; |
| CHECK_DIF_OK(dif_flash_ctrl_start(&flash_state, transaction)); |
| CHECK_DIF_OK( |
| dif_flash_ctrl_read_fifo_pop(&flash_state, kNumTestWords, readback_data)); |
| CHECK(transaction_end_check_read_error()); |
| |
| transaction.byte_address = region_addresses[kAddressOwnerSecret]; |
| CHECK_DIF_OK(dif_flash_ctrl_start(&flash_state, transaction)); |
| CHECK_DIF_OK( |
| dif_flash_ctrl_read_fifo_pop(&flash_state, kNumTestWords, readback_data)); |
| CHECK(transaction_end_check_read_error()); |
| |
| transaction.byte_address = region_addresses[kAddressIsoPart]; |
| CHECK_DIF_OK(dif_flash_ctrl_start(&flash_state, transaction)); |
| CHECK_DIF_OK( |
| dif_flash_ctrl_read_fifo_pop(&flash_state, kNumTestWords, readback_data)); |
| CHECK(transaction_end_check_read_error()); |
| } |
| |
| static void flash_init(void) { |
| CHECK_DIF_OK(dif_flash_ctrl_start_controller_init(&flash_state)); |
| dif_flash_ctrl_status_t flash_ctrl_status; |
| while (true) { |
| CHECK_DIF_OK(dif_flash_ctrl_get_status(&flash_state, &flash_ctrl_status)); |
| if (flash_ctrl_status.controller_init_wip == false) { |
| break; |
| } |
| } |
| } |
| |
| static void check_unscrambled_init(void) { |
| setup_unscrambled_regions(); |
| erase_and_write_regions(); |
| flash_init(); |
| check_readback_data_match(); |
| test_status_set(kTestStatusInWfi); |
| wait_for_interrupt(); |
| } |
| |
| static void check_scrambled_init(void) { |
| setup_scrambled_regions(); |
| erase_and_write_regions(); |
| check_readback_data_match(); |
| flash_init(); |
| check_readback_fail(); |
| test_status_set(kTestStatusInWfi); |
| wait_for_interrupt(); |
| } |
| |
| static void check_scrambled_backdoor_data(void) { |
| setup_scrambled_regions(); |
| flash_init(); |
| check_readback_data_match(); |
| test_status_set(kTestStatusInWfi); |
| wait_for_interrupt(); |
| } |
| |
| bool rom_test_main(void) { |
| // We need to set the test status as "in test" to indicate to the test code |
| // has been reached, even though this test is also in the "boot ROM". |
| test_status_set(kTestStatusInTest); |
| dif_pinmux_t pinmux; |
| CHECK_DIF_OK(dif_pinmux_init( |
| mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux)); |
| pinmux_testutils_init(&pinmux); |
| |
| // We need to initialize the UART regardless if we LOG any messages, since |
| // Verilator and FPGA platforms use the UART to communicate the test results. |
| if (kDeviceType != kDeviceSimDV) { |
| CHECK_DIF_OK(dif_uart_init( |
| mmio_region_from_addr(TOP_EARLGREY_UART0_BASE_ADDR), &uart0)); |
| CHECK_DIF_OK( |
| dif_uart_configure(&uart0, (dif_uart_config_t){ |
| .baudrate = kUartBaudrate, |
| .clk_freq_hz = kClockFreqPeripheralHz, |
| .parity_enable = kDifToggleDisabled, |
| .parity = kDifUartParityEven, |
| .tx_enable = kDifToggleEnabled, |
| .rx_enable = kDifToggleEnabled, |
| })); |
| base_uart_stdout(&uart0); |
| } |
| |
| // Test code. |
| mmio_region_t sram_region_ret_base_addr = |
| mmio_region_from_addr(TOP_EARLGREY_SRAM_CTRL_RET_AON_RAM_BASE_ADDR); |
| |
| mmio_region_memcpy_from_mmio32(sram_region_ret_base_addr, |
| kCreatorSecretDataRetSramAddress, |
| &kCreatorSecretData, kNumTestBytes); |
| mmio_region_memcpy_from_mmio32(sram_region_ret_base_addr, |
| kOwnerSecretDataRetSramAddress, |
| &kOwnerSecretData, kNumTestBytes); |
| mmio_region_memcpy_from_mmio32(sram_region_ret_base_addr, |
| kIsoPartDataRetSramAddress, &kIsoPartData, |
| kNumTestBytes); |
| mmio_region_memcpy_from_mmio32(sram_region_ret_base_addr, |
| kBank0Page0DataRetSramAddress, |
| &kBank0Page0Data, kNumTestBytes); |
| mmio_region_memcpy_from_mmio32(sram_region_ret_base_addr, |
| kBank1Page0DataRetSramAddress, |
| &kBank1Page0Data, kNumTestBytes); |
| |
| CHECK_DIF_OK(dif_flash_ctrl_init_state( |
| &flash_state, |
| mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR))); |
| |
| switch (kTestPhase) { |
| case kTestPhaseCheckUnscrambledInit0: |
| case kTestPhaseCheckUnscrambledInit1: |
| case kTestPhaseCheckUnscrambledInit2: |
| check_unscrambled_init(); |
| break; |
| case kTestPhaseCheckScrambledInit0: |
| case kTestPhaseCheckScrambledInit1: |
| check_scrambled_init(); |
| break; |
| case kTestPhaseCheckBackdoor0: |
| case kTestPhaseCheckBackdoor1: |
| check_scrambled_backdoor_data(); |
| break; |
| case kTestPhaseKeymgrPrep: |
| case kTestPhaseKeymgrTest0: |
| case kTestPhaseKeymgrTest1: |
| flash_init(); |
| test_status_set(kTestStatusInWfi); |
| wait_for_interrupt(); |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |