|  | // 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/dif/dif_otbn.h" | 
|  | #include "sw/device/lib/dif/dif_rv_plic.h" | 
|  | #include "sw/device/lib/runtime/ibex.h" | 
|  | #include "sw/device/lib/runtime/irq.h" | 
|  | #include "sw/device/lib/runtime/log.h" | 
|  | #include "sw/device/lib/testing/entropy_testutils.h" | 
|  | #include "sw/device/lib/testing/otbn_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" | 
|  |  | 
|  | OTBN_DECLARE_APP_SYMBOLS(err_test); | 
|  |  | 
|  | static const otbn_app_t kAppErrTest = OTBN_APP_T_INIT(err_test); | 
|  |  | 
|  | OTTF_DEFINE_TEST_CONFIG(); | 
|  |  | 
|  | static dif_rv_plic_t plic; | 
|  | static volatile bool otbn_finished; | 
|  |  | 
|  | /** | 
|  | * Get OTBN error bits; check they match expected_err_bits. | 
|  | */ | 
|  | static void check_otbn_err_bits(dif_otbn_t *otbn, | 
|  | dif_otbn_err_bits_t expected_err_bits) { | 
|  | dif_otbn_err_bits_t otbn_err_bits; | 
|  | CHECK_DIF_OK(dif_otbn_get_err_bits(otbn, &otbn_err_bits)); | 
|  | CHECK(otbn_err_bits == expected_err_bits, | 
|  | "dif_otbn_get_err_bits() produced unexpected error bits: %x", | 
|  | otbn_err_bits); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get the OTBN instruction count; check that it matches expected_insn_cnt. | 
|  | */ | 
|  | static void check_otbn_insn_cnt(dif_otbn_t *otbn, uint32_t expected_insn_cnt) { | 
|  | uint32_t insn_cnt; | 
|  | CHECK_DIF_OK(dif_otbn_get_insn_cnt(otbn, &insn_cnt)); | 
|  | CHECK(insn_cnt == expected_insn_cnt, | 
|  | "Expected to execute %d instructions, but got %d.", expected_insn_cnt, | 
|  | insn_cnt); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get OTBN's status; check that it matches expected_status. | 
|  | */ | 
|  | static void check_otbn_status(dif_otbn_t *otbn, | 
|  | dif_otbn_status_t expected_status) { | 
|  | dif_otbn_status_t status; | 
|  | CHECK_DIF_OK(dif_otbn_get_status(otbn, &status)); | 
|  | CHECK(status == expected_status, "Unexpected status: expected %d but got %d.", | 
|  | expected_status, status); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Run a binary on OTBN, waiting for completion by interrupt. | 
|  | * | 
|  | * Once the binary has finished, check for expected status, error bits and | 
|  | * instruction count. | 
|  | */ | 
|  | static void run_test_with_irqs(dif_otbn_t *otbn, otbn_app_t app, | 
|  | dif_otbn_status_t expected_status, | 
|  | dif_otbn_err_bits_t expected_err_bits, | 
|  | uint32_t expected_insn_cnt) { | 
|  | // Clear the otbn_finished flag: we'll set it in the interrupt handler when | 
|  | // we see the Done interrupt fire. | 
|  | otbn_finished = false; | 
|  |  | 
|  | otbn_testutils_load_app(otbn, app); | 
|  |  | 
|  | // If the the CTRL.SOFTWARE_ERRS_FATAL flag is set, a software error will be | 
|  | // promoted to a fatal error (which, among other things, bricks OTBN until | 
|  | // next reset). Make sure that's not turned on. | 
|  | CHECK(dif_otbn_set_ctrl_software_errs_fatal(otbn, false) == kDifOk); | 
|  |  | 
|  | // Enable Done interrupt | 
|  | CHECK_DIF_OK( | 
|  | dif_otbn_irq_set_enabled(otbn, kDifOtbnIrqDone, kDifToggleEnabled)); | 
|  |  | 
|  | // Start OTBN | 
|  | otbn_testutils_execute(otbn); | 
|  |  | 
|  | // At this point, OTBN should be running. Wait for an interrupt that says | 
|  | // it's done. | 
|  | for (;;) { | 
|  | // This looks a bit odd, but is needed to avoid a race condition where the | 
|  | // OTBN interrupt comes in after we load the otbn_finished flag but before | 
|  | // we run the WFI instruction. The trick is that WFI returns when an | 
|  | // interrupt comes in even if interrupts are globally disabled, which means | 
|  | // that the WFI can actually sit *inside* the critical section. | 
|  | irq_global_ctrl(false); | 
|  | if (otbn_finished) | 
|  | break; | 
|  | wait_for_interrupt(); | 
|  | irq_global_ctrl(true); | 
|  | } | 
|  | irq_global_ctrl(true); | 
|  |  | 
|  | check_otbn_status(otbn, expected_status); | 
|  | check_otbn_err_bits(otbn, expected_insn_cnt); | 
|  | check_otbn_insn_cnt(otbn, expected_err_bits); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Initialize PLIC and enable OTBN interrupt. | 
|  | */ | 
|  | static void plic_init_with_irqs(void) { | 
|  | mmio_region_t base_addr = | 
|  | mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR); | 
|  | CHECK_DIF_OK(dif_rv_plic_init(base_addr, &plic)); | 
|  |  | 
|  | dif_rv_plic_irq_id_t irq_id = kTopEarlgreyPlicIrqIdOtbnDone; | 
|  |  | 
|  | // Set interrupt priority to be positive | 
|  | CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&plic, irq_id, 0x1)); | 
|  |  | 
|  | // Enable the interrupt | 
|  | CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( | 
|  | &plic, irq_id, kTopEarlgreyPlicTargetIbex0, kDifToggleEnabled)); | 
|  |  | 
|  | // Set the threshold for Ibex to 0. | 
|  | CHECK_DIF_OK(dif_rv_plic_target_set_threshold( | 
|  | &plic, kTopEarlgreyPlicTargetIbex0, 0x0)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * The ISR for this test. | 
|  | * | 
|  | * This function overrides the default OTTF external ISR. | 
|  | */ | 
|  | void ottf_external_isr(void) { | 
|  | // Find which interrupt fired at PLIC by claiming it. | 
|  | dif_rv_plic_irq_id_t irq_id; | 
|  | CHECK_DIF_OK( | 
|  | dif_rv_plic_irq_claim(&plic, kTopEarlgreyPlicTargetIbex0, &irq_id)); | 
|  |  | 
|  | // Check it was from OTBN | 
|  | top_earlgrey_plic_peripheral_t peri = | 
|  | top_earlgrey_plic_interrupt_for_peripheral[irq_id]; | 
|  | CHECK(peri == kTopEarlgreyPlicPeripheralOtbn, | 
|  | "Interrupt from incorrect peripheral: (exp: %d, obs: %s)", | 
|  | kTopEarlgreyPlicPeripheralOtbn, peri); | 
|  |  | 
|  | // Check this is the interrupt we expected | 
|  | CHECK(irq_id == kTopEarlgreyPlicIrqIdOtbnDone); | 
|  |  | 
|  | // otbn_finished should currently be false (we're supposed to clear it before | 
|  | // starting OTBN) | 
|  | CHECK(!otbn_finished); | 
|  |  | 
|  | // Set otbn_finished, which we'll pick up in run_test_with_irqs. | 
|  | otbn_finished = true; | 
|  | } | 
|  |  | 
|  | bool test_main(void) { | 
|  | entropy_testutils_auto_mode_init(); | 
|  | plic_init_with_irqs(); | 
|  |  | 
|  | // Enable the external IRQ (so that we see the interrupt from the PLIC) | 
|  | irq_global_ctrl(true); | 
|  | irq_external_ctrl(true); | 
|  |  | 
|  | mmio_region_t base_addr = mmio_region_from_addr(TOP_EARLGREY_OTBN_BASE_ADDR); | 
|  |  | 
|  | dif_otbn_t otbn; | 
|  | CHECK_DIF_OK(dif_otbn_init(base_addr, &otbn)); | 
|  |  | 
|  | run_test_with_irqs(&otbn, kAppErrTest, kDifOtbnStatusIdle, | 
|  | kDifOtbnErrBitsBadDataAddr, 1); | 
|  |  | 
|  | return true; | 
|  | } |