Files
yggdrasil/kernel/driver/bsp/riscv/src/plic.rs
T

335 lines
9.7 KiB
Rust

use alloc::{sync::Arc, vec::Vec};
use device_api::{
device::{Device, DeviceInitContext},
interrupt::{
ExternalInterruptController, FixedInterruptTable, FullIrq, InterruptHandler,
InterruptTable, Irq, IrqOptions, IrqVector,
},
};
use device_tree::{
driver::{
device_tree_driver, lookup_phandle, DeviceTreeInterruptController, Node, ProbeContext,
},
DeviceTreePropertyRead, TProp,
};
use kernel_arch_riscv64::boot_hart_id;
use libk::{arch::Cpu, device::register_external_interrupt_controller};
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
use tock_registers::{
interfaces::{Readable, Writeable},
register_structs,
registers::{ReadOnly, ReadWrite},
};
use yggdrasil_abi::{error::Error, primitive_enum};
const MAX_IRQS: usize = 1024;
const ENABLE_BASE: usize = 0x2000;
const ENABLE_STRIDE: usize = 0x80;
const CONTROL_BASE: usize = 0x200000;
const CONTROL_STRIDE: usize = 0x1000;
primitive_enum! {
enum ContextMode: u32 {
SoftU = 0,
SoftS = 1,
SoftH = 2,
SoftM = 3,
TimerU = 4,
TimerS = 5,
TimerH = 6,
TimerM = 7,
ExternalU = 8,
ExternalS = 9,
ExternalH = 10,
ExternalM = 11,
}
}
register_structs! {
#[allow(non_snake_case)]
CommonRegs {
(0x0000 => _0),
(0x0004 => PRIORITY: [ReadWrite<u32>; 1023]),
(0x1000 => PENDING: [ReadOnly<u32>; 32]),
(0x1080 => @END),
}
}
register_structs! {
#[allow(non_snake_case)]
ContextEnableRegs {
(0x0000 => ENABLE: [ReadWrite<u32>; 32]),
(0x0080 => @END),
}
}
register_structs! {
#[allow(non_snake_case)]
ContextControlRegs {
(0x0000 => THRESHOLD: ReadWrite<u32>),
(0x0004 => CLAIM: ReadWrite<u32>),
(0x0008 => @END),
}
}
struct Context {
enable: IrqSafeRwLock<DeviceMemoryIo<'static, ContextEnableRegs>>,
control: IrqSafeRwLock<DeviceMemoryIo<'static, ContextControlRegs>>,
// TODO scale the table depending on effective MAX_IRQS value
table: IrqSafeRwLock<FixedInterruptTable<128>>,
}
struct Inner {
#[allow(unused)]
common: IrqSafeRwLock<DeviceMemoryIo<'static, CommonRegs>>,
}
struct HartContext {
hart: u32,
index: usize,
context: OneTimeInit<Context>,
}
/// RISC-V Platform-Level Interrupt Controller (PLIC) device
pub struct Plic {
base: PhysicalAddress,
max_irqs: usize,
// hart id -> context map
context_map: Vec<HartContext>,
inner: OneTimeInit<Inner>,
}
impl Plic {
fn hart_context(&self, hart: u32) -> Option<&HartContext> {
self.context_map.iter().find(|c| c.hart == hart)
}
fn validate_irq(&self, irq: Irq) -> Result<u32, Error> {
let Irq::External(irq) = irq else {
log::error!("plic: irq {irq:?} is not an external interrupt");
return Err(Error::InvalidArgument);
};
if irq == 0 {
log::error!("plic: irq cannot be zero");
return Err(Error::InvalidArgument);
}
if irq as usize >= self.max_irqs {
log::error!("plic: irq ({}) >= max_irqs ({})", irq, self.max_irqs);
return Err(Error::InvalidArgument);
}
Ok(irq)
}
}
impl ContextEnableRegs {
fn enable_irq(&self, irq: u32) {
let reg = &self.ENABLE[irq as usize / 32];
reg.set(reg.get() | (1 << (irq % 32)));
}
}
impl ExternalInterruptController for Plic {
fn enable_irq(&self, irq: Irq) -> Result<(), Error> {
// TODO balance IRQs between harts?
let irq = self.validate_irq(irq)?;
let bsp_hart_id = boot_hart_id() as u32;
let context = self
.hart_context(bsp_hart_id)
.ok_or(Error::InvalidArgument)
.inspect_err(|_| log::error!("plic: no context for hart {bsp_hart_id}"))?
.context
.get();
let enable = context.enable.write();
enable.enable_irq(irq);
Ok(())
}
fn register_irq(
&self,
irq: Irq,
_options: IrqOptions,
handler: Arc<dyn InterruptHandler>,
) -> Result<(), Error> {
let bsp_hart_id = boot_hart_id() as u32;
let irq = self.validate_irq(irq)?;
let context = self
.hart_context(bsp_hart_id)
.ok_or(Error::InvalidArgument)
.inspect_err(|_| log::error!("plic: no context for hart {bsp_hart_id}"))?
.context
.get();
let mut table = context.table.write();
log::info!(
"Bind irq #{irq} -> hart {bsp_hart_id}, {:?}",
handler.display_name()
);
table.insert(irq as usize, handler)?;
Ok(())
}
fn handle_pending_irqs(&self) {
let hart_id = Cpu::local().id();
let Some(context) = self.hart_context(hart_id) else {
log::warn!("plic: irq on hart without a context: {hart_id}");
return;
};
let context = context.context.get();
let control = context.control.write();
let table = context.table.read();
loop {
let irq = control.CLAIM.get();
if irq == 0 {
break;
}
let vector = IrqVector::Irq(Irq::External(irq));
if let Some(handler) = table.handler(irq as usize) {
handler.clone().handle_irq(vector);
} else {
log::warn!("plic: no handler for IRQ #{irq}");
}
// Done servicing
control.CLAIM.set(irq);
}
}
}
impl Device for Plic {
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
log::info!("Initialize RISC-V PLIC");
let common = DeviceMemoryIo::<CommonRegs>::map(self.base, Default::default())?;
for i in 0..self.max_irqs - 1 {
common.PRIORITY[i].set(3);
}
for context in self.context_map.iter() {
let enable_offset = ENABLE_BASE + context.index * ENABLE_STRIDE;
let control_offset = CONTROL_BASE + context.index * CONTROL_STRIDE;
log::info!(
"* HART {}: context {}, enable={:#x}, control={:#x}",
context.hart,
context.index,
enable_offset,
control_offset
);
let enable = DeviceMemoryIo::<ContextEnableRegs>::map(
self.base.add(enable_offset),
Default::default(),
)?;
let control = DeviceMemoryIo::<ContextControlRegs>::map(
self.base.add(control_offset),
Default::default(),
)?;
for i in 0..self.max_irqs.div_ceil(32) {
enable.ENABLE[i].set(0);
}
control.THRESHOLD.set(0);
context.context.init(Context {
enable: IrqSafeRwLock::new(enable),
control: IrqSafeRwLock::new(control),
table: IrqSafeRwLock::new(FixedInterruptTable::new()),
});
}
self.inner.init(Inner {
common: IrqSafeRwLock::new(common),
});
register_external_interrupt_controller(self);
Ok(())
}
fn display_name(&self) -> &str {
"RISC-V PLIC"
}
}
impl DeviceTreeInterruptController for Plic {
fn map_interrupt(&self, property: &TProp, offset: usize) -> Option<FullIrq> {
let num = property.read_cell(offset, 1)?;
Some(FullIrq {
irq: Irq::External(num as _),
options: IrqOptions::default(),
})
}
fn as_interrupt_controller(self: Arc<Self>) -> Arc<dyn ExternalInterruptController> {
self
}
}
fn map_context_to_hart(target: u32) -> Option<u32> {
let hart_intc = lookup_phandle(target, false)?;
let parent = hart_intc.parent()?;
let reg = parent.prop_usize("reg")?;
Some(reg as u32)
}
device_tree_driver! {
compatible: ["starfive,jh7110-plic", "sifive,plic-1.0.0", "riscv,plic0"],
driver: {
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
let base = node.map_base(context, 0)?;
let ndev = node.prop_usize("riscv,ndev")?;
let iext = node.property("interrupts-extended")?;
let max_irqs = MAX_IRQS.min(ndev);
// Parse the context -> hart mapping, only select S-mode external interrupts
let mut context_map = Vec::new();
let mut mapped_harts = 0u64;
for (context, (target, mode)) in iext.iter_cells((1, 1)).enumerate() {
let (Ok(mode), Some(hart_id)) = (
ContextMode::try_from(mode as u32),
map_context_to_hart(target as u32)
) else {
continue;
};
if mode != ContextMode::ExternalS {
continue;
}
// Don't map the same hart to two contexts
if mapped_harts & (1 << hart_id) == 0 {
mapped_harts |= 1 << hart_id;
context_map.push(HartContext {
hart: hart_id,
index: context,
context: OneTimeInit::new()
});
}
}
if context_map.is_empty() {
log::warn!("{:?}: could not map any contexts to harts", node.name());
return None;
}
let intc = Arc::new(Plic {
base,
max_irqs,
context_map,
inner: OneTimeInit::new(),
});
node.make_interrupt_controller(intc.clone());
Some(intc)
}
}
}