mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-23 17:33:23 +00:00
Add abstractions for Clock and Timer
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
0d5131c822
commit
d019de29f9
@ -12,7 +12,7 @@ use x86::cpuid::cpuid;
|
||||
use crate::{
|
||||
arch::timer::{
|
||||
pit::{self, OperatingMode},
|
||||
TIMER_FREQ, TIMER_IRQ_NUM,
|
||||
TIMER_FREQ,
|
||||
},
|
||||
trap::IrqLine,
|
||||
};
|
||||
@ -71,7 +71,7 @@ pub fn determine_tsc_freq_via_cpuid() -> Option<u64> {
|
||||
/// the PIT to calculate this frequency.
|
||||
pub fn determine_tsc_freq_via_pit() -> u64 {
|
||||
// Allocate IRQ
|
||||
let mut irq = IrqLine::alloc_specific(TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
||||
let mut irq = IrqLine::alloc().unwrap();
|
||||
irq.on_active(pit_callback);
|
||||
|
||||
// Enable PIT
|
||||
|
@ -12,7 +12,7 @@ pub(crate) mod pci;
|
||||
pub mod qemu;
|
||||
#[cfg(feature = "intel_tdx")]
|
||||
pub(crate) mod tdx_guest;
|
||||
pub(crate) mod timer;
|
||||
pub mod timer;
|
||||
|
||||
use core::{arch::x86_64::_rdtsc, sync::atomic::Ordering};
|
||||
|
||||
|
@ -27,14 +27,16 @@ use crate::{
|
||||
trap::IrqLine,
|
||||
};
|
||||
|
||||
pub fn init() {
|
||||
/// Init APIC with tsc deadline mode or periodic mode.
|
||||
/// Return the corresponding `IrqLine` for the System Timer.
|
||||
pub(super) fn init() -> IrqLine {
|
||||
init_tsc_freq();
|
||||
if is_tsc_deadline_mode_supported() {
|
||||
info!("[Timer]: Enable APIC TSC deadline mode.");
|
||||
init_tsc_mode();
|
||||
init_tsc_mode()
|
||||
} else {
|
||||
info!("[Timer]: Enable APIC periodic mode.");
|
||||
init_periodic_mode();
|
||||
init_periodic_mode()
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,10 +49,11 @@ fn is_tsc_deadline_mode_supported() -> bool {
|
||||
(cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0
|
||||
}
|
||||
|
||||
fn init_tsc_mode() {
|
||||
fn init_tsc_mode() -> IrqLine {
|
||||
let timer_irq = IrqLine::alloc().unwrap();
|
||||
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock_irq_disabled();
|
||||
// Enable tsc deadline mode
|
||||
apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 18));
|
||||
apic_lock.set_lvt_timer(timer_irq.num() as u64 | (1 << 18));
|
||||
drop(apic_lock);
|
||||
let tsc_step = TSC_FREQ.load(Ordering::Relaxed) / TIMER_FREQ;
|
||||
|
||||
@ -62,11 +65,13 @@ fn init_tsc_mode() {
|
||||
|
||||
callback.call(());
|
||||
APIC_TIMER_CALLBACK.call_once(|| Arc::new(callback));
|
||||
|
||||
timer_irq
|
||||
}
|
||||
|
||||
fn init_periodic_mode() {
|
||||
fn init_periodic_mode() -> IrqLine {
|
||||
// Allocate IRQ
|
||||
let mut irq = IrqLine::alloc_specific(super::TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
||||
let mut irq = IrqLine::alloc().unwrap();
|
||||
irq.on_active(pit_callback);
|
||||
|
||||
// Enable PIT
|
||||
@ -80,6 +85,8 @@ fn init_periodic_mode() {
|
||||
drop(apic_lock);
|
||||
|
||||
static IS_FINISH: AtomicBool = AtomicBool::new(false);
|
||||
static INIT_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
x86_64::instructions::interrupts::enable();
|
||||
while !IS_FINISH.load(Ordering::Acquire) {
|
||||
x86_64::instructions::hlt();
|
||||
@ -87,6 +94,16 @@ fn init_periodic_mode() {
|
||||
x86_64::instructions::interrupts::disable();
|
||||
drop(irq);
|
||||
|
||||
// Init APIC Timer
|
||||
let timer_irq = IrqLine::alloc().unwrap();
|
||||
|
||||
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock_irq_disabled();
|
||||
apic_lock.set_timer_init_count(INIT_COUNT.load(Ordering::Relaxed));
|
||||
apic_lock.set_lvt_timer(timer_irq.num() as u64 | (1 << 17));
|
||||
apic_lock.set_timer_div_config(DivideConfig::Divide64);
|
||||
|
||||
return timer_irq;
|
||||
|
||||
fn pit_callback(trap_frame: &TrapFrame) {
|
||||
static IN_TIME: AtomicU64 = AtomicU64::new(0);
|
||||
static APIC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
@ -108,17 +125,13 @@ fn init_periodic_mode() {
|
||||
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock_irq_disabled();
|
||||
let remain_ticks = apic_lock.timer_current_count();
|
||||
apic_lock.set_timer_init_count(0);
|
||||
|
||||
// Init APIC Timer
|
||||
let ticks = (0xFFFF_FFFF - remain_ticks - APIC_FIRST_COUNT.load(Ordering::Relaxed))
|
||||
/ CALLBACK_TIMES;
|
||||
apic_lock.set_timer_init_count(ticks);
|
||||
apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 17));
|
||||
apic_lock.set_timer_div_config(DivideConfig::Divide64);
|
||||
info!(
|
||||
"APIC Timer ticks count:{:x}, remain ticks: {:x},Timer Freq:{} Hz",
|
||||
ticks, remain_ticks, TIMER_FREQ
|
||||
);
|
||||
INIT_COUNT.store(ticks, Ordering::Release);
|
||||
IS_FINISH.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
45
framework/aster-frame/src/arch/x86/timer/jiffies.rs
Normal file
45
framework/aster-frame/src/arch/x86/timer/jiffies.rs
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use core::{
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::TIMER_FREQ;
|
||||
|
||||
/// Jiffies is a term used to denote the units of time measurement by the kernel.
|
||||
///
|
||||
/// A jiffy represents one tick of the system timer interrupt,
|
||||
/// whose frequency is equal to [`TIMER_FREQ`] Hz.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Jiffies(u64);
|
||||
|
||||
pub(super) static ELAPSED: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
impl Jiffies {
|
||||
/// Creates a new instance.
|
||||
pub fn new(value: u64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Returns the elapsed time since the system boots up.
|
||||
pub fn elapsed() -> Self {
|
||||
Self::new(ELAPSED.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Gets the number of jiffies.
|
||||
pub fn as_u64(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Gets the `Duration` calculated from the jiffies counts.
|
||||
pub fn as_duration(self) -> Duration {
|
||||
Duration::from_millis(self.0 * 1000 / TIMER_FREQ)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Jiffies> for Duration {
|
||||
fn from(value: Jiffies) -> Self {
|
||||
value.as_duration()
|
||||
}
|
||||
}
|
@ -1,20 +1,19 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod apic;
|
||||
pub mod hpet;
|
||||
pub mod pit;
|
||||
mod apic;
|
||||
mod hpet;
|
||||
mod jiffies;
|
||||
pub(crate) mod pit;
|
||||
|
||||
use alloc::{boxed::Box, collections::BinaryHeap, sync::Arc, vec::Vec};
|
||||
use core::{
|
||||
any::Any,
|
||||
sync::atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering},
|
||||
};
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use core::{cell::RefCell, sync::atomic::Ordering};
|
||||
|
||||
pub use jiffies::Jiffies;
|
||||
use spin::Once;
|
||||
use trapframe::TrapFrame;
|
||||
|
||||
use self::apic::APIC_TIMER_CALLBACK;
|
||||
use crate::{arch::x86::kernel, sync::SpinLock, trap::IrqLine};
|
||||
use crate::{arch::x86::kernel, cpu_local, trap::IrqLine, CpuLocal};
|
||||
|
||||
/// 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
|
||||
@ -27,147 +26,50 @@ use crate::{arch::x86::kernel, sync::SpinLock, trap::IrqLine};
|
||||
/// frequencies lower than 19Hz = 1193182 / 65536 (Timer rate / Divider)
|
||||
pub const TIMER_FREQ: u64 = 1000;
|
||||
|
||||
pub static TIMER_IRQ_NUM: AtomicU8 = AtomicU8::new(32);
|
||||
pub static TICK: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
static TIMER_IRQ: Once<IrqLine> = Once::new();
|
||||
|
||||
pub fn init() {
|
||||
if kernel::apic::APIC_INSTANCE.is_completed() {
|
||||
// Get the free irq number first. Use `allocate_target_irq` to get the Irq handle after dropping it.
|
||||
// Because the function inside `apic::init` will allocate this irq.
|
||||
let irq = IrqLine::alloc().unwrap();
|
||||
TIMER_IRQ_NUM.store(irq.num(), Ordering::Relaxed);
|
||||
drop(irq);
|
||||
apic::init();
|
||||
pub(super) fn init() {
|
||||
/// In PIT mode, channel 0 is connected directly to IRQ0, which is
|
||||
/// the `IrqLine` with the `irq_num` 32 (0-31 `IrqLine`s are reserved).
|
||||
///
|
||||
/// Ref: https://wiki.osdev.org/Programmable_Interval_Timer#Outputs.
|
||||
const PIT_MODE_TIMER_IRQ_NUM: u8 = 32;
|
||||
|
||||
let mut timer_irq = if kernel::apic::APIC_INSTANCE.is_completed() {
|
||||
apic::init()
|
||||
} else {
|
||||
pit::init(pit::OperatingMode::SquareWaveGenerator);
|
||||
IrqLine::alloc_specific(PIT_MODE_TIMER_IRQ_NUM).unwrap()
|
||||
};
|
||||
TIMEOUT_LIST.call_once(|| SpinLock::new(BinaryHeap::new()));
|
||||
let mut timer_irq = IrqLine::alloc_specific(TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
||||
|
||||
timer_irq.on_active(timer_callback);
|
||||
TIMER_IRQ.call_once(|| timer_irq);
|
||||
}
|
||||
|
||||
fn timer_callback(trap_frame: &TrapFrame) {
|
||||
let current_ticks = TICK.fetch_add(1, Ordering::SeqCst);
|
||||
cpu_local! {
|
||||
static INTERRUPT_CALLBACKS: RefCell<Vec<Box<dyn Fn() + Sync + Send>>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
let callbacks = {
|
||||
let mut callbacks = Vec::new();
|
||||
let mut timeout_list = TIMEOUT_LIST.get().unwrap().lock_irq_disabled();
|
||||
/// Register a function that will be executed during the system timer interruption.
|
||||
pub fn register_callback<F>(func: F)
|
||||
where
|
||||
F: Fn() + Sync + Send + 'static,
|
||||
{
|
||||
CpuLocal::borrow_with(&INTERRUPT_CALLBACKS, |callbacks| {
|
||||
callbacks.borrow_mut().push(Box::new(func));
|
||||
});
|
||||
}
|
||||
|
||||
while let Some(t) = timeout_list.peek() {
|
||||
if t.is_cancelled() {
|
||||
// Just ignore the cancelled callback
|
||||
timeout_list.pop();
|
||||
} else if t.expire_ticks <= current_ticks {
|
||||
callbacks.push(timeout_list.pop().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
fn timer_callback(_: &TrapFrame) {
|
||||
jiffies::ELAPSED.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
CpuLocal::borrow_with(&INTERRUPT_CALLBACKS, |callbacks| {
|
||||
for callback in callbacks.borrow().iter() {
|
||||
(callback)();
|
||||
}
|
||||
callbacks
|
||||
};
|
||||
|
||||
for callback in callbacks {
|
||||
(callback.callback)(&callback);
|
||||
}
|
||||
});
|
||||
|
||||
if APIC_TIMER_CALLBACK.is_completed() {
|
||||
APIC_TIMER_CALLBACK.get().unwrap().call(());
|
||||
}
|
||||
}
|
||||
|
||||
static TIMEOUT_LIST: Once<SpinLock<BinaryHeap<Arc<TimerCallback>>>> = Once::new();
|
||||
|
||||
pub struct TimerCallback {
|
||||
expire_ticks: u64,
|
||||
data: Arc<dyn Any + Send + Sync>,
|
||||
callback: Box<dyn Fn(&TimerCallback) + Send + Sync>,
|
||||
is_cancelled: AtomicBool,
|
||||
}
|
||||
|
||||
impl TimerCallback {
|
||||
fn new(
|
||||
timeout_ticks: u64,
|
||||
data: Arc<dyn Any + Send + Sync>,
|
||||
callback: Box<dyn Fn(&TimerCallback) + Send + Sync>,
|
||||
) -> Self {
|
||||
Self {
|
||||
expire_ticks: timeout_ticks,
|
||||
data,
|
||||
callback,
|
||||
is_cancelled: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &Arc<dyn Any + Send + Sync> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Whether the set timeout is reached
|
||||
pub fn is_expired(&self) -> bool {
|
||||
let current_tick = TICK.load(Ordering::Acquire);
|
||||
self.expire_ticks <= current_tick
|
||||
}
|
||||
|
||||
/// Cancel a timer callback. If the callback function has not been called,
|
||||
/// it will never be called again.
|
||||
pub fn cancel(&self) {
|
||||
self.is_cancelled.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
// Whether the timer callback is cancelled.
|
||||
fn is_cancelled(&self) -> bool {
|
||||
self.is_cancelled.load(Ordering::Acquire)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TimerCallback {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.expire_ticks == other.expire_ticks
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TimerCallback {}
|
||||
|
||||
impl PartialOrd for TimerCallback {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for TimerCallback {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.expire_ticks.cmp(&other.expire_ticks).reverse()
|
||||
}
|
||||
}
|
||||
|
||||
/// add timeout task into timeout list, the frequency see const TIMER_FREQ
|
||||
///
|
||||
/// user should ensure that the callback function cannot take too much time
|
||||
///
|
||||
pub fn add_timeout_list<F, T>(timeout: u64, data: T, callback: F) -> Arc<TimerCallback>
|
||||
where
|
||||
F: Fn(&TimerCallback) + Send + Sync + 'static,
|
||||
T: Any + Send + Sync,
|
||||
{
|
||||
let timer_callback = TimerCallback::new(
|
||||
TICK.load(Ordering::Acquire) + timeout,
|
||||
Arc::new(data),
|
||||
Box::new(callback),
|
||||
);
|
||||
let arc = Arc::new(timer_callback);
|
||||
TIMEOUT_LIST
|
||||
.get()
|
||||
.unwrap()
|
||||
.lock_irq_disabled()
|
||||
.push(arc.clone());
|
||||
arc
|
||||
}
|
||||
|
||||
/// The time since the system boots up.
|
||||
/// The currently returned results are in milliseconds.
|
||||
pub fn read_monotonic_milli_seconds() -> u64 {
|
||||
TICK.load(Ordering::Acquire) * (1000 / TIMER_FREQ)
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ pub type IrqCallbackFunction = dyn Fn(&TrapFrame) + Sync + Send + 'static;
|
||||
/// An Interrupt ReQuest(IRQ) line. User can use `alloc` or `alloc_specific` to get specific IRQ line.
|
||||
///
|
||||
/// The IRQ number is guaranteed to be external IRQ number and user can register callback functions to this IRQ resource.
|
||||
/// When this resrouce is dropped, all the callback in this will be unregistered automatically.
|
||||
/// When this resource is dropped, all the callback in this will be unregistered automatically.
|
||||
#[derive(Debug)]
|
||||
#[must_use]
|
||||
pub struct IrqLine {
|
||||
|
Reference in New Issue
Block a user