/* * Copyright (C) 2021 Intel Corporation. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #define CAL_MS 10U #define HPET_PERIOD 0x004U #define HPET_CFG 0x010U #define HPET_COUNTER 0x0F0U #define HPET_CFG_ENABLE 0x001UL static uint32_t tsc_khz; static void *hpet_hva; static uint64_t pit_calibrate_tsc(uint32_t cal_ms_arg) { #define PIT_TICK_RATE 1193182U #define PIT_TARGET 0x3FFFU #define PIT_MAX_COUNT 0xFFFFU uint32_t cal_ms = cal_ms_arg; uint32_t initial_pit; uint16_t current_pit; uint32_t max_cal_ms; uint64_t current_tsc; uint8_t initial_pit_high, initial_pit_low; max_cal_ms = ((PIT_MAX_COUNT - PIT_TARGET) * 1000U) / PIT_TICK_RATE; cal_ms = min(cal_ms, max_cal_ms); /* Assume the 8254 delivers 18.2 ticks per second when 16 bits fully * wrap. This is about 1.193MHz or a clock period of 0.8384uSec */ initial_pit = (cal_ms * PIT_TICK_RATE) / 1000U; initial_pit += PIT_TARGET; initial_pit_high = (uint8_t)(initial_pit >> 8U); initial_pit_low = (uint8_t)initial_pit; /* Port 0x43 ==> Control word write; Data 0x30 ==> Select Counter 0, * Read/Write least significant byte first, mode 0, 16 bits. */ pio_write8(0x30U, 0x43U); pio_write8(initial_pit_low, 0x40U); /* Write LSB */ pio_write8(initial_pit_high, 0x40U); /* Write MSB */ current_tsc = rdtsc(); do { /* Port 0x43 ==> Control word write; 0x00 ==> Select * Counter 0, Counter Latch Command, Mode 0; 16 bits */ pio_write8(0x00U, 0x43U); current_pit = (uint16_t)pio_read8(0x40U); /* Read LSB */ current_pit |= (uint16_t)pio_read8(0x40U) << 8U; /* Read MSB */ /* Let the counter count down to PIT_TARGET */ } while (current_pit > PIT_TARGET); current_tsc = rdtsc() - current_tsc; return (current_tsc / cal_ms) * 1000U; } void hpet_init(void) { uint64_t cfg; hpet_hva = parse_hpet(); if (hpet_hva != NULL) { cfg = mmio_read64(hpet_hva + HPET_CFG); if ((cfg & HPET_CFG_ENABLE) == 0UL) { cfg |= HPET_CFG_ENABLE; mmio_write64(cfg, hpet_hva + HPET_CFG); } } } static inline bool is_hpet_capable(void) { return (hpet_hva != NULL); } static inline uint32_t hpet_read(uint32_t offset) { return mmio_read32(hpet_hva + offset); } static inline uint64_t tsc_read_hpet(uint64_t *p) { uint64_t current_tsc; /* read hpet first */ *p = hpet_read(HPET_COUNTER); current_tsc = rdtsc(); return current_tsc; } static uint64_t hpet_calibrate_tsc(uint32_t cal_ms_arg) { uint64_t tsc1, tsc2, hpet1, hpet2; uint64_t delta_tsc, delta_fs; uint64_t rflags, tsc_khz; CPU_INT_ALL_DISABLE(&rflags); tsc1 = tsc_read_hpet(&hpet1); pit_calibrate_tsc(cal_ms_arg); tsc2 = tsc_read_hpet(&hpet2); CPU_INT_ALL_RESTORE(rflags); /* in case counter wrap happened in the low 32 bits */ if (hpet2 <= hpet1) { hpet2 |= (1UL << 32U); } delta_fs = (hpet2 - hpet1) * hpet_read(HPET_PERIOD); delta_tsc = tsc2 - tsc1; /* * FS_PER_S = 10 ^ 15 * * tsc_khz = delta_tsc / (delta_fs / FS_PER_S) / 1000UL; * = delta_tsc / delta_fs * (10 ^ 12) * = (delta_tsc * (10 ^ 6)) / (delta_fs / (10 ^ 6)) */ tsc_khz = (delta_tsc * 1000000UL) / (delta_fs / 1000000UL); return tsc_khz * 1000U; } static uint64_t pit_hpet_calibrate_tsc(uint32_t cal_ms_arg, uint64_t tsc_ref_hz) { uint64_t tsc_hz, delta; if (is_hpet_capable()) { tsc_hz = hpet_calibrate_tsc(cal_ms_arg); } else { tsc_hz = pit_calibrate_tsc(cal_ms_arg); } if (tsc_ref_hz != 0UL) { delta = (tsc_hz * 100UL) / tsc_ref_hz; if ((delta < 95UL) || (delta > 105UL)) { tsc_hz = tsc_ref_hz; } } return tsc_hz; } /* * Determine TSC frequency via CPUID 0x15. */ static uint64_t native_calculate_tsc_cpuid_0x15(void) { uint64_t tsc_hz = 0UL; const struct cpuinfo_x86 *cpu_info = get_pcpu_info(); if (cpu_info->cpuid_level >= 0x15U) { uint32_t eax_denominator, ebx_numerator, ecx_hz, reserved; cpuid_subleaf(0x15U, 0x0U, &eax_denominator, &ebx_numerator, &ecx_hz, &reserved); if ((eax_denominator != 0U) && (ebx_numerator != 0U)) { tsc_hz = ((uint64_t) ecx_hz * ebx_numerator) / eax_denominator; } } return tsc_hz; } /* * Determine TSC frequency via CPUID 0x16. */ static uint64_t native_calculate_tsc_cpuid_0x16(void) { uint64_t tsc_hz = 0UL; const struct cpuinfo_x86 *cpu_info = get_pcpu_info(); if (cpu_info->cpuid_level >= 0x16U) { uint32_t eax_base_mhz, ebx_max_mhz, ecx_bus_mhz, edx; cpuid_subleaf(0x16U, 0x0U, &eax_base_mhz, &ebx_max_mhz, &ecx_bus_mhz, &edx); tsc_hz = (uint64_t) eax_base_mhz * 1000000U; } return tsc_hz; } void calibrate_tsc(void) { uint64_t tsc_hz; tsc_hz = native_calculate_tsc_cpuid_0x15(); if (tsc_hz == 0UL) { tsc_hz = pit_hpet_calibrate_tsc(CAL_MS, native_calculate_tsc_cpuid_0x16()); } tsc_khz = (uint32_t)(tsc_hz / 1000UL); pr_acrnlog("%s: tsc_khz = %ld", __func__, tsc_khz); } uint32_t get_tsc_khz(void) { return tsc_khz; } /* external API */ uint64_t cpu_ticks(void) { return rdtsc(); } uint32_t cpu_tickrate(void) { return tsc_khz; }