From 09cc216a914fe37e7a0dfb99fea78f2bd1fbd3ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Mon, 19 Oct 2026 09:24:00 +0900 Subject: [PATCH] feat(riscv): add plic and interrupt controller --- awkernel_lib/src/arch/rv_common.rs | 1 + awkernel_lib/src/arch/rv_common/clint.rs | 53 +++- .../arch/rv_common/interrupt_controller.rs | 91 ++++++- awkernel_lib/src/arch/rv_common/plic.rs | 257 ++++++++++++++++++ 4 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 awkernel_lib/src/arch/rv_common/plic.rs diff --git a/awkernel_lib/src/arch/rv_common.rs b/awkernel_lib/src/arch/rv_common.rs index bb904e550..dbb6da1d0 100644 --- a/awkernel_lib/src/arch/rv_common.rs +++ b/awkernel_lib/src/arch/rv_common.rs @@ -15,6 +15,7 @@ pub(super) mod interrupt; pub mod interrupt_controller; pub(super) mod page_table; pub(super) mod paging; +pub mod plic; pub(super) mod power; pub(super) mod vm; diff --git a/awkernel_lib/src/arch/rv_common/clint.rs b/awkernel_lib/src/arch/rv_common/clint.rs index 923a598b0..5d0e304a1 100644 --- a/awkernel_lib/src/arch/rv_common/clint.rs +++ b/awkernel_lib/src/arch/rv_common/clint.rs @@ -1,5 +1,6 @@ use crate::cpu::{cpu_id, num_cpu}; use core::ptr::{read_volatile, write_volatile}; +use riscv::register::mie; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] @@ -33,6 +34,36 @@ impl Clint { (self.base_addr + Self::MTIME_OFFSET) as *const u64 } + #[inline] + pub fn enable_soft_interrupt(&self) { + unsafe { mie::set_msoft() }; + } + + #[inline] + pub fn disable_soft_interrupt(&self) { + unsafe { mie::clear_msoft() }; + } + + #[inline] + pub fn enable_timer_interrupt(&self) { + unsafe { mie::set_mtimer() }; + } + + #[inline] + pub fn disable_timer_interrupt(&self) { + unsafe { mie::clear_mtimer() }; + } + + #[inline] + pub fn is_soft_interrupt_pending(&self) -> bool { + mie::read().msoft() + } + + #[inline] + pub fn is_timer_interrupt_pending(&self) -> bool { + mie::read().mtimer() + } + /// Busy wait for a given number of microseconds. #[inline] pub fn wait_usec(&self, usec: u64) { @@ -65,7 +96,7 @@ impl Clint { /// /// The caller must ensure that the hart_id is valid. #[inline] - unsafe fn soft_interrupt_unchecked(&self, hart_id: usize) { + unsafe fn set_soft_interrupt_unchecked(&self, hart_id: usize) { // Safety: The caller must ensure that the hart_id is valid. unsafe { write_volatile(self.msip(hart_id), 1) }; } @@ -74,33 +105,41 @@ impl Clint { /// /// If the hart_id is invalid, return an error. #[inline] - pub fn soft_interrupt(&self, hart_id: usize) -> Result<(), Error> { + pub fn set_soft_interrupt(&self, hart_id: usize) -> Result<(), Error> { if hart_id >= num_cpu() { return Err(Error::InvalidHartId); } // Safety: hart_id is within the range of available harts - unsafe { self.soft_interrupt_unchecked(hart_id) }; + unsafe { self.set_soft_interrupt_unchecked(hart_id) }; Ok(()) } /// Trigger a software interrupt to all harts, including the current hart. #[inline] - pub fn soft_interrupt_broadcast(&self) { + pub fn set_soft_interrupt_broadcast(&self) { for hart_id in 0..num_cpu() { // Safety: hart_id is within the range of available harts - unsafe { self.soft_interrupt_unchecked(hart_id) }; + unsafe { self.set_soft_interrupt_unchecked(hart_id) }; } } /// Trigger a software interrupt to all harts, excluding the current hart. #[inline] - pub fn soft_interrupt_broadcast_without_self(&self) { + pub fn set_soft_interrupt_broadcast_without_self(&self) { let self_hart_id = cpu_id(); for hart_id in 0..num_cpu() { if hart_id != self_hart_id { // Safety: hart_id is within the range of available harts - unsafe { self.soft_interrupt_unchecked(hart_id) }; + unsafe { self.set_soft_interrupt_unchecked(hart_id) }; } } } + + /// Clear the software interrupt for the current hart. + #[inline] + pub fn clear_soft_interrupt(&self) { + let hart_id = cpu_id(); + // Safety: hart_id is within the range of available harts + unsafe { write_volatile(self.msip(hart_id), 0) }; + } } diff --git a/awkernel_lib/src/arch/rv_common/interrupt_controller.rs b/awkernel_lib/src/arch/rv_common/interrupt_controller.rs index dfd378e6d..a369587f8 100644 --- a/awkernel_lib/src/arch/rv_common/interrupt_controller.rs +++ b/awkernel_lib/src/arch/rv_common/interrupt_controller.rs @@ -1,4 +1,6 @@ -use super::clint::Clint; +use super::{clint::Clint, plic::Plic}; +use crate::{cpu::cpu_id, interrupt}; +use alloc::boxed::Box; // TODO: get info from device tree static INTERRUPT_CONTROLLER: InterruptController = InterruptController { @@ -6,13 +8,100 @@ static INTERRUPT_CONTROLLER: InterruptController = InterruptController { base_addr: 0x0200_0000, mtime_freq: 10_000_000, }, + plic: Plic { + base_addr: 0x0c00_0000, + max_priority: 7, + num_sources: 53, + num_contexts: 16, + }, }; pub struct InterruptController { clint: Clint, + plic: Plic, } #[inline] pub(super) const fn clint() -> &'static Clint { &INTERRUPT_CONTROLLER.clint } + +impl interrupt::InterruptController for InterruptController { + #[inline] + fn enable_irq(&mut self, irq: u16) { + match irq { + 0 => self.clint.enable_soft_interrupt(), + _ => { + let context = cpu_id() as u16; // TODO: support S-mode contexts + let _ = self.plic.enable_interrupt(context, irq); + } + } + } + + #[inline] + fn disable_irq(&mut self, irq: u16) { + match irq { + 0 => self.clint.disable_soft_interrupt(), + _ => { + let context = cpu_id() as u16; // TODO: support S-mode contexts + let _ = self.plic.disable_interrupt(context, irq); + } + } + } + + #[inline] + fn pending_irqs(&self) -> alloc::boxed::Box> { + let context = cpu_id() as u16; // TODO: support S-mode contexts + let mut pending_irqs = alloc::vec::Vec::new(); + if self.clint.is_soft_interrupt_pending() { + self.clint.clear_soft_interrupt(); + pending_irqs.push(0); + } + // Safety: context is in range + while let Some(irq) = unsafe { self.plic.claim_interrupt_unchecked(context) } { + pending_irqs.push(irq); + } + Box::new(pending_irqs.into_iter()) + } + + #[inline] + fn send_ipi(&mut self, irq: u16, cpu_id: u32) { + if irq == 0 { + if let Err(e) = self.clint.set_soft_interrupt(cpu_id as _) { + log::warn!("Failed to send IPI to CPU {}: {:?}", cpu_id, e); + } + } else { + log::warn!("PLIC does not support sending IPIs, ignoring IRQ {}", irq); + } + } + + #[inline] + fn send_ipi_broadcast(&mut self, irq: u16) { + if irq == 0 { + self.clint.set_soft_interrupt_broadcast(); + } else { + log::warn!("PLIC does not support sending IPIs, ignoring IRQ {}", irq); + } + } + + #[inline] + fn send_ipi_broadcast_without_self(&mut self, irq: u16) { + if irq == 0 { + self.clint.set_soft_interrupt_broadcast_without_self(); + } else { + log::warn!("PLIC does not support sending IPIs, ignoring IRQ {}", irq); + } + } + + #[inline] + fn irq_range(&self) -> (u16, u16) { + // 0 is CLINT, 1..=num_sources are PLIC + (0, self.plic.num_sources + 1) + } + + #[inline] + fn irq_range_for_pnp(&self) -> (u16, u16) { + // PLIC cannot be used for IPIs, so the range is 1..=num_sources + (1, self.plic.num_sources + 1) + } +} diff --git a/awkernel_lib/src/arch/rv_common/plic.rs b/awkernel_lib/src/arch/rv_common/plic.rs new file mode 100644 index 000000000..09dc626ba --- /dev/null +++ b/awkernel_lib/src/arch/rv_common/plic.rs @@ -0,0 +1,257 @@ +use core::ptr::{read_volatile, write_volatile}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum Error { + InvalidPriority, + InvalidSource, + InvalidContext, + InternalError, + NotImplemented, + Other, +} + +impl Error { + /// Returns a string slice describing the error. + pub const fn description(&self) -> &'static str { + match self { + Error::InvalidPriority => "Invalid priority", + Error::InvalidSource => "Invalid source", + Error::InvalidContext => "Invalid context", + Error::InternalError => "Internal error", + Error::NotImplemented => "Not implemented", + Error::Other => "Other error", + } + } +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl core::error::Error for Error {} + +pub type Result = core::result::Result; + +pub struct Plic { + pub(crate) base_addr: usize, + pub(crate) max_priority: u32, + pub(crate) num_sources: u16, + pub(crate) num_contexts: u16, +} + +impl Plic { + const PRIORITIES_BASE: usize = 0x0000; + + const PENDING_BASE: usize = 0x1000; + + const ENABLES_OFFSET: usize = 0x2000; + const ENABLES_SEPARATION: usize = 0x80; + + const THRESHOLDS_OFFSET: usize = 0x20_0000; + const THRESHOLDS_SEPARATION: usize = 0x1000; + + const CLAIMS_OFFSET: usize = 0x20_0004; + const CLAIMS_SEPARATION: usize = 0x1000; + + #[inline] + const fn check_priority(&self, priority: u32) -> Result<()> { + match priority <= self.max_priority { + true => Ok(()), + false => Err(Error::InvalidPriority), + } + } + + #[inline] + const fn check_source(&self, source: u16) -> Result<()> { + match source > 0 && source <= self.num_sources { + true => Ok(()), + false => Err(Error::InvalidSource), + } + } + + #[inline] + const fn check_context(&self, context_id: u16) -> Result<()> { + match context_id < self.num_contexts { + true => Ok(()), + false => Err(Error::InvalidContext), + } + } + + #[inline] + const fn priority(&self, source: u16) -> *mut u32 { + (self.base_addr + Self::PRIORITIES_BASE + source as usize * 4) as _ + } + + #[inline] + const fn pending(&self, source: u16) -> *const u32 { + (self.base_addr + Self::PENDING_BASE + (source as usize / 32) * 4) as _ + } + + #[inline] + const fn enable(&self, context_id: u16, source: u16) -> *mut u32 { + let context_id = context_id as usize; + let reg_index = source as usize / 32; + let base = self.base_addr + Self::ENABLES_OFFSET + context_id * Self::ENABLES_SEPARATION; + (base + reg_index * 4) as _ + } + + #[inline] + const fn threshold(&self, context_id: u16) -> *mut u32 { + let context_id = context_id as usize; + (self.base_addr + Self::THRESHOLDS_OFFSET + context_id * Self::THRESHOLDS_SEPARATION) as _ + } + + #[inline] + const fn claim_complete(&self, context_id: u16) -> *mut u32 { + let context_id = context_id as usize; + (self.base_addr + Self::CLAIMS_OFFSET + context_id * Self::CLAIMS_SEPARATION) as _ + } + + /// Get the priority of a given interrupt source. + /// + /// If the source is out of range, this function will return an error. + pub fn get_priority(&self, source: u16) -> Result { + self.check_source(source)?; + let reg = self.priority(source); + // Safety: source is in range + let priority = unsafe { read_volatile(reg) }; + Ok(priority) + } + + /// Set the priority of a given interrupt source. + /// + /// If the source or priority is out of range, this function will return an error. + pub fn set_priority(&self, source: u16, priority: u32) -> Result<()> { + self.check_source(source)?; + self.check_priority(priority)?; + let reg = self.priority(source); + // Safety: source and priority are in range + unsafe { write_volatile(reg, priority) }; + Ok(()) + } + + /// Check if a given interrupt source is pending. + /// + /// If the source is out of range, this function will return an error. + pub fn is_pending(&self, source: u16) -> Result { + self.check_source(source)?; + let reg = self.pending(source); + let mask = 1 << (source % 32); + // Safety: source is in range + let pending = unsafe { read_volatile(reg) } & mask != 0; + Ok(pending) + } + + /// Enable an interrupt for a given context. + /// + /// If the context_id or source is out of range, this function will return an error. + pub fn enable_interrupt(&self, context_id: u16, source: u16) -> Result<()> { + self.check_context(context_id)?; + self.check_source(source)?; + let enables_reg = self.enable(context_id, source); + let mask = 1 << (source % 32); + // Safety: context and source are in range + unsafe { + let current = read_volatile(enables_reg); + write_volatile(enables_reg, current | mask); + } + Ok(()) + } + + /// Disable an interrupt for a given context. + /// + /// If context_id or source is out of range, this function will return an error. + pub fn disable_interrupt(&self, context_id: u16, source: u16) -> Result<()> { + self.check_context(context_id)?; + self.check_source(source)?; + let enables_reg = self.enable(context_id, source); + let mask = !(1 << (source % 32)); + // Safety: context and source are in range + unsafe { + let current = read_volatile(enables_reg); + write_volatile(enables_reg, current & mask); + } + Ok(()) + } + + /// Get the threshold for a given context. + /// + /// If the context_id is out of range, this function will return an error. + pub fn get_threshold(&self, context_id: u16) -> Result { + self.check_context(context_id)?; + let reg = self.threshold(context_id); + // Safety: context_id is in range + let threshold = unsafe { read_volatile(reg) }; + Ok(threshold) + } + + /// Set the threshold for a given context. + /// + /// If the context_id or threshold is out of range, this function will return an error + pub fn set_threshold(&self, context_id: u16, threshold: u32) -> Result<()> { + self.check_context(context_id)?; + self.check_priority(threshold)?; + let reg = self.threshold(context_id); + // Safety: context_id is in range + unsafe { write_volatile(reg, threshold) }; + Ok(()) + } + + /// Claim an interrupt for a given context. + /// + /// If context_id is out of range, this function will return an error. + /// If no pending interrupt is available, it will return `Ok(None)`. + /// If a pending interrupt is claimed, it is completed and returned as `Ok(Some(irq))`. + /// + /// # Safety + /// + /// The caller must ensure that the context_id is valid. + pub unsafe fn claim_interrupt_unchecked(&self, context_id: u16) -> Option { + let claim_reg = self.claim_complete(context_id); + // Safety: context is in range + let irq = unsafe { read_volatile(claim_reg) }; + match irq { + 0 => None, + _ => { + // Safety: context is in range and irq is a valid interrupt source + unsafe { + if let Err(e) = clear_ext_interrupt(irq as u16) { + log::warn!("Failed to clear external interrupt {}: {:?}", irq, e); + } + write_volatile(claim_reg, irq); // Complete the interrupt + } + Some(irq as u16) + } + } + } + + /// Claim an interrupt for a given context. + /// + /// If context_id is out of range, this function will return an error. + /// If no pending interrupt is available, it will return `Ok(None)`. + /// If a pending interrupt is claimed, it is completed and returned as `Ok(Some(irq))`. + pub fn claim_interrupt(&self, context_id: u16) -> Result> { + self.check_context(context_id)?; + // Safety: context is in range + let irq = unsafe { self.claim_interrupt_unchecked(context_id) }; + Ok(irq) + } +} + +extern "Rust" { + fn __awkernel_plic_clear_ext_interrupt(irq: u16) -> Result<()>; +} + +#[inline] +fn clear_ext_interrupt(irq: u16) -> Result<()> { + unsafe { __awkernel_plic_clear_ext_interrupt(irq) } +} + +#[linkage = "weak"] +#[export_name = "__awkernel_plic_clear_ext_interrupt"] +extern "Rust" fn default_clear_ext_interrupt() -> Result<()> { + Err(Error::NotImplemented) +}