[RFC PATCH] x86/cpu: Change AMD uarch detection

From: Borislav Petkov

Date: Thu Oct 22 2026 - 00:02:00 EST


Hi,

I have been wondering how to improve that Zen uarch detection and handling and
be able to do range checks of uarches, i.e., "this code needs to run on Zen
microarchitectures 3 to 5". And I asked AI.

It gave me a lot of blurb but the basic idea is good so I went and did it. See
below.

It boots in a VM, I'm not even trying to cause the cat to catch fire by
booting on a real machine but that will happen soon too.

The thing can easily be extended to pre-Zen but that is not really needed, I'd
say.

X86_FEATURE_ZEN to mark all Zens remains, that will be a future patch. Perhaps
something as simple as:

if (boot_cpu_data.x86_uarch)

but I have to move Hygon off of that usage. We'll see. Small steps.

Thx.

---

Author: Borislav Petkov (AMD) <bp@xxxxxxxxx>
Date: Mon Oct 12 22:42:51 2026 -0700

Not-Signed-off-by: Borislav Petkov (AMD) <bp@xxxxxxxxx>

diff --git a/arch/x86/events/amd/core.c b/arch/x86/events/amd/core.c
index 49b6b8fce566..e61857c74c16 100644
--- a/arch/x86/events/amd/core.c
+++ b/arch/x86/events/amd/core.c
@@ -291,13 +291,13 @@ static const u64 amd_zen4_perfmon_event_map[PERF_COUNT_HW_MAX] =

static u64 amd_pmu_event_map(int hw_event)
{
- if (cpu_feature_enabled(X86_FEATURE_ZEN4) || boot_cpu_data.x86 >= 0x1a)
+ if (boot_cpu_data.x86_uarch >= AMD_UARCH_ZEN4)
return amd_zen4_perfmon_event_map[hw_event];

- if (cpu_feature_enabled(X86_FEATURE_ZEN2) || boot_cpu_data.x86 >= 0x19)
+ if (boot_cpu_data.x86_uarch >= AMD_UARCH_ZEN2)
return amd_zen2_perfmon_event_map[hw_event];

- if (cpu_feature_enabled(X86_FEATURE_ZEN1))
+ if (boot_cpu_data.x86_uarch >= AMD_UARCH_ZEN1)
return amd_zen1_perfmon_event_map[hw_event];

return amd_perfmon_event_map[hw_event];
diff --git a/arch/x86/events/amd/ibs.c b/arch/x86/events/amd/ibs.c
index 3531f9c23b8c..bc699050131a 100644
--- a/arch/x86/events/amd/ibs.c
+++ b/arch/x86/events/amd/ibs.c
@@ -415,7 +415,8 @@ static int perf_ibs_init(struct perf_event *event)
config |= (ldlat - 1) << IBS_OP_LDLAT_THRSH_SHIFT;

config |= IBS_OP_LDLAT_EN;
- if (cpu_feature_enabled(X86_FEATURE_ZEN5))
+
+ if (boot_cpu_data.x86_uarch == AMD_UARCH_ZEN5)
config |= IBS_OP_L3MISSONLY;
}

diff --git a/arch/x86/include/asm/cpu_device_id.h b/arch/x86/include/asm/cpu_device_id.h
index c62d8fae52c3..50ea0eefcd06 100644
--- a/arch/x86/include/asm/cpu_device_id.h
+++ b/arch/x86/include/asm/cpu_device_id.h
@@ -5,13 +5,13 @@
/*
* Can't use <linux/bitfield.h> because it generates expressions that
* cannot be used in structure initializers. Bitfield construction
- * here must match the union in struct cpuinfo_86:
+ * here must match the union in struct cpuinfo_x86:
* union {
* struct {
* __u8 x86_model;
* __u8 x86;
* __u8 x86_vendor;
- * __u8 x86_reserved;
+ * __u8 x86_uarch;
* };
* __u32 x86_vfm;
* };
@@ -19,11 +19,11 @@
#define VFM_MODEL_BIT 0
#define VFM_FAMILY_BIT 8
#define VFM_VENDOR_BIT 16
-#define VFM_RSVD_BIT 24
+#define VFM_UARCH_BIT 24

#define VFM_MODEL_MASK GENMASK(VFM_FAMILY_BIT - 1, VFM_MODEL_BIT)
#define VFM_FAMILY_MASK GENMASK(VFM_VENDOR_BIT - 1, VFM_FAMILY_BIT)
-#define VFM_VENDOR_MASK GENMASK(VFM_RSVD_BIT - 1, VFM_VENDOR_BIT)
+#define VFM_VENDOR_MASK GENMASK(VFM_UARCH_BIT - 1, VFM_VENDOR_BIT)

#define VFM_MODEL(vfm) (((vfm) & VFM_MODEL_MASK) >> VFM_MODEL_BIT)
#define VFM_FAMILY(vfm) (((vfm) & VFM_FAMILY_MASK) >> VFM_FAMILY_BIT)
diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
index f62944700975..1302cd3edfb8 100644
--- a/arch/x86/include/asm/cpufeatures.h
+++ b/arch/x86/include/asm/cpufeatures.h
@@ -74,8 +74,8 @@
#define X86_FEATURE_CYRIX_ARR ( 3*32+ 2) /* "cyrix_arr" Cyrix ARRs (= MTRRs) */
#define X86_FEATURE_CENTAUR_MCR ( 3*32+ 3) /* "centaur_mcr" Centaur MCRs (= MTRRs) */
#define X86_FEATURE_K8 ( 3*32+ 4) /* Opteron, Athlon64 */
-#define X86_FEATURE_ZEN5 ( 3*32+ 5) /* CPU based on Zen5 microarchitecture */
-#define X86_FEATURE_ZEN6 ( 3*32+ 6) /* CPU based on Zen6 microarchitecture */
+/* unused, was: #define X86_FEATURE_ZEN5 ( 3*32+ 5) * CPU based on Zen5 microarchitecture */
+/* unused, was: #define X86_FEATURE_ZEN6 ( 3*32+ 6) * CPU based on Zen6 microarchitecture */
#define X86_FEATURE_RMPOPT ( 3*32+ 7) /* Support for AMD RMPOPT instruction */
#define X86_FEATURE_CONSTANT_TSC ( 3*32+ 8) /* "constant_tsc" TSC ticks at a constant rate */
/* free: was #define X86_FEATURE_UP ( 3*32+ 9) * "up" SMP kernel running on UP */
@@ -313,10 +313,10 @@
#define X86_FEATURE_SRSO_ALIAS (11*32+25) /* AMD BTB untrain RETs through aliasing */
#define X86_FEATURE_IBPB_ON_VMEXIT (11*32+26) /* Issue an IBPB only on VMEXIT */
#define X86_FEATURE_APIC_MSRS_FENCE (11*32+27) /* IA32_TSC_DEADLINE and X2APIC MSRs need fencing */
-#define X86_FEATURE_ZEN2 (11*32+28) /* CPU based on Zen2 microarchitecture */
-#define X86_FEATURE_ZEN3 (11*32+29) /* CPU based on Zen3 microarchitecture */
-#define X86_FEATURE_ZEN4 (11*32+30) /* CPU based on Zen4 microarchitecture */
-#define X86_FEATURE_ZEN1 (11*32+31) /* CPU based on Zen1 microarchitecture */
+/* unused, was #define X86_FEATURE_ZEN2 (11*32+28) * CPU based on Zen2 microarchitecture */
+/* unused, was: #define X86_FEATURE_ZEN3 (11*32+29) * CPU based on Zen3 microarchitecture */
+/* unused, was: #define X86_FEATURE_ZEN4 (11*32+30) * CPU based on Zen4 microarchitecture */
+/* unused, was: #define X86_FEATURE_ZEN1 (11*32+31) * CPU based on Zen1 microarchitecture */

/* Intel-defined CPU features, CPUID level 0x00000007:1 (EAX), word 12 */
#define X86_FEATURE_SHA512 (12*32+ 0) /* SHA512 instructions */
diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h
index 77488ff5eb8c..03e75b6ad9d3 100644
--- a/arch/x86/include/asm/processor.h
+++ b/arch/x86/include/asm/processor.h
@@ -77,6 +77,19 @@ enum x86_topology_cpu_type {
TOPO_CPU_TYPE_UNKNOWN,
};

+/*
+ * Ordered list of uarches so that range etc checks can work.
+ */
+enum amd_uarch {
+ AMD_UARCH_UNKNOWN = 0,
+ AMD_UARCH_ZEN1,
+ AMD_UARCH_ZEN2,
+ AMD_UARCH_ZEN3,
+ AMD_UARCH_ZEN4,
+ AMD_UARCH_ZEN5,
+ AMD_UARCH_ZEN6
+};
+
struct cpuinfo_topology {
// Real APIC ID read from the local APIC
u32 apicid;
@@ -145,7 +158,8 @@ struct cpuinfo_x86 {
__u8 x86;
/* CPU vendor */
__u8 x86_vendor;
- __u8 x86_reserved;
+ /* AMD: CPU uarch number */
+ __u8 x86_uarch;
};
/* combined vendor, family, model */
__u32 x86_vfm;
diff --git a/arch/x86/kernel/acpi/cppc.c b/arch/x86/kernel/acpi/cppc.c
index bbade0da5130..fc06a78b3cfc 100644
--- a/arch/x86/kernel/acpi/cppc.c
+++ b/arch/x86/kernel/acpi/cppc.c
@@ -260,7 +260,7 @@ int amd_get_boost_ratio_numerator(unsigned int cpu, u64 *numerator)
* the highest performance level is set to 196.
* https://bugzilla.kernel.org/show_bug.cgi?id=218759
*/
- if (cpu_feature_enabled(X86_FEATURE_ZEN4)) {
+ if (boot_cpu_data.x86_uarch == AMD_UARCH_ZEN4) {
switch (boot_cpu_data.x86_model) {
case 0x70 ... 0x7f:
*numerator = CPPC_HIGHEST_PERF_PERFORMANCE;
diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c
index 3972966cb023..2df33d59c185 100644
--- a/arch/x86/kernel/cpu/amd.c
+++ b/arch/x86/kernel/cpu/amd.c
@@ -367,8 +367,7 @@ static void bsp_determine_snp(struct cpuinfo_x86 *c)
* instruction.
*/
if (!cpu_has(c, X86_FEATURE_HYPERVISOR) &&
- (cpu_feature_enabled(X86_FEATURE_ZEN3) ||
- cpu_feature_enabled(X86_FEATURE_ZEN4) ||
+ (c->x86_uarch >= AMD_UARCH_ZEN3 ||
cpu_feature_enabled(X86_FEATURE_RMPREAD)) &&
snp_probe_rmptable_info()) {
cc_platform_set(CC_ATTR_HOST_SEV_SNP);
@@ -409,8 +408,8 @@ static void tsa_init(struct cpuinfo_x86 *c)
if (cpu_has(c, X86_FEATURE_HYPERVISOR))
return;

- if (cpu_has(c, X86_FEATURE_ZEN3) ||
- cpu_has(c, X86_FEATURE_ZEN4)) {
+ if (c->x86_uarch >= AMD_UARCH_ZEN3 &&
+ c->x86_uarch <= AMD_UARCH_ZEN4) {
if (x86_match_min_microcode_rev(amd_tsa_microcode))
setup_force_cpu_cap(X86_FEATURE_VERW_CLEAR);
else
@@ -483,13 +482,13 @@ static void bsp_init_amd(struct cpuinfo_x86 *c)
switch (c->x86_model) {
case 0x00 ... 0x2f:
case 0x50 ... 0x5f:
- setup_force_cpu_cap(X86_FEATURE_ZEN1);
+ c->x86_uarch = AMD_UARCH_ZEN1;
break;
case 0x30 ... 0x4f:
case 0x60 ... 0x7f:
case 0x90 ... 0x91:
case 0xa0 ... 0xaf:
- setup_force_cpu_cap(X86_FEATURE_ZEN2);
+ c->x86_uarch = AMD_UARCH_ZEN2;
break;
default:
goto warn;
@@ -500,11 +499,11 @@ static void bsp_init_amd(struct cpuinfo_x86 *c)
switch (c->x86_model) {
case 0x00 ... 0x0f:
case 0x20 ... 0x5f:
- setup_force_cpu_cap(X86_FEATURE_ZEN3);
+ c->x86_uarch = AMD_UARCH_ZEN3;
break;
case 0x10 ... 0x1f:
case 0x60 ... 0xaf:
- setup_force_cpu_cap(X86_FEATURE_ZEN4);
+ c->x86_uarch = AMD_UARCH_ZEN4;
break;
default:
goto warn;
@@ -517,13 +516,13 @@ static void bsp_init_amd(struct cpuinfo_x86 *c)
case 0x40 ... 0x4f:
case 0x60 ... 0x7f:
case 0xd0 ... 0xd7:
- setup_force_cpu_cap(X86_FEATURE_ZEN5);
+ c->x86_uarch = AMD_UARCH_ZEN5;
break;
case 0x50 ... 0x5f:
case 0x80 ... 0xaf:
case 0xc0 ... 0xcf:
case 0xd8 ... 0xef:
- setup_force_cpu_cap(X86_FEATURE_ZEN6);
+ c->x86_uarch = AMD_UARCH_ZEN6;
break;
default:
goto warn;
@@ -1090,16 +1089,15 @@ static void init_amd(struct cpuinfo_x86 *c)
if (c->x86 >= 0x17)
init_amd_zen_common();

- if (boot_cpu_has(X86_FEATURE_ZEN1))
- init_amd_zen1(c);
- else if (boot_cpu_has(X86_FEATURE_ZEN2))
- init_amd_zen2(c);
- else if (boot_cpu_has(X86_FEATURE_ZEN3))
- init_amd_zen3(c);
- else if (boot_cpu_has(X86_FEATURE_ZEN4))
- init_amd_zen4(c);
- else if (boot_cpu_has(X86_FEATURE_ZEN5))
- init_amd_zen5(c);
+ switch (c->x86_uarch) {
+ case AMD_UARCH_ZEN1: init_amd_zen1(c); break;
+ case AMD_UARCH_ZEN2: init_amd_zen2(c); break;
+ case AMD_UARCH_ZEN3: init_amd_zen3(c); break;
+ case AMD_UARCH_ZEN4: init_amd_zen4(c); break;
+ case AMD_UARCH_ZEN5: init_amd_zen5(c); break;
+ default: break;
+ }
+

/*
* Enable workaround for FXSAVE leak on CPUs
@@ -1329,7 +1327,7 @@ void amd_check_microcode(void)
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
return;

- if (cpu_feature_enabled(X86_FEATURE_ZEN2))
+ if (boot_cpu_data.x86_uarch == AMD_UARCH_ZEN2)
on_each_cpu(zenbleed_check_cpu, NULL, 1);
}

diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index 3b037e385523..5b5177d44f8d 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -1258,15 +1258,11 @@ static bool __init avic_want_avic_enabled(void)
{
/*
* In "auto" mode, enable AVIC by default for Zen4+ if x2AVIC is
- * supported (to avoid enabling partial support by default, and because
- * x2AVIC should be supported by all Zen4+ CPUs). Explicitly check for
- * family 0x1A and later (Zen5+), as the kernel's synthetic ZenX flags
- * aren't inclusive of previous generations, i.e. the kernel will set
- * at most one ZenX feature flag.
+ * supported (to avoid enabling partial support by default, and
+ * because x2AVIC should be supported by all Zen4+ CPUs).
*/
if (avic == AVIC_AUTO_MODE)
- avic = boot_cpu_has(X86_FEATURE_X2AVIC) &&
- (cpu_feature_enabled(X86_FEATURE_ZEN4) || boot_cpu_data.x86 >= 0x1A);
+ avic = boot_cpu_has(X86_FEATURE_X2AVIC) && boot_cpu_data.x86_uarch >= AMD_UARCH_ZEN4;

if (!avic || !npt_enabled)
return false;
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c
index 8bfd46d60843..258d95e5ff6d 100644
--- a/drivers/cpufreq/amd-pstate.c
+++ b/drivers/cpufreq/amd-pstate.c
@@ -165,7 +165,7 @@ static int __init dmi_matched_7k62_bios_bug(const struct dmi_system_id *dmi)
* broken BIOS lack of nominal_freq and lowest_freq capabilities
* definition in ACPI tables
*/
- if (cpu_feature_enabled(X86_FEATURE_ZEN2)) {
+ if (boot_cpu_data.x86_uarch == AMD_UARCH_ZEN2) {
quirks = dmi->driver_data;
pr_info("Overriding nominal and lowest frequencies for %s\n", dmi->ident);
return 1;
@@ -2218,27 +2218,35 @@ static bool amd_cppc_supported(void)
* debugging purposes. Besides, it may still be possible for
* the driver to work using the shared-memory mechanism.
*/
- if (!cpu_feature_enabled(X86_FEATURE_CPPC)) {
- if (cpu_feature_enabled(X86_FEATURE_ZEN2)) {
- switch (c->x86_model) {
- case 0x60 ... 0x6F:
- case 0x80 ... 0xAF:
- warn = true;
- break;
- }
- } else if (cpu_feature_enabled(X86_FEATURE_ZEN3) ||
- cpu_feature_enabled(X86_FEATURE_ZEN4)) {
- switch (c->x86_model) {
- case 0x10 ... 0x1F:
- case 0x40 ... 0xAF:
- warn = true;
- break;
- }
- } else if (cpu_feature_enabled(X86_FEATURE_ZEN5)) {
+ if (cpu_feature_enabled(X86_FEATURE_CPPC))
+ goto skip;
+
+ switch (boot_cpu_data.x86_uarch) {
+ case AMD_UARCH_ZEN2:
+ switch (c->x86_model) {
+ case 0x60 ... 0x6F:
+ case 0x80 ... 0xAF:
+ warn = true;
+ break;
+ }
+ break;
+ case AMD_UARCH_ZEN3:
+ case AMD_UARCH_ZEN4:
+ switch (c->x86_model) {
+ case 0x10 ... 0x1F:
+ case 0x40 ... 0xAF:
warn = true;
+ break;
}
+ break;
+ case AMD_UARCH_ZEN5:
+ warn = true;
+ break;
+ default:
+ break;
}

+skip:
if (warn)
pr_warn_once("The CPPC feature is supported but currently disabled by the BIOS.\n"
"Please enable it if your BIOS has the CPPC option.\n");
diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
index 5c996ab63895..0070421c6687 100644
--- a/drivers/crypto/ccp/sev-dev.c
+++ b/drivers/crypto/ccp/sev-dev.c
@@ -3136,12 +3136,15 @@ static int get_v2_svn(struct sev_device *sev)
static bool sev_firmware_allows_es(struct sev_device *sev)
{
/* Documented in AMD-SB-3023 */
- if (boot_cpu_has(X86_FEATURE_ZEN4) || boot_cpu_has(X86_FEATURE_ZEN3))
+ switch (boot_cpu_data.x86_uarch) {
+ case AMD_UARCH_ZEN3:
+ case AMD_UARCH_ZEN4:
return get_v1_svn(sev) < 0x1b;
- else if (boot_cpu_has(X86_FEATURE_ZEN5))
+ case AMD_UARCH_ZEN5:
return get_v2_svn(sev) < 0x4;
- else
+ default:
return true;
+ }
}

int sev_firmware_supported_vm_types(void)
diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c
index 8a33e75b71c3..67a5c468171f 100644
--- a/drivers/platform/x86/amd/pmc/mp1_stb.c
+++ b/drivers/platform/x86/amd/pmc/mp1_stb.c
@@ -245,7 +245,7 @@ static const struct file_operations amd_stb_debugfs_fops_v2 = {

static void amd_stb_update_args(struct amd_pmc_dev *dev)
{
- if (cpu_feature_enabled(X86_FEATURE_ZEN5))
+ if (boot_cpu_data.x86_uarch == AMD_UARCH_ZEN5) {
switch (boot_cpu_data.x86_model) {
case 0x44:
dev->stb_arg.msg = AMD_GNR_REGISTER_MESSAGE;
@@ -254,6 +254,7 @@ static void amd_stb_update_args(struct amd_pmc_dev *dev)
return;
default:
break;
+ }
}

dev->stb_arg.msg = AMD_S2D_REGISTER_MESSAGE;

--
Regards/Gruss,
Boris.

https://people.kernel.org/tglx/notes-about-netiquette