|  | // 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_rv_plic.h" | 
|  |  | 
|  | #include <array> | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "sw/device/lib/base/mmio.h" | 
|  | #include "sw/device/lib/base/mock_mmio.h" | 
|  | #include "sw/device/lib/dif/dif_test_base.h" | 
|  |  | 
|  | #include "rv_plic_regs.h"  // Generated. | 
|  |  | 
|  | namespace dif_rv_plic_unittest { | 
|  | namespace { | 
|  | using mock_mmio::MmioTest; | 
|  | using mock_mmio::MockDevice; | 
|  | using testing::Test; | 
|  |  | 
|  | // If either of these static assertions fail, then the unit-tests for related | 
|  | // API should be revisited. | 
|  | static_assert(RV_PLIC_PARAM_NUM_SRC == 185, | 
|  | "PLIC instantiation parameters have changed."); | 
|  | static_assert(RV_PLIC_PARAM_NUM_TARGET == 1, | 
|  | "PLIC instantiation parameters have changed."); | 
|  |  | 
|  | constexpr uint32_t kTarget0 = 0; | 
|  | constexpr uint32_t kFirstIrq = 1; | 
|  |  | 
|  | class PlicTest : public Test, public MmioTest { | 
|  | protected: | 
|  | dif_rv_plic_t plic_ = {.base_addr = dev().region()}; | 
|  | }; | 
|  |  | 
|  | class ResetTest : public PlicTest { | 
|  | protected: | 
|  | void ExpectReset() { | 
|  | // Priority registers. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | ptrdiff_t offset = RV_PLIC_PRIO0_REG_OFFSET + (sizeof(uint32_t) * i); | 
|  | EXPECT_WRITE32(offset, 0); | 
|  | } | 
|  |  | 
|  | // Interrupt enable multireg. | 
|  | EXPECT_WRITE32(RV_PLIC_IE0_0_REG_OFFSET, 0); | 
|  | EXPECT_WRITE32(RV_PLIC_IE0_1_REG_OFFSET, 0); | 
|  | EXPECT_WRITE32(RV_PLIC_IE0_2_REG_OFFSET, 0); | 
|  | EXPECT_WRITE32(RV_PLIC_IE0_3_REG_OFFSET, 0); | 
|  | EXPECT_WRITE32(RV_PLIC_IE0_4_REG_OFFSET, 0); | 
|  | EXPECT_WRITE32(RV_PLIC_IE0_5_REG_OFFSET, 0); | 
|  |  | 
|  | // Target threshold registers. | 
|  | EXPECT_WRITE32(RV_PLIC_THRESHOLD0_REG_OFFSET, 0); | 
|  |  | 
|  | // Software interrupt pending register. | 
|  | EXPECT_WRITE32(RV_PLIC_MSIP0_REG_OFFSET, 0); | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_F(ResetTest, NullArgs) { EXPECT_DIF_BADARG(dif_rv_plic_reset(nullptr)); } | 
|  |  | 
|  | TEST_F(ResetTest, Success) { | 
|  | ExpectReset(); | 
|  |  | 
|  | EXPECT_DIF_OK(dif_rv_plic_reset(&plic_)); | 
|  | } | 
|  |  | 
|  | class IrqTest : public PlicTest { | 
|  | protected: | 
|  | IrqTest() { | 
|  | // Make sure to change the `last_bit` when `RV_PLIC_PARAM_NUM_SRC` changes. | 
|  | // As `last_bit` represents the bit index in a register, we need to count | 
|  | // all of the last bits of a multireg to get the total number of bits. | 
|  | // The bit count in IE and IP registers is expected to be the same. | 
|  | // | 
|  | // This check has been added to help diagnose the mismatch of test values | 
|  | // with the HW defines. One of the recent PRs ran into this problem, and | 
|  | // the failure message was not descriptive, so some engineering time was | 
|  | // lost to investigation. | 
|  | uint8_t number_of_sources = 0; | 
|  | for (const auto ® : kEnableRegisters) { | 
|  | number_of_sources += (reg.last_bit + 1); | 
|  | } | 
|  | EXPECT_EQ(RV_PLIC_PARAM_NUM_SRC, number_of_sources) | 
|  | << "make sure to update the IrqTest register arrays!"; | 
|  |  | 
|  | EXPECT_EQ(RV_PLIC_PARAM_NUM_TARGET, 1); | 
|  | } | 
|  |  | 
|  | struct Register { | 
|  | ptrdiff_t offset;  // Register offset from the base. | 
|  | uint8_t last_bit;  // Last bit index in the register. | 
|  | }; | 
|  | static constexpr std::array<Register, RV_PLIC_IE0_MULTIREG_COUNT> | 
|  | kEnableRegisters{{ | 
|  | {RV_PLIC_IE0_0_REG_OFFSET, RV_PLIC_IE0_0_E_31_BIT}, | 
|  | {RV_PLIC_IE0_1_REG_OFFSET, RV_PLIC_IE0_1_E_63_BIT}, | 
|  | {RV_PLIC_IE0_2_REG_OFFSET, RV_PLIC_IE0_2_E_95_BIT}, | 
|  | {RV_PLIC_IE0_3_REG_OFFSET, RV_PLIC_IE0_3_E_127_BIT}, | 
|  | {RV_PLIC_IE0_4_REG_OFFSET, RV_PLIC_IE0_4_E_159_BIT}, | 
|  | {RV_PLIC_IE0_5_REG_OFFSET, RV_PLIC_IE0_5_E_184_BIT}, | 
|  | }}; | 
|  | static constexpr std::array<Register, RV_PLIC_IP_MULTIREG_COUNT> | 
|  | kPendingRegisters{{ | 
|  | {RV_PLIC_IP_0_REG_OFFSET, RV_PLIC_IP_0_P_31_BIT}, | 
|  | {RV_PLIC_IP_1_REG_OFFSET, RV_PLIC_IP_1_P_63_BIT}, | 
|  | {RV_PLIC_IP_2_REG_OFFSET, RV_PLIC_IP_2_P_95_BIT}, | 
|  | {RV_PLIC_IP_3_REG_OFFSET, RV_PLIC_IP_3_P_127_BIT}, | 
|  | {RV_PLIC_IP_4_REG_OFFSET, RV_PLIC_IP_4_P_159_BIT}, | 
|  | {RV_PLIC_IP_5_REG_OFFSET, RV_PLIC_IP_5_P_184_BIT}, | 
|  | }}; | 
|  |  | 
|  | // Set enable/disable multireg expectations, one bit per call. | 
|  | template <size_t n> | 
|  | void ExpectIrqSetTests(const std::array<Register, n> ®s, bool enabled) { | 
|  | for (const auto ® : regs) { | 
|  | for (uint32_t i = 0; i <= reg.last_bit; ++i) { | 
|  | EXPECT_MASK32(reg.offset, {{i, 0x1, enabled}}); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Set multireg get status expectations, one bit per call. | 
|  | template <size_t n> | 
|  | void ExpectIrqGetTests(const std::array<Register, n> ®s, bool enabled) { | 
|  | for (const auto ® : regs) { | 
|  | for (int i = 0; i <= reg.last_bit; ++i) { | 
|  | uint32_t value = 0x1 << i; | 
|  | if (!enabled) { | 
|  | value = ~value; | 
|  | } | 
|  |  | 
|  | EXPECT_READ32(reg.offset, value); | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | constexpr std::array<IrqTest::Register, RV_PLIC_IE0_MULTIREG_COUNT> | 
|  | IrqTest::kEnableRegisters; | 
|  | constexpr std::array<IrqTest::Register, RV_PLIC_IP_MULTIREG_COUNT> | 
|  | IrqTest::kPendingRegisters; | 
|  |  | 
|  | class IrqEnableSetTest : public IrqTest {}; | 
|  |  | 
|  | TEST_F(IrqEnableSetTest, NullArgs) { | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_irq_set_enabled(nullptr, kFirstIrq, kTarget0, | 
|  | kDifToggleEnabled)); | 
|  | } | 
|  |  | 
|  | TEST_F(IrqEnableSetTest, Target0Enable) { | 
|  | ExpectIrqSetTests(kEnableRegisters, true); | 
|  |  | 
|  | // Enable every IRQ, one at a time. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | EXPECT_DIF_OK( | 
|  | dif_rv_plic_irq_set_enabled(&plic_, i, kTarget0, kDifToggleEnabled)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(IrqEnableSetTest, Target0Disable) { | 
|  | ExpectIrqSetTests(kEnableRegisters, false); | 
|  |  | 
|  | // Disable every bit, one at a time. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | EXPECT_DIF_OK( | 
|  | dif_rv_plic_irq_set_enabled(&plic_, i, kTarget0, kDifToggleDisabled)); | 
|  | } | 
|  | } | 
|  |  | 
|  | class IrqPrioritySetTest : public PlicTest {}; | 
|  |  | 
|  | TEST_F(IrqPrioritySetTest, NullArgs) { | 
|  | EXPECT_DIF_BADARG( | 
|  | dif_rv_plic_irq_set_priority(nullptr, kFirstIrq, kDifRvPlicMaxPriority)); | 
|  | } | 
|  |  | 
|  | TEST_F(IrqPrioritySetTest, PriorityInvalid) { | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_irq_set_priority(nullptr, kFirstIrq, | 
|  | kDifRvPlicMaxPriority + 1)); | 
|  | } | 
|  |  | 
|  | TEST_F(IrqPrioritySetTest, Success) { | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | // Set expectations for every priority set call. | 
|  | ptrdiff_t offset = RV_PLIC_PRIO0_REG_OFFSET + (sizeof(uint32_t) * i); | 
|  | EXPECT_WRITE32(offset, kDifRvPlicMaxPriority); | 
|  |  | 
|  | EXPECT_DIF_OK( | 
|  | dif_rv_plic_irq_set_priority(&plic_, i, kDifRvPlicMaxPriority)); | 
|  | } | 
|  | } | 
|  |  | 
|  | class TargetThresholdSetTest : public PlicTest {}; | 
|  |  | 
|  | TEST_F(TargetThresholdSetTest, NullArgs) { | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_target_set_threshold(nullptr, kTarget0, | 
|  | kDifRvPlicMaxPriority)); | 
|  | } | 
|  |  | 
|  | TEST_F(TargetThresholdSetTest, Target0PriorityInvalid) { | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_target_set_threshold( | 
|  | &plic_, kTarget0, kDifRvPlicMaxPriority + 1)); | 
|  | } | 
|  |  | 
|  | TEST_F(TargetThresholdSetTest, Target0Success) { | 
|  | EXPECT_WRITE32(RV_PLIC_THRESHOLD0_REG_OFFSET, kDifRvPlicMaxPriority); | 
|  |  | 
|  | EXPECT_DIF_OK(dif_rv_plic_target_set_threshold(&plic_, kTarget0, | 
|  | kDifRvPlicMaxPriority)); | 
|  | } | 
|  |  | 
|  | class IrqPendingStatusGetTest : public IrqTest {}; | 
|  |  | 
|  | TEST_F(IrqPendingStatusGetTest, NullArgs) { | 
|  | bool status; | 
|  | dif_result_t result = dif_rv_plic_irq_is_pending(nullptr, kFirstIrq, &status); | 
|  | EXPECT_DIF_BADARG(result); | 
|  |  | 
|  | result = dif_rv_plic_irq_is_pending(&plic_, kFirstIrq, nullptr); | 
|  | EXPECT_DIF_BADARG(result); | 
|  |  | 
|  | result = dif_rv_plic_irq_is_pending(nullptr, kFirstIrq, nullptr); | 
|  | EXPECT_DIF_BADARG(result); | 
|  | } | 
|  |  | 
|  | TEST_F(IrqPendingStatusGetTest, Enabled) { | 
|  | ExpectIrqGetTests(kPendingRegisters, true); | 
|  |  | 
|  | // Get status of every IRQ, one at a time. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | bool status; | 
|  | dif_result_t result = dif_rv_plic_irq_is_pending(&plic_, i, &status); | 
|  | EXPECT_DIF_OK(result); | 
|  | EXPECT_TRUE(status); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(IrqPendingStatusGetTest, Disabled) { | 
|  | ExpectIrqGetTests(kPendingRegisters, false); | 
|  |  | 
|  | // Get status of every IRQ, one at a time. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | bool status; | 
|  | dif_result_t result = dif_rv_plic_irq_is_pending(&plic_, i, &status); | 
|  | EXPECT_DIF_OK(result); | 
|  | EXPECT_FALSE(status); | 
|  | } | 
|  | } | 
|  |  | 
|  | class IrqClaimTest : public PlicTest { | 
|  | static_assert(RV_PLIC_PARAM_NUM_TARGET == 1, ""); | 
|  | }; | 
|  |  | 
|  | TEST_F(IrqClaimTest, NullArgs) { | 
|  | dif_rv_plic_irq_id_t data; | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_irq_claim(nullptr, kTarget0, &data)); | 
|  |  | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_irq_claim(&plic_, kTarget0, nullptr)); | 
|  |  | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_irq_claim(nullptr, kTarget0, nullptr)); | 
|  | } | 
|  |  | 
|  | TEST_F(IrqClaimTest, Target0Success) { | 
|  | // Set expectations for every claim call. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | EXPECT_READ32(RV_PLIC_CC0_REG_OFFSET, i); | 
|  | } | 
|  |  | 
|  | // Claim every IRQ, one per a call. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | dif_rv_plic_irq_id_t data; | 
|  | EXPECT_DIF_OK(dif_rv_plic_irq_claim(&plic_, kTarget0, &data)); | 
|  | EXPECT_EQ(data, i); | 
|  | } | 
|  | } | 
|  |  | 
|  | class IrqCompleteTest : public PlicTest { | 
|  | static_assert(RV_PLIC_PARAM_NUM_TARGET == 1, ""); | 
|  | }; | 
|  |  | 
|  | TEST_F(IrqCompleteTest, NullArgs) { | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_irq_complete(nullptr, kTarget0, 0)); | 
|  | } | 
|  |  | 
|  | TEST_F(IrqCompleteTest, Target0Success) { | 
|  | // Set expectations for every complete call. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | EXPECT_WRITE32(RV_PLIC_CC0_REG_OFFSET, i); | 
|  | } | 
|  |  | 
|  | // Complete all of the IRQs. | 
|  | for (int i = 0; i < RV_PLIC_PARAM_NUM_SRC; ++i) { | 
|  | EXPECT_DIF_OK(dif_rv_plic_irq_complete(&plic_, kTarget0, i)); | 
|  | } | 
|  | } | 
|  |  | 
|  | class SoftwareIrqForceTest : public PlicTest { | 
|  | static_assert(RV_PLIC_PARAM_NUM_TARGET == 1, ""); | 
|  | }; | 
|  |  | 
|  | TEST_F(SoftwareIrqForceTest, NullArgs) { | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_software_irq_force(nullptr, kTarget0)); | 
|  | } | 
|  |  | 
|  | TEST_F(SoftwareIrqForceTest, BadTarget) { | 
|  | EXPECT_DIF_BADARG( | 
|  | dif_rv_plic_software_irq_force(&plic_, RV_PLIC_PARAM_NUM_TARGET)); | 
|  | } | 
|  |  | 
|  | TEST_F(SoftwareIrqForceTest, Target0Success) { | 
|  | EXPECT_WRITE32(RV_PLIC_MSIP0_REG_OFFSET, 1); | 
|  | EXPECT_DIF_OK(dif_rv_plic_software_irq_force(&plic_, kTarget0)); | 
|  | } | 
|  |  | 
|  | class SoftwareIrqAcknowledgeTest : public PlicTest { | 
|  | static_assert(RV_PLIC_PARAM_NUM_TARGET == 1, ""); | 
|  | }; | 
|  |  | 
|  | TEST_F(SoftwareIrqAcknowledgeTest, NullArgs) { | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_software_irq_acknowledge(nullptr, kTarget0)); | 
|  | } | 
|  |  | 
|  | TEST_F(SoftwareIrqAcknowledgeTest, BadTarget) { | 
|  | EXPECT_DIF_BADARG( | 
|  | dif_rv_plic_software_irq_acknowledge(&plic_, RV_PLIC_PARAM_NUM_TARGET)); | 
|  | } | 
|  |  | 
|  | TEST_F(SoftwareIrqAcknowledgeTest, Target0Success) { | 
|  | EXPECT_WRITE32(RV_PLIC_MSIP0_REG_OFFSET, 0); | 
|  | EXPECT_DIF_OK(dif_rv_plic_software_irq_acknowledge(&plic_, kTarget0)); | 
|  | } | 
|  |  | 
|  | class SoftwareIrqIsPendingTest : public PlicTest { | 
|  | static_assert(RV_PLIC_PARAM_NUM_TARGET == 1, ""); | 
|  | }; | 
|  |  | 
|  | TEST_F(SoftwareIrqIsPendingTest, NullArgs) { | 
|  | EXPECT_DIF_BADARG( | 
|  | dif_rv_plic_software_irq_is_pending(nullptr, kTarget0, nullptr)); | 
|  |  | 
|  | EXPECT_DIF_BADARG( | 
|  | dif_rv_plic_software_irq_is_pending(&plic_, kTarget0, nullptr)); | 
|  |  | 
|  | bool is_pending; | 
|  | EXPECT_DIF_BADARG( | 
|  | dif_rv_plic_software_irq_is_pending(nullptr, kTarget0, &is_pending)); | 
|  | } | 
|  |  | 
|  | TEST_F(SoftwareIrqIsPendingTest, BadTarget) { | 
|  | bool is_pending; | 
|  | EXPECT_DIF_BADARG(dif_rv_plic_software_irq_is_pending( | 
|  | &plic_, RV_PLIC_PARAM_NUM_TARGET, &is_pending)); | 
|  | } | 
|  |  | 
|  | TEST_F(SoftwareIrqIsPendingTest, Target0Success) { | 
|  | bool is_pending = false; | 
|  | EXPECT_READ32(RV_PLIC_MSIP0_REG_OFFSET, 1); | 
|  |  | 
|  | EXPECT_DIF_OK( | 
|  | dif_rv_plic_software_irq_is_pending(&plic_, kTarget0, &is_pending)); | 
|  | EXPECT_TRUE(is_pending); | 
|  |  | 
|  | // Cleared | 
|  | is_pending = true; | 
|  | EXPECT_READ32(RV_PLIC_MSIP0_REG_OFFSET, 0); | 
|  |  | 
|  | EXPECT_DIF_OK( | 
|  | dif_rv_plic_software_irq_is_pending(&plic_, kTarget0, &is_pending)); | 
|  | EXPECT_FALSE(is_pending); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace dif_rv_plic_unittest |