mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-11 06:16:49 +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)
|
/// 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() {
|
pub fn init_tsc_freq() {
|
||||||
let tsc_freq =
|
let tsc_freq =
|
||||||
|
@ -91,9 +91,10 @@ pub(crate) unsafe fn late_init_on_bsp() {
|
|||||||
}
|
}
|
||||||
serial::callback_init();
|
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!({
|
if_tdx_enabled!({
|
||||||
} else {
|
} else {
|
||||||
@ -114,8 +115,7 @@ pub(crate) unsafe fn late_init_on_bsp() {
|
|||||||
/// This function must be called only once on each application processor.
|
/// 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`].
|
/// And it should be called after the BSP's call to [`init_on_bsp`].
|
||||||
pub(crate) unsafe fn init_on_ap() {
|
pub(crate) unsafe fn init_on_ap() {
|
||||||
// Trigger the initialization of the local APIC.
|
timer::init_ap();
|
||||||
crate::arch::x86::kernel::apic::with_borrow(|_| {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn interrupts_ack(irq_number: usize) {
|
pub(crate) fn interrupts_ack(irq_number: usize) {
|
||||||
|
@ -1,76 +1,112 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
#![expect(unused_variables)]
|
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
use core::{
|
use core::{
|
||||||
arch::x86_64::_rdtsc,
|
arch::x86_64::_rdtsc,
|
||||||
sync::atomic::{AtomicBool, AtomicU64, Ordering},
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use spin::Once;
|
|
||||||
use x86::{
|
|
||||||
cpuid::cpuid,
|
|
||||||
msr::{wrmsr, IA32_TSC_DEADLINE},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::TIMER_FREQ;
|
use super::TIMER_FREQ;
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{
|
arch::{
|
||||||
kernel::tsc::init_tsc_freq,
|
kernel::apic::{self, DivideConfig},
|
||||||
timer::pit::OperatingMode,
|
timer::pit::OperatingMode,
|
||||||
x86::kernel::{
|
tsc_freq,
|
||||||
apic::{self, DivideConfig},
|
|
||||||
tsc::TSC_FREQ,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
trap::{IrqLine, TrapFrame},
|
trap::{IrqLine, TrapFrame},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initializes APIC with tsc deadline mode or periodic mode.
|
/// Initializes APIC with TSC-deadline mode or periodic mode.
|
||||||
/// Return the corresponding [`IrqLine`] for the System Timer.
|
///
|
||||||
pub(super) fn init() -> IrqLine {
|
/// Return the corresponding [`IrqLine`] for the system timer.
|
||||||
init_tsc_freq();
|
pub(super) fn init_bsp() -> IrqLine {
|
||||||
if is_tsc_deadline_mode_supported() {
|
if is_tsc_deadline_mode_supported() {
|
||||||
info!("[Timer]: Enable APIC TSC deadline mode.");
|
init_deadline_mode_config();
|
||||||
init_tsc_mode()
|
|
||||||
} else {
|
} else {
|
||||||
info!("[Timer]: Enable APIC periodic mode.");
|
init_periodic_mode_config();
|
||||||
init_periodic_mode()
|
}
|
||||||
|
|
||||||
|
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
|
/// Determines if the current system supports tsc_deadline mode APIC timer
|
||||||
fn is_tsc_deadline_mode_supported() -> bool {
|
fn is_tsc_deadline_mode_supported() -> bool {
|
||||||
|
use x86::cpuid::cpuid;
|
||||||
|
|
||||||
const TSC_DEADLINE_MODE_SUPPORT: u32 = 1 << 24;
|
const TSC_DEADLINE_MODE_SUPPORT: u32 = 1 << 24;
|
||||||
let cpuid = cpuid!(1);
|
let cpuid = cpuid!(1);
|
||||||
(cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0
|
(cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_tsc_mode() -> IrqLine {
|
fn init_timer(timer_irq: &IrqLine) {
|
||||||
let timer_irq = IrqLine::alloc().unwrap();
|
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
|
// Enable tsc deadline mode
|
||||||
apic::with_borrow(|apic| {
|
apic::with_borrow(|apic| {
|
||||||
apic.set_lvt_timer(timer_irq.num() as u64 | (1 << 18));
|
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 {
|
timer_callback();
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Allocate IRQ
|
||||||
let mut irq = IrqLine::alloc().unwrap();
|
let mut irq = IrqLine::alloc().unwrap();
|
||||||
irq.on_active(pit_callback);
|
irq.on_active(pit_callback);
|
||||||
@ -85,34 +121,22 @@ fn init_periodic_mode() -> IrqLine {
|
|||||||
apic.set_timer_init_count(0xFFFF_FFFF);
|
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();
|
x86_64::instructions::interrupts::enable();
|
||||||
while !IS_FINISH.load(Ordering::Acquire) {
|
while !CONFIG.is_completed() {
|
||||||
x86_64::instructions::hlt();
|
x86_64::instructions::hlt();
|
||||||
}
|
}
|
||||||
x86_64::instructions::interrupts::disable();
|
x86_64::instructions::interrupts::disable();
|
||||||
drop(irq);
|
drop(irq);
|
||||||
|
|
||||||
// Init APIC Timer
|
fn pit_callback(_trap_frame: &TrapFrame) {
|
||||||
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) {
|
|
||||||
static IN_TIME: AtomicU64 = AtomicU64::new(0);
|
static IN_TIME: AtomicU64 = AtomicU64::new(0);
|
||||||
static APIC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0);
|
static APIC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
// Set a certain times of callbacks to calculate the frequency
|
// 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;
|
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 {
|
if IN_TIME.load(Ordering::Relaxed) == 0 {
|
||||||
let remain_ticks = apic::with_borrow(|apic| apic.timer_current_count());
|
let remain_ticks = apic::with_borrow(|apic| apic.timer_current_count());
|
||||||
APIC_FIRST_COUNT.store(0xFFFF_FFFF - remain_ticks, Ordering::Relaxed);
|
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",
|
"APIC Timer ticks count:{:x}, remain ticks: {:x},Timer Freq:{} Hz",
|
||||||
ticks, remain_ticks, TIMER_FREQ
|
ticks, remain_ticks, TIMER_FREQ
|
||||||
);
|
);
|
||||||
INIT_COUNT.store(ticks, Ordering::Release);
|
CONFIG.call_once(|| Config::PeriodicMode { init_count: ticks });
|
||||||
IS_FINISH.store(true, Ordering::Release);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ use core::sync::atomic::Ordering;
|
|||||||
|
|
||||||
use spin::Once;
|
use spin::Once;
|
||||||
|
|
||||||
use self::apic::APIC_TIMER_CALLBACK;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::x86::kernel,
|
arch::x86::kernel,
|
||||||
|
cpu::{CpuId, PinCurrentCpu},
|
||||||
timer::INTERRUPT_CALLBACKS,
|
timer::INTERRUPT_CALLBACKS,
|
||||||
trap::{self, IrqLine, TrapFrame},
|
trap::{self, IrqLine, TrapFrame},
|
||||||
};
|
};
|
||||||
@ -33,17 +33,19 @@ pub const TIMER_FREQ: u64 = 1000;
|
|||||||
|
|
||||||
static TIMER_IRQ: Once<IrqLine> = Once::new();
|
static TIMER_IRQ: Once<IrqLine> = Once::new();
|
||||||
|
|
||||||
pub(super) fn init() {
|
/// Initializes the timer state and enable timer interrupts on BSP.
|
||||||
/// In PIT mode, channel 0 is connected directly to IRQ0, which is
|
pub(super) fn init_bsp() {
|
||||||
/// 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() {
|
let mut timer_irq = if kernel::apic::exists() {
|
||||||
apic::init()
|
apic::init_bsp()
|
||||||
} else {
|
} else {
|
||||||
pit::init(pit::OperatingMode::SquareWaveGenerator);
|
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()
|
IrqLine::alloc_specific(PIT_MODE_TIMER_IRQ_NUM).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,17 +53,24 @@ pub(super) fn init() {
|
|||||||
TIMER_IRQ.call_once(|| timer_irq);
|
TIMER_IRQ.call_once(|| timer_irq);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timer_callback(_: &TrapFrame) {
|
/// Enables timer interrupt on this AP.
|
||||||
crate::timer::jiffies::ELAPSED.fetch_add(1, Ordering::SeqCst);
|
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();
|
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);
|
let callbacks_guard = INTERRUPT_CALLBACKS.get_with(&irq_guard);
|
||||||
for callback in callbacks_guard.borrow().iter() {
|
for callback in callbacks_guard.borrow().iter() {
|
||||||
(callback)();
|
(callback)();
|
||||||
}
|
}
|
||||||
drop(callbacks_guard);
|
drop(callbacks_guard);
|
||||||
|
|
||||||
if APIC_TIMER_CALLBACK.is_completed() {
|
apic::timer_callback();
|
||||||
APIC_TIMER_CALLBACK.get().unwrap().call(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user