| // 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>; |