| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| #include <cheri.hh> | |
| #include <compartment-macros.h> | |
| #include <optional> | |
| #include <stdint.h> | |
| #include <utils.hh> | |
| /** | |
| * Driver for the standard RISC-V Platform-Local Interrupt Controller (PLIC). | |
| * | |
| * `MaxIntrID` is the largest interrupt number that is used. `SourceID` and | |
| * `Priority` are the types used for interrupt source numbers and priorities, | |
| * respectively. | |
| */ | |
| template<size_t MaxIntrID, typename SourceID, typename Priority> | |
| class StandardPlic : private utils::NoCopyNoMove | |
| { | |
| public: | |
| /** | |
| * Constructor. Initialises the interrupt controller with all interrupts | |
| * disabled. | |
| */ | |
| StandardPlic() | |
| { | |
| volatile uint32_t *range = MMIO_CAPABILITY(uint32_t, plic); | |
| size_t nSources = (MaxIntrID + 32U) & ~0x1f; | |
| // We program the enable bits in groups of 32. | |
| size_t nSourcesGroups = nSources >> 5; | |
| auto setField = [&](auto &field, size_t offset, size_t size) { | |
| CHERI::Capability capability{range}; | |
| capability.address() += offset; | |
| capability.bounds() = size; | |
| field = capability; | |
| }; | |
| setField(plicPrios, PriorityOffset, nSources * sizeof(uint32_t)); | |
| setField(plicPendings, PendingOffset, nSources / 8); | |
| setField(plicEnables, EnableOffset, nSources / 8); | |
| setField(plicThres, ThresholdOffset, sizeof(uint32_t)); | |
| setField(plicClaim, ClaimOffset, sizeof(uint32_t)); | |
| for (size_t i = 0; i < nSourcesGroups; i++) | |
| { | |
| plicEnables[i] = 0U; | |
| } | |
| for (size_t i = 1; i <= MaxIntrID; i++) | |
| { | |
| plicPrios[i] = 0U; | |
| } | |
| // We don't make use of threshold control. Leave it at 0 so all | |
| // configured interrupts can fire. | |
| *plicThres = 0U; | |
| } | |
| /** | |
| * Enable the specified interrupt. | |
| */ | |
| void interrupt_enable(SourceID src) | |
| { | |
| size_t idx = src >> 5; | |
| size_t bitPos = src & 0x1f; | |
| uint32_t enables = plicEnables[idx] | (1U << bitPos); | |
| plicEnables[idx] = enables; | |
| } | |
| /** | |
| * Disable the specified interrupt. | |
| */ | |
| void interrupt_disable(SourceID src) | |
| { | |
| size_t idx = src >> 5; | |
| size_t bitPos = src & 0x1f; | |
| uint32_t enables = plicEnables[idx] & ~(1U << bitPos); | |
| plicEnables[idx] = enables; | |
| } | |
| /** | |
| * Set the priority of the specified interrupt. | |
| */ | |
| void priority_set(SourceID src, Priority prio) | |
| { | |
| plicPrios[src] = prio; | |
| } | |
| /** | |
| * Fetch the interrupt number that fired and prevent it from firing. If | |
| * two or more interrupts have fired then this will return the | |
| * highest-priority one. | |
| * | |
| * If no interrupt has fired (for example, because the interrupt line on | |
| * the core was raised spuriously) then this returns `stdd:nullopt`. | |
| */ | |
| std::optional<SourceID> interrupt_claim() | |
| { | |
| uint32_t claim = *plicClaim; | |
| // PLIC reserves source ID 0, which means no interrupts. | |
| return claim == 0 ? std::nullopt : std::optional{claim}; | |
| } | |
| /** | |
| * Tell the interrupt controller we've handled a specified interrupt ID. | |
| */ | |
| void interrupt_complete(SourceID src) | |
| { | |
| *plicClaim = src; | |
| } | |
| private: | |
| // generic offsets according to spec | |
| static constexpr size_t PriorityOffset = 0x0U; | |
| static constexpr size_t PendingOffset = 0x1000U; | |
| static constexpr size_t EnableOffset = 0x2000U; | |
| static constexpr size_t ThresholdOffset = 0x200000U; | |
| static constexpr size_t ClaimOffset = 0x200004U; | |
| // Bounded capabilities to the individual structure fields. | |
| // Ideally these should be contained in one volatile struct, but | |
| // they are so far apart (and this lets us apply the bounds once). | |
| volatile uint32_t *plicPrios; | |
| volatile uint32_t *plicPendings; | |
| volatile uint32_t *plicEnables; | |
| volatile uint32_t *plicThres; | |
| volatile uint32_t *plicClaim; | |
| }; | |
| /** | |
| * Type representing no interrupt controller. | |
| * | |
| * Contains stub implementations of all of the methods. | |
| */ | |
| template<size_t MaxIntrID, typename SourceID, typename Priority> | |
| class NoPlic : private utils::NoCopyNoMove | |
| { | |
| public: | |
| void interrupt_enable(SourceID) {} | |
| void interrupt_disable(SourceID) {} | |
| void priority_set(SourceID, Priority) {} | |
| std::optional<SourceID> interrupt_claim() | |
| { | |
| return std::nullopt; | |
| } | |
| void interrupt_complete(SourceID) {} | |
| }; | |
| /** | |
| * The type for the Programmable Local Interrupt Controller (PLIC) to use. | |
| * | |
| * If there is no plic device then this provides a stub version. | |
| */ | |
| template<size_t MaxIntrID, typename SourceID, typename Priority> | |
| using Plic = | |
| #if DEVICE_EXISTS(plic) | |
| StandardPlic | |
| #else | |
| NoPlic | |
| #endif | |
| <MaxIntrID, SourceID, Priority>; |