mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-18 03:56:42 +00:00
Refactor timer in framework
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
8d6915d0e6
commit
748a92d278
@ -1,17 +1,28 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use core::sync::atomic::AtomicU64;
|
use core::{
|
||||||
|
arch::x86_64::_rdtsc,
|
||||||
|
sync::atomic::{AtomicBool, AtomicU64, Ordering},
|
||||||
|
};
|
||||||
|
use log::info;
|
||||||
|
use trapframe::TrapFrame;
|
||||||
use x86::cpuid::cpuid;
|
use x86::cpuid::cpuid;
|
||||||
|
|
||||||
/// The frequency of tsc. The unit is Hz.
|
use crate::{
|
||||||
|
arch::timer::{
|
||||||
|
pit::{self, OperatingMode},
|
||||||
|
TIMER_FREQ, TIMER_IRQ_NUM,
|
||||||
|
},
|
||||||
|
trap::IrqLine,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The frequency of TSC(Hz)
|
||||||
pub(crate) static TSC_FREQ: AtomicU64 = AtomicU64::new(0);
|
pub(crate) static TSC_FREQ: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
const TSC_DEADLINE_MODE_SUPPORT: u32 = 1 << 24;
|
pub fn init_tsc_freq() {
|
||||||
|
let tsc_freq = determine_tsc_freq_via_cpuid().map_or(determine_tsc_freq_via_pit(), |freq| freq);
|
||||||
/// Determine if the current system supports tsc_deadline mode.
|
TSC_FREQ.store(tsc_freq, Ordering::Relaxed);
|
||||||
pub fn is_tsc_deadline_mode_supported() -> bool {
|
info!("TSC frequency:{:?} Hz", tsc_freq);
|
||||||
let cpuid = cpuid!(1);
|
|
||||||
(cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine TSC frequency via CPUID. If the CPU does not support calculating TSC frequency by
|
/// Determine TSC frequency via CPUID. If the CPU does not support calculating TSC frequency by
|
||||||
@ -19,7 +30,7 @@ pub fn is_tsc_deadline_mode_supported() -> bool {
|
|||||||
///
|
///
|
||||||
/// Ref: function `native_calibrate_tsc` in linux `arch/x86/kernel/tsc.c`
|
/// Ref: function `native_calibrate_tsc` in linux `arch/x86/kernel/tsc.c`
|
||||||
///
|
///
|
||||||
pub fn determine_tsc_freq_via_cpuid() -> Option<u32> {
|
pub fn determine_tsc_freq_via_cpuid() -> Option<u64> {
|
||||||
// Check the max cpuid supported
|
// Check the max cpuid supported
|
||||||
let cpuid = cpuid!(0);
|
let cpuid = cpuid!(0);
|
||||||
let max_cpuid = cpuid.eax;
|
let max_cpuid = cpuid.eax;
|
||||||
@ -49,6 +60,53 @@ pub fn determine_tsc_freq_via_cpuid() -> Option<u32> {
|
|||||||
if crystal_khz == 0 {
|
if crystal_khz == 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(crystal_khz * ebx_numerator / eax_denominator)
|
let crystal_hz = crystal_khz as u64 * 1000;
|
||||||
|
Some(crystal_hz * ebx_numerator as u64 / eax_denominator as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When kernel cannot get the TSC frequency from CPUID, it can leverage
|
||||||
|
/// the PIT to calculate this frequency.
|
||||||
|
pub fn determine_tsc_freq_via_pit() -> u64 {
|
||||||
|
// Allocate IRQ
|
||||||
|
let mut irq = IrqLine::alloc_specific(TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
||||||
|
irq.on_active(pit_callback);
|
||||||
|
|
||||||
|
// Enable PIT
|
||||||
|
pit::init(OperatingMode::RateGenerator);
|
||||||
|
pit::enable_ioapic_line(irq.clone());
|
||||||
|
|
||||||
|
static IS_FINISH: AtomicBool = AtomicBool::new(false);
|
||||||
|
static FREQUENCY: AtomicU64 = AtomicU64::new(0);
|
||||||
|
x86_64::instructions::interrupts::enable();
|
||||||
|
while !IS_FINISH.load(Ordering::Acquire) {
|
||||||
|
x86_64::instructions::hlt();
|
||||||
|
}
|
||||||
|
x86_64::instructions::interrupts::disable();
|
||||||
|
drop(irq);
|
||||||
|
return FREQUENCY.load(Ordering::Acquire);
|
||||||
|
|
||||||
|
fn pit_callback(trap_frame: &TrapFrame) {
|
||||||
|
static IN_TIME: AtomicU64 = AtomicU64::new(0);
|
||||||
|
static TSC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
|
// Set a certain times of callbacks to calculate the frequency
|
||||||
|
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) == 0 {
|
||||||
|
unsafe {
|
||||||
|
TSC_FIRST_COUNT.store(_rdtsc(), Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IN_TIME.fetch_add(1, Ordering::Relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pit::disable_ioapic_line();
|
||||||
|
let tsc_count = unsafe { _rdtsc() };
|
||||||
|
let freq =
|
||||||
|
(tsc_count - TSC_FIRST_COUNT.load(Ordering::Relaxed)) * (TIMER_FREQ / CALLBACK_TIMES);
|
||||||
|
FREQUENCY.store(freq, Ordering::Release);
|
||||||
|
IS_FINISH.store(true, Ordering::Release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,21 @@
|
|||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use core::arch::x86_64::_rdtsc;
|
use core::arch::x86_64::_rdtsc;
|
||||||
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
|
use x86::cpuid::cpuid;
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use spin::Once;
|
use spin::Once;
|
||||||
use trapframe::TrapFrame;
|
use trapframe::TrapFrame;
|
||||||
use x86::msr::{wrmsr, IA32_TSC_DEADLINE};
|
use x86::msr::{wrmsr, IA32_TSC_DEADLINE};
|
||||||
|
|
||||||
use crate::arch::kernel::apic::ioapic::IO_APIC;
|
use crate::arch::kernel::tsc::init_tsc_freq;
|
||||||
use crate::arch::kernel::tsc::is_tsc_deadline_mode_supported;
|
use crate::arch::timer::pit::OperatingMode;
|
||||||
use crate::arch::x86::kernel::apic::{DivideConfig, APIC_INSTANCE};
|
use crate::arch::x86::kernel::apic::{DivideConfig, APIC_INSTANCE};
|
||||||
use crate::arch::x86::kernel::tsc::{determine_tsc_freq_via_cpuid, TSC_FREQ};
|
use crate::arch::x86::kernel::tsc::TSC_FREQ;
|
||||||
use crate::config::TIMER_FREQ;
|
|
||||||
use crate::trap::IrqLine;
|
use crate::trap::IrqLine;
|
||||||
|
|
||||||
|
use super::TIMER_FREQ;
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
init_tsc_freq();
|
init_tsc_freq();
|
||||||
if is_tsc_deadline_mode_supported() {
|
if is_tsc_deadline_mode_supported() {
|
||||||
@ -29,16 +31,16 @@ pub fn init() {
|
|||||||
|
|
||||||
pub(super) static APIC_TIMER_CALLBACK: Once<Arc<dyn Fn() + Sync + Send>> = Once::new();
|
pub(super) static APIC_TIMER_CALLBACK: Once<Arc<dyn Fn() + Sync + Send>> = Once::new();
|
||||||
|
|
||||||
fn init_tsc_freq() {
|
/// Determine if the current system supports tsc_deadline mode APIC timer
|
||||||
let tsc_freq = determine_tsc_freq_via_cpuid()
|
fn is_tsc_deadline_mode_supported() -> bool {
|
||||||
.map_or(determine_tsc_freq_via_pit(), |freq| freq as u64 * 1000);
|
const TSC_DEADLINE_MODE_SUPPORT: u32 = 1 << 24;
|
||||||
TSC_FREQ.store(tsc_freq, Ordering::Relaxed);
|
let cpuid = cpuid!(1);
|
||||||
info!("TSC frequency:{:?} Hz", tsc_freq);
|
(cpuid.ecx & TSC_DEADLINE_MODE_SUPPORT) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_tsc_mode() {
|
fn init_tsc_mode() {
|
||||||
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
||||||
// Enable tsc deadline mode.
|
// Enable tsc deadline mode
|
||||||
apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 18));
|
apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 18));
|
||||||
drop(apic_lock);
|
drop(apic_lock);
|
||||||
let tsc_step = TSC_FREQ.load(Ordering::Relaxed) / TIMER_FREQ;
|
let tsc_step = TSC_FREQ.load(Ordering::Relaxed) / TIMER_FREQ;
|
||||||
@ -53,97 +55,54 @@ fn init_tsc_mode() {
|
|||||||
APIC_TIMER_CALLBACK.call_once(|| Arc::new(callback));
|
APIC_TIMER_CALLBACK.call_once(|| Arc::new(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When kernel cannot get the TSC frequency from CPUID, it can leverage
|
fn init_periodic_mode() {
|
||||||
/// the PIT to calculate this frequency.
|
// Allocate IRQ
|
||||||
fn determine_tsc_freq_via_pit() -> u64 {
|
|
||||||
let mut irq = IrqLine::alloc_specific(super::TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
let mut irq = IrqLine::alloc_specific(super::TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
||||||
irq.on_active(pit_callback);
|
irq.on_active(pit_callback);
|
||||||
let mut io_apic = IO_APIC.get().unwrap().first().unwrap().lock();
|
|
||||||
debug_assert_eq!(io_apic.interrupt_base(), 0);
|
|
||||||
io_apic.enable(2, irq.clone()).unwrap();
|
|
||||||
drop(io_apic);
|
|
||||||
|
|
||||||
super::pit::init();
|
// Enable PIT
|
||||||
|
super::pit::init(OperatingMode::RateGenerator);
|
||||||
|
super::pit::enable_ioapic_line(irq.clone());
|
||||||
|
|
||||||
x86_64::instructions::interrupts::enable();
|
// Set APIC timer count
|
||||||
static IS_FINISH: AtomicBool = AtomicBool::new(false);
|
|
||||||
static FREQUENCY: AtomicU64 = AtomicU64::new(0);
|
|
||||||
while !IS_FINISH.load(Ordering::Acquire) {
|
|
||||||
x86_64::instructions::hlt();
|
|
||||||
}
|
|
||||||
x86_64::instructions::interrupts::disable();
|
|
||||||
drop(irq);
|
|
||||||
return FREQUENCY.load(Ordering::Acquire);
|
|
||||||
|
|
||||||
fn pit_callback(trap_frame: &TrapFrame) {
|
|
||||||
static mut IN_TIME: u64 = 0;
|
|
||||||
static mut TSC_FIRST_COUNT: u64 = 0;
|
|
||||||
// Set a certain times of callbacks to calculate the frequency.
|
|
||||||
const CALLBACK_TIMES: u64 = TIMER_FREQ / 10;
|
|
||||||
unsafe {
|
|
||||||
if IN_TIME < CALLBACK_TIMES || IS_FINISH.load(Ordering::Acquire) {
|
|
||||||
// drop the first entry, since it may not be the time we want
|
|
||||||
if IN_TIME == 0 {
|
|
||||||
TSC_FIRST_COUNT = _rdtsc();
|
|
||||||
}
|
|
||||||
IN_TIME += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut io_apic = IO_APIC.get().unwrap().first().unwrap().lock();
|
|
||||||
io_apic.disable(2).unwrap();
|
|
||||||
drop(io_apic);
|
|
||||||
let tsc_count = _rdtsc();
|
|
||||||
let freq = (tsc_count - TSC_FIRST_COUNT) * (TIMER_FREQ / CALLBACK_TIMES);
|
|
||||||
FREQUENCY.store(freq, Ordering::Release);
|
|
||||||
}
|
|
||||||
IS_FINISH.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_periodic_mode() {
|
|
||||||
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
||||||
let mut irq = IrqLine::alloc_specific(super::TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
|
||||||
irq.on_active(init_function);
|
|
||||||
let mut io_apic = IO_APIC.get().unwrap().first().unwrap().lock();
|
|
||||||
debug_assert_eq!(io_apic.interrupt_base(), 0);
|
|
||||||
io_apic.enable(2, irq.clone()).unwrap();
|
|
||||||
drop(io_apic);
|
|
||||||
// divide by 64
|
|
||||||
apic_lock.set_timer_div_config(DivideConfig::Divide64);
|
apic_lock.set_timer_div_config(DivideConfig::Divide64);
|
||||||
apic_lock.set_timer_init_count(0xFFFF_FFFF);
|
apic_lock.set_timer_init_count(0xFFFF_FFFF);
|
||||||
drop(apic_lock);
|
drop(apic_lock);
|
||||||
super::pit::init();
|
|
||||||
// wait until it is finish
|
|
||||||
x86_64::instructions::interrupts::enable();
|
|
||||||
static IS_FINISH: AtomicBool = AtomicBool::new(false);
|
static IS_FINISH: AtomicBool = AtomicBool::new(false);
|
||||||
|
x86_64::instructions::interrupts::enable();
|
||||||
while !IS_FINISH.load(Ordering::Acquire) {
|
while !IS_FINISH.load(Ordering::Acquire) {
|
||||||
x86_64::instructions::hlt();
|
x86_64::instructions::hlt();
|
||||||
}
|
}
|
||||||
x86_64::instructions::interrupts::disable();
|
x86_64::instructions::interrupts::disable();
|
||||||
drop(irq);
|
drop(irq);
|
||||||
|
|
||||||
fn init_function(trap_frame: &TrapFrame) {
|
fn pit_callback(trap_frame: &TrapFrame) {
|
||||||
static mut IN_TIME: u8 = 0;
|
static IN_TIME: AtomicU64 = AtomicU64::new(0);
|
||||||
static mut FIRST_TIME_COUNT: u64 = 0;
|
static APIC_FIRST_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
unsafe {
|
// Set a certain times of callbacks to calculate the frequency
|
||||||
if IS_FINISH.load(Ordering::Acquire) || IN_TIME == 0 {
|
const CALLBACK_TIMES: u64 = TIMER_FREQ / 10;
|
||||||
// drop the first entry, since it may not be the time we want
|
|
||||||
IN_TIME += 1;
|
if IN_TIME.load(Ordering::Relaxed) < CALLBACK_TIMES || IS_FINISH.load(Ordering::Acquire) {
|
||||||
|
if IN_TIME.load(Ordering::Relaxed) == 0 {
|
||||||
let apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
let apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
||||||
let remain_ticks = apic_lock.timer_current_count();
|
let remain_ticks = apic_lock.timer_current_count();
|
||||||
FIRST_TIME_COUNT = 0xFFFF_FFFF - remain_ticks;
|
APIC_FIRST_COUNT.store(0xFFFF_FFFF - remain_ticks, Ordering::Relaxed);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
IN_TIME.fetch_add(1, Ordering::Relaxed);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let mut io_apic = IO_APIC.get().unwrap().first().unwrap().lock();
|
|
||||||
io_apic.disable(2).unwrap();
|
// Stop PIT and APIC Timer
|
||||||
drop(io_apic);
|
super::pit::disable_ioapic_line();
|
||||||
// stop APIC Timer, get the number of tick we need
|
|
||||||
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
let mut apic_lock = APIC_INSTANCE.get().unwrap().lock();
|
||||||
let remain_ticks = apic_lock.timer_current_count();
|
let remain_ticks = apic_lock.timer_current_count();
|
||||||
apic_lock.set_timer_init_count(0);
|
apic_lock.set_timer_init_count(0);
|
||||||
let ticks = unsafe { 0xFFFF_FFFF - remain_ticks - FIRST_TIME_COUNT };
|
|
||||||
// periodic mode, divide 64, freq: TIMER_FREQ Hz
|
// Init APIC Timer
|
||||||
|
let ticks = (0xFFFF_FFFF - remain_ticks - APIC_FIRST_COUNT.load(Ordering::Relaxed))
|
||||||
|
/ CALLBACK_TIMES;
|
||||||
apic_lock.set_timer_init_count(ticks);
|
apic_lock.set_timer_init_count(ticks);
|
||||||
apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 17));
|
apic_lock.set_lvt_timer(super::TIMER_IRQ_NUM.load(Ordering::Relaxed) as u64 | (1 << 17));
|
||||||
apic_lock.set_timer_div_config(DivideConfig::Divide64);
|
apic_lock.set_timer_div_config(DivideConfig::Divide64);
|
||||||
|
@ -12,19 +12,28 @@ use spin::Once;
|
|||||||
use trapframe::TrapFrame;
|
use trapframe::TrapFrame;
|
||||||
|
|
||||||
use crate::arch::x86::kernel;
|
use crate::arch::x86::kernel;
|
||||||
use crate::config::TIMER_FREQ;
|
|
||||||
use crate::sync::SpinLock;
|
use crate::sync::SpinLock;
|
||||||
use crate::trap::IrqLine;
|
use crate::trap::IrqLine;
|
||||||
|
|
||||||
use self::apic::APIC_TIMER_CALLBACK;
|
use self::apic::APIC_TIMER_CALLBACK;
|
||||||
|
|
||||||
|
/// The timer frequency (Hz). Here we choose 1000Hz since 1000Hz is easier for unit conversion and
|
||||||
|
/// convenient for timer. What's more, the frequency cannot be set too high or too low, 1000Hz is
|
||||||
|
/// a modest choice.
|
||||||
|
///
|
||||||
|
/// For system performance reasons, this rate cannot be set too high, otherwise most of the time
|
||||||
|
/// is spent executing timer code.
|
||||||
|
///
|
||||||
|
/// Due to hardware limitations, this value cannot be set too low; for example, PIT cannot accept
|
||||||
|
/// frequencies lower than 19Hz = 1193182 / 65536 (Timer rate / Divider)
|
||||||
|
pub const TIMER_FREQ: u64 = 1000;
|
||||||
|
|
||||||
pub static TIMER_IRQ_NUM: AtomicU8 = AtomicU8::new(32);
|
pub static TIMER_IRQ_NUM: AtomicU8 = AtomicU8::new(32);
|
||||||
pub static TICK: AtomicU64 = AtomicU64::new(0);
|
pub static TICK: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
static TIMER_IRQ: Once<IrqLine> = Once::new();
|
static TIMER_IRQ: Once<IrqLine> = Once::new();
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
TIMEOUT_LIST.call_once(|| SpinLock::new(BinaryHeap::new()));
|
|
||||||
if kernel::apic::APIC_INSTANCE.is_completed() {
|
if kernel::apic::APIC_INSTANCE.is_completed() {
|
||||||
// Get the free irq number first. Use `allocate_target_irq` to get the Irq handle after dropping it.
|
// Get the free irq number first. Use `allocate_target_irq` to get the Irq handle after dropping it.
|
||||||
// Because the function inside `apic::init` will allocate this irq.
|
// Because the function inside `apic::init` will allocate this irq.
|
||||||
@ -33,8 +42,9 @@ pub fn init() {
|
|||||||
drop(irq);
|
drop(irq);
|
||||||
apic::init();
|
apic::init();
|
||||||
} else {
|
} else {
|
||||||
pit::init();
|
pit::init(pit::OperatingMode::SquareWaveGenerator);
|
||||||
};
|
};
|
||||||
|
TIMEOUT_LIST.call_once(|| SpinLock::new(BinaryHeap::new()));
|
||||||
let mut timer_irq = IrqLine::alloc_specific(TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
let mut timer_irq = IrqLine::alloc_specific(TIMER_IRQ_NUM.load(Ordering::Relaxed)).unwrap();
|
||||||
timer_irq.on_active(timer_callback);
|
timer_irq.on_active(timer_callback);
|
||||||
TIMER_IRQ.call_once(|| timer_irq);
|
TIMER_IRQ.call_once(|| timer_irq);
|
||||||
|
@ -1,21 +1,191 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
//! used for PIT Timer
|
//! The Programmable Interval Timer (PIT) chip (Intel 8253/8254) basically consists of an oscillator,
|
||||||
|
//! a prescaler and 3 independent frequency dividers. Each frequency divider has an output, which is
|
||||||
|
//! used to allow the timer to control external circuitry (for example, IRQ 0).
|
||||||
|
//!
|
||||||
|
//! Reference: https://wiki.osdev.org/Programmable_Interval_Timer
|
||||||
|
//!
|
||||||
|
|
||||||
use crate::config::TIMER_FREQ;
|
use crate::{
|
||||||
|
arch::{
|
||||||
|
kernel::IO_APIC,
|
||||||
|
timer::TIMER_FREQ,
|
||||||
|
x86::device::io_port::{IoPort, WriteOnlyAccess},
|
||||||
|
},
|
||||||
|
trap::IrqLine,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::arch::x86::device::io_port::{IoPort, WriteOnlyAccess};
|
/// PIT Operating Mode.
|
||||||
|
///
|
||||||
|
/// Usually, only the rate generator, which is used to determine the base frequency of other timers
|
||||||
|
/// (e.g. APIC Timer), and the Square wave generator, which is used to generate interrupts directly, are used.
|
||||||
|
///
|
||||||
|
/// Note that if IOAPIC is used to manage interrupts and square wave mode is enabled, the frequency at which
|
||||||
|
/// clock interrupts are generated is `Frequency/2`.
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum OperatingMode {
|
||||||
|
/// Triggers an interrupt (only on channel 0) when the counter is terminated (1 -> 0).
|
||||||
|
/// The data port needs to be reset before the next interrupt.
|
||||||
|
/// ```text,ignore
|
||||||
|
/// software reload counter
|
||||||
|
/// ⬇
|
||||||
|
/// +------+ +----
|
||||||
|
/// | | |
|
||||||
|
/// --------------+ +-------------+
|
||||||
|
/// ⬆ ⬆ ⬆
|
||||||
|
/// init() counter 1 -> 0 counter 1 -> 0
|
||||||
|
/// ```
|
||||||
|
InterruptOnTerminalCount = 0b000,
|
||||||
|
/// This mode is similar to `InterruptOnTerminalCount` mode, however counting doesn't start until
|
||||||
|
/// a rising edge of the gate input is detected. For this reason it is not usable for PIT channels
|
||||||
|
/// 0 or 1(where the gate input can't be changed).
|
||||||
|
OneShotHardwareRetriggerable = 0b001,
|
||||||
|
/// Rate generator, which produces a pulse at a fixed frequency.
|
||||||
|
/// ```text,ignore
|
||||||
|
/// init() counter 2 -> 1 counter 2 -> 1
|
||||||
|
/// ⬇ ⬇ ⬇
|
||||||
|
/// --------------+ +-------------+
|
||||||
|
/// | | |
|
||||||
|
/// +--+ +--
|
||||||
|
/// ⬆
|
||||||
|
/// counter 1 -> 0, auto reload counter
|
||||||
|
/// ```
|
||||||
|
RateGenerator = 0b010,
|
||||||
|
/// In this mode, the current count is **decremented twice** on each falling edge of the input signal.
|
||||||
|
/// The output will change state and then set to reload value.
|
||||||
|
/// ```text,ignore
|
||||||
|
/// init() auto reload counter
|
||||||
|
/// ⬇ ⬇
|
||||||
|
/// --------------+ +--------------
|
||||||
|
/// | |
|
||||||
|
/// +--------------+
|
||||||
|
/// ⬆
|
||||||
|
/// auto reload counter
|
||||||
|
/// ```
|
||||||
|
SquareWaveGenerator = 0b011,
|
||||||
|
/// Similar to a Rate generator, but requires a software reset to start counting.
|
||||||
|
/// ```text,ignore
|
||||||
|
/// init() counter: 1 software reload counter
|
||||||
|
/// ⬇ ⬇ ⬇
|
||||||
|
/// --------------+ +---------------------------+ +--
|
||||||
|
/// | | | |
|
||||||
|
/// +-+ +-+
|
||||||
|
/// ⬆
|
||||||
|
/// counter: 0
|
||||||
|
/// ```
|
||||||
|
SoftwareTriggeredStrobe = 0b100,
|
||||||
|
/// This mode is similar to `SoftwareTriggeredStrobe` mode, except that it waits for the rising
|
||||||
|
/// edge of the gate input to trigger (or re-trigger) the delay period (like `OneShotHardwareRetriggerable`
|
||||||
|
/// mode).
|
||||||
|
HardwareTriggeredStrobe = 0b101,
|
||||||
|
// 0b110 -> Rate Generator
|
||||||
|
// 0b111 -> Square Wave Generator
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This bits tell the PIT what access mode is used for the selected channel.
|
||||||
|
#[repr(u8)]
|
||||||
|
enum AccessMode {
|
||||||
|
/// When this command is sent, the current count is copied into latch register which can be read
|
||||||
|
/// through the data port corresponding to the selected channel (I/O ports 0x40 to 0x42).
|
||||||
|
LatchCountValueCommand = 0b00,
|
||||||
|
/// Only the lowest 8 bits of the count value are used in this mode.
|
||||||
|
LowByteOnly = 0b01,
|
||||||
|
/// Only the highest 8 bits of the count value are used in this mode.
|
||||||
|
HighByteOnly = 0b10,
|
||||||
|
/// 16 bits are used in this mode. User should sent the lowest 8 bits followed by the highest 8 bits
|
||||||
|
/// to the same data port.
|
||||||
|
LowAndHighByte = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to select the configured channel in the `MODE_COMMAND_PORT` of the PIT.
|
||||||
|
#[repr(u8)]
|
||||||
|
enum Channel {
|
||||||
|
/// Channel 0. For more details, check `CHANNEL0_PORT` static variable
|
||||||
|
Channel0 = 0b00,
|
||||||
|
/// Channel 1. For more details, check `CHANNEL1_PORT` static variable
|
||||||
|
Channel1 = 0b01,
|
||||||
|
/// Channel 2. For more details, check `CHANNEL2_PORT` static variable
|
||||||
|
Channel2 = 0b10,
|
||||||
|
/// The read back command is a special command sent to the mode/command register.
|
||||||
|
/// The register uses the following format if set to read back command:
|
||||||
|
/// ```text
|
||||||
|
/// Bits Usage
|
||||||
|
/// 7 and 6 Must be set for the read back command
|
||||||
|
/// 5 Latch count flag (0 = latch count, 1 = don't latch count)
|
||||||
|
/// 4 Latch status flag (0 = latch status, 1 = don't latch status)
|
||||||
|
/// 3 Read back timer channel 2 (1 = yes, 0 = no)
|
||||||
|
/// 2 Read back timer channel 1 (1 = yes, 0 = no)
|
||||||
|
/// 1 Read back timer channel 0 (1 = yes, 0 = no)
|
||||||
|
/// 0 Reserved
|
||||||
|
/// ```
|
||||||
|
/// Bits 1 to 3 of the read back command select which PIT channels are affected,
|
||||||
|
/// and allow multiple channels to be selected at the same time.
|
||||||
|
///
|
||||||
|
/// If bit 5 is clear, then any/all PIT channels selected with bits 1 to 3 will
|
||||||
|
/// have their current count copied into their latch register.
|
||||||
|
///
|
||||||
|
/// If bit 4 is clear, then for any/all PIT channels selected with bits 1 to 3,
|
||||||
|
/// the next read of the corresponding data port will return a status byte.
|
||||||
|
///
|
||||||
|
/// Ref: https://wiki.osdev.org/Programmable_Interval_Timer#Read_Back_Command
|
||||||
|
ReadBackCommand = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The output from PIT channel 0 is connected to the PIC chip and generate "IRQ 0".
|
||||||
|
/// If connected to PIC, the IRQ0 will generate by the **rising edge** of the output voltage.
|
||||||
|
static CHANNEL0_PORT: IoPort<u8, WriteOnlyAccess> = unsafe { IoPort::new(0x40) };
|
||||||
|
|
||||||
|
/// The output from PIT channel 1 was once used for refreshing the DRAM or RAM so that
|
||||||
|
/// the capacitors don't forget their state.
|
||||||
|
///
|
||||||
|
/// On later machines, the DRAM refresh is done with dedicated hardware and this channel
|
||||||
|
/// is no longer used.
|
||||||
|
#[allow(unused)]
|
||||||
|
static CHANNEL1_PORT: IoPort<u8, WriteOnlyAccess> = unsafe { IoPort::new(0x41) };
|
||||||
|
|
||||||
|
/// The output from PIT channel 2 is connected to the PC speaker, so the frequency of the
|
||||||
|
/// output determines the frequency of the sound produced by the speaker. For more information,
|
||||||
|
/// check https://wiki.osdev.org/PC_Speaker.
|
||||||
|
#[allow(unused)]
|
||||||
|
static CHANNEL2_PORT: IoPort<u8, WriteOnlyAccess> = unsafe { IoPort::new(0x42) };
|
||||||
|
|
||||||
|
/// PIT command port.
|
||||||
|
/// ```text
|
||||||
|
/// Bits Usage
|
||||||
|
/// 6 and 7 channel
|
||||||
|
/// 4 and 5 Access mode
|
||||||
|
/// 1 to 3 Operating mode
|
||||||
|
/// 0 BCD/Binary mode: 0 = 16-bit binary, 1 = four-digit BCD
|
||||||
|
/// ```
|
||||||
|
static MODE_COMMAND_PORT: IoPort<u8, WriteOnlyAccess> = unsafe { IoPort::new(0x43) };
|
||||||
const TIMER_RATE: u32 = 1193182;
|
const TIMER_RATE: u32 = 1193182;
|
||||||
|
|
||||||
static TIMER_PERIOD: IoPort<u8, WriteOnlyAccess> = unsafe { IoPort::new(0x40) };
|
pub(crate) fn init(operating_mode: OperatingMode) {
|
||||||
static TIMER_MOD: IoPort<u8, WriteOnlyAccess> = unsafe { IoPort::new(0x43) };
|
// Set PIT mode
|
||||||
static TIMER_SQUARE_WAVE: u8 = 0x34;
|
// Bit 0 is BCD/binary mode, which is always set to binary mode(value: 0)
|
||||||
|
MODE_COMMAND_PORT.write(
|
||||||
|
((operating_mode as u8) << 1)
|
||||||
|
| (AccessMode::LowAndHighByte as u8) << 4
|
||||||
|
| (Channel::Channel0 as u8) << 6,
|
||||||
|
);
|
||||||
|
|
||||||
pub(crate) fn init() {
|
// Set timer frequency
|
||||||
// Initialize timer.
|
const CYCLE: u32 = TIMER_RATE / TIMER_FREQ as u32;
|
||||||
let cycle = TIMER_RATE / TIMER_FREQ as u32;
|
CHANNEL0_PORT.write((CYCLE & 0xFF) as _);
|
||||||
TIMER_MOD.write(TIMER_SQUARE_WAVE);
|
CHANNEL0_PORT.write((CYCLE >> 8) as _);
|
||||||
TIMER_PERIOD.write((cycle & 0xFF) as _);
|
}
|
||||||
TIMER_PERIOD.write((cycle >> 8) as _);
|
|
||||||
|
/// Enable the IOAPIC line that connected to PIC
|
||||||
|
pub(crate) fn enable_ioapic_line(irq: IrqLine) {
|
||||||
|
let mut io_apic = IO_APIC.get().unwrap().first().unwrap().lock();
|
||||||
|
debug_assert_eq!(io_apic.interrupt_base(), 0);
|
||||||
|
io_apic.enable(2, irq.clone()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable the IOAPIC line that connected to PIC
|
||||||
|
pub(crate) fn disable_ioapic_line() {
|
||||||
|
let mut io_apic = IO_APIC.get().unwrap().first().unwrap().lock();
|
||||||
|
debug_assert_eq!(io_apic.interrupt_base(), 0);
|
||||||
|
io_apic.disable(2).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,5 @@ pub const PAGE_SIZE_BITS: usize = 0xc;
|
|||||||
pub const KVA_START: usize = (usize::MAX) << PAGE_SIZE_BITS;
|
pub const KVA_START: usize = (usize::MAX) << PAGE_SIZE_BITS;
|
||||||
|
|
||||||
pub const DEFAULT_LOG_LEVEL: Level = Level::Error;
|
pub const DEFAULT_LOG_LEVEL: Level = Level::Error;
|
||||||
/// This value represent the base timer frequency in Hz
|
|
||||||
pub const TIMER_FREQ: u64 = 500;
|
|
||||||
|
|
||||||
pub const REAL_TIME_TASK_PRI: u16 = 100;
|
pub const REAL_TIME_TASK_PRI: u16 = 100;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use super::SpinLock;
|
use super::SpinLock;
|
||||||
use crate::arch::timer::add_timeout_list;
|
use crate::arch::timer::{add_timeout_list, TIMER_FREQ};
|
||||||
use crate::config::TIMER_FREQ;
|
|
||||||
use alloc::{collections::VecDeque, sync::Arc};
|
use alloc::{collections::VecDeque, sync::Arc};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
//! Timer.
|
//! Timer.
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
use crate::arch::timer::{add_timeout_list, TimerCallback, TICK};
|
||||||
use crate::arch::x86::timer::{add_timeout_list, TimerCallback, TICK};
|
use crate::prelude::*;
|
||||||
use crate::sync::SpinLock;
|
use crate::{arch::timer::TIMER_FREQ, sync::SpinLock};
|
||||||
use crate::{config::TIMER_FREQ, prelude::*};
|
|
||||||
use core::{sync::atomic::Ordering, time::Duration};
|
use core::{sync::atomic::Ordering, time::Duration};
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
pub use crate::arch::timer::read_monotonic_milli_seconds;
|
||||||
pub use crate::arch::x86::timer::read_monotonic_milli_seconds;
|
|
||||||
|
|
||||||
/// A timer invokes a callback function after a specified span of time elapsed.
|
/// A timer invokes a callback function after a specified span of time elapsed.
|
||||||
///
|
///
|
||||||
|
Reference in New Issue
Block a user