blob: 99874d52e0a09f96c4f5b2afe7ebcc779be3dc69 [file] [log] [blame]
// 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