mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-05-02 21:46:58 +00:00
On some platforms CPUID.0x15:ECX is zero and CPUID.0x16 can only return the TSC frequency in MHZ which is not accurate. For example the TSC frequency obtained by CPUID.0x16 is 2300 MHZ and the TSC frequency calibrated by HPET is 2303.998 MHZ which is much closer to the actual TSC frequency 2304.000 MHZ. This patch adds the support of using HPET to calibrate TSC when HPET is available and CPUID.0x15:ECX is zero. v3->v4: - move calc_tsc_by_hpet into hpet_calibrate_tsc v2->v3: - remove the NULL check in hpet_init - remove ""& 0xFFFFFFFFU" in tsc_read_hpet - add comment for the counter wrap in the low 32 bits in calc_tsc_by_hpet - use a dedicated function for hpet_calibrate_tsc v1->v2: - change native_calibrate_tsc_cpuid_0x15/0x16 to native_calculate_tsc_cpuid_0x15/0x16 - move hpet_init to BSP init - encapsulate both HPET and PIT calibration to one function - revise the commit message with an example" Tracked-On: #7876 Signed-off-by: Jian Jun Chen <jian.jun.chen@intel.com> Reviewed-by: Fei Li <fei1.li@intel.com>
219 lines
4.8 KiB
C
219 lines
4.8 KiB
C
/*
|
|
* Copyright (C) 2021 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <types.h>
|
|
#include <util.h>
|
|
#include <asm/cpuid.h>
|
|
#include <asm/cpu_caps.h>
|
|
#include <asm/io.h>
|
|
#include <asm/tsc.h>
|
|
#include <asm/cpu.h>
|
|
#include <logmsg.h>
|
|
#include <acpi.h>
|
|
|
|
#define CAL_MS 10U
|
|
|
|
#define HPET_PERIOD 0x004U
|
|
#define HPET_COUNTER 0x0F0U
|
|
|
|
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)
|
|
{
|
|
hpet_hva = parse_hpet();
|
|
}
|
|
|
|
static inline bool is_hpet_enabled(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_enabled()) {
|
|
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;
|
|
}
|