mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2025-06-22 15:23:23 +00:00
添加rust重构版本的HPET驱动和tsc驱动,并使用HPET校准tsc频率和cpu总线频率 (#412)
* 添加rust重构版本的HPET驱动和tsc驱动,并使用HPET校准tsc频率和cpu总线频率 * 把hpet.c移动到arch文件夹下
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
use super::setup::setup_arch;
|
||||
use crate::time::TimeArch;
|
||||
|
||||
use super::{driver::tsc::TSCManager, setup::setup_arch, CurrentTimeArch};
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rs_setup_arch() -> i32 {
|
||||
@ -6,3 +8,14 @@ unsafe extern "C" fn rs_setup_arch() -> i32 {
|
||||
.map(|_| 0)
|
||||
.unwrap_or_else(|e| e.to_posix_errno());
|
||||
}
|
||||
|
||||
/// 获取当前的时间戳
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rs_get_cycles() -> u64 {
|
||||
return CurrentTimeArch::get_cycles() as u64;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rs_tsc_get_cpu_khz() -> u64 {
|
||||
return TSCManager::cpu_khz();
|
||||
}
|
||||
|
31
kernel/src/arch/x86_64/driver/c_adapter.rs
Normal file
31
kernel/src/arch/x86_64/driver/c_adapter.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use super::{
|
||||
hpet::{hpet_init, hpet_instance},
|
||||
tsc::TSCManager,
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rs_hpet_init() -> i32 {
|
||||
hpet_init()
|
||||
.map(|_| 0)
|
||||
.unwrap_or_else(|e| e.to_posix_errno())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rs_hpet_enable() -> i32 {
|
||||
hpet_instance()
|
||||
.hpet_enable()
|
||||
.map(|_| 0)
|
||||
.unwrap_or_else(|e| e.to_posix_errno())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rs_tsc_init() -> i32 {
|
||||
TSCManager::init()
|
||||
.map(|_| 0)
|
||||
.unwrap_or_else(|e| e.to_posix_errno())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rs_handle_hpet_irq(timer_num: u32) {
|
||||
hpet_instance().handle_irq(timer_num);
|
||||
}
|
26
kernel/src/arch/x86_64/driver/hpet.c
Normal file
26
kernel/src/arch/x86_64/driver/hpet.c
Normal file
@ -0,0 +1,26 @@
|
||||
#include <common/glib.h>
|
||||
#include <common/kprint.h>
|
||||
#include <driver/interrupt/apic/apic.h>
|
||||
|
||||
extern void rs_handle_hpet_irq(uint32_t timer_num);
|
||||
|
||||
hardware_intr_controller HPET_intr_controller =
|
||||
{
|
||||
.enable = apic_ioapic_enable,
|
||||
.disable = apic_ioapic_disable,
|
||||
.install = apic_ioapic_install,
|
||||
.uninstall = apic_ioapic_uninstall,
|
||||
.ack = apic_ioapic_edge_ack,
|
||||
};
|
||||
|
||||
void HPET_handler(uint64_t number, uint64_t param, struct pt_regs *regs)
|
||||
{
|
||||
rs_handle_hpet_irq(param);
|
||||
}
|
||||
|
||||
void c_hpet_register_irq()
|
||||
{
|
||||
struct apic_IO_APIC_RTE_entry entry;
|
||||
apic_make_rte_entry(&entry, 34, IO_APIC_FIXED, DEST_PHYSICAL, IDLE, POLARITY_HIGH, IRR_RESET, EDGE_TRIGGER, MASKED, 0);
|
||||
irq_register(34, &entry, &HPET_handler, 0, &HPET_intr_controller, "HPET0");
|
||||
}
|
251
kernel/src/arch/x86_64/driver/hpet.rs
Normal file
251
kernel/src/arch/x86_64/driver/hpet.rs
Normal file
@ -0,0 +1,251 @@
|
||||
use core::{
|
||||
ffi::c_void,
|
||||
intrinsics::unlikely,
|
||||
mem::size_of,
|
||||
ptr::NonNull,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use acpi::HpetInfo;
|
||||
|
||||
use crate::{
|
||||
driver::{
|
||||
acpi::acpi_manager,
|
||||
timers::hpet::{HpetRegisters, HpetTimerRegisters},
|
||||
},
|
||||
exception::softirq::{softirq_vectors, SoftirqNumber},
|
||||
kdebug, kerror, kinfo,
|
||||
libs::{
|
||||
rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
volatile::volwrite,
|
||||
},
|
||||
mm::{
|
||||
mmio_buddy::{mmio_pool, MMIOSpaceGuard},
|
||||
PhysAddr,
|
||||
},
|
||||
syscall::SystemError,
|
||||
time::timer::{clock, timer_get_first_expire, update_timer_jiffies},
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
fn c_hpet_register_irq() -> c_void;
|
||||
}
|
||||
|
||||
static mut HPET_INSTANCE: Option<Hpet> = None;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn hpet_instance() -> &'static Hpet {
|
||||
unsafe { HPET_INSTANCE.as_ref().unwrap() }
|
||||
}
|
||||
|
||||
pub struct Hpet {
|
||||
info: HpetInfo,
|
||||
_mmio_guard: MMIOSpaceGuard,
|
||||
inner: RwLock<InnerHpet>,
|
||||
enabled: AtomicBool,
|
||||
}
|
||||
|
||||
struct InnerHpet {
|
||||
registers_ptr: NonNull<HpetRegisters>,
|
||||
timer_registers_ptr: NonNull<HpetTimerRegisters>,
|
||||
}
|
||||
|
||||
impl Hpet {
|
||||
/// HPET0 中断间隔为500us
|
||||
pub const HPET0_INTERVAL_USEC: u64 = 500;
|
||||
|
||||
fn new(mut hpet_info: HpetInfo) -> Result<Self, SystemError> {
|
||||
let paddr = PhysAddr::new(hpet_info.base_address);
|
||||
let map_size = size_of::<HpetRegisters>();
|
||||
let mmio = mmio_pool().create_mmio(map_size)?;
|
||||
unsafe { mmio.map_phys(paddr, map_size)? };
|
||||
let hpet = unsafe {
|
||||
(mmio.vaddr().data() as *const HpetRegisters)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
};
|
||||
let tm_num = hpet.timers_num();
|
||||
kinfo!("HPET has {} timers", tm_num);
|
||||
hpet_info.hpet_number = tm_num as u8;
|
||||
drop(hpet);
|
||||
drop(mmio);
|
||||
if tm_num == 0 {
|
||||
return Err(SystemError::ENODEV);
|
||||
}
|
||||
|
||||
let bytes_to_map = size_of::<HpetRegisters>()
|
||||
+ hpet_info.hpet_number as usize * size_of::<HpetTimerRegisters>();
|
||||
let mmio = mmio_pool().create_mmio(bytes_to_map)?;
|
||||
|
||||
unsafe { mmio.map_phys(paddr, bytes_to_map)? };
|
||||
let ptr = NonNull::new(mmio.vaddr().data() as *mut HpetRegisters).unwrap();
|
||||
let timer_ptr = NonNull::new(
|
||||
(mmio.vaddr().data() + size_of::<HpetRegisters>()) as *mut HpetTimerRegisters,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let hpet = Hpet {
|
||||
info: hpet_info,
|
||||
_mmio_guard: mmio,
|
||||
inner: RwLock::new(InnerHpet {
|
||||
registers_ptr: ptr,
|
||||
timer_registers_ptr: timer_ptr,
|
||||
}),
|
||||
enabled: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
return Ok(hpet);
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.enabled.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// 使能HPET
|
||||
pub(super) fn hpet_enable(&self) -> Result<(), SystemError> {
|
||||
// !!!这里是临时糊代码的,需要在apic重构的时候修改!!!
|
||||
let (inner_guard, regs) = unsafe { self.hpet_regs_mut() };
|
||||
let freq = regs.frequency();
|
||||
kdebug!("HPET frequency: {} Hz", freq);
|
||||
let ticks = Self::HPET0_INTERVAL_USEC * freq / 1000000;
|
||||
if ticks <= 0 || ticks > freq * 8 {
|
||||
kerror!("HPET enable: ticks '{ticks}' is invalid");
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
if unlikely(regs.timers_num() == 0) {
|
||||
return Err(SystemError::ENODEV);
|
||||
}
|
||||
|
||||
unsafe { regs.write_main_counter_value(0) };
|
||||
|
||||
drop(regs);
|
||||
drop(inner_guard);
|
||||
|
||||
let (inner_guard, timer_reg) = unsafe { self.timer_mut(0).ok_or(SystemError::ENODEV) }?;
|
||||
|
||||
let timer_reg = NonNull::new(timer_reg as *mut HpetTimerRegisters).unwrap();
|
||||
|
||||
unsafe {
|
||||
// 设置定时器0为周期定时,边沿触发,默认投递到IO APIC的2号引脚(看conf寄存器的高32bit,哪一位被置1,则可以投递到哪一个I/O apic引脚)
|
||||
volwrite!(timer_reg, config, 0x004c);
|
||||
volwrite!(timer_reg, comparator_value, ticks);
|
||||
}
|
||||
drop(timer_reg);
|
||||
drop(inner_guard);
|
||||
|
||||
// todo!("register irq in C");
|
||||
unsafe { c_hpet_register_irq() };
|
||||
self.enabled.store(true, Ordering::SeqCst);
|
||||
|
||||
let (inner_guard, regs) = unsafe { self.hpet_regs_mut() };
|
||||
|
||||
// 置位旧设备中断路由兼容标志位、定时器组使能标志位
|
||||
unsafe { regs.write_general_config(3) };
|
||||
|
||||
drop(regs);
|
||||
drop(inner_guard);
|
||||
|
||||
kinfo!("HPET enabled");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fn inner(&self) -> RwLockReadGuard<InnerHpet> {
|
||||
self.inner.read()
|
||||
}
|
||||
|
||||
fn inner_mut(&self) -> RwLockWriteGuard<InnerHpet> {
|
||||
self.inner.write()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn timer(&self, index: u8) -> Option<(RwLockReadGuard<InnerHpet>, &HpetTimerRegisters)> {
|
||||
let inner = self.inner();
|
||||
if index >= self.info.hpet_number {
|
||||
return None;
|
||||
}
|
||||
let timer_regs = unsafe {
|
||||
inner
|
||||
.timer_registers_ptr
|
||||
.as_ptr()
|
||||
.add(index as usize)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
};
|
||||
return Some((inner, timer_regs));
|
||||
}
|
||||
|
||||
unsafe fn timer_mut(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> Option<(RwLockWriteGuard<InnerHpet>, &mut HpetTimerRegisters)> {
|
||||
let inner = self.inner_mut();
|
||||
if index >= self.info.hpet_number {
|
||||
return None;
|
||||
}
|
||||
let timer_regs = unsafe {
|
||||
inner
|
||||
.timer_registers_ptr
|
||||
.as_ptr()
|
||||
.add(index as usize)
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
};
|
||||
return Some((inner, timer_regs));
|
||||
}
|
||||
|
||||
unsafe fn hpet_regs(&self) -> (RwLockReadGuard<InnerHpet>, &HpetRegisters) {
|
||||
let inner = self.inner();
|
||||
let regs = unsafe { inner.registers_ptr.as_ref() };
|
||||
return (inner, regs);
|
||||
}
|
||||
|
||||
unsafe fn hpet_regs_mut(&self) -> (RwLockWriteGuard<InnerHpet>, &mut HpetRegisters) {
|
||||
let mut inner = self.inner_mut();
|
||||
let regs = unsafe { inner.registers_ptr.as_mut() };
|
||||
return (inner, regs);
|
||||
}
|
||||
|
||||
pub fn main_counter_value(&self) -> u64 {
|
||||
let (inner_guard, regs) = unsafe { self.hpet_regs() };
|
||||
let value = regs.main_counter_value();
|
||||
drop(regs);
|
||||
drop(inner_guard);
|
||||
return value;
|
||||
}
|
||||
|
||||
pub fn period(&self) -> u64 {
|
||||
let (inner_guard, regs) = unsafe { self.hpet_regs() };
|
||||
let period = regs.counter_clock_period();
|
||||
kdebug!("HPET period: {}", period);
|
||||
drop(regs);
|
||||
drop(inner_guard);
|
||||
return period;
|
||||
}
|
||||
|
||||
/// 处理HPET的中断
|
||||
pub(super) fn handle_irq(&self, timer_num: u32) {
|
||||
if timer_num == 0 {
|
||||
update_timer_jiffies(Self::HPET0_INTERVAL_USEC);
|
||||
|
||||
if let Ok(first_expire) = timer_get_first_expire() {
|
||||
if first_expire <= clock() {
|
||||
softirq_vectors().raise_softirq(SoftirqNumber::TIMER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hpet_init() -> Result<(), SystemError> {
|
||||
let hpet_info = HpetInfo::new(acpi_manager().tables().unwrap()).map_err(|e| {
|
||||
kerror!("Failed to get HPET info: {:?}", e);
|
||||
SystemError::ENODEV
|
||||
})?;
|
||||
|
||||
let hpet_instance = Hpet::new(hpet_info)?;
|
||||
unsafe {
|
||||
HPET_INSTANCE = Some(hpet_instance);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
3
kernel/src/arch/x86_64/driver/mod.rs
Normal file
3
kernel/src/arch/x86_64/driver/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod c_adapter;
|
||||
pub mod hpet;
|
||||
pub mod tsc;
|
384
kernel/src/arch/x86_64/driver/tsc.rs
Normal file
384
kernel/src/arch/x86_64/driver/tsc.rs
Normal file
@ -0,0 +1,384 @@
|
||||
use core::{
|
||||
cmp::{max, min},
|
||||
intrinsics::unlikely,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
arch::{io::PortIOArch, CurrentIrqArch, CurrentPortIOArch, CurrentTimeArch},
|
||||
driver::acpi::pmtmr::{ACPI_PM_OVERRUN, PMTMR_TICKS_PER_SEC},
|
||||
exception::InterruptArch,
|
||||
kdebug, kerror, kinfo, kwarn,
|
||||
syscall::SystemError,
|
||||
time::TimeArch,
|
||||
};
|
||||
|
||||
use super::hpet::hpet_instance;
|
||||
|
||||
/// The clock frequency of the i8253/i8254 PIT
|
||||
const PIT_TICK_RATE: u64 = 1193182;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TSCManager;
|
||||
|
||||
static mut TSC_KHZ: u64 = 0;
|
||||
static mut CPU_KHZ: u64 = 0;
|
||||
|
||||
impl TSCManager {
|
||||
const DEFAULT_THRESHOLD: u64 = 0x20000;
|
||||
|
||||
/// 初始化TSC
|
||||
///
|
||||
/// 目前由于未支持acpi pm timer, 因此调用该函数时,HPET应当完成初始化,否则将无法校准TSC
|
||||
///
|
||||
/// 参考 https://opengrok.ringotek.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#1511
|
||||
pub fn init() -> Result<(), SystemError> {
|
||||
let cpuid = x86::cpuid::CpuId::new();
|
||||
let feat = cpuid.get_feature_info().ok_or(SystemError::ENODEV)?;
|
||||
if !feat.has_tsc() {
|
||||
kerror!("TSC is not available");
|
||||
return Err(SystemError::ENODEV);
|
||||
}
|
||||
|
||||
if unsafe { TSC_KHZ == 0 } {
|
||||
if let Err(e) = Self::determine_cpu_tsc_frequency(false) {
|
||||
kerror!("Failed to determine CPU TSC frequency: {:?}", e);
|
||||
// todo: mark TSC as unstable clock source
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: register TSC as clock source and deal with unstable clock source
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// 获取TSC和CPU总线的频率
|
||||
///
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `early`:是否在早期初始化
|
||||
///
|
||||
/// 参考 https://opengrok.ringotek.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#1438
|
||||
fn determine_cpu_tsc_frequency(early: bool) -> Result<(), SystemError> {
|
||||
if unlikely(Self::cpu_khz() != 0 || Self::tsc_khz() != 0) {
|
||||
kwarn!("TSC and CPU frequency already determined");
|
||||
}
|
||||
|
||||
if early {
|
||||
// todo: 先根据cpuid或者读取msr或者pit来测量TSC和CPU总线的频率
|
||||
todo!("detect TSC and CPU frequency by cpuid or msr or pit");
|
||||
} else {
|
||||
// 使用pit来测量TSC和CPU总线的频率
|
||||
Self::set_cpu_khz(Self::calibrate_cpu_by_pit_hpet_ptimer()?);
|
||||
}
|
||||
|
||||
// 认为非0的TSC频率是可靠的,并且使用它来检查CPU总线的频率
|
||||
if Self::tsc_khz() == 0 {
|
||||
Self::set_tsc_khz(Self::cpu_khz());
|
||||
} else if (Self::cpu_khz() as i64 - Self::tsc_khz() as i64).abs() * 10
|
||||
> Self::cpu_khz() as i64
|
||||
{
|
||||
// 如果TSC和CPU总线的频率相差太大,那么认为CPU总线的频率是不可靠的,使用TSC的频率
|
||||
Self::set_cpu_khz(Self::tsc_khz());
|
||||
}
|
||||
|
||||
if Self::cpu_khz() == 0 {
|
||||
kerror!("Failed to determine CPU frequency");
|
||||
return Err(SystemError::ENODEV);
|
||||
}
|
||||
|
||||
kinfo!(
|
||||
"Detected {}.{} MHz processor",
|
||||
Self::cpu_khz() / 1000,
|
||||
Self::cpu_khz() % 1000
|
||||
);
|
||||
kinfo!(
|
||||
"Detected {}.{} MHz TSC",
|
||||
Self::tsc_khz() / 1000,
|
||||
Self::tsc_khz() % 1000
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// 测量CPU总线的频率
|
||||
///
|
||||
/// 使用pit、hpet、ptimer来测量CPU总线的频率
|
||||
fn calibrate_cpu_by_pit_hpet_ptimer() -> Result<u64, SystemError> {
|
||||
let hpet = hpet_instance().enabled();
|
||||
kdebug!(
|
||||
"Calibrating TSC with {}",
|
||||
if hpet { "HPET" } else { "PMTIMER" }
|
||||
);
|
||||
|
||||
let mut tsc_pit_min = u64::MAX;
|
||||
let mut tsc_ref_min = u64::MAX;
|
||||
|
||||
// 默认的校准参数
|
||||
let cal_ms = 10;
|
||||
let cal_latch = PIT_TICK_RATE / (1000 / cal_ms);
|
||||
let cal_pit_loops = 1000;
|
||||
|
||||
// 如果第一轮校准失败,那么使用这些参数(因为虚拟化平台的问题,第一轮校准可能失败)
|
||||
let cal2_ms = 50;
|
||||
let cal2_latch = PIT_TICK_RATE / (1000 / cal2_ms);
|
||||
let cal2_pit_loops = 5000;
|
||||
|
||||
let mut latch = cal_latch;
|
||||
let mut loopmin = cal_pit_loops;
|
||||
let mut ms = cal_ms;
|
||||
|
||||
let mut global_ref1 = 0;
|
||||
let mut global_ref2 = 0;
|
||||
|
||||
for i in 0..3 {
|
||||
let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() };
|
||||
|
||||
let (tsc1, ref1) = Self::read_refs(hpet);
|
||||
let tsc_pit_khz = Self::pit_calibrate_tsc(latch, ms, loopmin).unwrap_or(u64::MAX);
|
||||
let (tsc2, ref2) = Self::read_refs(hpet);
|
||||
drop(irq_guard);
|
||||
|
||||
global_ref1 = ref1;
|
||||
global_ref2 = ref2;
|
||||
|
||||
// 选用最小的tsc_pit_khz
|
||||
tsc_pit_min = min(tsc_pit_min, tsc_pit_khz);
|
||||
|
||||
// HPET或者PTIMER可能是不可用的
|
||||
if ref1 == ref2 {
|
||||
kdebug!("HPET/PMTIMER not available");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查采样是否被打断
|
||||
if tsc1 == u64::MAX || tsc2 == u64::MAX {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut tsc2 = (tsc2 - tsc1) * 1000000;
|
||||
|
||||
if hpet {
|
||||
tsc2 = Self::calc_hpet_ref(tsc2, ref1, ref2);
|
||||
} else {
|
||||
tsc2 = Self::calc_pmtimer_ref(tsc2, ref1, ref2);
|
||||
}
|
||||
|
||||
tsc_ref_min = min(tsc_ref_min, tsc2);
|
||||
|
||||
// 检查与参考值的误差
|
||||
let mut delta = tsc_pit_min * 100;
|
||||
delta /= tsc_ref_min;
|
||||
|
||||
// 如果误差在10%以内,那么认为测量成功
|
||||
// 返回参考值,因为它是更精确的
|
||||
if delta >= 90 && delta <= 110 {
|
||||
kinfo!(
|
||||
"PIT calibration matches {}. {} loops",
|
||||
if hpet { "HPET" } else { "PMTIMER" },
|
||||
i + 1
|
||||
);
|
||||
return Ok(tsc_ref_min);
|
||||
}
|
||||
|
||||
if i == 1 && tsc_pit_min == u64::MAX {
|
||||
latch = cal2_latch;
|
||||
ms = cal2_ms;
|
||||
loopmin = cal2_pit_loops;
|
||||
}
|
||||
}
|
||||
|
||||
if tsc_pit_min == u64::MAX {
|
||||
kwarn!("Unable to calibrate against PIT");
|
||||
|
||||
// 如果没有参考值,那么禁用tsc
|
||||
if (!hpet) && (global_ref1 == 0) && (global_ref2 == 0) {
|
||||
kwarn!("No reference (HPET/PMTIMER) available");
|
||||
return Err(SystemError::ENODEV);
|
||||
}
|
||||
|
||||
if tsc_ref_min == u64::MAX {
|
||||
kwarn!("Unable to calibrate against HPET/PMTIMER");
|
||||
return Err(SystemError::ENODEV);
|
||||
}
|
||||
|
||||
kinfo!(
|
||||
"Using {} reference calibration",
|
||||
if hpet { "HPET" } else { "PMTIMER" }
|
||||
);
|
||||
return Ok(tsc_ref_min);
|
||||
}
|
||||
|
||||
// We don't have an alternative source, use the PIT calibration value
|
||||
if (!hpet) && (global_ref1 == 0) && (global_ref2 == 0) {
|
||||
kinfo!("Using PIT calibration value");
|
||||
return Ok(tsc_pit_min);
|
||||
}
|
||||
|
||||
// The alternative source failed, use the PIT calibration value
|
||||
if tsc_ref_min == u64::MAX {
|
||||
kwarn!("Unable to calibrate against HPET/PMTIMER, using PIT calibration value");
|
||||
return Ok(tsc_pit_min);
|
||||
}
|
||||
|
||||
// The calibration values differ too much. In doubt, we use
|
||||
// the PIT value as we know that there are PMTIMERs around
|
||||
// running at double speed. At least we let the user know:
|
||||
kwarn!(
|
||||
"PIT calibration deviates from {}: tsc_pit_min={}, tsc_ref_min={}",
|
||||
if hpet { "HPET" } else { "PMTIMER" },
|
||||
tsc_pit_min,
|
||||
tsc_ref_min
|
||||
);
|
||||
|
||||
kinfo!("Using PIT calibration value");
|
||||
return Ok(tsc_pit_min);
|
||||
}
|
||||
|
||||
/// 尝试使用PIT来校准tsc时间,并且返回tsc的频率(khz)。
|
||||
/// 如果失败,那么返回None
|
||||
///
|
||||
/// 参考 https://opengrok.ringotek.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#389
|
||||
fn pit_calibrate_tsc(latch: u64, ms: u64, loopmin: u64) -> Option<u64> {
|
||||
unsafe {
|
||||
// Set the Gate high, disable speaker
|
||||
let d = (CurrentPortIOArch::in8(0x61) & (!0x02)) | 0x01;
|
||||
CurrentPortIOArch::out8(0x61, d);
|
||||
|
||||
// Setup CTC channel 2* for mode 0, (interrupt on terminal
|
||||
// count mode), binary count. Set the latch register to 50ms
|
||||
// (LSB then MSB) to begin countdown.
|
||||
CurrentPortIOArch::out8(0x43, 0xb0);
|
||||
CurrentPortIOArch::out8(0x42, (latch & 0xff) as u8);
|
||||
CurrentPortIOArch::out8(0x42, ((latch >> 8) & 0xff) as u8);
|
||||
}
|
||||
|
||||
let mut tsc = CurrentTimeArch::get_cycles() as u64;
|
||||
let t1 = tsc;
|
||||
let mut t2 = tsc;
|
||||
let mut pitcnt = 0u64;
|
||||
let mut tscmax = 0u64;
|
||||
let mut tscmin = u64::MAX;
|
||||
while unsafe { (CurrentPortIOArch::in8(0x61) & 0x20) == 0 } {
|
||||
t2 = CurrentTimeArch::get_cycles() as u64;
|
||||
let delta = t2 - tsc;
|
||||
tsc = t2;
|
||||
|
||||
tscmin = min(tscmin, delta);
|
||||
tscmax = max(tscmax, delta);
|
||||
|
||||
pitcnt += 1;
|
||||
}
|
||||
|
||||
// Sanity checks:
|
||||
//
|
||||
// If we were not able to read the PIT more than loopmin
|
||||
// times, then we have been hit by a massive SMI
|
||||
//
|
||||
// If the maximum is 10 times larger than the minimum,
|
||||
// then we got hit by an SMI as well.
|
||||
if pitcnt < loopmin || tscmax > 10 * tscmin {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut delta = t2 - t1;
|
||||
delta /= ms;
|
||||
|
||||
return Some(delta);
|
||||
}
|
||||
|
||||
/// 读取tsc和参考值
|
||||
///
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `hpet_enabled`:是否启用hpet
|
||||
///
|
||||
/// ## 返回
|
||||
///
|
||||
/// - `Ok((tsc, ref))`:tsc和参考值
|
||||
///
|
||||
/// 参考 https://opengrok.ringotek.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#317
|
||||
fn read_refs(hpet_enabled: bool) -> (u64, u64) {
|
||||
let thresh = if Self::tsc_khz() == 0 {
|
||||
Self::DEFAULT_THRESHOLD
|
||||
} else {
|
||||
Self::tsc_khz() >> 5
|
||||
};
|
||||
|
||||
let mut ref_ret = 0;
|
||||
for _ in 0..5 {
|
||||
let t1 = CurrentTimeArch::get_cycles() as u64;
|
||||
if hpet_enabled {
|
||||
ref_ret = hpet_instance().main_counter_value();
|
||||
} else {
|
||||
todo!("read pmtimer")
|
||||
}
|
||||
let t2 = CurrentTimeArch::get_cycles() as u64;
|
||||
if (t2 - t1) < thresh {
|
||||
return (t2, ref_ret);
|
||||
}
|
||||
}
|
||||
|
||||
kwarn!("TSCManager: Failed to read reference value, tsc delta too high");
|
||||
return (u64::MAX, ref_ret);
|
||||
}
|
||||
|
||||
/// 根据HPET的参考值计算tsc的频率
|
||||
///
|
||||
/// https://opengrok.ringotek.cn/xref/linux-6.1.9/arch/x86/kernel/tsc.c#339
|
||||
fn calc_hpet_ref(mut deltatsc: u64, ref1: u64, mut ref2: u64) -> u64 {
|
||||
if ref2 <= ref1 {
|
||||
ref2 += 0x100000000;
|
||||
}
|
||||
|
||||
ref2 -= ref1;
|
||||
let mut tmp = ref2 * hpet_instance().period();
|
||||
|
||||
tmp /= 1000000;
|
||||
|
||||
deltatsc /= tmp;
|
||||
|
||||
return deltatsc;
|
||||
}
|
||||
|
||||
/// 根据PMtimer的参考值计算tsc的频率
|
||||
fn calc_pmtimer_ref(mut deltatsc: u64, ref1: u64, mut ref2: u64) -> u64 {
|
||||
if unlikely(ref1 == 0 && ref2 == 0) {
|
||||
return u64::MAX;
|
||||
}
|
||||
|
||||
if ref2 < ref1 {
|
||||
ref2 += ACPI_PM_OVERRUN;
|
||||
}
|
||||
|
||||
ref2 -= ref1;
|
||||
|
||||
let mut tmp = ref2 * 1000000000;
|
||||
|
||||
tmp /= PMTMR_TICKS_PER_SEC;
|
||||
|
||||
deltatsc /= tmp;
|
||||
|
||||
return deltatsc;
|
||||
}
|
||||
|
||||
pub fn tsc_khz() -> u64 {
|
||||
unsafe { TSC_KHZ }
|
||||
}
|
||||
|
||||
pub fn cpu_khz() -> u64 {
|
||||
unsafe { CPU_KHZ }
|
||||
}
|
||||
|
||||
fn set_cpu_khz(khz: u64) {
|
||||
unsafe {
|
||||
CPU_KHZ = khz;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_tsc_khz(khz: u64) {
|
||||
unsafe {
|
||||
TSC_KHZ = khz;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ pub mod asm;
|
||||
mod acpi;
|
||||
mod c_adapter;
|
||||
pub mod cpu;
|
||||
pub mod driver;
|
||||
pub mod fpu;
|
||||
pub mod interrupt;
|
||||
pub mod ipc;
|
||||
@ -17,6 +18,7 @@ pub mod sched;
|
||||
pub mod setup;
|
||||
pub mod smp;
|
||||
pub mod syscall;
|
||||
pub mod time;
|
||||
|
||||
pub use self::pci::pci::X86_64PciArch as PciArch;
|
||||
|
||||
@ -29,3 +31,4 @@ pub use crate::arch::asm::pio::X86_64PortIOArch as CurrentPortIOArch;
|
||||
pub use kvm::X86_64KVMArch as KVMArch;
|
||||
|
||||
pub use crate::arch::ipc::signal::X86_64SignalArch as CurrentSignalArch;
|
||||
pub use crate::arch::time::X86_64TimeArch as CurrentTimeArch;
|
||||
|
9
kernel/src/arch/x86_64/time.rs
Normal file
9
kernel/src/arch/x86_64/time.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use crate::time::TimeArch;
|
||||
|
||||
pub struct X86_64TimeArch;
|
||||
|
||||
impl TimeArch for X86_64TimeArch {
|
||||
fn get_cycles() -> usize {
|
||||
unsafe { x86::time::rdtsc() as usize }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user