lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250813112020.345622-1-s.horvath@outlook.com.au>
Date: Wed, 13 Aug 2025 11:23:38 +0000
From: Stephen Horvath <s.horvath@...look.com.au>
To: Thomas Gleixner <tglx@...utronix.de>, Ingo Molnar <mingo@...hat.com>,
	Borislav Petkov <bp@...en8.de>, Dave Hansen <dave.hansen@...ux.intel.com>,
	"H. Peter Anvin" <hpa@...or.com>, "x86@...nel.org" <x86@...nel.org>,
	"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
CC: Stephen Horvath <s.horvath@...look.com.au>
Subject: [PATCH] x86/tsc: Read AMD CPU frequency from
 Core::X86::Msr::PStateDef

AMD's Zen CPUs (17h and newer) have an MSR that provides the CPU/TSC
frequency directly, instead of calibrating it against the PIT.

My understanding of the PIT calibration code is that it loops between
two and three times and takes 10ms (or 50ms) each loop, taking at least
20ms total to calibrate. This patch skips that calibration time.

Through experimentation, this patch seems to save approximately 30ms on
boot time for Zen 4 (19h) CPUs (Ryzen 7 7800X3D & Ryzen 7 7840U).

This has also been tested to not interfere with KVM guests running a
custom TSC frequency.

Signed-off-by: Stephen Horvath <s.horvath@...look.com.au>
---
 arch/x86/include/asm/tsc.h |  1 +
 arch/x86/kernel/tsc.c      |  2 +
 arch/x86/kernel/tsc_msr.c  | 98 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 101 insertions(+)

diff --git a/arch/x86/include/asm/tsc.h b/arch/x86/include/asm/tsc.h
index 4f7f09f50552..a7e2710aa7f9 100644
--- a/arch/x86/include/asm/tsc.h
+++ b/arch/x86/include/asm/tsc.h
@@ -119,5 +119,6 @@ extern void tsc_save_sched_clock_state(void);
 extern void tsc_restore_sched_clock_state(void);
 
 unsigned long cpu_khz_from_msr(void);
+unsigned long cpu_khz_from_msr_amd(void);
 
 #endif /* _ASM_X86_TSC_H */
diff --git a/arch/x86/kernel/tsc.c b/arch/x86/kernel/tsc.c
index 87e749106dda..9acb7d13719d 100644
--- a/arch/x86/kernel/tsc.c
+++ b/arch/x86/kernel/tsc.c
@@ -911,6 +911,8 @@ unsigned long native_calibrate_cpu_early(void)
 {
 	unsigned long flags, fast_calibrate = cpu_khz_from_cpuid();
 
+	if (!fast_calibrate)
+		fast_calibrate = cpu_khz_from_msr_amd();
 	if (!fast_calibrate)
 		fast_calibrate = cpu_khz_from_msr();
 	if (!fast_calibrate) {
diff --git a/arch/x86/kernel/tsc_msr.c b/arch/x86/kernel/tsc_msr.c
index 48e6cc1cb017..bea62f8f1eb1 100644
--- a/arch/x86/kernel/tsc_msr.c
+++ b/arch/x86/kernel/tsc_msr.c
@@ -234,3 +234,101 @@ unsigned long cpu_khz_from_msr(void)
 
 	return res;
 }
+
+/*
+ * MSR-based CPU/TSC frequency discovery for AMD Zen CPUs.
+ *
+ * Return processor base frequency in KHz, or 0 on failure.
+ */
+unsigned long cpu_khz_from_msr_amd(void)
+{
+	u64 hwcr, pstatedef;
+	unsigned long cpufid, cpudfsid, p0_freq;
+
+	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
+		return 0;
+
+	/*
+	 * This register mapping is only valid for Zen and later CPUs.
+	 * X86_FEATURE_ZEN is not set yet, so we just check the cpuid.
+	 */
+	if (boot_cpu_data.x86 < 0x17)
+		return 0;
+
+	/*
+	 * PPR states for MSR0000_0010:
+	 * The TSC increments at the P0 frequency. The TSC counts at the
+	 * same rate in all P-states, all C states, S0, or S1.
+	 */
+
+	/* Read the Hardware Configuration MSR (MSRC001_0015) */
+	if (rdmsrq_safe(MSR_K7_HWCR, &hwcr))
+		return 0;
+
+	/*
+	 * Check TscFreqSel (bit 24) is set.
+	 * This verifies the TSC does actually increment at P0 frequency.
+	 * E.g. VMs may be configured to increment at a different rate.
+	 */
+	if (!(hwcr & BIT_64(24)))
+		return 0;
+
+	/* Read the zeroth PStateDef MSR (MSRC001_0064) */
+	if (rdmsrq_safe(MSR_AMD_PSTATE_DEF_BASE, &pstatedef))
+		return 0;
+
+	/* Check PstateEn is set (bit 63) */
+	if (!(pstatedef & BIT_64(63)))
+		return 0;
+
+	/* CpuFid is the first 8 bits (7:0) */
+	cpufid = pstatedef & 0xff;
+
+	/* Values between 0Fh-00h are reserved */
+	if (cpufid < 0x0F)
+		return 0;
+
+	/* The PPR defines the core multiplier as CpuFid * 25MHz */
+	p0_freq = cpufid * 25;
+
+	/* Convert from MHz to KHz before dividing */
+	p0_freq *= 1000;
+
+	/* CpuDfsId is the next 6 bits (13:8) */
+	cpudfsid = (pstatedef >> 8) & 0x3f;
+
+	/* Calculate the core divisor */
+	switch (cpudfsid) {
+	case 0x08:
+		/* VCO/1 */
+		break;
+	case 0x09:
+		/* VCO/1.125 */
+		p0_freq = (unsigned long)(p0_freq * 1125ull / 1000);
+		break;
+	case 0x0A ... 0x1A:
+	case 0x1C:
+	case 0x1E:
+	case 0x20:
+	case 0x22:
+	case 0x24:
+	case 0x26:
+	case 0x28:
+	case 0x2A:
+	case 0x2C:
+		/* VCO/<Value/8> */
+		p0_freq /= cpudfsid / 8;
+		break;
+	default:
+		/* Reserved */
+		return 0;
+	}
+
+	/*
+	 * TSC frequency determined by MSR is always considered "known"
+	 * because it is reported by HW.
+	 */
+	setup_force_cpu_cap(X86_FEATURE_TSC_KNOWN_FREQ);
+
+	return p0_freq;
+}
-- 
2.47.2


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ