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(+)
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@outlook.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
On Wed, Aug 13, 2025 at 11:23:38AM +0000, Stephen Horvath wrote: > + /* The PPR defines the core multiplier as CpuFid * 25MHz */ > + p0_freq = cpufid * 25; As someone already pointed out: PPR Vol 1 for AMD Family 1Ah Model 02h C1 ... MSRC001_006[4...B] [P-state [7:0]] (Core::X86::Msr::PStateDef) ... CpuFid[11:0]: core frequency ID. FFFh- <Value>*5 010h So we need to do per-family checks here. Not sure if that is worth it, frankly. -- Regards/Gruss, Boris. https://people.kernel.org/tglx/notes-about-netiquette
Hi Stephen, kernel test robot noticed the following build errors: [auto build test ERROR on tip/x86/core] [also build test ERROR on tip/master linus/master v6.17-rc1 next-20250814] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Stephen-Horvath/x86-tsc-Read-AMD-CPU-frequency-from-Core-X86-Msr-PStateDef/20250813-192644 base: tip/x86/core patch link: https://lore.kernel.org/r/20250813112020.345622-1-s.horvath%40outlook.com.au patch subject: [PATCH] x86/tsc: Read AMD CPU frequency from Core::X86::Msr::PStateDef config: i386-randconfig-007-20250814 (https://download.01.org/0day-ci/archive/20250814/202508141439.JO8YA6fq-lkp@intel.com/config) compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250814/202508141439.JO8YA6fq-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202508141439.JO8YA6fq-lkp@intel.com/ All errors (new ones prefixed by >>): ld: arch/x86/kernel/tsc_msr.o: in function `cpu_khz_from_msr_amd': >> arch/x86/kernel/tsc_msr.c:307: undefined reference to `__udivdi3' vim +307 arch/x86/kernel/tsc_msr.c 237 238 /* 239 * MSR-based CPU/TSC frequency discovery for AMD Zen CPUs. 240 * 241 * Return processor base frequency in KHz, or 0 on failure. 242 */ 243 unsigned long cpu_khz_from_msr_amd(void) 244 { 245 u64 hwcr, pstatedef; 246 unsigned long cpufid, cpudfsid, p0_freq; 247 248 if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) 249 return 0; 250 251 /* 252 * This register mapping is only valid for Zen and later CPUs. 253 * X86_FEATURE_ZEN is not set yet, so we just check the cpuid. 254 */ 255 if (boot_cpu_data.x86 < 0x17) 256 return 0; 257 258 /* 259 * PPR states for MSR0000_0010: 260 * The TSC increments at the P0 frequency. The TSC counts at the 261 * same rate in all P-states, all C states, S0, or S1. 262 */ 263 264 /* Read the Hardware Configuration MSR (MSRC001_0015) */ 265 if (rdmsrq_safe(MSR_K7_HWCR, &hwcr)) 266 return 0; 267 268 /* 269 * Check TscFreqSel (bit 24) is set. 270 * This verifies the TSC does actually increment at P0 frequency. 271 * E.g. VMs may be configured to increment at a different rate. 272 */ 273 if (!(hwcr & BIT_64(24))) 274 return 0; 275 276 /* Read the zeroth PStateDef MSR (MSRC001_0064) */ 277 if (rdmsrq_safe(MSR_AMD_PSTATE_DEF_BASE, &pstatedef)) 278 return 0; 279 280 /* Check PstateEn is set (bit 63) */ 281 if (!(pstatedef & BIT_64(63))) 282 return 0; 283 284 /* CpuFid is the first 8 bits (7:0) */ 285 cpufid = pstatedef & 0xff; 286 287 /* Values between 0Fh-00h are reserved */ 288 if (cpufid < 0x0F) 289 return 0; 290 291 /* The PPR defines the core multiplier as CpuFid * 25MHz */ 292 p0_freq = cpufid * 25; 293 294 /* Convert from MHz to KHz before dividing */ 295 p0_freq *= 1000; 296 297 /* CpuDfsId is the next 6 bits (13:8) */ 298 cpudfsid = (pstatedef >> 8) & 0x3f; 299 300 /* Calculate the core divisor */ 301 switch (cpudfsid) { 302 case 0x08: 303 /* VCO/1 */ 304 break; 305 case 0x09: 306 /* VCO/1.125 */ > 307 p0_freq = (unsigned long)(p0_freq * 1125ull / 1000); -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
© 2016 - 2025 Red Hat, Inc.