|
@@ -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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|