Kernel: Support the RISC-V PLIC

By supporting the RISC-V PLIC (Platform-Level Interrupt Controller)
we can now handle device (external) interrupts.
This commit is contained in:
Idan Horowitz 2024-04-23 20:28:11 +03:00 committed by Andrew Kaster
parent 0a2d520b15
commit dfa2c98497
Notes: sideshowbarker 2024-07-17 05:41:34 +09:00
7 changed files with 195 additions and 8 deletions

View file

@ -296,9 +296,6 @@ extern "C" [[noreturn]] UNMAP_AFTER_INIT NO_SANITIZE_COVERAGE void init([[maybe_
for (ctor_func_t* ctor = start_ctors; ctor < end_ctors; ctor++)
(*ctor)();
InterruptManagement::initialize();
ACPI::initialize();
#if ARCH(RISCV64)
MUST(unflatten_fdt());
@ -308,6 +305,9 @@ extern "C" [[noreturn]] UNMAP_AFTER_INIT NO_SANITIZE_COVERAGE void init([[maybe_
init_delay_loop();
#endif
InterruptManagement::initialize();
ACPI::initialize();
// Initialize TimeManagement before using randomness!
TimeManagement::initialize(0);

View file

@ -24,9 +24,9 @@ public:
virtual void enable(GenericInterruptHandler const&) = 0;
virtual void disable(GenericInterruptHandler const&) = 0;
virtual void eoi(GenericInterruptHandler const&) const = 0;
virtual void eoi(GenericInterruptHandler const&) = 0;
virtual u64 pending_interrupts() const = 0;
virtual u8 pending_interrupt() const = 0;
virtual StringView model() const = 0;

View file

@ -5,8 +5,10 @@
*/
#include <Kernel/Arch/Interrupts.h>
#include <Kernel/Arch/riscv64/CPU.h>
#include <Kernel/Arch/riscv64/IRQController.h>
#include <Kernel/Arch/riscv64/InterruptManagement.h>
#include <Kernel/Arch/riscv64/Interrupts/PLIC.h>
#include <Kernel/Interrupts/SharedIRQHandler.h>
namespace Kernel {
@ -34,7 +36,64 @@ void InterruptManagement::initialize()
void InterruptManagement::find_controllers()
{
// TODO: Once device tree support is in place, find interrupt controllers using that.
auto const& device_tree = DeviceTree::get();
auto maybe_soc = device_tree.get_child("soc"sv);
if (!maybe_soc.has_value()) {
dmesgln("Interrupts: No `soc` node found in the device tree, Interrupts initialization will be skipped");
return;
}
auto const& soc = maybe_soc.value();
auto soc_address_cells = soc.get_property("#address-cells"sv).value().as<u32>();
auto soc_size_cells = soc.get_property("#size-cells"sv).value().as<u32>();
auto interrupt_controllers_seen = 0;
for (auto const& [node_name, node] : soc.children()) {
if (!node.has_property("interrupt-controller"sv))
continue;
interrupt_controllers_seen++;
auto maybe_compatible = node.get_property("compatible"sv);
if (!maybe_compatible.has_value()) {
dmesgln("Interrupts: Devicetree node for {} does not have a 'compatible' string, rejecting", node_name);
continue;
}
// Reject non sifive-compatible interrupt controllers
auto sifive_plic = false;
maybe_compatible->for_each_string([&sifive_plic](StringView compatible_string) -> IterationDecision {
if (compatible_string == "sifive,plic-1.0.0"sv) {
sifive_plic = true;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (!sifive_plic)
continue;
auto maybe_reg = node.get_property("reg"sv);
if (!maybe_reg.has_value()) {
dmesgln("Interrupts: Devicetree node for {} does not have a physical address assigned to it, rejecting", node_name);
continue;
}
auto reg = maybe_reg.value();
auto stream = reg.as_stream();
FlatPtr paddr;
if (soc_address_cells == 1)
paddr = MUST(stream.read_value<BigEndian<u32>>());
else
paddr = MUST(stream.read_value<BigEndian<u64>>());
size_t size;
if (soc_size_cells == 1)
size = MUST(stream.read_value<BigEndian<u32>>());
else
size = MUST(stream.read_value<BigEndian<u64>>());
auto max_interrupt_id = node.get_property("riscv,ndev"sv).value().as<u32>();
m_interrupt_controllers.append(adopt_lock_ref(*new (nothrow) PLIC(PhysicalAddress(paddr), size, max_interrupt_id + 1)));
}
if (interrupt_controllers_seen > 0 && m_interrupt_controllers.is_empty()) {
dmesgln("Interrupts: {} interrupt controllers seen, but none are compatible", interrupt_controllers_seen);
}
}
u8 InterruptManagement::acquire_mapped_interrupt_number(u8 original_irq)
@ -49,7 +108,9 @@ Vector<NonnullLockRefPtr<IRQController>> const& InterruptManagement::controllers
NonnullLockRefPtr<IRQController> InterruptManagement::get_responsible_irq_controller(size_t)
{
TODO_RISCV64();
// TODO: Support more interrupt controllers
VERIFY(m_interrupt_controllers.size() == 1);
return m_interrupt_controllers[0];
}
void InterruptManagement::enumerate_interrupt_handlers(Function<void(GenericInterruptHandler&)>)

View file

@ -23,7 +23,7 @@ namespace Kernel {
extern "C" void syscall_handler(TrapFrame const*);
// FIXME: Share this array with x86_64/aarch64 somehow and consider if this really needs to use raw pointers and not OwnPtrs
static Array<GenericInterruptHandler*, 64> s_interrupt_handlers;
static Array<GenericInterruptHandler*, 256> s_interrupt_handlers;
void dump_registers(RegisterState const& regs)
{
@ -55,6 +55,17 @@ extern "C" void trap_handler(TrapFrame& trap_frame)
if (scause == RISCV64::CSR::SCAUSE::SupervisorTimerInterrupt) {
RISCV64::Timer::the().handle_interrupt(*trap_frame.regs);
} else if (scause == RISCV64::CSR::SCAUSE::SupervisorExternalInterrupt) {
for (auto& interrupt_controller : InterruptManagement::the().controllers()) {
u8 pending_interrupt = 0;
while ((pending_interrupt = interrupt_controller->pending_interrupt())) {
auto* handler = s_interrupt_handlers[pending_interrupt];
VERIFY(handler);
handler->increment_call_count();
handler->handle_interrupt(*trap_frame.regs);
handler->eoi();
}
}
} else {
TODO_RISCV64();
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2024, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/riscv64/Interrupts/PLIC.h>
namespace Kernel {
UNMAP_AFTER_INIT PLIC::PLIC(PhysicalAddress address, size_t size, u32 interrupt_count)
: m_registers(Memory::map_typed<RegisterMap volatile>(address, size, Memory::Region::Access::ReadWrite).release_value_but_fixme_should_propagate_errors())
, m_interrupt_count(interrupt_count)
{
VERIFY(m_interrupt_count < 256); // TODO: Serenity currently only supports up to 256 unique interrupts, but the PLIC supports up to 1024
initialize();
}
UNMAP_AFTER_INIT void PLIC::initialize()
{
for (auto i = 1u; i < m_interrupt_count; ++i) {
// Initialize all interrupt priorities to 1 (0 means never-interrupt)
m_registers->interrupt_priority[i] = 1;
}
for (auto i = 0u; i <= ((m_interrupt_count - 1) >> 5); ++i) {
// Initialize all interrupt sources to disabled
m_registers->interrupt_enable_bitmap[interrupt_context][i] = 0;
}
// Initialize priority-threshold to 0 (accept any interrupt of priority 1 or above)
m_registers->contexts[interrupt_context].priority_threshold = 0;
// Enable external interrupts in the current hart
RISCV64::CSR::set_bits(RISCV64::CSR::Address::SIE, 1 << (to_underlying(RISCV64::CSR::SCAUSE::SupervisorExternalInterrupt) & ~RISCV64::CSR::SCAUSE_INTERRUPT_MASK));
}
void PLIC::enable(GenericInterruptHandler const& handler)
{
auto interrupt_number = handler.interrupt_number();
VERIFY(interrupt_number > 0); // Interrupt number 0 is reserved to mean no-interrupt
m_registers->interrupt_enable_bitmap[interrupt_context][interrupt_number >> 5] |= 1u << (interrupt_number & 0x1F);
}
void PLIC::disable(GenericInterruptHandler const& handler)
{
auto interrupt_number = handler.interrupt_number();
VERIFY(interrupt_number > 0); // Interrupt number 0 is reserved to mean no-interrupt
m_registers->interrupt_enable_bitmap[interrupt_context][interrupt_number >> 5] &= ~(1u << (interrupt_number & 0x1F));
}
void PLIC::eoi(GenericInterruptHandler const& handler)
{
m_registers->contexts[interrupt_context].claim_complete = handler.interrupt_number();
}
u8 PLIC::pending_interrupt() const
{
return m_registers->contexts[interrupt_context].claim_complete;
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2024, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Arch/riscv64/IRQController.h>
#include <Kernel/Memory/TypedMapping.h>
namespace Kernel {
class PLIC final : public IRQController {
public:
PLIC(PhysicalAddress, size_t size, u32 interrupt_count);
virtual void enable(GenericInterruptHandler const&) override;
virtual void disable(GenericInterruptHandler const&) override;
virtual void eoi(GenericInterruptHandler const&) override;
virtual u8 pending_interrupt() const override;
virtual StringView model() const override { return "PLIC"sv; }
private:
enum {
M_MODE_CONTEXT = 0,
S_MODE_CONTEXT,
CONTEXTS_PER_HART,
};
static constexpr size_t interrupt_context = (0 * CONTEXTS_PER_HART) + S_MODE_CONTEXT; // We assume we only have hart 0, change this once we support SMP
struct RegisterMap {
u32 interrupt_priority[1024];
u32 interrupt_pending_bitmap[32];
u32 reserved1[992];
u32 interrupt_enable_bitmap[15872][32];
u32 reserved2[14336];
struct {
u32 priority_threshold;
u32 claim_complete;
u32 reserved3[1022];
} contexts[15872];
};
static_assert(AssertSize<RegisterMap, 0x4000000>());
void initialize();
Memory::TypedMapping<RegisterMap volatile> m_registers;
u32 m_interrupt_count { 0 };
};
}

View file

@ -539,6 +539,7 @@ elseif("${SERENITY_ARCH}" STREQUAL "riscv64")
Arch/riscv64/Delay.cpp
Arch/riscv64/InterruptManagement.cpp
Arch/riscv64/Interrupts.cpp
Arch/riscv64/Interrupts/PLIC.cpp
Arch/riscv64/MMU.cpp
Arch/riscv64/PageDirectory.cpp
Arch/riscv64/Panic.cpp