diff --git a/ostd/src/arch/x86/kernel/tsc.rs b/ostd/src/arch/x86/kernel/tsc.rs index fbffbedc8..20ee6bf19 100644 --- a/ostd/src/arch/x86/kernel/tsc.rs +++ b/ostd/src/arch/x86/kernel/tsc.rs @@ -19,7 +19,7 @@ use crate::{ }; /// The frequency of TSC(Hz) -pub(crate) static TSC_FREQ: AtomicU64 = AtomicU64::new(0); +pub(in crate::arch::x86) static TSC_FREQ: AtomicU64 = AtomicU64::new(0); pub fn init_tsc_freq() { let tsc_freq = diff --git a/ostd/src/arch/x86/mod.rs b/ostd/src/arch/x86/mod.rs index 80adb8a59..a56789576 100644 --- a/ostd/src/arch/x86/mod.rs +++ b/ostd/src/arch/x86/mod.rs @@ -91,9 +91,10 @@ pub(crate) unsafe fn late_init_on_bsp() { } serial::callback_init(); - crate::boot::smp::boot_all_aps(); + kernel::tsc::init_tsc_freq(); + timer::init_bsp(); - timer::init(); + crate::boot::smp::boot_all_aps(); if_tdx_enabled!({ } else { @@ -114,8 +115,7 @@ pub(crate) unsafe fn late_init_on_bsp() { /// This function must be called only once on each application processor. /// And it should be called after the BSP's call to [`init_on_bsp`]. pub(crate) unsafe fn init_on_ap() { - // Trigger the initialization of the local APIC. - crate::arch::x86::kernel::apic::with_borrow(|_| {}); + timer::init_ap(); } pub(crate) fn interrupts_ack(irq_number: usize) { diff --git a/ostd/src/arch/x86/timer/apic.rs b/ostd/src/arch/x86/timer/apic.rs index e04a6304f..8f33d1b3b 100644 --- a/ostd/src/arch/x86/timer/apic.rs +++ b/ostd/src/arch/x86/timer/apic.rs @@ -1,76 +1,112 @@ // SPDX-License-Identifier: MPL-2.0 -#![expect(unused_variables)] - -use alloc::sync::Arc; use core::{ arch::x86_64::_rdtsc, - sync::atomic::{AtomicBool, AtomicU64, Ordering}, + sync::atomic::{AtomicU64, Ordering}, }; use log::info; -use spin::Once; -use x86::{ - cpuid::cpuid, - msr::{wrmsr, IA32_TSC_DEADLINE}, -}; use super::TIMER_FREQ; use crate::{ arch::{ - kernel::tsc::init_tsc_freq, + kernel::apic::{self, DivideConfig}, timer::pit::OperatingMode, - x86::kernel::{ - apic::{self, DivideConfig}, - tsc::TSC_FREQ, - }, + tsc_freq, }, trap::{IrqLine, TrapFrame}, }; -/// Initializes APIC with tsc deadline mode or periodic mode. -/// Return the corresponding [`IrqLine`] for the System Timer. -pub(super) fn init() -> IrqLine { - init_tsc_freq(); +/// Initializes APIC with TSC-deadline mode or periodic mode. +/// +/// Return the corresponding [`IrqLine`] for the system timer. +pub(super) fn init_bsp() -> IrqLine { if is_tsc_deadline_mode_supported() { - info!("[Timer]: Enable APIC TSC deadline mode."); - init_tsc_mode() + init_deadline_mode_config(); } else { - info!("[Timer]: Enable APIC periodic mode."); - init_periodic_mode() + init_periodic_mode_config(); + } + + let timer_irq = IrqLine::alloc().unwrap(); + init_timer(&timer_irq); + timer_irq +} + +/// Initializes APIC timer on AP. +/// +/// The caller should provide the [`IrqLine`] for the system timer. +pub(super) fn init_ap(timer_irq: &IrqLine) { + init_timer(timer_irq); +} + +/// A callback that needs to be called on timer interrupt. +pub(super) fn timer_callback() { + use x86::msr::{wrmsr, IA32_TSC_DEADLINE}; + + match CONFIG.get().expect("ACPI timer config is not initialized") { + Config::DeadlineMode { tsc_interval } => { + let tsc_value = unsafe { _rdtsc() }; + let next_tsc_value = tsc_interval + tsc_value; + unsafe { wrmsr(IA32_TSC_DEADLINE, next_tsc_value) }; + } + Config::PeriodicMode { .. } => {} } } -pub(super) static APIC_TIMER_CALLBACK: Once> = Once::new(); - /// Determines if the current system supports tsc_deadline mode APIC timer fn is_tsc_deadline_mode_supported() -> bool { + use x86::cpuid::cpuid; + const TSC_DEADLINE_MODE_SUPPORT: u32 = 1 << 24; let cpuid = cpuid!(1); (cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0 } -fn init_tsc_mode() -> IrqLine { - let timer_irq = IrqLine::alloc().unwrap(); +fn init_timer(timer_irq: &IrqLine) { + match CONFIG.get().expect("ACPI timer config is not initialized") { + Config::DeadlineMode { .. } => { + init_deadline_mode(timer_irq); + } + Config::PeriodicMode { init_count } => { + init_periodic_mode(timer_irq, *init_count); + } + } +} + +fn init_deadline_mode(timer_irq: &IrqLine) { // Enable tsc deadline mode apic::with_borrow(|apic| { apic.set_lvt_timer(timer_irq.num() as u64 | (1 << 18)); }); - let tsc_step = TSC_FREQ.load(Ordering::Relaxed) / 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)); - - timer_irq + timer_callback(); } -fn init_periodic_mode() -> IrqLine { +fn init_periodic_mode(timer_irq: &IrqLine, init_count: u64) { + apic::with_borrow(|apic| { + apic.set_timer_init_count(init_count); + apic.set_lvt_timer(timer_irq.num() as u64 | (1 << 17)); + apic.set_timer_div_config(DivideConfig::Divide64); + }); +} + +static CONFIG: spin::Once = spin::Once::new(); + +enum Config { + DeadlineMode { tsc_interval: u64 }, + PeriodicMode { init_count: u64 }, +} + +fn init_deadline_mode_config() { + info!("[Timer]: Enable APIC TSC deadline mode"); + + let tsc_interval = tsc_freq() / TIMER_FREQ; + CONFIG.call_once(|| Config::DeadlineMode { tsc_interval }); +} + +fn init_periodic_mode_config() { + info!("[Timer]: Enable APIC periodic mode"); + // Allocate IRQ let mut irq = IrqLine::alloc().unwrap(); irq.on_active(pit_callback); @@ -85,34 +121,22 @@ fn init_periodic_mode() -> IrqLine { apic.set_timer_init_count(0xFFFF_FFFF); }); - 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) { + while !CONFIG.is_completed() { x86_64::instructions::hlt(); } x86_64::instructions::interrupts::disable(); drop(irq); - // Init APIC Timer - let timer_irq = IrqLine::alloc().unwrap(); - - apic::with_borrow(|apic| { - apic.set_timer_init_count(INIT_COUNT.load(Ordering::Relaxed)); - apic.set_lvt_timer(timer_irq.num() as u64 | (1 << 17)); - apic.set_timer_div_config(DivideConfig::Divide64); - }); - - return timer_irq; - - fn pit_callback(trap_frame: &TrapFrame) { + fn pit_callback(_trap_frame: &TrapFrame) { static IN_TIME: AtomicU64 = AtomicU64::new(0); static APIC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0); // Set a certain times of callbacks to calculate the frequency + // The number of callbacks needed to calculate the APIC timer frequency. + // This is set to 1/10th of the TIMER_FREQ to ensure enough samples for accurate calculation. const CALLBACK_TIMES: u64 = TIMER_FREQ / 10; - if IN_TIME.load(Ordering::Relaxed) < CALLBACK_TIMES || IS_FINISH.load(Ordering::Acquire) { + if IN_TIME.load(Ordering::Relaxed) < CALLBACK_TIMES || CONFIG.is_completed() { if IN_TIME.load(Ordering::Relaxed) == 0 { let remain_ticks = apic::with_borrow(|apic| apic.timer_current_count()); APIC_FIRST_COUNT.store(0xFFFF_FFFF - remain_ticks, Ordering::Relaxed); @@ -134,7 +158,6 @@ fn init_periodic_mode() -> IrqLine { "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); + CONFIG.call_once(|| Config::PeriodicMode { init_count: ticks }); } } diff --git a/ostd/src/arch/x86/timer/mod.rs b/ostd/src/arch/x86/timer/mod.rs index edf32f5ad..f823857ac 100644 --- a/ostd/src/arch/x86/timer/mod.rs +++ b/ostd/src/arch/x86/timer/mod.rs @@ -10,9 +10,9 @@ use core::sync::atomic::Ordering; use spin::Once; -use self::apic::APIC_TIMER_CALLBACK; use crate::{ arch::x86::kernel, + cpu::{CpuId, PinCurrentCpu}, timer::INTERRUPT_CALLBACKS, trap::{self, IrqLine, TrapFrame}, }; @@ -33,17 +33,19 @@ pub const TIMER_FREQ: u64 = 1000; static TIMER_IRQ: Once = Once::new(); -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; - +/// Initializes the timer state and enable timer interrupts on BSP. +pub(super) fn init_bsp() { let mut timer_irq = if kernel::apic::exists() { - apic::init() + apic::init_bsp() } else { pit::init(pit::OperatingMode::SquareWaveGenerator); + + /// 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; + IrqLine::alloc_specific(PIT_MODE_TIMER_IRQ_NUM).unwrap() }; @@ -51,17 +53,24 @@ pub(super) fn init() { TIMER_IRQ.call_once(|| timer_irq); } -fn timer_callback(_: &TrapFrame) { - crate::timer::jiffies::ELAPSED.fetch_add(1, Ordering::SeqCst); +/// Enables timer interrupt on this AP. +pub(super) fn init_ap() { + if kernel::apic::exists() { + apic::init_ap(TIMER_IRQ.get().unwrap()); + } +} +fn timer_callback(_: &TrapFrame) { let irq_guard = trap::disable_local(); + if irq_guard.current_cpu() == CpuId::bsp() { + crate::timer::jiffies::ELAPSED.fetch_add(1, Ordering::SeqCst); + } + let callbacks_guard = INTERRUPT_CALLBACKS.get_with(&irq_guard); for callback in callbacks_guard.borrow().iter() { (callback)(); } drop(callbacks_guard); - if APIC_TIMER_CALLBACK.is_completed() { - APIC_TIMER_CALLBACK.get().unwrap().call(()); - } + apic::timer_callback(); }