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