Enable timer IRQs on x86 APs with APIC timer interrupt

This commit is contained in:
Zhang Junyang 2025-03-05 14:39:53 +08:00 committed by Tate, Hongliang Tian
parent f1c7564184
commit 265bc25dd7
4 changed files with 108 additions and 76 deletions

View File

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

View File

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

View File

@ -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<Arc<dyn Fn() + Sync + Send>> = 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<Config> = 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 });
}
}

View File

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