hv: tsc: calibrate TSC by HPET

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>
This commit is contained in:
Jian Jun Chen 2022-07-05 15:36:41 +08:00 committed by acrnsi-robot
parent 047a11dff3
commit 97a2919138
5 changed files with 133 additions and 6 deletions

View File

@ -235,6 +235,9 @@ void init_pcpu_post(uint16_t pcpu_id)
/* Print Hypervisor Banner */
print_hv_banner();
/* Initialie HPET */
hpet_init();
/* Calibrate TSC Frequency */
calibrate_tsc();

View File

@ -10,10 +10,17 @@
#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)
{
@ -65,10 +72,85 @@ static uint64_t pit_calibrate_tsc(uint32_t cal_ms_arg)
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 and 0x16.
* Determine TSC frequency via CPUID 0x15.
*/
static uint64_t native_calibrate_tsc(void)
static uint64_t native_calculate_tsc_cpuid_0x15(void)
{
uint64_t tsc_hz = 0UL;
const struct cpuinfo_x86 *cpu_info = get_pcpu_info();
@ -85,7 +167,18 @@ static uint64_t native_calibrate_tsc(void)
}
}
if ((tsc_hz == 0UL) && (cpu_info->cpuid_level >= 0x16U)) {
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);
@ -99,11 +192,12 @@ void calibrate_tsc(void)
{
uint64_t tsc_hz;
tsc_hz = native_calibrate_tsc();
if (tsc_hz == 0U) {
tsc_hz = pit_calibrate_tsc(CAL_MS);
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)

View File

@ -244,3 +244,15 @@ uint8_t parse_madt_ioapic(struct ioapic_info *ioapic_id_array)
return ioapic_idx;
}
void *parse_hpet(void)
{
const struct acpi_table_hpet *hpet = (const struct acpi_table_hpet *)get_acpi_tbl(ACPI_SIG_HPET);
uint64_t addr = 0UL;
if (hpet != NULL) {
addr = hpet->address.address;
}
return hpa2hva(addr);
}

View File

@ -59,6 +59,7 @@
#define ACPI_SIG_TPM2 "TPM2" /* Trusted Platform Module hardware interface table */
#define ACPI_SIG_RTCT "PTCT" /* Platform Tuning Configuration Table (Real-Time Configuration Table) */
#define ACPI_SIG_RTCT_V2 "RTCT" /* Platform Tuning Configuration Table (Real-Time Configuration Table) V2 */
#define ACPI_SIG_HPET "HPET" /* High Precision Event Timer table */
struct packed_gas {
uint8_t space_id;
@ -242,12 +243,22 @@ struct acpi_table_tpm2 {
#endif
} __packed;
struct acpi_table_hpet {
struct acpi_table_header header;
uint32_t id;
struct packed_gas address;
uint8_t sequence;
uint16_t minimum_tick;
uint8_t flags;
} __packed;
void init_acpi(void);
void *get_acpi_tbl(const char *signature);
struct ioapic_info;
uint16_t parse_madt(uint32_t lapic_id_array[MAX_PCPU_NUM]);
uint8_t parse_madt_ioapic(struct ioapic_info *ioapic_id_array);
void *parse_hpet(void);
#ifdef CONFIG_ACPI_PARSE_ENABLED
int32_t acpi_fixup(void);

View File

@ -41,4 +41,11 @@ uint32_t get_tsc_khz(void);
*/
void calibrate_tsc(void);
/**
* @brief Initialize HPET.
*
* @return None
*/
void hpet_init(void);
#endif /* ARCH_X86_TSC_H */