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
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 crate::{SystemTime, rtc::Driver};
pub struct RtcGoldfish;
pub struct RtcGoldfish {
io_mem: IoMem,
}
impl Driver for RtcGoldfish {
fn try_new() -> Option<RtcGoldfish> {
GOLDFISH_IO_MEM.get()?;
Some(RtcGoldfish)
let chosen = DEVICE_TREE.get().unwrap().find_node("/soc/rtc").unwrap();
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 {
const TIME_LOW: usize = 0;
const TIME_HIGH: usize = 4;
let io_mem = GOLDFISH_IO_MEM.get().unwrap();
let mut last_time_high = io_mem.read_once(TIME_HIGH).unwrap();
let mut last_time_high = self.io_mem.read_once(TIME_HIGH).unwrap();
let timestamp = loop {
let time_low: u32 = io_mem.read_once(TIME_LOW).unwrap();
let time_high: u32 = io_mem.read_once(TIME_HIGH).unwrap();
let time_low: u32 = self.io_mem.read_once(TIME_LOW).unwrap();
let time_high: u32 = self.io_mem.read_once(TIME_HIGH).unwrap();
if last_time_high == time_high {
break ((time_high as u64) << 32) | time_low as u64;
}

View File

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

View File

@ -15,8 +15,6 @@ pub mod task;
pub mod timer;
pub mod trap;
use core::sync::atomic::Ordering;
#[cfg(feature = "cvm_guest")]
pub(crate) fn init_cvm_guest() {
// 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.
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();
}
@ -43,7 +44,7 @@ pub(crate) fn interrupts_ack(irq_number: usize) {
/// Return the frequency of TSC. The unit is Hz.
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).

View File

@ -2,48 +2,123 @@
//! 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 be set too high or too low, 1000Hz is
/// a modest choice.
/// 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
/// 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
/// is spent executing timer code.
/// For system performance reasons, this rate cannot be set too high, otherwise
/// most of the time is spent executing timer code.
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`.
pub static GOLDFISH_IO_MEM: Once<IoMem> = Once::new();
/// Initializes the timer module.
///
/// # Safety
///
/// 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()
.unwrap()
.cpus()
.next()
.unwrap()
.timebase_frequency() as u64,
Ordering::Relaxed,
);
TIMER_INTERVAL.store(
TIMEBASE_FREQ.load(Ordering::Relaxed) / TIMER_FREQ,
Ordering::Relaxed,
);
pub(super) fn init() {
let timer_freq = DEVICE_TREE
.get()
.unwrap()
.cpus()
.next()
.unwrap()
.timebase_frequency() as u64;
TIMEBASE_FREQ.store(timer_freq, Ordering::Relaxed);
let chosen = DEVICE_TREE.get().unwrap().find_node("/soc/rtc").unwrap();
if let Some(compatible) = chosen.compatible()
&& compatible.all().any(|c| c == "google,goldfish-rtc")
{
let region = chosen.reg().unwrap().next().unwrap();
let io_mem = unsafe {
IoMem::new(
(region.starting_address as usize)
..(region.starting_address as usize) + region.size.unwrap(),
crate::mm::page_prop::PageFlags::RW,
crate::mm::page_prop::CachePolicy::Uncacheable,
)
};
GOLDFISH_IO_MEM.call_once(|| io_mem);
if is_sstc_enabled() {
// SAFETY: Mutating the static variable `SET_NEXT_TIMER_FN` is safe here
// because we ensure that it is only modified during the initialization
// phase of the timer.
unsafe {
SET_NEXT_TIMER_FN = set_next_timer_sstc;
}
}
set_next_timer();
// SAFETY: Accessing the `sie` CSR to enable the timer interrupt is safe
// 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;
};
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;
use riscv::register::scause::Interrupt;
use spin::Once;
pub use trap::{GeneralRegs, TrapFrame, UserContext};
@ -32,9 +33,20 @@ extern "C" fn trap_handler(f: &mut TrapFrame) {
use riscv::register::scause::Trap;
match riscv::register::scause::read().cause() {
Trap::Interrupt(_) => {
Trap::Interrupt(interrupt) => {
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);
}
Trap::Exception(e) => {