Add abstractions for Clock and Timer

This commit is contained in:
Chen Chengjun
2024-05-16 15:45:44 +08:00
committed by Tate, Hongliang Tian
parent 0d5131c822
commit d019de29f9
21 changed files with 756 additions and 220 deletions

View File

@ -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

View File

@ -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};

View File

@ -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);
}
}

View 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()
}
}

View File

@ -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)
}

View File

@ -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 {