From f0f498e46a3d35ef53c47bee1ed0e5d282a816c8 Mon Sep 17 00:00:00 2001 From: Yuke Peng Date: Wed, 9 Aug 2023 17:09:08 +0800 Subject: [PATCH] Support TSC-Deadline mode APIC timer --- .../jinux-frame/src/arch/x86/kernel/mod.rs | 1 + .../jinux-frame/src/arch/x86/kernel/tsc.rs | 40 +++++++++++ .../jinux-frame/src/arch/x86/timer/apic.rs | 69 ++++++++++++++++--- .../jinux-frame/src/arch/x86/timer/mod.rs | 5 ++ 4 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 framework/jinux-frame/src/arch/x86/kernel/tsc.rs diff --git a/framework/jinux-frame/src/arch/x86/kernel/mod.rs b/framework/jinux-frame/src/arch/x86/kernel/mod.rs index 3b2b3328f..867e5dfd5 100644 --- a/framework/jinux-frame/src/arch/x86/kernel/mod.rs +++ b/framework/jinux-frame/src/arch/x86/kernel/mod.rs @@ -6,3 +6,4 @@ pub mod acpi; pub mod apic; pub mod pic; +pub mod tsc; diff --git a/framework/jinux-frame/src/arch/x86/kernel/tsc.rs b/framework/jinux-frame/src/arch/x86/kernel/tsc.rs new file mode 100644 index 000000000..7e2af95a7 --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/kernel/tsc.rs @@ -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 { + // 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) + } +} diff --git a/framework/jinux-frame/src/arch/x86/timer/apic.rs b/framework/jinux-frame/src/arch/x86/timer/apic.rs index cf1520816..a9ce87ec2 100644 --- a/framework/jinux-frame/src/arch/x86/timer/apic.rs +++ b/framework/jinux-frame/src/arch/x86/timer/apic.rs @@ -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> = 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); } diff --git a/framework/jinux-frame/src/arch/x86/timer/mod.rs b/framework/jinux-frame/src/arch/x86/timer/mod.rs index a83f82054..2a7bfd2c1 100644 --- a/framework/jinux-frame/src/arch/x86/timer/mod.rs +++ b/framework/jinux-frame/src/arch/x86/timer/mod.rs @@ -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>>> = Once::new();