mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-09 05:16:47 +00:00
Enable timer IRQs on x86 APs with APIC timer interrupt
This commit is contained in:
parent
f1c7564184
commit
265bc25dd7
@ -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 =
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
pub(super) static APIC_TIMER_CALLBACK: Once<Arc<dyn Fn() + Sync + Send>> = Once::new();
|
||||
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 { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 });
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
/// 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_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;
|
||||
|
||||
let mut timer_irq = if kernel::apic::exists() {
|
||||
apic::init()
|
||||
} else {
|
||||
pit::init(pit::OperatingMode::SquareWaveGenerator);
|
||||
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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user