Re: [PATCH] hv: tsc: calibrate TSC by HPET


Zhao, Yakui
 

On 2022/7/7 15:47, Chen, Jian Jun wrote:
On some platforms CPUID.0x15:ECX is zero and CPUID.0x16 can
only return the TSC frequency in MHZ which is not accurate > This patch adds the support of using HPET to calibrate TSC
when HPET is available which is more accurate than PIT.
Tracked-On: #7876
Signed-off-by: Jian Jun Chen <jian.jun.chen@...>
---
hypervisor/arch/x86/tsc.c | 119 +++++++++++++++++++++++++++++++--
hypervisor/boot/acpi_base.c | 12 ++++
hypervisor/boot/include/acpi.h | 11 +++
3 files changed, 136 insertions(+), 6 deletions(-)
diff --git a/hypervisor/arch/x86/tsc.c b/hypervisor/arch/x86/tsc.c
index 4f3aaec74..ef794ffc7 100644
--- a/hypervisor/arch/x86/tsc.c
+++ b/hypervisor/arch/x86/tsc.c
@@ -10,10 +10,16 @@
#include <asm/cpu_caps.h>
#include <asm/io.h>
#include <asm/tsc.h>
+#include <asm/cpu.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 +71,94 @@ static uint64_t pit_calibrate_tsc(uint32_t cal_ms_arg)
return (current_tsc / cal_ms) * 1000U;
}
+static inline void hpet_init(void)
+{
+ if (hpet_hva == NULL) {
+ 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) & 0xFFFFFFFFU;
+ current_tsc = rdtsc();
+
+ return current_tsc;
+}
+
+static uint64_t calc_tsc_by_hpet(uint64_t tsc1, uint64_t hpet1,
+ uint64_t tsc2, uint64_t hpet2)
+{
+ uint64_t delta_tsc, delta_fs, tsc_khz;
+
+ if (hpet2 < hpet1) {
+ hpet2 += 0x100000000ULL;
+ }
+ 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 hpet_calibrate_tsc(uint32_t cal_ms_arg, uint64_t tsc_ref_hz)
+{
+ uint64_t tsc1, tsc2, hpet1, hpet2, delta;
+ uint64_t tsc_pit_hz = 0UL, tsc_hpet_hz, ret_tsc_hz = 0UL;
+ uint64_t rflags;
+ uint32_t i;
+
+ for (i = 0U; i < 3U; i++) {
+ CPU_INT_ALL_DISABLE(&rflags);
+ tsc1 = tsc_read_hpet(&hpet1);
+ tsc_pit_hz = pit_calibrate_tsc(cal_ms_arg);
+ tsc2 = tsc_read_hpet(&hpet2);
+ CPU_INT_ALL_RESTORE(rflags);
+
+ tsc_hpet_hz = calc_tsc_by_hpet(tsc1, hpet1, tsc2, hpet2);
+ if (tsc_ref_hz != 0UL) {
+ delta = (tsc_hpet_hz * 100UL) / tsc_ref_hz;
Should the delta be limited to [99,101] for more accuracy?
+ if (delta >= 95UL && delta <= 105UL) {
+ ret_tsc_hz = tsc_hpet_hz;
+ break;
+ }
+ } else {
+ ret_tsc_hz = tsc_hpet_hz;
+ break;
+ }
+ }
+
+ /* if hpet calibraion failed, use tsc_pit_hz */
+ if (ret_tsc_hz == 0UL) {
+ ret_tsc_hz = tsc_pit_hz;
+ }
+
+ return ret_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_calibrate_tsc_cpuid_0x15(void)
{
uint64_t tsc_hz = 0UL;
const struct cpuinfo_x86 *cpu_info = get_pcpu_info();
@@ -85,7 +175,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_calibrate_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,9 +200,15 @@ void calibrate_tsc(void)
{
uint64_t tsc_hz;
- tsc_hz = native_calibrate_tsc();
- if (tsc_hz == 0U) {
- tsc_hz = pit_calibrate_tsc(CAL_MS);
+ hpet_init();
+ tsc_hz = native_calibrate_tsc_cpuid_0x15();
+ if (tsc_hz == 0UL) {
+ tsc_hz = native_calibrate_tsc_cpuid_0x16();
+ if (is_hpet_enabled()) {
+ tsc_hz = hpet_calibrate_tsc(CAL_MS, tsc_hz);
If the tsc_hz is zero, maybe it can't be used as the parameter of hpet_calibrate_tsc. Then the GP will be triggered in function of hpet_calibrate_tsc.
Can we change the logic as the below?
check the tsc_hz from cpuid_0x16
check the tsc_hpet_hz based on hpet_calibration
Compare delta between tsc_hpet_hz and tsc_hz to select one approrpiate tsc_hz
If tsc_hz is still zero, fallback to PIT again.
+ } else {
+ tsc_hz = pit_calibrate_tsc(CAL_MS);
+ }
}
tsc_khz = (uint32_t)(tsc_hz / 1000UL);
}
diff --git a/hypervisor/boot/acpi_base.c b/hypervisor/boot/acpi_base.c
index 75d2c105a..83b104074 100644
--- a/hypervisor/boot/acpi_base.c
+++ b/hypervisor/boot/acpi_base.c
@@ -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);
+}
diff --git a/hypervisor/boot/include/acpi.h b/hypervisor/boot/include/acpi.h
index 7028e0ece..e7f10c39c 100644
--- a/hypervisor/boot/include/acpi.h
+++ b/hypervisor/boot/include/acpi.h
@@ -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);
--
2.35.1

Join acrn-dev@lists.projectacrn.org to automatically receive all group messages.