Add RISC-V timer support

This commit is contained in:
Zejun Zhao 2025-04-28 11:04:29 +08:00 committed by Tate, Hongliang Tian
parent 63daf69e17
commit ffb4097436
5 changed files with 159 additions and 53 deletions

View File

@ -1,28 +1,40 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use ostd::{arch::timer::GOLDFISH_IO_MEM, mm::VmIoOnce}; use ostd::{arch::boot::DEVICE_TREE, io::IoMem, mm::VmIoOnce};
use chrono::{DateTime, Datelike, Timelike}; use chrono::{DateTime, Datelike, Timelike};
use crate::{SystemTime, rtc::Driver}; use crate::{SystemTime, rtc::Driver};
pub struct RtcGoldfish; pub struct RtcGoldfish {
io_mem: IoMem,
}
impl Driver for RtcGoldfish { impl Driver for RtcGoldfish {
fn try_new() -> Option<RtcGoldfish> { fn try_new() -> Option<RtcGoldfish> {
GOLDFISH_IO_MEM.get()?; let chosen = DEVICE_TREE.get().unwrap().find_node("/soc/rtc").unwrap();
Some(RtcGoldfish) if let Some(compatible) = chosen.compatible()
&& compatible.all().any(|c| c == "google,goldfish-rtc")
{
let region = chosen.reg().unwrap().next().unwrap();
let io_mem = IoMem::acquire(
region.starting_address as usize
..region.starting_address as usize + region.size.unwrap(),
)
.unwrap();
Some(RtcGoldfish { io_mem })
} else {
None
}
} }
fn read_rtc(&self) -> SystemTime { fn read_rtc(&self) -> SystemTime {
const TIME_LOW: usize = 0; const TIME_LOW: usize = 0;
const TIME_HIGH: usize = 4; const TIME_HIGH: usize = 4;
let io_mem = GOLDFISH_IO_MEM.get().unwrap(); let mut last_time_high = self.io_mem.read_once(TIME_HIGH).unwrap();
let mut last_time_high = io_mem.read_once(TIME_HIGH).unwrap();
let timestamp = loop { let timestamp = loop {
let time_low: u32 = io_mem.read_once(TIME_LOW).unwrap(); let time_low: u32 = self.io_mem.read_once(TIME_LOW).unwrap();
let time_high: u32 = io_mem.read_once(TIME_HIGH).unwrap(); let time_high: u32 = self.io_mem.read_once(TIME_HIGH).unwrap();
if last_time_high == time_high { if last_time_high == time_high {
break ((time_high as u64) << 32) | time_low as u64; break ((time_high as u64) << 32) | time_low as u64;
} }

View File

@ -4,11 +4,14 @@
use core::fmt::Debug; use core::fmt::Debug;
use riscv::register::scause::{Exception, Trap}; use riscv::register::scause::{Exception, Interrupt, Trap};
pub use crate::arch::trap::GeneralRegs as RawGeneralRegs; pub use crate::arch::trap::GeneralRegs as RawGeneralRegs;
use crate::{ use crate::{
arch::trap::{TrapFrame, UserContext as RawUserContext}, arch::{
timer::handle_timer_interrupt,
trap::{TrapFrame, UserContext as RawUserContext},
},
user::{ReturnReason, UserContextApi, UserContextApiInternal}, user::{ReturnReason, UserContextApi, UserContextApiInternal},
}; };
@ -111,6 +114,9 @@ impl UserContextApiInternal for UserContext {
let ret = loop { let ret = loop {
self.user_context.run(); self.user_context.run();
match riscv::register::scause::read().cause() { match riscv::register::scause::read().cause() {
Trap::Interrupt(Interrupt::SupervisorTimer) => {
handle_timer_interrupt();
}
Trap::Interrupt(_) => todo!(), Trap::Interrupt(_) => todo!(),
Trap::Exception(Exception::UserEnvCall) => { Trap::Exception(Exception::UserEnvCall) => {
self.user_context.sepc += 4; self.user_context.sepc += 4;

View File

@ -15,8 +15,6 @@ pub mod task;
pub mod timer; pub mod timer;
pub mod trap; pub mod trap;
use core::sync::atomic::Ordering;
#[cfg(feature = "cvm_guest")] #[cfg(feature = "cvm_guest")]
pub(crate) fn init_cvm_guest() { pub(crate) fn init_cvm_guest() {
// Unimplemented, no-op // Unimplemented, no-op
@ -29,7 +27,10 @@ pub(crate) unsafe fn late_init_on_bsp() {
// SAFETY: We're on the BSP and we're ready to boot all APs. // SAFETY: We're on the BSP and we're ready to boot all APs.
unsafe { crate::boot::smp::boot_all_aps() }; unsafe { crate::boot::smp::boot_all_aps() };
timer::init(); // SAFETY: This function is called once and at most once at a proper timing
// in the boot context of the BSP, with no timer-related operations having
// been performed.
unsafe { timer::init() };
let _ = pci::init(); let _ = pci::init();
} }
@ -43,7 +44,7 @@ pub(crate) fn interrupts_ack(irq_number: usize) {
/// Return the frequency of TSC. The unit is Hz. /// Return the frequency of TSC. The unit is Hz.
pub fn tsc_freq() -> u64 { pub fn tsc_freq() -> u64 {
timer::TIMEBASE_FREQ.load(Ordering::Relaxed) timer::get_timebase_freq()
} }
/// Reads the current value of the processors time-stamp counter (TSC). /// Reads the current value of the processors time-stamp counter (TSC).

View File

@ -2,48 +2,123 @@
//! The timer support. //! The timer support.
use core::sync::atomic::{AtomicU64, Ordering}; use core::{
arch::asm,
sync::atomic::{AtomicU64, Ordering},
};
use spin::Once; use crate::{
arch::boot::DEVICE_TREE,
cpu::{CpuId, PinCurrentCpu},
timer::INTERRUPT_CALLBACKS,
trap,
};
use crate::{arch::boot::DEVICE_TREE, io::IoMem}; /// The timer frequency (Hz). Here we choose 1000Hz since 1000Hz is easier for
/// unit conversion and convenient for timer. What's more, the frequency cannot
/// The timer frequency (Hz). Here we choose 1000Hz since 1000Hz is easier for unit conversion and /// be set too high or too low, 1000Hz is a modest choice.
/// convenient for timer. What's more, the frequency cannot be set too high or too low, 1000Hz is
/// a modest choice.
/// ///
/// For system performance reasons, this rate cannot be set too high, otherwise most of the time /// For system performance reasons, this rate cannot be set too high, otherwise
/// is spent executing timer code. /// most of the time is spent executing timer code.
pub const TIMER_FREQ: u64 = 1000; pub const TIMER_FREQ: u64 = 1000;
pub(crate) static TIMEBASE_FREQ: AtomicU64 = AtomicU64::new(1); static TIMEBASE_FREQ: AtomicU64 = AtomicU64::new(0);
static TIMER_INTERVAL: AtomicU64 = AtomicU64::new(0);
/// [`IoMem`] of goldfish RTC, which will be used by `aster-time`. /// Initializes the timer module.
pub static GOLDFISH_IO_MEM: Once<IoMem> = Once::new(); ///
/// # Safety
pub(super) fn init() { ///
let timer_freq = DEVICE_TREE /// This function is safe to call on the following conditions:
/// 1. It is called once and at most once at a proper timing in the boot context.
/// 2. It is called before any other public functions of this module is called.
pub(super) unsafe fn init() {
TIMEBASE_FREQ.store(
DEVICE_TREE
.get() .get()
.unwrap() .unwrap()
.cpus() .cpus()
.next() .next()
.unwrap() .unwrap()
.timebase_frequency() as u64; .timebase_frequency() as u64,
TIMEBASE_FREQ.store(timer_freq, Ordering::Relaxed); Ordering::Relaxed,
);
TIMER_INTERVAL.store(
TIMEBASE_FREQ.load(Ordering::Relaxed) / TIMER_FREQ,
Ordering::Relaxed,
);
let chosen = DEVICE_TREE.get().unwrap().find_node("/soc/rtc").unwrap(); if is_sstc_enabled() {
if let Some(compatible) = chosen.compatible() // SAFETY: Mutating the static variable `SET_NEXT_TIMER_FN` is safe here
&& compatible.all().any(|c| c == "google,goldfish-rtc") // because we ensure that it is only modified during the initialization
{ // phase of the timer.
let region = chosen.reg().unwrap().next().unwrap(); unsafe {
let io_mem = unsafe { SET_NEXT_TIMER_FN = set_next_timer_sstc;
IoMem::new( }
(region.starting_address as usize) }
..(region.starting_address as usize) + region.size.unwrap(), set_next_timer();
crate::mm::page_prop::PageFlags::RW, // SAFETY: Accessing the `sie` CSR to enable the timer interrupt is safe
crate::mm::page_prop::CachePolicy::Uncacheable, // here because this function is only called during timer initialization,
) // and we ensure that only the timer interrupt bit is set without affecting
// other interrupt sources.
unsafe {
riscv::register::sie::set_stimer();
}
}
pub(super) fn handle_timer_interrupt() {
let irq_guard = trap::disable_local();
if irq_guard.current_cpu() == CpuId::bsp() {
crate::timer::jiffies::ELAPSED.fetch_add(1, Ordering::Relaxed);
}
let callbacks_guard = INTERRUPT_CALLBACKS.get_with(&irq_guard);
for callback in callbacks_guard.borrow().iter() {
(callback)();
}
drop(callbacks_guard);
set_next_timer();
}
fn set_next_timer() {
// SAFETY: Calling the `SET_NEXT_TIMER_FN` function pointer is safe here
// because we ensure that it is set to a valid function during the timer
// initialization, and we never modify it after that.
unsafe {
SET_NEXT_TIMER_FN();
}
}
static mut SET_NEXT_TIMER_FN: fn() = set_next_timer_sbi;
fn set_next_timer_sbi() {
sbi_rt::set_timer(TIMER_INTERVAL.load(Ordering::Relaxed));
}
fn set_next_timer_sstc() {
// SAFETY: Setting the next timer using the `stimecmp` CSR is safe here
// because we are using the `stimecmp` CSR to set the next timer interrupt
// only when we're handling a timer interrupt, which is a standard operation
// specified by RISC-V SSTC extension.
unsafe {
asm!("csrrw {}, stimecmp, {}", out(reg) _, in(reg) get_next_when());
}
}
fn is_sstc_enabled() -> bool {
let Some(misa) = riscv::register::misa::read() else {
return false;
}; };
GOLDFISH_IO_MEM.call_once(|| io_mem); misa.has_extension('S')
} }
fn get_next_when() -> u64 {
let current = riscv::register::time::read64();
let interval = TIMER_INTERVAL.load(Ordering::Relaxed);
current + interval
}
pub(crate) fn get_timebase_freq() -> u64 {
TIMEBASE_FREQ.load(Ordering::Relaxed)
} }

View File

@ -4,6 +4,7 @@
mod trap; mod trap;
use riscv::register::scause::Interrupt;
use spin::Once; use spin::Once;
pub use trap::{GeneralRegs, TrapFrame, UserContext}; pub use trap::{GeneralRegs, TrapFrame, UserContext};
@ -32,9 +33,20 @@ extern "C" fn trap_handler(f: &mut TrapFrame) {
use riscv::register::scause::Trap; use riscv::register::scause::Trap;
match riscv::register::scause::read().cause() { match riscv::register::scause::read().cause() {
Trap::Interrupt(_) => { Trap::Interrupt(interrupt) => {
IS_KERNEL_INTERRUPTED.store(true); IS_KERNEL_INTERRUPTED.store(true);
todo!(); match interrupt {
Interrupt::SupervisorTimer => {
crate::arch::timer::handle_timer_interrupt();
}
Interrupt::SupervisorExternal => todo!(),
Interrupt::SupervisorSoft => todo!(),
_ => {
panic!(
"cannot handle unknown supervisor interrupt: {interrupt:?}. trapframe: {f:#x?}.",
);
}
}
IS_KERNEL_INTERRUPTED.store(false); IS_KERNEL_INTERRUPTED.store(false);
} }
Trap::Exception(e) => { Trap::Exception(e) => {