| // Copyright Microsoft and CHERIoT Contributors. |
| // SPDX-License-Identifier: MIT |
| |
| #pragma once |
| |
| #include "common.h" |
| #include <compartment.h> |
| #include <optional> |
| #include <platform-plic.hh> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <utils.hh> |
| |
| /* |
| * Platform specific low-level implementations of the interrupt controller. Not |
| * to be used directly. These should be inherited by the interrupt controller |
| * wrapper class. |
| */ |
| namespace |
| { |
| using Priority = uint32_t; |
| using SourceID = uint32_t; |
| |
| template<typename T, size_t MaxIntrID, typename SourceID, typename Priority> |
| concept IsPlic = requires(T v, SourceID id, Priority p) |
| { |
| {v.interrupt_enable(id)}; |
| {v.interrupt_disable(id)}; |
| {v.interrupt_disable(id)}; |
| {v.priority_set(id, p)}; |
| { |
| v.interrupt_claim() |
| } -> std::same_as<std::optional<SourceID>>; |
| {v.interrupt_complete(id)}; |
| }; |
| |
| /* |
| * FIXME: Sail doesn't have an interrupt controller at all, but we pretend |
| * it does just like FLUTE build to let things compile. We need tons of |
| * #ifdefs or a big rewrite to make the entire external interrupt path |
| * optional. |
| * |
| * FIXME: Here should only be platform-agnostic code but we still hardcode |
| * an Ethernet handler. We should be generic and auto-generate event |
| * channels and intr_complete() functions here. |
| */ |
| |
| /** |
| * The PLIC class that wraps platform-specific implementations and |
| * provides higher-level abstractions. |
| */ |
| class InterruptController final |
| { |
| /** |
| * Structure representing the configuration for an interrupt. |
| */ |
| struct Interrupt |
| { |
| /** |
| * The interrupt number. |
| */ |
| uint32_t number; |
| /** |
| * The priority for this interrupt. |
| */ |
| uint32_t priority; |
| /** |
| * True if this interrupt is edge triggered, false otherwise. Edge |
| * triggered interrupts are automatically acknowledged, level |
| * triggered interrupts must be explicitly acknowledged. |
| */ |
| bool isEdgeTriggered; |
| }; |
| |
| /** |
| * The array of interrupts that are configured. |
| */ |
| static constexpr Interrupt ConfiguredInterrupts[] = { |
| #ifdef CHERIOT_INTERRUPT_CONFIGURATION |
| CHERIOT_INTERRUPT_CONFIGURATION |
| #endif |
| }; |
| |
| /** |
| * The number of interrupts that are configured. |
| * |
| * We only allocate state for configured interrupts. |
| */ |
| static constexpr size_t NumberOfInterrupts = |
| std::extent_v<decltype(ConfiguredInterrupts)>; |
| |
| static constexpr uint32_t LargestInterruptNumber = []() { |
| uint32_t max = 0; |
| for (auto i : ConfiguredInterrupts) |
| { |
| max = std::max(max, i.number); |
| } |
| return max; |
| }(); |
| |
| using PlicType = Plic<LargestInterruptNumber, SourceID, Priority>; |
| |
| static_assert( |
| IsPlic<PlicType, LargestInterruptNumber, SourceID, Priority>, |
| "Provided PLIC does not implement the required interface"); |
| /** |
| * The platform local interrupt controller device. |
| */ |
| PlicType device; |
| |
| /** |
| * The futex words corresponding to each interrupt. Each word is |
| * incremented whenever the interrupt fires. This allows handlers to |
| * use this in a pseudo-polling style. We could turn this into a real |
| * polling model if we were to design a custom interrupt controller. |
| */ |
| uint32_t futexWords[NumberOfInterrupts]; |
| |
| public: |
| /** |
| * Returns a reference that allows callers to directly poke the device. |
| * This is intended to be used only by timers that need to directly |
| * interact with the interrupt controller. |
| */ |
| PlicType &plic_device() |
| { |
| return device; |
| } |
| |
| /** |
| * If source corresponds to a valid interrupt, return a reference to |
| * it, otherwise return an invalid value. |
| * |
| * If the template parameter is true then this completes the interrupt |
| * if it is edge triggered. |
| */ |
| template<bool CompleteInterruptIfEdgeTriggered = false> |
| utils::OptionalReference<uint32_t> |
| futex_word_for_source(SourceID source) |
| { |
| for (size_t i = 0; i < NumberOfInterrupts; i++) |
| { |
| if (ConfiguredInterrupts[i].number == uint32_t(source)) |
| { |
| if constexpr (CompleteInterruptIfEdgeTriggered) |
| { |
| if (ConfiguredInterrupts[i].isEdgeTriggered) |
| { |
| master().interrupt_complete(source); |
| } |
| } |
| return {futexWords[i]}; |
| } |
| } |
| return nullptr; |
| } |
| |
| static InterruptController &master() |
| { |
| // masterPlic is the statically-allocated space for the main Plic, |
| // and we know it's been placement-newed during boot in |
| // master_init(), so it contains a valid Plic object. |
| return reinterpret_cast<InterruptController &>(masterPlic); |
| } |
| |
| static void master_init() |
| { |
| // The Clang analyser has a false positive here, not seeing the |
| // `alignas` directive on masterPlic |
| new (masterPlic) // NOLINT(clang-analyzer-cplusplus.PlacementNew) |
| InterruptController(); |
| } |
| |
| InterruptController() |
| { |
| // Set the priority for each interrupt and then enable it. |
| for (auto interrupt : ConfiguredInterrupts) |
| { |
| device.priority_set(interrupt.number, interrupt.priority); |
| device.interrupt_enable(interrupt.number); |
| } |
| } |
| |
| /** |
| * Handling external interrupts. |
| * @return true if a task is preempted and needs reschedule |
| */ |
| [[nodiscard]] utils::OptionalReference<uint32_t> do_external_interrupt() |
| { |
| // TODO: Replace this with and_then() when we update libc++ to |
| // support C++23. |
| std::optional<SourceID> src = device.interrupt_claim(); |
| if (!src) |
| { |
| // We entered external interrupt but for whatever reason the |
| // interrupt controller says it sees nothing, so just return. |
| return nullptr; |
| } |
| |
| return futex_word_for_source< |
| /*Complete edge triggered interrupt*/ true>(*src); |
| } |
| |
| void interrupt_complete(SourceID id) |
| { |
| device.interrupt_complete(id); |
| } |
| |
| private: |
| /** |
| * The reserved space for the master PLIC. In theory there could be |
| * multiple PLICs in the system and we can easily have multiple PLIC |
| * instances in addition to master, although for the MCU we will |
| * probably only ever have just the one. |
| */ |
| static char masterPlic[]; |
| }; |
| |
| alignas(InterruptController) inline char InterruptController::masterPlic |
| [sizeof(InterruptController)]; |
| } // namespace |