rv64: riscv plic driver, time accouting

This commit is contained in:
Mark Poliakov 2025-01-20 13:22:24 +02:00
parent 8ba37c9762
commit 86509e39c1
11 changed files with 477 additions and 42 deletions

View File

@ -73,7 +73,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
let mut stack = StackBuilder::new(stack_base, USER_TASK_PAGES * 0x1000);
log::info!(
log::debug!(
"Set up user task: pc={:#x}, sp={:#x}, tp={:#x}",
context.entry,
context.stack_pointer,
@ -144,8 +144,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
}
fn align_stack_for_entry(sp: usize) -> usize {
let _ = sp;
todo!()
sp
}
unsafe fn enter(&self) -> ! {

View File

@ -139,6 +139,11 @@ impl Node {
device
}
/// Returns the parent node of this node
pub fn parent(&self) -> Option<Arc<Node>> {
self.parent.as_ref().and_then(Weak::upgrade)
}
/// When called from an interrupt-controller driver, informs the node of its capability as
/// an interrupt controller, allowing any other nodes which refer to it to map their
/// interrupts.

View File

@ -30,7 +30,7 @@ impl InterruptHandler for ArmTimer {
CNTP_TVAL_EL0.set(TICK_INTERVAL);
if Cpu::local().id() == 0 {
if Cpu::local().is_bootstrap() {
let last = LAST_TICKS.swap(count, Ordering::Relaxed);
let freq = CNTFRQ_EL0.get();
if let Some(delta) = count.checked_sub(last) {

View File

@ -1,21 +1,16 @@
use core::arch::global_asm;
use abi::{arch::SavedFrame, primitive_enum, process::Signal, SyscallFunction};
use kernel_arch::{
task::{Scheduler, TaskFrame},
Architecture,
};
use libk::{arch::Cpu, task::thread::Thread};
use kernel_arch::{task::TaskFrame, Architecture};
use libk::{device::external_interrupt_controller, task::thread::Thread};
use tock_registers::interfaces::ReadWriteable;
use kernel_arch_riscv64::{
intrinsics,
registers::{SSTATUS, STVEC},
sbi, ArchitectureImpl,
};
use kernel_arch_riscv64::{registers::STVEC, ArchitectureImpl};
use crate::syscall;
use super::timer;
primitive_enum! {
pub enum Cause: u64 {
MisalignedInstruction = 0,
@ -92,9 +87,6 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
true
}
Some(Cause::EcallUmode) => {
// TODO more granular control over how U-mode pages are accessed from S-mode
SSTATUS.modify(SSTATUS::SUM::SET);
let func = frame.an[0];
if func == usize::from(SyscallFunction::ExitSignal) {
todo!()
@ -162,17 +154,24 @@ unsafe fn smode_exception_handler(frame: &mut TrapFrame) {
}
unsafe extern "C" fn smode_interrupt_handler(frame: *mut TrapFrame) {
let frame = &*frame;
let frame = &mut *frame;
let smode = frame.sstatus & (1 << 8) != 0;
match frame.scause & !(1 << 63) {
// S-mode timer interrupt
5 => {
sbi::sbi_set_timer(intrinsics::rdtime() + 100_000);
// TODO runtime tick, time accounting
Cpu::local().scheduler().yield_cpu();
5 => timer::handle_interrupt(),
// S-mode external interrupt
9 => {
if let Ok(intc) = external_interrupt_controller() {
intc.handle_pending_irqs();
}
}
n => todo!("Unhandled interrupt #{n}"),
}
if !smode && let Some(thread) = Thread::get_current() {
thread.handle_pending_signals(frame);
}
}
unsafe extern "C" fn smode_general_trap_handler(frame: *mut TrapFrame) {

View File

@ -1,5 +1,5 @@
#![allow(missing_docs)]
use core::sync::atomic::{self, AtomicUsize, Ordering};
use core::sync::atomic::{self, AtomicU32, AtomicUsize, Ordering};
use abi::error::Error;
use alloc::sync::Arc;
@ -10,10 +10,9 @@ use device_api::{
use device_tree::{driver::unflatten_device_tree, DeviceTree, DeviceTreeNodeExt};
use kernel_arch::Architecture;
use kernel_arch_riscv64::{
intrinsics,
mem::{self, KERNEL_VIRT_OFFSET},
registers::SIE,
sbi, ArchitectureImpl, PerCpuData,
registers::{SIE, SSTATUS},
ArchitectureImpl, PerCpuData,
};
use libk::{arch::Cpu, config};
use libk_mm::{
@ -37,6 +36,9 @@ use super::Platform;
pub mod boot;
pub mod debug;
pub mod exception;
pub mod timer;
pub static BOOT_HART_ID: AtomicU32 = AtomicU32::new(u32::MAX);
pub struct Riscv64 {
dt: OneTimeInit<DeviceTree<'static>>,
@ -174,6 +176,8 @@ impl Riscv64 {
exception::init_smode_exceptions();
if is_bsp {
BOOT_HART_ID.store(hart_id, Ordering::Release);
call_init_array();
atomic::compiler_fence(Ordering::SeqCst);
@ -191,6 +195,9 @@ impl Riscv64 {
unflatten_device_tree(dt);
if let Err(error) = Self::setup_clock_timebase(dt) {
log::error!("Could not setup clock timebase from device tree: {error:?}");
}
Self::setup_chosen_stdout(dt).ok();
libk::debug::disable_early_sinks();
@ -221,12 +228,12 @@ impl Riscv64 {
PciBusManager::setup_bus_devices()?;
}
// Setup the timer
SIE.modify(SIE::STIE::SET);
sbi::sbi_set_timer(intrinsics::rdtime() + 100_000);
// TODO more granular control over how U-mode pages are accessed from S-mode
SSTATUS.modify(SSTATUS::SUM::SET);
// Test call into M-mode
// core::arch::asm!("ecall", in("a0") MModeFunction::WriteTimerComparator as u64, in("a1") 0x4321);
// Setup the timer
SIE.modify(SIE::SSIE::SET + SIE::SEIE::SET);
timer::init_hart(is_bsp);
Ok(())
}
@ -238,6 +245,15 @@ impl Riscv64 {
)
}
fn setup_clock_timebase(dt: &'static DeviceTree) -> Result<(), Error> {
let cpus = dt.root().child("cpus").ok_or(Error::DoesNotExist)?;
let timebase_frequency = cpus
.prop_cell("timebase-frequency", 1)
.ok_or(Error::DoesNotExist)?;
timer::FREQUENCY.store(timebase_frequency, Ordering::Release);
Ok(())
}
#[inline(never)]
unsafe fn setup_chosen_stdout(dt: &'static DeviceTree) -> Result<(), Error> {
// Get /chosen.stdout-path to get early debug printing

View File

@ -0,0 +1,55 @@
use core::sync::atomic::{AtomicU64, Ordering};
use abi::time::NANOSECONDS_IN_SECOND;
use kernel_arch::task::Scheduler;
use kernel_arch_riscv64::{intrinsics, registers::SIE, sbi};
use libk::{arch::Cpu, task::runtime, time};
use tock_registers::interfaces::ReadWriteable;
pub static LAST_TICK: AtomicU64 = AtomicU64::new(0);
pub static FREQUENCY: AtomicU64 = AtomicU64::new(0);
// 1kHz
const TICK_RATE: u64 = 1000;
// TODO use stimecmp instead of sbi calls if sstc extension is available
pub fn handle_interrupt() {
let frequency = FREQUENCY.load(Ordering::Acquire);
// TODO merge this code with other system timer implementations
let now = intrinsics::rdtime();
sbi::sbi_set_timer(now.wrapping_add(frequency / TICK_RATE));
if Cpu::local().is_bootstrap() {
let frequency = frequency * 1000;
let last = LAST_TICK.swap(now, Ordering::Release);
if frequency != 0 {
if let Some(delta) = now.checked_sub(last) {
// Only update time from local CPU
let dt = delta * NANOSECONDS_IN_SECOND / frequency;
time::add_nanoseconds(dt);
}
}
LAST_TICK.store(now, Ordering::Release);
runtime::tick();
}
unsafe { Cpu::local().scheduler().yield_cpu() };
}
pub fn init_hart(is_bsp: bool) {
let frequency = FREQUENCY.load(Ordering::Acquire);
if frequency != 0 {
SIE.modify(SIE::STIE::SET);
let now = intrinsics::rdtime();
if is_bsp {
LAST_TICK.store(now, Ordering::Release);
}
sbi::sbi_set_timer(now + frequency / TICK_RATE);
} else {
SIE.modify(SIE::STIE::CLEAR);
sbi::sbi_set_timer(u64::MAX);
}
}

View File

@ -0,0 +1,4 @@
//! Interrupt controller drivers
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
pub mod riscv_plic;

View File

@ -0,0 +1,326 @@
//! RISC-V PLIC driver
use core::sync::atomic::Ordering;
use abi::{error::Error, primitive_enum};
use alloc::{sync::Arc, vec::Vec};
use device_api::{
device::Device,
interrupt::{
ExternalInterruptController, FixedInterruptTable, FullIrq, InterruptHandler,
InterruptTable, Irq, IrqOptions,
},
};
use device_tree::{
driver::{
device_tree_driver, lookup_phandle, DeviceTreeInterruptController, Node, ProbeContext,
},
DeviceTreePropertyRead, TProp,
};
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 crate::arch::riscv64::BOOT_HART_ID;
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<32>>,
}
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 {
return Err(Error::InvalidArgument);
};
if irq == 0 {
return Err(Error::InvalidArgument);
}
if irq as usize >= 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.load(Ordering::Acquire);
let context = self
.hart_context(bsp_hart_id)
.ok_or(Error::InvalidArgument)?
.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.load(Ordering::Acquire);
let irq = self.validate_irq(irq)?;
let context = self
.hart_context(bsp_hart_id)
.ok_or(Error::InvalidArgument)?
.context
.get();
let mut table = context.table.write();
log::info!("Bind irq #{irq} -> {:?}", handler.display_name());
table.insert(irq as usize, handler)?;
// TODO
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;
}
if let Some(handler) = table.handler(irq as usize) {
handler.clone().handle_irq(None);
} 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>) -> 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 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: ["sifive,plic-1.0.0", "riscv,plic0"],
driver: {
fn probe(&self, node: &Arc<Node>, context: &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)
}
}
}

View File

@ -6,6 +6,7 @@ use libk_util::OneTimeInit;
pub mod bus;
pub mod clock;
pub mod display;
pub mod interrupt;
pub mod power;
pub mod serial;
// pub mod timer;

View File

@ -1,17 +1,20 @@
//! 16550-style UART device driver
use abi::{error::Error, io::TerminalOptions};
use alloc::sync::Arc;
use device_api::device::Device;
use device_api::{
device::Device,
interrupt::{FullIrq, InterruptHandler},
};
use device_tree::driver::{device_tree_driver, Node, ProbeContext};
use libk::{
debug::DebugSink,
device::manager::DEVICE_REGISTRY,
device::{external_interrupt_controller, manager::DEVICE_REGISTRY},
vfs::{Terminal, TerminalInput, TerminalOutput},
};
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
use tock_registers::{
interfaces::{Readable, Writeable},
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
registers::{ReadOnly, ReadWrite, WriteOnly},
};
@ -52,7 +55,7 @@ register_structs! {
Regs {
// Read: receive buffer, write: transmit buffer
(0x00 => DR: ReadWrite<u8>),
(0x01 => IER: ReadWrite<u8>),
(0x01 => IER: ReadWrite<u8, IER::Register>),
// Read: interrupt idenditication, write: FIFO control
(0x02 => FCR: ReadWrite<u8>),
(0x03 => LCR: ReadWrite<u8, LCR::Register>),
@ -76,7 +79,7 @@ struct Inner {
pub struct Ns16550a {
inner: OneTimeInit<Arc<Terminal<Inner>>>,
base: PhysicalAddress,
// irq: FullIrq,
irq: FullIrq,
}
impl Io {
@ -93,6 +96,15 @@ impl Io {
}
self.regs.DR.set(byte);
}
fn handle_irq(&self) -> Option<u8> {
let status = self.regs.FCR.get() & 0xF;
if status == 0b1100 || status == 0b0100 {
Some(self.regs.DR.get())
} else {
None
}
}
}
impl Device for Ns16550a {
@ -122,7 +134,11 @@ impl Device for Ns16550a {
}
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
log::warn!("TODO: init ns16550a irq");
let intc = external_interrupt_controller()?;
intc.register_irq(self.irq.irq, self.irq.options, self.clone())?;
intc.enable_irq(self.irq.irq)?;
let io = self.inner.get().output().io.lock();
io.regs.IER.modify(IER::RDR::SET);
Ok(())
}
@ -131,6 +147,20 @@ impl Device for Ns16550a {
}
}
impl InterruptHandler for Ns16550a {
fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
let inner = self.inner.get();
let output = inner.output();
let byte = output.io.lock().handle_irq();
if let Some(byte) = byte {
inner.write_to_input(byte);
true
} else {
false
}
}
}
impl TerminalOutput for Inner {
fn write(&self, byte: u8) -> Result<(), Error> {
self.io.lock().send(byte);
@ -161,11 +191,11 @@ device_tree_driver!(
driver: {
fn probe(&self, node: &Arc<Node>, context: &ProbeContext) -> Option<Arc<dyn Device>> {
let base = node.map_base(context, 0)?;
// let irq = node.interrupt(0)?;
let irq = node.interrupt(0)?;
Some(Arc::new(Ns16550a {
base,
// irq,
irq,
inner: OneTimeInit::new(),
}))
}

View File

@ -178,7 +178,7 @@ impl Dtv {
/// Will panic if key is longer than the DTV itself.
pub fn set_specific(&mut self, key: usize, value: *mut c_void, grow: bool) {
if self.try_set_specific(key, value, grow).is_err() {
crate::debug_trace!("Dtv::set_specific(): invalid key {key}");
crate::debug_trace!("Dtv::set_specific(): invalid key {key}, grow={grow}");
panic!("Dtv::set_specific(): invalid key {key})")
}
}
@ -190,7 +190,7 @@ impl Dtv {
value: *mut c_void,
grow: bool,
) -> Result<(), Error> {
if key > self.entries.len() && grow {
if key > self.specific.len() && grow {
self.specific.resize(key, null_mut());
}
if !Self::set_key(&mut self.specific, key, value) {