| // 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/mmio.h" | |
| #include "sw/device/lib/dif/dif_csrng.h" | |
| #include "sw/device/lib/dif/dif_edn.h" | |
| #include "sw/device/lib/dif/dif_entropy_src.h" | |
| #include "sw/device/lib/dif/dif_otbn.h" | |
| #include "sw/device/lib/runtime/irq.h" | |
| #include "sw/device/lib/runtime/log.h" | |
| #include "sw/device/lib/testing/csrng_testutils.h" | |
| #include "sw/device/lib/testing/entropy_testutils.h" | |
| #include "sw/device/lib/testing/otbn_testutils.h" | |
| #include "sw/device/lib/testing/rand_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 "sw/device/tests/otbn_randomness_impl.h" | |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" | |
| #include "sw/device/lib/testing/autogen/isr_testutils.h" | |
| static dif_csrng_t csrng; | |
| static dif_edn_t edn0; | |
| static dif_edn_t edn1; | |
| static dif_entropy_src_t entropy_src; | |
| static dif_otbn_t otbn; | |
| static dif_rv_plic_t plic; | |
| OTTF_DEFINE_TEST_CONFIG(); | |
| enum { | |
| /** | |
| * The size of the buffer used in firmware to process the entropy bits in | |
| * firmware override mode. | |
| */ | |
| kTestParamFifoBufferSize = 12, | |
| /** | |
| * The number of test iterations per target. | |
| */ | |
| kTestParamNumIterationsSim = 1, | |
| kTestParamNumIterationsOther = 20, | |
| }; | |
| /** | |
| * Interrupt flag IDs. Used to index the interrupt flags used in this test. | |
| */ | |
| typedef enum irq_flag_id { | |
| kTestIrqFlagIdCsrngEntropyReq, | |
| kTestIrqFlagIdEdn1CmdDone, | |
| kTestIrqFlagIdEdn0CmdDone, | |
| kTestIrqFlagCount, | |
| } irq_flag_id_t; | |
| /** | |
| * Interrupt flags. Set by `ottf_external_isr()` and cleared by | |
| * `plic_interrupts_enable()`. | |
| */ | |
| static volatile bool irq_flags[kTestIrqFlagCount]; | |
| /** | |
| * Initializes the peripherals used in this test. | |
| */ | |
| static void init_peripherals(void) { | |
| CHECK_DIF_OK(dif_csrng_init( | |
| mmio_region_from_addr(TOP_EARLGREY_CSRNG_BASE_ADDR), &csrng)); | |
| CHECK_DIF_OK( | |
| dif_edn_init(mmio_region_from_addr(TOP_EARLGREY_EDN0_BASE_ADDR), &edn0)); | |
| CHECK_DIF_OK( | |
| dif_edn_init(mmio_region_from_addr(TOP_EARLGREY_EDN1_BASE_ADDR), &edn1)); | |
| CHECK_DIF_OK(dif_entropy_src_init( | |
| mmio_region_from_addr(TOP_EARLGREY_ENTROPY_SRC_BASE_ADDR), &entropy_src)); | |
| CHECK_DIF_OK( | |
| dif_otbn_init(mmio_region_from_addr(TOP_EARLGREY_OTBN_BASE_ADDR), &otbn)); | |
| CHECK_DIF_OK(dif_rv_plic_init( | |
| mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &plic)); | |
| } | |
| /** | |
| * Enables the interrupts required by this test. | |
| */ | |
| static void plic_interrupts_enable(void) { | |
| irq_external_ctrl(false); | |
| irq_global_ctrl(false); | |
| for (size_t i = 0; i < kTestIrqFlagCount; ++i) { | |
| irq_flags[i] = false; | |
| } | |
| dif_rv_plic_irq_id_t irq_ids[] = {kTopEarlgreyPlicIrqIdCsrngCsEntropyReq, | |
| kTopEarlgreyPlicIrqIdEdn0EdnCmdReqDone, | |
| kTopEarlgreyPlicIrqIdEdn1EdnCmdReqDone}; | |
| for (size_t i = 0; i < ARRAYSIZE(irq_ids); ++i) { | |
| CHECK_DIF_OK( | |
| dif_rv_plic_irq_set_priority(&plic, irq_ids[i], /*priority=*/1u)); | |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( | |
| &plic, irq_ids[i], kTopEarlgreyPlicTargetIbex0, kDifToggleEnabled)); | |
| } | |
| CHECK_DIF_OK(dif_rv_plic_target_set_threshold( | |
| &plic, kTopEarlgreyPlicTargetIbex0, /*threshold=*/0u)); | |
| CHECK_DIF_OK(dif_csrng_irq_set_enabled(&csrng, kDifCsrngIrqCsEntropyReq, | |
| kDifToggleEnabled)); | |
| CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn0, kDifEdnIrqEdnCmdReqDone, | |
| kDifToggleEnabled)); | |
| CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn1, kDifEdnIrqEdnCmdReqDone, | |
| kDifToggleEnabled)); | |
| irq_global_ctrl(true); | |
| irq_external_ctrl(true); | |
| } | |
| /** | |
| * Blocks until the interrupt flag with `isr_id` is set to true by the | |
| * `ottf_external_isr()` routine. | |
| */ | |
| static void irq_block_wait(irq_flag_id_t isr_id) { | |
| // The interrupt can come before we enter sleep, so we check beforehand. | |
| while (true) { | |
| if (irq_flags[isr_id]) { | |
| break; | |
| } | |
| wait_for_interrupt(); | |
| } | |
| switch (isr_id) { | |
| case kTestIrqFlagIdCsrngEntropyReq: | |
| CHECK_DIF_OK(dif_csrng_irq_set_enabled(&csrng, kDifCsrngIrqCsEntropyReq, | |
| kDifToggleDisabled)); | |
| break; | |
| case kTestIrqFlagIdEdn0CmdDone: | |
| CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn0, kDifEdnIrqEdnCmdReqDone, | |
| kDifToggleDisabled)); | |
| break; | |
| case kTestIrqFlagIdEdn1CmdDone: | |
| CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn0, kDifEdnIrqEdnCmdReqDone, | |
| kDifToggleDisabled)); | |
| break; | |
| default: | |
| CHECK(false, "Invalid isr_id: %d", isr_id); | |
| } | |
| } | |
| /** | |
| * Requests data from CSRNG software instance. | |
| * | |
| * Asserts error if there are any repeated words in the output data, or if there | |
| * are any errors set in the CSRNG status registers. | |
| */ | |
| static void csrng_generate_output_check(void) { | |
| uint32_t output[kTestParamFifoBufferSize] = {0}; | |
| csrng_testutils_cmd_generate_run(&csrng, output, ARRAYSIZE(output)); | |
| uint32_t prev_data = 0; | |
| for (size_t i = 0; i < ARRAYSIZE(output); ++i) { | |
| CHECK(prev_data != output[i], | |
| "Received duplicate data. Last index: %d, value: 0x%x", i, prev_data); | |
| } | |
| } | |
| /** | |
| * Verifies that the entropy req interrupt is triggered on CSRNG instantiate and | |
| * reseed commands. | |
| */ | |
| static void test_csrng_sw_entropy_req_interrupt( | |
| const dif_csrng_seed_material_t *seed_material) { | |
| entropy_testutils_stop_all(); | |
| CHECK_DIF_OK(dif_entropy_src_configure( | |
| &entropy_src, entropy_testutils_config_default(), kDifToggleEnabled)); | |
| CHECK_DIF_OK(dif_csrng_configure(&csrng)); | |
| csrng_testutils_cmd_ready_wait(&csrng); | |
| plic_interrupts_enable(); | |
| CHECK_DIF_OK(dif_csrng_instantiate(&csrng, kDifCsrngEntropySrcToggleEnable, | |
| seed_material)); | |
| irq_block_wait(kTestIrqFlagIdCsrngEntropyReq); | |
| csrng_generate_output_check(); | |
| plic_interrupts_enable(); | |
| CHECK_DIF_OK(dif_csrng_reseed(&csrng, seed_material)); | |
| irq_block_wait(kTestIrqFlagIdCsrngEntropyReq); | |
| csrng_generate_output_check(); | |
| csrng_testutils_cmd_status_check(&csrng); | |
| csrng_testutils_recoverable_alerts_check(&csrng); | |
| } | |
| /** | |
| * Blocks until EDN is ready to process commands. | |
| * | |
| * @param edn A EDN instance. | |
| */ | |
| static void edn_ready_wait(const dif_edn_t *edn) { | |
| bool ready = false; | |
| while (!ready) { | |
| CHECK_DIF_OK(dif_edn_get_status(edn, kDifEdnStatusReady, &ready)); | |
| } | |
| bool ack_err; | |
| CHECK_DIF_OK(dif_edn_get_status(edn, kDifEdnStatusCsrngAck, &ack_err)); | |
| CHECK(!ack_err, "Unexpected CSRNG ack error"); | |
| } | |
| /** | |
| * Configures the `edn` instance. | |
| * | |
| * Verifies that the entropy req interrupt is triggered on EDN instantiate and | |
| * reseed commands. | |
| * | |
| * @param edn A EDN instance. | |
| * @param irq_flag_id The interrupt flag ID to poll after each command is sent | |
| * to EDN. | |
| * @param seed_material Seed material used in instantiate and reseed commands. | |
| */ | |
| static void edn_configure(const dif_edn_t *edn, irq_flag_id_t irq_flag_id, | |
| const dif_edn_seed_material_t *seed_material) { | |
| CHECK_DIF_OK(dif_edn_configure(edn)); | |
| edn_ready_wait(edn); | |
| plic_interrupts_enable(); | |
| CHECK_DIF_OK( | |
| dif_edn_instantiate(edn, kDifEdnEntropySrcToggleEnable, seed_material)); | |
| irq_block_wait(kTestIrqFlagIdCsrngEntropyReq); | |
| irq_block_wait(irq_flag_id); | |
| edn_ready_wait(edn); | |
| plic_interrupts_enable(); | |
| CHECK_DIF_OK(dif_edn_reseed(edn, seed_material)); | |
| irq_block_wait(kTestIrqFlagIdCsrngEntropyReq); | |
| irq_block_wait(irq_flag_id); | |
| edn_ready_wait(edn); | |
| } | |
| /** | |
| * Initializes EDN instances using the `SW_CMD_REQ` interface and runs the OTBN | |
| * randomness test to verify the entropy delivered by EDN0 and EDN1. | |
| * | |
| * @param seed_material Seed material used in EDN instantiate and reseed | |
| * commands. | |
| */ | |
| static void test_edn_cmd_done(const dif_edn_seed_material_t *seed_material) { | |
| entropy_testutils_stop_all(); | |
| CHECK_DIF_OK(dif_entropy_src_configure( | |
| &entropy_src, entropy_testutils_config_default(), kDifToggleEnabled)); | |
| CHECK_DIF_OK(dif_csrng_configure(&csrng)); | |
| edn_configure(&edn0, kTestIrqFlagIdEdn0CmdDone, seed_material); | |
| edn_configure(&edn1, kTestIrqFlagIdEdn1CmdDone, seed_material); | |
| plic_interrupts_enable(); | |
| // Generate enough entropy for the otbn randomness test. | |
| // The len provided here is in number of words as opposed to num of 128b | |
| // blocks. The requested len **must** also be equal to the amount of entroy | |
| // required by the OTBN randomness test, otherwise the Generate command will | |
| // not be fully executed, causing a hang in the `irq_block_wait()` calls | |
| // following the end of the OTBN test. | |
| // EDN0: 20 words = 5x128b blocks | |
| // EDN1: 44 words = 11x128b blocks | |
| CHECK_DIF_OK(dif_edn_generate_start(&edn0, /*len=*/1)); | |
| CHECK_DIF_OK(dif_edn_generate_start(&edn1, /*len=*/1)); | |
| edn_ready_wait(&edn0); | |
| edn_ready_wait(&edn1); | |
| entropy_testutils_error_check(&entropy_src, &csrng, &edn0, &edn1); | |
| LOG_INFO("OTBN:START"); | |
| otbn_randomness_test_start(&otbn); | |
| bool busy = true; | |
| while (busy) { | |
| // Clearing `irq_flags` is ok here since there are no other in-flight | |
| // commands being sent from the EDN instances to the CSRNG block. | |
| if (irq_flags[kTestIrqFlagIdEdn0CmdDone]) { | |
| irq_flags[kTestIrqFlagIdEdn0CmdDone] = false; | |
| CHECK_DIF_OK(dif_edn_generate_start(&edn0, /*len=*/1)); | |
| } | |
| if (irq_flags[kTestIrqFlagIdEdn1CmdDone]) { | |
| irq_flags[kTestIrqFlagIdEdn1CmdDone] = false; | |
| CHECK_DIF_OK(dif_edn_generate_start(&edn1, /*len=*/1)); | |
| } | |
| // Check if OTBN is still running. | |
| dif_otbn_status_t status; | |
| CHECK_DIF_OK(dif_otbn_get_status(&otbn, &status)); | |
| busy = status != kDifOtbnStatusIdle && status != kDifOtbnStatusLocked; | |
| } | |
| CHECK(otbn_randomness_test_end(&otbn, /*skip_otbn_done_check=*/false)); | |
| LOG_INFO("OTBN:END"); | |
| // See comment above regarding generate command length and potential test | |
| // locking issues. | |
| irq_block_wait(kTestIrqFlagIdEdn1CmdDone); | |
| irq_block_wait(kTestIrqFlagIdEdn0CmdDone); | |
| LOG_INFO("DONE"); | |
| plic_interrupts_enable(); | |
| CHECK_DIF_OK(dif_edn_uninstantiate(&edn0)); | |
| irq_block_wait(kTestIrqFlagIdEdn0CmdDone); | |
| plic_interrupts_enable(); | |
| CHECK_DIF_OK(dif_edn_uninstantiate(&edn1)); | |
| irq_block_wait(kTestIrqFlagIdEdn1CmdDone); | |
| csrng_testutils_recoverable_alerts_check(&csrng); | |
| entropy_testutils_error_check(&entropy_src, &csrng, &edn0, &edn1); | |
| } | |
| void ottf_external_isr(void) { | |
| // Claim the IRQ at the PLIC. | |
| dif_rv_plic_irq_id_t plic_irq_id; | |
| CHECK_DIF_OK( | |
| dif_rv_plic_irq_claim(&plic, kTopEarlgreyPlicTargetIbex0, &plic_irq_id)); | |
| // Get the peripheral the IRQ belongs to. | |
| top_earlgrey_plic_peripheral_t peripheral_serviced = | |
| (top_earlgrey_plic_peripheral_t) | |
| top_earlgrey_plic_interrupt_for_peripheral[plic_irq_id]; | |
| // Get the IRQ that was fired from the PLIC IRQ ID and set the corresponding | |
| // `irq_flags`. | |
| if (peripheral_serviced == kTopEarlgreyPlicPeripheralCsrng) { | |
| dif_csrng_irq_t irq = | |
| (dif_csrng_irq_t)(plic_irq_id - kTopEarlgreyPlicIrqIdCsrngCsCmdReqDone); | |
| CHECK(irq == kDifCsrngIrqCsEntropyReq, "Unexpected irq: 0x%x", irq); | |
| CHECK_DIF_OK(dif_csrng_irq_acknowledge(&csrng, irq)); | |
| irq_flags[kTestIrqFlagIdCsrngEntropyReq] = true; | |
| } else if (peripheral_serviced == kTopEarlgreyPlicPeripheralEdn0) { | |
| dif_edn_irq_t irq = | |
| (dif_edn_irq_t)(plic_irq_id - kTopEarlgreyPlicIrqIdEdn0EdnCmdReqDone); | |
| CHECK(irq == kDifEdnIrqEdnCmdReqDone, "Unexpected irq: 0x%x", irq); | |
| CHECK_DIF_OK(dif_edn_irq_acknowledge(&edn0, irq)); | |
| irq_flags[kTestIrqFlagIdEdn0CmdDone] = true; | |
| } else if (peripheral_serviced == kTopEarlgreyPlicPeripheralEdn1) { | |
| dif_edn_irq_t irq = | |
| (dif_edn_irq_t)(plic_irq_id - kTopEarlgreyPlicIrqIdEdn1EdnCmdReqDone); | |
| CHECK(irq == kDifEdnIrqEdnCmdReqDone, "Unexpected irq: 0x%x", irq); | |
| CHECK_DIF_OK(dif_edn_irq_acknowledge(&edn1, irq)); | |
| irq_flags[kTestIrqFlagIdEdn1CmdDone] = true; | |
| } | |
| CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic, kTopEarlgreyPlicTargetIbex0, | |
| plic_irq_id)); | |
| } | |
| bool test_main(void) { | |
| init_peripherals(); | |
| // Get test random parameters before we disable the entropy complex. | |
| // rand_testutils relies on the ibex rnd CSR which is connected to EDN0. | |
| dif_csrng_seed_material_t csrng_seed; | |
| csrng_seed.seed_material_len = | |
| rand_testutils_gen32_range(/*min=*/0, kDifCsrngSeedMaterialMaxWordLen); | |
| for (size_t i = 0; i < csrng_seed.seed_material_len; ++i) { | |
| csrng_seed.seed_material[i] = rand_testutils_gen32(); | |
| } | |
| dif_edn_seed_material_t edn_seed; | |
| edn_seed.len = | |
| rand_testutils_gen32_range(/*min=*/0, kDifEntropySeedMaterialMaxWordLen); | |
| for (size_t i = 0; i < edn_seed.len; ++i) { | |
| edn_seed.data[i] = rand_testutils_gen32(); | |
| } | |
| uint32_t num_iterations = kTestParamNumIterationsSim; | |
| if (kDeviceType != kDeviceSimDV && kDeviceType != kDeviceSimVerilator) { | |
| num_iterations = kTestParamNumIterationsOther; | |
| } | |
| for (size_t i = 0; i < num_iterations; ++i) { | |
| test_csrng_sw_entropy_req_interrupt(&csrng_seed); | |
| test_edn_cmd_done(&edn_seed); | |
| } | |
| return true; | |
| } |