arch/x86_64: Implement TSC frequency query via CPUID 0x40000010

This commit introduces support for querying TSC frequency using CPUID 0x40000010. This function can be tested with the following command: `sudo qemu-system-x86_64 -enable-kvm -cpu host,+invtsc,+vmware-cpuid-freq -m 2G -kernel nuttx -nographic -serial mon:stdio -s`

Signed-off-by: ouyangxiangzhen <ouyangxiangzhen@xiaomi.com>
This commit is contained in:
ouyangxiangzhen 2025-02-21 18:00:37 +08:00 committed by Mateusz Szafoni
parent c009c386cc
commit 1ea3c01062
4 changed files with 96 additions and 21 deletions

View file

@ -240,6 +240,7 @@
# define X86_64_CPUID_07_AVX512VL (1 << 31)
#define X86_64_CPUID_XSAVE 0x0d
#define X86_64_CPUID_TSC 0x15
#define X86_64_CPUID_TSC_VMWARE 0x40000010
#define X86_64_CPUID_EXTINFO 0x80000001
# define X86_64_CPUID_EXTINFO_RDTSCP (1 << 27)

View file

@ -193,6 +193,16 @@ extern uint8_t _etbss[]; /* End+1 of .tbss */
* Inline Functions
****************************************************************************/
static inline void x86_64_cpuid(uint32_t leaf, uint32_t subleaf,
uint32_t *eax, uint32_t *ebx,
uint32_t *ecx, uint32_t *edx)
{
__asm__ volatile("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "a" (leaf), "c" (subleaf)
: "memory");
}
#ifdef CONFIG_ARCH_KERNEL_STACK
static inline_function uint64_t *x86_64_get_ktopstk(void)
{

View file

@ -88,6 +88,13 @@ config ARCH_INTEL64_CORE_FREQ_KHZ
to set the TSC deadline timer frequency. If set to 0 we try to get
frequency from CPUID.
config ARCH_INTEL64_TSC_FREQ_VMWARE
bool "Use CPUID 0x40000010 to get CPU Core frequency"
default n
depends on ARCH_INTEL64_CORE_FREQ_KHZ = 0
---help---
Use CPUID 0x40000010 defined by VMware to get CPU Core frequency
in virtualized environments.
endif
if ARCH_INTEL64_TSC

View file

@ -36,12 +36,74 @@
#include <nuttx/board.h>
#include <arch/board/board.h>
#include "x86_64_internal.h"
/****************************************************************************
* Public Data
****************************************************************************/
unsigned long g_x86_64_timer_freq;
/****************************************************************************
* Inline Functions
****************************************************************************/
static inline uint64_t x86_64_timer_tsc_freq_vmware(void)
{
uint32_t eax_tsc;
uint32_t ebx_apic;
uint32_t ecx;
uint32_t edx;
/* CPUID Leaf 0x40000010, Timing Information.
* Timing information leaf first defined by VMware.
* It is also adopted by many hypervisors such as ACRN Hypervisor.
* The leaf returns the TSC frequency and APIC frequency.
* EAX - TSC frequency in kHz.
* EBX - APIC frequency in kHz.
*/
x86_64_cpuid(X86_64_CPUID_TSC_VMWARE, 0x0,
&eax_tsc, &ebx_apic, &ecx, &edx);
/* Suppress the warning. */
UNUSED(ecx);
UNUSED(edx);
UNUSED(ebx_apic);
return 1000ul * eax_tsc;
}
static inline uint64_t x86_64_timer_tsc_freq_15h(void)
{
uint32_t crystal_freq;
uint32_t numerator;
uint32_t denominator;
uint32_t edx;
/* CPUID Leaf 0x15h, TSC frequency properties.
* The leaf returns the TSC frequency properties.
* EAX - Denominator.
* EBX - Numerator.
* ECX - Crystal Frequency.
*/
x86_64_cpuid(X86_64_CPUID_TSC, 0x0,
&denominator, &numerator, &crystal_freq, &edx);
/* Suppress the warning. */
UNUSED(edx);
if (numerator == 0 || denominator == 0 || crystal_freq == 0)
{
return 0;
}
return crystal_freq / denominator * numerator;
}
/****************************************************************************
* Public Functions
****************************************************************************/
@ -74,29 +136,24 @@ unsigned long g_x86_64_timer_freq;
void x86_64_timer_calibrate_freq(void)
{
#ifdef CONFIG_ARCH_INTEL64_TSC_DEADLINE
# if CONFIG_ARCH_INTEL64_CORE_FREQ_KHZ == 0
unsigned long crystal_freq;
unsigned long numerator;
unsigned long denominator;
g_x86_64_timer_freq = CONFIG_ARCH_INTEL64_CORE_FREQ_KHZ * 1000ul;
__asm__ volatile("cpuid"
: "=c" (crystal_freq), "=b" (numerator),
"=a" (denominator)
: "a" (X86_64_CPUID_TSC)
: "rdx", "memory");
if (numerator == 0 || denominator == 0 || crystal_freq == 0)
if (CONFIG_ARCH_INTEL64_CORE_FREQ_KHZ == 0)
{
# ifndef CONFIG_ARCH_INTEL64_TSC_FREQ_VMWARE
g_x86_64_timer_freq = x86_64_timer_tsc_freq_15h();
# else
g_x86_64_timer_freq = x86_64_timer_tsc_freq_vmware();
# endif
}
#elif defined(CONFIG_ARCH_INTEL64_TSC)
g_x86_64_timer_freq = CONFIG_ARCH_INTEL64_APIC_FREQ_KHZ * 1000ul;
#endif
if (g_x86_64_timer_freq == 0)
{
/* The TSC frequency is not available */
PANIC();
}
else
{
g_x86_64_timer_freq = crystal_freq / denominator * numerator;
}
# else
g_x86_64_timer_freq = CONFIG_ARCH_INTEL64_CORE_FREQ_KHZ * 1000L;
# endif
#elif defined(CONFIG_ARCH_INTEL64_TSC)
g_x86_64_timer_freq = CONFIG_ARCH_INTEL64_APIC_FREQ_KHZ * 1000L;
#endif
}