| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // clang-format off |
| ${gencmd} |
| <% |
| irq_peripheral_names = sorted({p.name for p in helper.irq_peripherals}) |
| |
| # For some rv_timer DIFs, tha hart argument follows the instance handle. |
| def args(p): |
| extra_arg = ", kHart" if p.name == "rv_timer" else "" |
| return f"&{p.inst_name}{extra_arg}" |
| %>\ |
| #include <limits.h> |
| |
| #include "sw/device/lib/base/csr.h" |
| #include "sw/device/lib/base/mmio.h" |
| % for n in sorted(irq_peripheral_names + ["rv_plic"]): |
| #include "sw/device/lib/dif/dif_${n}.h" |
| % endfor |
| #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/test_framework/check.h" |
| #include "sw/device/lib/testing/rv_plic_testutils.h" |
| #include "sw/device/lib/testing/test_framework/ottf_main.h" |
| #include "sw/device/lib/testing/test_framework/status.h" |
| #include "hw/top_${top["name"]}/sw/autogen/top_${top["name"]}.h" |
| |
| % for p in helper.irq_peripherals: |
| static dif_${p.name}_t ${p.inst_name}; |
| % endfor |
| static dif_rv_plic_t plic; |
| static const top_${top["name"]}_plic_target_t kHart = kTop${top["name"].capitalize()}PlicTargetIbex0; |
| |
| /** |
| * Flag indicating which peripheral is under test. |
| * |
| * Declared volatile because it is referenced in the main program flow as well |
| * as the ISR. |
| */ |
| static volatile top_${top["name"]}_plic_peripheral_t peripheral_expected; |
| |
| /** |
| * Flags indicating the IRQ expected to have triggered and serviced within the |
| * peripheral. |
| * |
| * Declared volatile because it is referenced in the main program flow as well |
| * as the ISR. |
| */ |
| % for n in irq_peripheral_names: |
| static volatile dif_${n}_irq_t ${n}_irq_expected; |
| static volatile dif_${n}_irq_t ${n}_irq_serviced; |
| % endfor |
| |
| /** |
| * Provides external IRQ handling for this test. |
| * |
| * This function overrides the default OTTF external ISR. |
| * |
| * For each IRQ, it performs the following: |
| * 1. Claims the IRQ fired (finds PLIC IRQ index). |
| * 2. Checks that the index belongs to the expected peripheral. |
| * 3. Checks that the correct and the only IRQ from the expected peripheral |
| * triggered. |
| * 4. Clears the IRQ at the peripheral. |
| * 5. Completes the IRQ service at PLIC. |
| */ |
| void ottf_external_isr(void) { |
| dif_rv_plic_irq_id_t plic_irq_id; |
| CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic, kHart, &plic_irq_id)); |
| |
| top_${top["name"]}_plic_peripheral_t peripheral = (top_${top["name"]}_plic_peripheral_t) |
| top_${top["name"]}_plic_interrupt_for_peripheral[plic_irq_id]; |
| CHECK(peripheral == peripheral_expected, |
| "Interrupt from incorrect peripheral: exp = %d, obs = %d", |
| peripheral_expected, peripheral); |
| |
| switch (peripheral) { |
| % for p in helper.irq_peripherals: |
| case ${p.plic_name}: { |
| dif_${p.name}_irq_t irq = (dif_${p.name}_irq_t)( |
| plic_irq_id - |
| (dif_rv_plic_irq_id_t)${p.plic_start_irq}); |
| CHECK(irq == ${p.name}_irq_expected, |
| "Incorrect ${p.inst_name} IRQ triggered: exp = %d, obs = %d", |
| ${p.name}_irq_expected, irq); |
| ${p.name}_irq_serviced = irq; |
| |
| dif_${p.name}_irq_state_snapshot_t snapshot; |
| CHECK_DIF_OK(dif_${p.name}_irq_get_state(${args(p)}, &snapshot)); |
| CHECK(snapshot == (dif_${p.name}_irq_state_snapshot_t)(1 << irq), |
| "Only ${p.inst_name} IRQ %d expected to fire. Actual interrupt " |
| "status = %x", |
| irq, snapshot); |
| |
| // TODO: Check Interrupt type then clear INTR_TEST if needed. |
| CHECK_DIF_OK(dif_${p.name}_irq_force(&${p.inst_name}, irq, false)); |
| CHECK_DIF_OK(dif_${p.name}_irq_acknowledge(&${p.inst_name}, irq)); |
| break; |
| } |
| |
| % endfor |
| default: |
| LOG_FATAL("ISR is not implemented!"); |
| test_status_set(kTestStatusFailed); |
| } |
| |
| // Complete the IRQ at PLIC. |
| CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic, kHart, plic_irq_id)); |
| } |
| |
| /** |
| * Initializes the handles to all peripherals. |
| */ |
| static void peripherals_init(void) { |
| mmio_region_t base_addr; |
| |
| % for p in helper.irq_peripherals: |
| base_addr = mmio_region_from_addr(${p.base_addr_name}); |
| CHECK_DIF_OK(dif_${p.name}_init(base_addr, &${p.inst_name})); |
| |
| % endfor |
| base_addr = mmio_region_from_addr(TOP_${top["name"].upper()}_RV_PLIC_BASE_ADDR); |
| CHECK_DIF_OK(dif_rv_plic_init(base_addr, &plic)); |
| } |
| |
| /** |
| * Clears pending IRQs in all peripherals. |
| */ |
| static void peripheral_irqs_clear(void) { |
| % for p in helper.irq_peripherals: |
| CHECK_DIF_OK(dif_${p.name}_irq_acknowledge_all(${args(p)})); |
| % endfor |
| } |
| |
| /** |
| * Enables all IRQs in all peripherals. |
| */ |
| static void peripheral_irqs_enable(void) { |
| % for n in irq_peripheral_names: |
| <% |
| if n == "aon_timer": continue |
| %>\ |
| dif_${n}_irq_state_snapshot_t ${n}_irqs = |
| (dif_${n}_irq_state_snapshot_t)UINT_MAX; |
| % endfor |
| |
| % for p in helper.irq_peripherals: |
| <% |
| if p.name == "aon_timer": continue |
| %>\ |
| <% |
| indent = "" |
| %>\ |
| % if p.inst_name == "uart0": |
| <% |
| indent = " " |
| %>\ |
| // lowrisc/opentitan#8656: Skip UART0 in non-DV setups due to interference |
| // from the logging facility. |
| if (kDeviceType == kDeviceSimDV) { |
| % endif |
| ${indent}CHECK_DIF_OK( |
| ${indent} dif_${p.name}_irq_restore_all(${args(p)}, &${p.name}_irqs)); |
| % if p.inst_name == "uart0": |
| } |
| % endif |
| % endfor |
| } |
| |
| /** |
| * Triggers all IRQs in all peripherals one by one. |
| * |
| * Walks through all instances of all peripherals and triggers an interrupt one |
| * by one, by forcing with the `intr_test` CSR. On trigger, the CPU instantly |
| * jumps into the ISR. The main flow of execution thus proceeds to check that |
| * the correct IRQ was serviced immediately. The ISR, in turn checks if the |
| * expected IRQ from the expected peripheral triggered. |
| */ |
| static void peripheral_irqs_trigger(void) { |
| % for p in helper.irq_peripherals: |
| <% |
| indent = "" |
| %>\ |
| % if p.inst_name == "uart0": |
| <% |
| indent = " " |
| %>\ |
| // lowrisc/opentitan#8656: Skip UART0 in non-DV setups due to interference |
| // from the logging facility. |
| if (kDeviceType == kDeviceSimDV) { |
| % endif |
| ${indent}peripheral_expected = ${p.plic_name}; |
| ${indent}for (dif_${p.name}_irq_t irq = ${p.start_irq}; |
| ${indent} irq <= ${p.end_irq}; ++irq) { |
| ${indent} ${p.name}_irq_expected = irq; |
| ${indent} LOG_INFO("Triggering ${p.inst_name} IRQ %d.", irq); |
| ${indent} CHECK_DIF_OK(dif_${p.name}_irq_force(&${p.inst_name}, irq, true)); |
| |
| ${indent} // This avoids a race where *irq_serviced is read before |
| ${indent} // entering the ISR. |
| ${indent} IBEX_SPIN_FOR(${p.name}_irq_serviced == irq, 1); |
| ${indent} LOG_INFO("IRQ %d from ${p.inst_name} is serviced.", irq); |
| ${indent}} |
| % if p.inst_name == "uart0": |
| } |
| % endif |
| ${"" if loop.last else "\n"}\ |
| % endfor |
| } |
| |
| /** |
| * Checks that the target ID corresponds to the ID of the hart on which |
| * this test is executed on. This check is meant to be used in a |
| * single-hart system only. |
| */ |
| static void check_hart_id(uint32_t exp_hart_id) { |
| uint32_t act_hart_id; |
| CSR_READ(CSR_REG_MHARTID, &act_hart_id); |
| CHECK(act_hart_id == exp_hart_id, "Processor has unexpected HART ID."); |
| } |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| bool test_main(void) { |
| irq_global_ctrl(true); |
| irq_external_ctrl(true); |
| peripherals_init(); |
| check_hart_id((uint32_t)kHart); |
| rv_plic_testutils_irq_range_enable( |
| &plic, kHart, kTop${top["name"].capitalize()}PlicIrqIdNone + 1, kTop${top["name"].capitalize()}PlicIrqIdLast); |
| peripheral_irqs_clear(); |
| peripheral_irqs_enable(); |
| peripheral_irqs_trigger(); |
| return true; |
| } |
| |
| // clang-format on |