Support TSC-Deadline mode APIC timer

This commit is contained in:
Yuke Peng 2023-08-09 17:09:08 +08:00 committed by Tate, Hongliang Tian
parent d1c0090d3a
commit f0f498e46a
4 changed files with 106 additions and 9 deletions

View File

@ -6,3 +6,4 @@
pub mod acpi;
pub mod apic;
pub mod pic;
pub mod tsc;

View File

@ -0,0 +1,40 @@
use x86::cpuid::cpuid;
/// Determine TSC frequency via CPUID. If the CPU does not support calculating TSC frequency by
/// CPUID, the function will return None. The unit of the return value is KHz.
///
/// Ref: function `native_calibrate_tsc` in linux `arch/x86/kernel/tsc.c`
///
pub fn tsc_freq() -> Option<u32> {
// Check the max cpuid supported
let cpuid = cpuid!(0);
let max_cpuid = cpuid.eax;
if max_cpuid <= 0x15 {
return None;
}
// TSC frequecny = ecx * ebx / eax
// CPUID 0x15: Time Stamp Counter and Nominal Core Crystal Clock Information Leaf
let mut cpuid = cpuid!(0x15);
if cpuid.eax == 0 || cpuid.ebx == 0 {
return None;
}
let eax_denominator = cpuid.eax;
let ebx_numerator = cpuid.ebx;
let mut crystal_khz = cpuid.ecx / 1000;
// Some Intel SoCs like Skylake and Kabylake don't report the crystal
// clock, but we can easily calculate it to a high degree of accuracy
// by considering the crystal ratio and the CPU speed.
if crystal_khz == 0 && max_cpuid >= 0x16 {
cpuid = cpuid!(0x16);
let base_mhz = cpuid.eax;
crystal_khz = base_mhz * 1000 * eax_denominator / ebx_numerator;
}
if crystal_khz == 0 {
None
} else {
Some(crystal_khz * ebx_numerator / eax_denominator)
}
}

View File

@ -1,14 +1,67 @@
use core::arch::x86_64::_rdtsc;
use core::sync::atomic::{AtomicBool, Ordering};
use log::info;
use trapframe::TrapFrame;
use crate::arch::x86::kernel::{
apic::{DivideConfig, APIC_INSTANCE},
pic,
use alloc::boxed::Box;
use alloc::sync::Arc;
use log::info;
use spin::Once;
use trapframe::TrapFrame;
use x86::cpuid::cpuid;
use x86::msr::{wrmsr, IA32_TSC_DEADLINE};
use crate::{
arch::x86::kernel::{
apic::{DivideConfig, APIC_INSTANCE},
pic,
tsc::tsc_freq,
},
config::TIMER_FREQ,
};
use crate::config;
pub fn init() {
if tsc_mode_support() {
info!("APIC Timer: Enable TSC deadline mode.");
tsc_mode_init();
} else {
info!("APIC Timer: Enable periodic mode.");
periodic_mode_init();
}
}
fn tsc_mode_support() -> bool {
let tsc_rate = tsc_freq();
if tsc_rate.is_none() {
return false;
}
let cpuid = cpuid!(0x1);
// bit 24
cpuid.ecx & 0x100_0000 != 0
}
pub(super) static APIC_TIMER_CALLBACK: Once<Arc<dyn Fn() + Sync + Send>> = Once::new();
fn tsc_mode_init() {
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
// Enable tsc deadline mode.
apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM as u64 | (1 << 18));
let tsc_step = {
let tsc_rate = tsc_freq().unwrap() as u64;
info!("TSC frequency:{:?} Hz", tsc_rate * 1000);
tsc_rate * 1000 / TIMER_FREQ
};
let callback = move || unsafe {
let tsc_value = _rdtsc();
let next_tsc_value = tsc_step + tsc_value;
wrmsr(IA32_TSC_DEADLINE, next_tsc_value);
};
callback.call(());
APIC_TIMER_CALLBACK.call_once(|| Arc::new(callback));
}
fn periodic_mode_init() {
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
let handle = unsafe { crate::trap::IrqLine::acquire(super::TIMER_IRQ_NUM) };
let a = handle.on_active(init_function);
@ -58,9 +111,7 @@ pub fn init() {
info!(
"APIC Timer ticks count:{:x}, remain ticks: {:x},Timer Freq:{} Hz",
ticks,
remain_ticks,
config::TIMER_FREQ
ticks, remain_ticks, TIMER_FREQ
);
IS_FINISH.store(true, Ordering::Release);
}

View File

@ -13,6 +13,8 @@ use crate::arch::x86::kernel;
use crate::config::TIMER_FREQ;
use crate::trap::IrqAllocateHandle;
use self::apic::APIC_TIMER_CALLBACK;
pub const TIMER_IRQ_NUM: u8 = 32;
pub static TICK: AtomicU64 = AtomicU64::new(0);
@ -46,6 +48,9 @@ fn timer_callback(trap_frame: &TrapFrame) {
for callback in callbacks {
callback.callback.call((&callback,));
}
if APIC_TIMER_CALLBACK.is_completed() {
APIC_TIMER_CALLBACK.get().unwrap().call(());
}
}
static TIMEOUT_LIST: Once<Mutex<BinaryHeap<Arc<TimerCallback>>>> = Once::new();