* [PATCH v4 0/5] Add support for Hygon family 18h models 4h-8h
@ 2026-07-10 6:49 Fu Hao
2026-07-10 6:50 ` [PATCH v4 1/5] x86/cpu/hygon: Adjust the die_id and logical_die_id for Hygon models 0x4 through 0x8 Fu Hao
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Fu Hao @ 2026-07-10 6:49 UTC (permalink / raw)
To: puwen, tglx, mingo, bp, dave.hansen, x86, peterz, acme, namhyung,
alexander.shishkin, jolsa, irogers, james.clark, hpa
Cc: linux-kernel, tingyin.duan, Fu Hao
This patch series introduce support for the new-generation Hygon
processor models 0x4–0x8, addressing five key areas:
1. CPU Topology Updates
The new processor introduces a revised CPU topology hierarchy
compared to previous generations. This patch updates the kernel's
topology detection logic to correctly identify core/socket
relationships and cache sharing patterns.
2. L3 Performance Monitoring Updates
The L3 perf registers of model 6h differ from those of the
previous processor.
3. Microcode loading
Add microcode loading support for Hygon processor.
4. Audio Controller Driver
Add initial support for the integrated HD-Audio controller.
5. New SB IOAPIC information support
The southbridge IOAPIC of Hygon's new-generation processors is
located at 00:0b.0.
v3->v4:
- Rebase to 7.2-rc2.
- Refine the commit messages and comments for patch x86/cpu/hygon.
- Add bounds checks to the microcode patch.
- The HD-Audio patch has been applied to for-next branch.
v2->v3:
- Refine the commit messages for patch x86/cpu/hygon.
- Update the logic for deriving the LLC ID from the APIC ID on Hygon
processors.
- Clean up and refine Hygon microcode support code.
- Refine the code of Hygon HD-Audio device ID.
- The iommu/hygon patch has been accepted and merged into iommu branch.
v1->v2:
- Refine the patch x86/cpu/hygon.
- Do a separate compilation unit on hygon microcode loading support.
- Use "access_sdnctl_in_dword" instead of "hygon_dword_acess" for
accessing the HDA registers.
Fu Hao (5):
x86/cpu/hygon: Adjust the die_id and logical_die_id for Hygon models
0x4 through 0x8
x86/cpu: Get LLC ID for Hygon processor models 0x6 through 0x8
x86/cpu/hygon: Remove Spectral Chicken for Hygon processors
perf/x86/uncore: Add L3 PMU support for Hygon family 18h model 6h
x86/microcode/hygon: Add microcode loading support for Hygon
processors
arch/x86/Kconfig | 2 +-
arch/x86/events/amd/uncore.c | 48 +-
arch/x86/include/asm/perf_event.h | 8 +
arch/x86/kernel/cpu/cacheinfo.c | 21 +-
arch/x86/kernel/cpu/hygon.c | 19 +-
arch/x86/kernel/cpu/microcode/Makefile | 1 +
arch/x86/kernel/cpu/microcode/core.c | 17 +-
arch/x86/kernel/cpu/microcode/hygon.c | 824 +++++++++++++++++++++++
arch/x86/kernel/cpu/microcode/internal.h | 20 +
9 files changed, 948 insertions(+), 12 deletions(-)
create mode 100644 arch/x86/kernel/cpu/microcode/hygon.c
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v4 1/5] x86/cpu/hygon: Adjust the die_id and logical_die_id for Hygon models 0x4 through 0x8
2026-07-10 6:49 [PATCH v4 0/5] Add support for Hygon family 18h models 4h-8h Fu Hao
@ 2026-07-10 6:50 ` Fu Hao
2026-07-10 6:51 ` [PATCH v4 2/5] x86/cpu: Get LLC ID for Hygon processor models 0x6 " Fu Hao
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Fu Hao @ 2026-07-10 6:50 UTC (permalink / raw)
To: puwen, tglx, mingo, bp, dave.hansen, x86, hpa
Cc: linux-kernel, tingyin.duan, Fu Hao
For Hygon processor models 0x4–0x8, the die ID should be obtained
from the NodeId field in ECX of CPUID leaf 0x8000001e.
Starting with these models, Hygon CPUs support CPUID leaf 0xb but
do not support leaf 0x80000026. Consequently, the die ID and
logical die ID cannot be derived from the APIC ID space.
CPUID leaf 0xb only supports core and package levels, it cannot
enumerate die IDs within a socket.
However, we found that CPUID leaf 0x8000001e (ECX) provides the
necessary information. In Hygon CPU design, the NodeId field [7:0]
encodes both socket and die IDs in the format:
Bits [7:4]: socket ID
Bits [3:0]: die ID
We can calculate the correct IDs(die_id and logical_die_id) by parsing
this field.
Signed-off-by: Fu Hao <fuhao@open-hieco.net>
Tested-by: Tingyin Duan <tingyin.duan@gmail.com>
---
arch/x86/kernel/cpu/hygon.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/x86/kernel/cpu/hygon.c b/arch/x86/kernel/cpu/hygon.c
index 3e8891a9c..a0657c5ad 100644
--- a/arch/x86/kernel/cpu/hygon.c
+++ b/arch/x86/kernel/cpu/hygon.c
@@ -192,6 +192,19 @@ static void init_hygon(struct cpuinfo_x86 *c)
init_hygon_cacheinfo(c);
+ /*
+ * Adjust the die_id and logical_die_id for Hygon models 0x4-0x8.
+ * In Hygon CPU design, the NodeId field of CPUID leaf 0x8000001e (ECX)
+ * is now fixed to represent the socket ID and die ID, allocated as
+ * 4 bits each: socket ID = NodeId[7:4], die ID = NodeId[3:0].
+ */
+ if (c->x86_model >= 0x4 && c->x86_model <= 0x8) {
+ c->topo.die_id = cpuid_ecx(0x8000001e) & 0xff;
+ c->topo.logical_die_id = (c->topo.die_id >> 4) *
+ topology_amd_nodes_per_pkg() +
+ (c->topo.die_id & 0xf);
+ }
+
if (cpu_has(c, X86_FEATURE_SVM)) {
rdmsrq(MSR_VM_CR, vm_cr);
if (vm_cr & SVM_VM_CR_SVM_DIS_MASK) {
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 2/5] x86/cpu: Get LLC ID for Hygon processor models 0x6 through 0x8
2026-07-10 6:49 [PATCH v4 0/5] Add support for Hygon family 18h models 4h-8h Fu Hao
2026-07-10 6:50 ` [PATCH v4 1/5] x86/cpu/hygon: Adjust the die_id and logical_die_id for Hygon models 0x4 through 0x8 Fu Hao
@ 2026-07-10 6:51 ` Fu Hao
2026-07-10 6:52 ` [PATCH v4 3/5] x86/cpu/hygon: Remove Spectral Chicken for Hygon processors Fu Hao
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Fu Hao @ 2026-07-10 6:51 UTC (permalink / raw)
To: tglx, mingo, bp, dave.hansen, x86, hpa; +Cc: linux-kernel, tingyin.duan, Fu Hao
For Hygon processors, the LLC ID comes from fixed bit of APIC ID
regardless of whether SMT is enabled or not. Add support for
Hygon processor models 0x6–0x8.
Signed-off-by: Fu Hao <fuhao@open-hieco.net>
Tested-by: Tingyin Duan <tingyin.duan@gmail.com>
---
arch/x86/kernel/cpu/cacheinfo.c | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/arch/x86/kernel/cpu/cacheinfo.c b/arch/x86/kernel/cpu/cacheinfo.c
index 51a95b078..e563e327b 100644
--- a/arch/x86/kernel/cpu/cacheinfo.c
+++ b/arch/x86/kernel/cpu/cacheinfo.c
@@ -342,10 +342,25 @@ void cacheinfo_hygon_init_llc_id(struct cpuinfo_x86 *c)
return;
/*
- * Hygons are similar to AMD Family 17h up to 1F models: LLC is
- * at the core complex level. Core complex ID is ApicId[3].
+ * LLC is at the core complex level. For Hygon processor
+ * models 0x0–0x5, core complex ID is similar to AMD Family
+ * 17h up to 1F models, which is ApicId[3]. For Hygon
+ * processor models 0x6 and 0x7/0x8, core complex ID is
+ * derived from APIC ID bit 4 and bit 5, respectively.
*/
- c->topo.llc_id = c->topo.apicid >> 3;
+
+ switch (c->x86_model) {
+ case 0x6:
+ c->topo.llc_id = c->topo.apicid >> 4;
+ break;
+ case 0x7:
+ case 0x8:
+ c->topo.llc_id = c->topo.apicid >> 5;
+ break;
+ default:
+ c->topo.llc_id = c->topo.apicid >> 3;
+ break;
+ }
}
void init_amd_cacheinfo(struct cpuinfo_x86 *c)
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 3/5] x86/cpu/hygon: Remove Spectral Chicken for Hygon processors
2026-07-10 6:49 [PATCH v4 0/5] Add support for Hygon family 18h models 4h-8h Fu Hao
2026-07-10 6:50 ` [PATCH v4 1/5] x86/cpu/hygon: Adjust the die_id and logical_die_id for Hygon models 0x4 through 0x8 Fu Hao
2026-07-10 6:51 ` [PATCH v4 2/5] x86/cpu: Get LLC ID for Hygon processor models 0x6 " Fu Hao
@ 2026-07-10 6:52 ` Fu Hao
2026-07-10 6:53 ` [PATCH v4 4/5] perf/x86/uncore: Add L3 PMU support for Hygon family 18h model 6h Fu Hao
2026-07-10 6:54 ` [PATCH v4 5/5] x86/microcode/hygon: Add microcode loading support for Hygon processors Fu Hao
4 siblings, 0 replies; 6+ messages in thread
From: Fu Hao @ 2026-07-10 6:52 UTC (permalink / raw)
To: puwen, tglx, mingo, bp, dave.hansen, x86, hpa
Cc: linux-kernel, tingyin.duan, Fu Hao
It's no need to set the Spectral chicken bit for all Hygon processors.
Fixes: d7caac991fee ("x86/cpu/amd: Add Spectral Chicken")
Signed-off-by: Fu Hao <fuhao@open-hieco.net>
---
arch/x86/kernel/cpu/hygon.c | 6 ------
1 file changed, 6 deletions(-)
diff --git a/arch/x86/kernel/cpu/hygon.c b/arch/x86/kernel/cpu/hygon.c
index a0657c5ad..24162311b 100644
--- a/arch/x86/kernel/cpu/hygon.c
+++ b/arch/x86/kernel/cpu/hygon.c
@@ -177,12 +177,6 @@ static void init_hygon(struct cpuinfo_x86 *c)
set_cpu_cap(c, X86_FEATURE_REP_GOOD);
- /*
- * XXX someone from Hygon needs to confirm this DTRT
- *
- init_spectral_chicken(c);
- */
-
set_cpu_cap(c, X86_FEATURE_ZEN);
set_cpu_cap(c, X86_FEATURE_CPB);
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 4/5] perf/x86/uncore: Add L3 PMU support for Hygon family 18h model 6h
2026-07-10 6:49 [PATCH v4 0/5] Add support for Hygon family 18h models 4h-8h Fu Hao
` (2 preceding siblings ...)
2026-07-10 6:52 ` [PATCH v4 3/5] x86/cpu/hygon: Remove Spectral Chicken for Hygon processors Fu Hao
@ 2026-07-10 6:53 ` Fu Hao
2026-07-10 6:54 ` [PATCH v4 5/5] x86/microcode/hygon: Add microcode loading support for Hygon processors Fu Hao
4 siblings, 0 replies; 6+ messages in thread
From: Fu Hao @ 2026-07-10 6:53 UTC (permalink / raw)
To: peterz, mingo, acme, namhyung, tglx, bp, dave.hansen, x86,
alexander.shishkin, jolsa, irogers, james.clark, hpa
Cc: linux-perf-users, linux-kernel, tingyin.duan, Fu Hao
Adjust the L3 PMU slicemask and threadmask for Hygon family 18h
model 6h processor.
Signed-off-by: Fu Hao <fuhao@open-hieco.net>
Tested-by: Tingyin Duan <tingyin.duan@gmail.com>
---
arch/x86/events/amd/uncore.c | 48 ++++++++++++++++++++++++++++++-
arch/x86/include/asm/perf_event.h | 8 ++++++
2 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c
index dbc00b6dd..fa7a1c70e 100644
--- a/arch/x86/events/amd/uncore.c
+++ b/arch/x86/events/amd/uncore.c
@@ -309,6 +309,14 @@ amd_f17h_uncore_is_visible(struct kobject *kobj, struct attribute *attr, int i)
attr->mode : 0;
}
+static umode_t
+hygon_f18h_m6h_uncore_is_visible(struct kobject *kobj, struct attribute *attr, int i)
+{
+ return boot_cpu_data.x86 == 0x18 &&
+ boot_cpu_data.x86_model >= 0x6 && boot_cpu_data.x86_model <= 0xf ?
+ attr->mode : 0;
+}
+
static umode_t
amd_f19h_uncore_is_visible(struct kobject *kobj, struct attribute *attr, int i)
{
@@ -360,6 +368,8 @@ DEFINE_UNCORE_FORMAT_ATTR(enallslices, enallslices, "config:46"); /* F19h L3
DEFINE_UNCORE_FORMAT_ATTR(enallcores, enallcores, "config:47"); /* F19h L3 */
DEFINE_UNCORE_FORMAT_ATTR(sliceid, sliceid, "config:48-50"); /* F19h L3 */
DEFINE_UNCORE_FORMAT_ATTR(rdwrmask, rdwrmask, "config:8-9"); /* PerfMonV2 UMC */
+DEFINE_UNCORE_FORMAT_ATTR(slicemask4, slicemask, "config:28-31"); /* F18h L3 */
+DEFINE_UNCORE_FORMAT_ATTR(threadmask32, threadmask, "config:32-63"); /* F18h L3 */
/* Common DF and NB attributes */
static struct attribute *amd_uncore_df_format_attr[] = {
@@ -389,6 +399,12 @@ static struct attribute *amd_f17h_uncore_l3_format_attr[] = {
NULL,
};
+/* F18h M06h unique L3 attributes */
+static struct attribute *hygon_f18h_m6h_uncore_l3_format_attr[] = {
+ &format_attr_slicemask4.attr, /* slicemask */
+ NULL,
+};
+
/* F19h unique L3 attributes */
static struct attribute *amd_f19h_uncore_l3_format_attr[] = {
&format_attr_coreid.attr, /* coreid */
@@ -414,6 +430,12 @@ static struct attribute_group amd_f17h_uncore_l3_format_group = {
.is_visible = amd_f17h_uncore_is_visible,
};
+static struct attribute_group hygon_f18h_m6h_uncore_l3_format_group = {
+ .name = "format",
+ .attrs = hygon_f18h_m6h_uncore_l3_format_attr,
+ .is_visible = hygon_f18h_m6h_uncore_is_visible,
+};
+
static struct attribute_group amd_f19h_uncore_l3_format_group = {
.name = "format",
.attrs = amd_f19h_uncore_l3_format_attr,
@@ -443,6 +465,11 @@ static const struct attribute_group *amd_uncore_l3_attr_update[] = {
NULL,
};
+static const struct attribute_group *hygon_uncore_l3_attr_update[] = {
+ &hygon_f18h_m6h_uncore_l3_format_group,
+ NULL,
+};
+
static const struct attribute_group *amd_uncore_umc_attr_groups[] = {
&amd_uncore_attr_group,
&amd_uncore_umc_format_group,
@@ -821,6 +848,12 @@ static int amd_uncore_l3_event_init(struct perf_event *event)
mask = AMD64_L3_F19H_THREAD_MASK | AMD64_L3_EN_ALL_SLICES |
AMD64_L3_EN_ALL_CORES;
+ if (boot_cpu_data.x86 == 0x18 &&
+ boot_cpu_data.x86_model >= 0x6 &&
+ boot_cpu_data.x86_model <= 0xf)
+ mask = ((config & HYGON_L3_SLICE_MASK) ? : HYGON_L3_SLICE_MASK) |
+ ((config & HYGON_L3_THREAD_MASK) ? : HYGON_L3_THREAD_MASK);
+
hwc->config |= mask;
return 0;
@@ -878,7 +911,8 @@ int amd_uncore_l3_ctx_init(struct amd_uncore *uncore, unsigned int cpu)
pmu->rdpmc_base = RDPMC_BASE_LLC;
pmu->group = amd_uncore_ctx_gid(uncore, cpu);
- if (boot_cpu_data.x86 >= 0x17) {
+ if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
+ boot_cpu_data.x86 >= 0x17) {
*l3_attr++ = &format_attr_event8.attr;
*l3_attr++ = &format_attr_umask8.attr;
*l3_attr++ = boot_cpu_data.x86 >= 0x19 ?
@@ -905,6 +939,18 @@ int amd_uncore_l3_ctx_init(struct amd_uncore *uncore, unsigned int cpu)
.module = THIS_MODULE,
};
+ if (boot_cpu_data.x86_vendor == X86_VENDOR_HYGON &&
+ boot_cpu_data.x86 == 0x18) {
+ *l3_attr++ = &format_attr_event8.attr;
+ *l3_attr++ = &format_attr_umask8.attr;
+ if (boot_cpu_data.x86_model >= 0x6 && boot_cpu_data.x86_model <= 0xf) {
+ *l3_attr++ = &format_attr_threadmask32.attr;
+ pmu->pmu.attr_update = hygon_uncore_l3_attr_update;
+ } else {
+ *l3_attr++ = &format_attr_threadmask8.attr;
+ }
+ }
+
if (perf_pmu_register(&pmu->pmu, pmu->pmu.name, -1)) {
free_percpu(pmu->ctx);
pmu->ctx = NULL;
diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h
index 1eb13673e..f448e93ec 100644
--- a/arch/x86/include/asm/perf_event.h
+++ b/arch/x86/include/asm/perf_event.h
@@ -91,6 +91,14 @@
#define AMD64_L3_COREID_MASK \
(0x7ULL << AMD64_L3_COREID_SHIFT)
+#define HYGON_L3_SLICE_SHIFT 28
+#define HYGON_L3_SLICE_MASK \
+ (0xFULL << HYGON_L3_SLICE_SHIFT)
+
+#define HYGON_L3_THREAD_SHIFT 32
+#define HYGON_L3_THREAD_MASK \
+ (0xFFFFFFFFULL << HYGON_L3_THREAD_SHIFT)
+
#define X86_RAW_EVENT_MASK \
(ARCH_PERFMON_EVENTSEL_EVENT | \
ARCH_PERFMON_EVENTSEL_UMASK | \
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 5/5] x86/microcode/hygon: Add microcode loading support for Hygon processors
2026-07-10 6:49 [PATCH v4 0/5] Add support for Hygon family 18h models 4h-8h Fu Hao
` (3 preceding siblings ...)
2026-07-10 6:53 ` [PATCH v4 4/5] perf/x86/uncore: Add L3 PMU support for Hygon family 18h model 6h Fu Hao
@ 2026-07-10 6:54 ` Fu Hao
4 siblings, 0 replies; 6+ messages in thread
From: Fu Hao @ 2026-07-10 6:54 UTC (permalink / raw)
To: tglx, mingo, bp, dave.hansen, x86, hpa; +Cc: linux-kernel, tingyin.duan, Fu Hao
Add support for loading Hygon microcode.
There are two loading methods:
1. Loading from the initrd/builtin.
2. Late loading, which depends on the kernel configuration option
CONFIG_MICROCODE_LATE_LOADING being enabled, and this option
is usually disabled by default, so it's not recommended.
Signed-off-by: Fu Hao <fuhao@open-hieco.net>
---
arch/x86/Kconfig | 2 +-
arch/x86/kernel/cpu/microcode/Makefile | 1 +
arch/x86/kernel/cpu/microcode/core.c | 17 +-
arch/x86/kernel/cpu/microcode/hygon.c | 824 +++++++++++++++++++++++
arch/x86/kernel/cpu/microcode/internal.h | 20 +
5 files changed, 862 insertions(+), 2 deletions(-)
create mode 100644 arch/x86/kernel/cpu/microcode/hygon.c
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index bdad90f21..a9245fa47 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1313,7 +1313,7 @@ config X86_REBOOTFIXUPS
config MICROCODE
def_bool y
- depends on CPU_SUP_AMD || CPU_SUP_INTEL
+ depends on CPU_SUP_AMD || CPU_SUP_INTEL || CPU_SUP_HYGON
select CRYPTO_LIB_SHA256 if CPU_SUP_AMD
config MICROCODE_INITRD32
diff --git a/arch/x86/kernel/cpu/microcode/Makefile b/arch/x86/kernel/cpu/microcode/Makefile
index 193d98b33..671d9a095 100644
--- a/arch/x86/kernel/cpu/microcode/Makefile
+++ b/arch/x86/kernel/cpu/microcode/Makefile
@@ -3,3 +3,4 @@ microcode-y := core.o
obj-$(CONFIG_MICROCODE) += microcode.o
microcode-$(CONFIG_CPU_SUP_INTEL) += intel.o
microcode-$(CONFIG_CPU_SUP_AMD) += amd.o
+microcode-$(CONFIG_CPU_SUP_HYGON) += hygon.o
diff --git a/arch/x86/kernel/cpu/microcode/core.c b/arch/x86/kernel/cpu/microcode/core.c
index 0dd0c7241..ed10266af 100644
--- a/arch/x86/kernel/cpu/microcode/core.c
+++ b/arch/x86/kernel/cpu/microcode/core.c
@@ -194,6 +194,9 @@ void __init load_ucode_bsp(void)
return;
intel = false;
break;
+ case X86_VENDOR_HYGON:
+ intel = false;
+ break;
default:
return;
@@ -202,7 +205,11 @@ void __init load_ucode_bsp(void)
if (intel)
load_ucode_intel_bsp(&early_data);
else
- load_ucode_amd_bsp(&early_data, cpuid_1_eax);
+ if (x86_cpuid_vendor() == X86_VENDOR_HYGON)
+ load_ucode_hygon_bsp(&early_data, cpuid_1_eax);
+ else
+ load_ucode_amd_bsp(&early_data, cpuid_1_eax);
+
}
void load_ucode_ap(void)
@@ -228,6 +235,9 @@ void load_ucode_ap(void)
if (x86_family(cpuid_1_eax) >= 0x10)
load_ucode_amd_ap(cpuid_1_eax);
break;
+ case X86_VENDOR_HYGON:
+ load_ucode_hygon_ap(cpuid_1_eax);
+ break;
default:
break;
}
@@ -287,6 +297,9 @@ static void reload_early_microcode(unsigned int cpu)
if (family >= 0x10)
reload_ucode_amd(cpu);
break;
+ case X86_VENDOR_HYGON:
+ reload_ucode_hygon(cpu);
+ break;
default:
break;
}
@@ -894,6 +907,8 @@ static int __init microcode_init(void)
microcode_ops = init_intel_microcode();
else if (c->x86_vendor == X86_VENDOR_AMD)
microcode_ops = init_amd_microcode();
+ else if (c->x86_vendor == X86_VENDOR_HYGON)
+ microcode_ops = init_hygon_microcode();
else
pr_err("no support for this CPU vendor\n");
diff --git a/arch/x86/kernel/cpu/microcode/hygon.c b/arch/x86/kernel/cpu/microcode/hygon.c
new file mode 100644
index 000000000..bea4375fb
--- /dev/null
+++ b/arch/x86/kernel/cpu/microcode/hygon.c
@@ -0,0 +1,824 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hygon CPU Microcode Update Driver for Linux
+ *
+ * This driver allows to upgrade microcode on Hygon CPUs.
+ *
+ */
+#define pr_fmt(fmt) "microcode: " fmt
+
+#include <linux/earlycpio.h>
+#include <linux/firmware.h>
+#include <linux/bsearch.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/initrd.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+#include <asm/microcode.h>
+#include <asm/processor.h>
+#include <asm/setup.h>
+#include <asm/cpu.h>
+#include <asm/msr.h>
+#include <asm/tlb.h>
+
+#include "internal.h"
+
+#define HYGON_UCODE_MAGIC 0x4859474F
+#define UCODE_EQUIV_CPU_TABLE_TYPE 0x00000000
+#define UCODE_UCODE_TYPE 0x00000001
+
+#define HYGON_CONTAINER_HDR_SIZE 12
+#define HYGON_CONTAINER_HDR_MAGIC_OFFSET 0
+#define HYGON_CONTAINER_HDR_TYPE_OFFSET 4
+#define HYGON_CONTAINER_HDR_LENGTH_OFFSET 8
+
+#define HYGON_SECTION_HDR_SIZE 8
+#define HYGON_SECTION_HDR_TYPE_OFFSET 0
+#define HYGON_SECTION_HDR_SIZE_OFFSET 4
+
+#define HYGON_MSR_PATCH_LEVEL 0x0000008b
+#define HYGON_MSR_PATCH_LOADER 0xc0010020
+
+#define HYGON_PROC_REV_ID_FAMILY_SHIFT 12
+#define HYGON_PROC_REV_ID_BASE_FAMILY 0xF
+
+#define HYGON_UCODE_FW_NAME "hygon-ucode/microcode_hygon_fam18h.bin"
+#define HYGON_UCODE_PATH "kernel/x86/microcode/HygonGenuine.bin"
+
+#define hygon_container_hdr(_buf, _field) \
+ (*(const u32 *)((_buf) + HYGON_CONTAINER_HDR_##_field##_OFFSET))
+
+#define hygon_section_hdr(_buf, _field) \
+ (*(const u32 *)((_buf) + HYGON_SECTION_HDR_##_field##_OFFSET))
+
+#define hygon_extract_proc_family(_proc_rev) \
+ (HYGON_PROC_REV_ID_BASE_FAMILY + \
+ ((_proc_rev) >> HYGON_PROC_REV_ID_FAMILY_SHIFT))
+
+#define hygon_pages_crossed(_start, _size) \
+ (((unsigned long)(_start) ^ ((unsigned long)(_start) + (_size) - 1)) >> PAGE_SHIFT)
+
+#define hygon_container_data(_buf) ((_buf) + HYGON_CONTAINER_HDR_SIZE)
+#define hygon_section_data(_buf) ((_buf) + HYGON_SECTION_HDR_SIZE)
+
+struct hygon_ucode_patch {
+ struct list_head list;
+ void *data;
+ unsigned int patch_size;
+ u32 patch_level;
+ u16 equiv_id;
+};
+
+struct hygon_microcode_header {
+ u32 data;
+ u32 patch_level;
+ u32 reserved0[4];
+ u16 processor_rev_id;
+ u16 reserved1;
+ u32 reserved2[9];
+} __packed;
+
+struct hygon_microcode {
+ struct hygon_microcode_header header;
+ unsigned int payload[];
+};
+
+struct hygon_cpu_equiv_entry {
+ u32 cpu_signature;
+ u16 equiv_id;
+ u16 reserved;
+} __packed;
+
+struct hygon_cpu_equiv_table {
+ unsigned int entry_count;
+ struct hygon_cpu_equiv_entry *entry;
+};
+
+struct hygon_container_desc {
+ struct hygon_microcode *mc;
+ u32 patch_size;
+ u8 *data;
+ size_t size;
+};
+
+static const char ucode_path[] __maybe_unused = HYGON_UCODE_PATH;
+static u32 bsp_cpuid_1_eax __ro_after_init;
+static LIST_HEAD(microcode_cache);
+static struct hygon_cpu_equiv_table equiv_table;
+
+static u32 get_current_patch_level(void)
+{
+ u32 rev, dummy __always_unused;
+
+ native_rdmsr(HYGON_MSR_PATCH_LEVEL, rev, dummy);
+
+ return rev;
+}
+
+static u16 find_equiv_id(struct hygon_cpu_equiv_table *et, u32 sig)
+{
+ unsigned int i;
+
+ if (!et || !et->entry_count)
+ return 0;
+
+ for (i = 0; i < et->entry_count; i++) {
+ if (sig == et->entry[i].cpu_signature)
+ return et->entry[i].equiv_id;
+ }
+ return 0;
+}
+
+static bool is_valid_microcode_container(const u8 *buf, size_t buf_size)
+{
+ if (buf_size <= HYGON_CONTAINER_HDR_SIZE) {
+ ucode_dbg("Truncated microcode container header.\n");
+ return false;
+ }
+
+ if (hygon_container_hdr(buf, MAGIC) != HYGON_UCODE_MAGIC) {
+ ucode_dbg("Invalid magic value (0x%08x).\n",
+ hygon_container_hdr(buf, MAGIC));
+ return false;
+ }
+
+ return true;
+}
+
+static bool verify_cpu_equivalence_table(const u8 *buf, size_t buf_size)
+{
+ u32 cont_type, equiv_tbl_len;
+
+ if (!is_valid_microcode_container(buf, buf_size))
+ return false;
+
+ cont_type = hygon_container_hdr(buf, TYPE);
+ if (cont_type != UCODE_EQUIV_CPU_TABLE_TYPE) {
+ ucode_dbg("Wrong microcode container equivalence table type: %u.\n",
+ cont_type);
+ return false;
+ }
+
+ buf_size -= HYGON_CONTAINER_HDR_SIZE;
+
+ equiv_tbl_len = hygon_container_hdr(buf, LENGTH);
+ if (equiv_tbl_len < sizeof(struct hygon_cpu_equiv_entry) ||
+ buf_size < equiv_tbl_len) {
+ ucode_dbg("Truncated equivalence table.\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool is_valid_patch_section(const u8 *buf, size_t buf_size, u32 *patch_size)
+{
+ if (buf_size < HYGON_SECTION_HDR_SIZE) {
+ ucode_dbg("Truncated patch section.\n");
+ return false;
+ }
+
+ *patch_size = hygon_section_hdr(buf, SIZE);
+
+ if (hygon_section_hdr(buf, TYPE) != UCODE_UCODE_TYPE) {
+ ucode_dbg("Invalid type field (0x%x) in container file section header.\n",
+ hygon_section_hdr(buf, TYPE));
+ return false;
+ }
+
+ if (*patch_size < sizeof(struct hygon_microcode_header)) {
+ ucode_dbg("Patch of size %u too short.\n", *patch_size);
+ return false;
+ }
+
+ return true;
+}
+
+static int validate_patch_section(const u8 *buf, size_t buf_size, u32 *patch_size)
+{
+ u8 family = x86_family(bsp_cpuid_1_eax);
+ struct hygon_microcode_header *mc_hdr;
+ u16 proc_id;
+
+ if (!is_valid_patch_section(buf, buf_size, patch_size))
+ return -1;
+
+ buf_size -= HYGON_SECTION_HDR_SIZE;
+
+ if (buf_size < *patch_size) {
+ ucode_dbg("Patch of size %u truncated.\n", *patch_size);
+ return -1;
+ }
+
+ mc_hdr = (struct hygon_microcode_header *)hygon_section_data(buf);
+
+ proc_id = mc_hdr->processor_rev_id;
+
+ if (hygon_extract_proc_family(proc_id) != family)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Attempt to consume one container from the ucode blob.
+ *
+ * This function processes a single container starting at @ucode. It verifies
+ * the container's CPU equivalence table, locates the equivalence ID for the
+ * current CPU, and then scans all patch sections inside the container.
+ *
+ * If a patch matching the CPU's equivalence ID is found, the function fills
+ * @desc with the patch metadata and the container's bounds, and returns 0.
+ * Otherwise, it returns the total number of bytes consumed (the entire size
+ * of this container) so the caller can skip it and continue scanning.
+ */
+static size_t try_consume_container(u8 *ucode, size_t size, struct hygon_container_desc *desc)
+{
+ struct hygon_cpu_equiv_table table;
+ size_t orig_size = size;
+ u32 equiv_data_len;
+ u16 cpu_equiv_id;
+ u8 *buf;
+
+ if (!verify_cpu_equivalence_table(ucode, size))
+ return 0;
+
+ buf = ucode;
+ equiv_data_len = hygon_container_hdr(buf, LENGTH);
+
+ table.entry = (struct hygon_cpu_equiv_entry *)hygon_container_data(buf);
+ table.entry_count = equiv_data_len / sizeof(struct hygon_cpu_equiv_entry);
+
+ cpu_equiv_id = find_equiv_id(&table, bsp_cpuid_1_eax);
+
+ buf += equiv_data_len + HYGON_CONTAINER_HDR_SIZE;
+ size -= equiv_data_len + HYGON_CONTAINER_HDR_SIZE;
+
+ while (size > 0) {
+ struct hygon_microcode *mc;
+ u32 patch_size;
+ int ret;
+
+ ret = validate_patch_section(buf, size, &patch_size);
+ if (ret < 0)
+ goto out;
+ else if (ret > 0)
+ goto skip;
+
+ mc = (struct hygon_microcode *)hygon_section_data(buf);
+
+ if (cpu_equiv_id == mc->header.processor_rev_id) {
+ desc->patch_size = patch_size;
+ desc->mc = mc;
+
+ ucode_dbg(" match: size: %d\n", patch_size);
+ }
+
+skip:
+ buf += patch_size + HYGON_SECTION_HDR_SIZE;
+ size -= patch_size + HYGON_SECTION_HDR_SIZE;
+ }
+
+out:
+ /*
+ * If we have found a matching patch, record the container's base pointer
+ * and its total size, then return 0 to indicate that @ucode already points
+ * to the correct container.
+ *
+ * Otherwise, return the total number of bytes scanned, allowing the caller
+ * to skip this entire container and try the next one.
+ */
+ if (desc->mc) {
+ desc->data = ucode;
+ desc->size = orig_size - size;
+
+ return 0;
+ }
+
+ return orig_size - size;
+}
+
+/*
+ * Find the container that contains a patch for the current CPU
+ *
+ * The ucode blob may consist of multiple containers concatenated together.
+ * This function iterates through them by repeatedly calling try_consume_container().
+ * Once a container with a matching patch is found, it fills @desc and returns.
+ * If no matching container exists in the entire blob, @desc remains unchanged.
+ */
+static void locate_patch_container(u8 *ucode, size_t size, struct hygon_container_desc *desc)
+{
+ while (size) {
+ size_t consumed = try_consume_container(ucode, size, desc);
+
+ if (!consumed)
+ return;
+
+ if (size >= consumed) {
+ ucode += consumed;
+ size -= consumed;
+ } else {
+ return;
+ }
+ }
+}
+
+static bool apply_hygon_patch(struct hygon_microcode *mc, u32 *cur_rev,
+ unsigned int patch_size)
+{
+ unsigned long patch_start = (unsigned long)&mc->header.data;
+ unsigned long patch_end = patch_start + patch_size - 1;
+
+ native_wrmsrq(HYGON_MSR_PATCH_LOADER, patch_start);
+ invlpg(patch_start);
+
+ /*
+ * Invalidate the next page if the patch image crosses a page boundary.
+ */
+ if (hygon_pages_crossed(patch_start, patch_size))
+ invlpg(patch_end);
+
+ if (IS_ENABLED(CONFIG_MICROCODE_DBG) && x86_hypervisor_present)
+ microcode_rev[smp_processor_id()] = mc->header.patch_level;
+
+ *cur_rev = get_current_patch_level();
+
+ ucode_dbg("updated rev: 0x%x\n", *cur_rev);
+
+ return *cur_rev == mc->header.patch_level;
+}
+
+static bool load_builtin_microcode(struct cpio_data *cp)
+{
+ struct firmware fw;
+
+ if (IS_ENABLED(CONFIG_X86_32))
+ return false;
+
+ if (firmware_request_builtin(&fw, HYGON_UCODE_FW_NAME)) {
+ cp->size = fw.size;
+ cp->data = (void *)fw.data;
+ return true;
+ }
+
+ return false;
+}
+
+static bool __init find_microcode_blob(struct cpio_data *ret)
+{
+ struct cpio_data cp;
+
+ if (!load_builtin_microcode(&cp))
+ cp = find_microcode_in_initrd(ucode_path);
+
+ if (cp.data && cp.size) {
+ *ret = cp;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * This function handles microcode loading during the VERY EARLY boot stage
+ * (before vmalloc is available). The entire operation must be done in-place
+ * within the initrd memory.
+ *
+ * Flow:
+ * 1. Read current microcode revision.
+ * 2. Locate the microcode container blob from initrd.
+ * 3. Traverse the Equivalent CPU Table to find the matching microcode patch.
+ * 4. Apply the patch if it is newer than the current revision.
+ */
+void __init load_ucode_hygon_bsp(struct early_load_data *ed, unsigned int cpuid_1_eax)
+{
+ struct hygon_container_desc desc = { };
+ struct hygon_microcode *mc;
+ struct cpio_data cp = { };
+ u32 rev;
+
+ bsp_cpuid_1_eax = cpuid_1_eax;
+
+ rev = get_current_patch_level();
+ ed->old_rev = rev;
+
+ /* Needed in hygon_load_microcode() */
+ ucode_cpu_info[0].cpu_sig.sig = cpuid_1_eax;
+
+ if (!find_microcode_blob(&cp))
+ return;
+
+ locate_patch_container(cp.data, cp.size, &desc);
+
+ mc = desc.mc;
+
+ if (!mc || (ed->old_rev > mc->header.patch_level))
+ return;
+
+ if (apply_hygon_patch(mc, &rev, desc.patch_size))
+ ed->new_rev = rev;
+}
+
+/*
+ * a small, trivial cache of per-family ucode patches
+ */
+static struct hygon_ucode_patch *find_cached_patch(u16 equiv_id)
+{
+ struct hygon_ucode_patch *p;
+
+ list_for_each_entry(p, µcode_cache, list)
+ if (p->equiv_id == equiv_id)
+ return p;
+
+ return NULL;
+}
+
+static void add_to_cache(struct hygon_ucode_patch *new_patch)
+{
+ struct hygon_ucode_patch *p;
+
+ list_for_each_entry(p, µcode_cache, list) {
+ if (p->equiv_id == new_patch->equiv_id) {
+ if (p->patch_level >= new_patch->patch_level) {
+ /* we already have the latest patch */
+ kfree(new_patch->data);
+ kfree(new_patch);
+ return;
+ }
+
+ list_replace(&p->list, &new_patch->list);
+ kfree(p->data);
+ kfree(p);
+ return;
+ }
+ }
+ /* no patch found, add it */
+ list_add_tail(&new_patch->list, µcode_cache);
+}
+
+static void clear_microcode_cache(void)
+{
+ struct hygon_ucode_patch *p, *tmp;
+
+ list_for_each_entry_safe(p, tmp, µcode_cache, list) {
+ __list_del(p->list.prev, p->list.next);
+ kfree(p->data);
+ kfree(p);
+ }
+}
+
+static struct hygon_ucode_patch *find_hygon_patch(unsigned int cpu)
+{
+ struct ucode_cpu_info *u_cpu_info = ucode_cpu_info + cpu;
+ u16 equiv_id;
+
+ u_cpu_info->cpu_sig.rev = get_current_patch_level();
+
+ equiv_id = find_equiv_id(&equiv_table, u_cpu_info->cpu_sig.sig);
+
+ return equiv_id ? find_cached_patch(equiv_id) : NULL;
+}
+
+void reload_ucode_hygon(unsigned int cpu)
+{
+ struct hygon_microcode *mc;
+ struct hygon_ucode_patch *p;
+ u32 rev;
+
+ p = find_hygon_patch(cpu);
+ if (!p)
+ return;
+
+ mc = p->data;
+
+ rev = get_current_patch_level();
+ if (rev < mc->header.patch_level) {
+ if (apply_hygon_patch(mc, &rev, p->patch_size))
+ pr_info_once("reload revision: 0x%08x\n", rev);
+ }
+}
+
+static int hygon_collect_cpu_info(int cpu, struct cpu_signature *csig)
+{
+ struct ucode_cpu_info *u_cpu_info = ucode_cpu_info + cpu;
+ struct hygon_ucode_patch *p;
+
+ csig->sig = cpuid_eax(0x00000001);
+ csig->rev = get_current_patch_level();
+
+ p = find_hygon_patch(cpu);
+ if (p && (p->patch_level == csig->rev))
+ u_cpu_info->mc = p->data;
+
+ return 0;
+}
+
+static enum ucode_state hygon_apply_microcode(int cpu)
+{
+ struct cpuinfo_x86 *c = &cpu_data(cpu);
+ struct hygon_microcode *mc_hygon;
+ struct ucode_cpu_info *u_cpu_info;
+ struct hygon_ucode_patch *p;
+ enum ucode_state ret;
+ u32 rev;
+
+ if (raw_smp_processor_id() != cpu) {
+ WARN_ON_ONCE(1);
+ return UCODE_ERROR;
+ }
+
+ u_cpu_info = ucode_cpu_info + cpu;
+
+ p = find_hygon_patch(cpu);
+ if (!p)
+ return UCODE_NFOUND;
+
+ rev = u_cpu_info->cpu_sig.rev;
+
+ mc_hygon = p->data;
+ u_cpu_info->mc = p->data;
+
+ /* Skip update if current revision is already newer than the patch */
+ if (rev > mc_hygon->header.patch_level) {
+ ret = UCODE_OK;
+ goto out;
+ }
+
+ if (!apply_hygon_patch(mc_hygon, &rev, p->patch_size)) {
+ pr_err("CPU%d: update failed for patch_level=0x%08x\n",
+ cpu, mc_hygon->header.patch_level);
+ return UCODE_ERROR;
+ }
+
+ rev = mc_hygon->header.patch_level;
+ ret = UCODE_UPDATED;
+
+out:
+ u_cpu_info->cpu_sig.rev = rev;
+ c->microcode = rev;
+
+ /* Update boot CPU's microcode revision as well */
+ if (c->cpu_index == boot_cpu_data.cpu_index)
+ boot_cpu_data.microcode = rev;
+
+ return ret;
+}
+
+void load_ucode_hygon_ap(unsigned int cpuid_1_eax)
+{
+ unsigned int cpu = smp_processor_id();
+
+ ucode_cpu_info[cpu].cpu_sig.sig = cpuid_1_eax;
+ hygon_apply_microcode(cpu);
+}
+
+static size_t load_equiv_cpu_table(const u8 *buf, size_t buf_size)
+{
+ u32 equiv_tbl_len;
+
+ if (!verify_cpu_equivalence_table(buf, buf_size))
+ return 0;
+
+ equiv_tbl_len = hygon_container_hdr(buf, LENGTH);
+
+ equiv_table.entry = vmalloc(equiv_tbl_len);
+ if (!equiv_table.entry) {
+ pr_err("failed to allocate equivalent CPU table\n");
+ return 0;
+ }
+
+ memcpy(equiv_table.entry, hygon_container_data(buf), equiv_tbl_len);
+ equiv_table.entry_count = equiv_tbl_len / sizeof(struct hygon_cpu_equiv_entry);
+
+ return equiv_tbl_len + HYGON_CONTAINER_HDR_SIZE;
+}
+
+static void clear_equiv_cpu_table(void)
+{
+ vfree(equiv_table.entry);
+ memset(&equiv_table, 0, sizeof(equiv_table));
+}
+
+static void hygon_cleanup_microcode(void)
+{
+ clear_equiv_cpu_table();
+ clear_microcode_cache();
+}
+
+/*
+ * Return a non-negative value even if some of the checks failed so that
+ * we can skip over the next patch. If we return a negative value, we
+ * signal a grave error like a memory allocation has failed and the
+ * driver cannot continue functioning normally. In such cases, we tear
+ * down everything we've used up so far and exit.
+ */
+static int try_verify_and_cache_patch(u8 family, u8 *fw, unsigned int leftover,
+ unsigned int *patch_size)
+{
+ struct hygon_microcode_header *mc_hdr;
+ struct hygon_ucode_patch *patch;
+ int ret;
+
+ ret = validate_patch_section(fw, leftover, patch_size);
+ if (ret)
+ return ret;
+
+ patch = kzalloc_obj(*patch);
+ if (!patch) {
+ pr_err("Patch allocation failure.\n");
+ return -EINVAL;
+ }
+
+ patch->data = kmemdup(hygon_section_data(fw), *patch_size, GFP_KERNEL);
+ if (!patch->data) {
+ pr_err("Patch data allocation failure.\n");
+ kfree(patch);
+ return -EINVAL;
+ }
+ patch->patch_size = *patch_size;
+
+ mc_hdr = (struct hygon_microcode_header *)hygon_section_data(fw);
+
+ INIT_LIST_HEAD(&patch->list);
+ patch->patch_level = mc_hdr->patch_level;
+ patch->equiv_id = mc_hdr->processor_rev_id;
+
+ ucode_dbg("%s: Adding patch_level: 0x%08x, proc_id: 0x%04x\n",
+ __func__, patch->patch_level, patch->equiv_id);
+
+ add_to_cache(patch);
+
+ return 0;
+}
+
+/* Scan the blob in @data and add microcode patches to the cache. */
+static enum ucode_state parse_and_cache_patches(u8 family, const u8 *data, size_t size)
+{
+ u8 *fw = (u8 *)data;
+ size_t offset;
+
+ offset = load_equiv_cpu_table(data, size);
+ if (!offset)
+ return UCODE_ERROR;
+
+ fw += offset;
+ size -= offset;
+ if (size < HYGON_SECTION_HDR_SIZE) {
+ pr_err("no patch sections in container file\n");
+ return UCODE_ERROR;
+ }
+
+ if (hygon_section_hdr(fw, TYPE) != UCODE_UCODE_TYPE) {
+ pr_err("invalid type field in container file section header\n");
+ clear_equiv_cpu_table();
+ return UCODE_ERROR;
+ }
+
+ while (size > 0) {
+ unsigned int crnt_size = 0;
+ int ret;
+
+ ret = try_verify_and_cache_patch(family, fw, size, &crnt_size);
+ if (ret < 0)
+ return UCODE_ERROR;
+
+ fw += crnt_size + HYGON_SECTION_HDR_SIZE;
+ size -= (crnt_size + HYGON_SECTION_HDR_SIZE);
+ }
+
+ return UCODE_OK;
+}
+
+static enum ucode_state prepare_microcode_blob(u8 family, const u8 *data, size_t size)
+{
+ enum ucode_state ret;
+
+ clear_equiv_cpu_table();
+
+ ret = parse_and_cache_patches(family, data, size);
+ if (ret != UCODE_OK)
+ hygon_cleanup_microcode();
+
+ return ret;
+}
+
+static enum ucode_state hygon_load_microcode(u8 family, const u8 *data, size_t size)
+{
+ struct cpuinfo_x86 *c;
+ unsigned int nid, cpu;
+ struct hygon_ucode_patch *p;
+ enum ucode_state ret;
+
+ ret = prepare_microcode_blob(family, data, size);
+ if (ret != UCODE_OK)
+ return ret;
+
+ for_each_node_with_cpus(nid) {
+ cpu = cpumask_first(cpumask_of_node(nid));
+ c = &cpu_data(cpu);
+
+ p = find_hygon_patch(cpu);
+ if (!p)
+ continue;
+
+ if (c->microcode >= p->patch_level)
+ continue;
+
+ ret = UCODE_NEW;
+ }
+
+ return ret;
+}
+
+static int __init save_microcode_in_initrd(void)
+{
+ struct cpuinfo_x86 *c = &boot_cpu_data;
+ struct hygon_container_desc desc = { 0 };
+ unsigned int cpuid_1_eax;
+ enum ucode_state ret;
+ struct cpio_data cp;
+
+ if (microcode_loader_disabled() || (c->x86_vendor != X86_VENDOR_HYGON))
+ return 0;
+
+ cpuid_1_eax = native_cpuid_eax(1);
+
+ if (!find_microcode_blob(&cp))
+ return -EINVAL;
+
+ locate_patch_container(cp.data, cp.size, &desc);
+ if (!desc.mc)
+ return -EINVAL;
+
+ ret = prepare_microcode_blob(x86_family(cpuid_1_eax), desc.data, desc.size);
+ return (ret > UCODE_UPDATED) ? -EINVAL : 0;
+}
+early_initcall(save_microcode_in_initrd);
+
+/*
+ * Hygon microcode firmware naming are in family-specific firmware files:
+ * hygon-ucode/microcode_hygon_fam18h.bin
+ */
+static enum ucode_state hygon_request_microcode(int cpu, struct device *device)
+{
+ struct cpuinfo_x86 *c = &cpu_data(cpu);
+ enum ucode_state ret = UCODE_NFOUND;
+ const struct firmware *fw;
+
+ if (force_minrev)
+ return UCODE_NFOUND;
+
+ if (request_firmware_direct(&fw, (const char *)HYGON_UCODE_FW_NAME, device)) {
+ ucode_dbg("failed to load file %s\n", HYGON_UCODE_FW_NAME);
+ goto out;
+ }
+
+ ret = UCODE_ERROR;
+ if (!is_valid_microcode_container(fw->data, fw->size))
+ goto fw_release;
+
+ ret = hygon_load_microcode(c->x86, fw->data, fw->size);
+
+ fw_release:
+ release_firmware(fw);
+
+ out:
+ return ret;
+}
+
+static void hygon_microcode_fini_cpu(int cpu)
+{
+ struct ucode_cpu_info *u_cpu_info = ucode_cpu_info + cpu;
+
+ u_cpu_info->mc = NULL;
+}
+
+static void hygon_finalize_late_load(int result)
+{
+ if (result)
+ hygon_cleanup_microcode();
+}
+
+static struct microcode_ops microcode_hygon_ops = {
+ .request_microcode_fw = hygon_request_microcode,
+ .collect_cpu_info = hygon_collect_cpu_info,
+ .apply_microcode = hygon_apply_microcode,
+ .microcode_fini_cpu = hygon_microcode_fini_cpu,
+ .finalize_late_load = hygon_finalize_late_load,
+ .nmi_safe = true,
+};
+
+struct microcode_ops * __init init_hygon_microcode(void)
+{
+ if (boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
+ return NULL;
+
+ return µcode_hygon_ops;
+}
+
+void __exit exit_hygon_microcode(void)
+{
+ hygon_cleanup_microcode();
+}
diff --git a/arch/x86/kernel/cpu/microcode/internal.h b/arch/x86/kernel/cpu/microcode/internal.h
index a10b547ed..f8b624255 100644
--- a/arch/x86/kernel/cpu/microcode/internal.h
+++ b/arch/x86/kernel/cpu/microcode/internal.h
@@ -60,6 +60,9 @@ struct cpio_data find_microcode_in_initrd(const char *path);
#define CPUID_AMD1 QCHAR('A', 'u', 't', 'h')
#define CPUID_AMD2 QCHAR('e', 'n', 't', 'i')
#define CPUID_AMD3 QCHAR('c', 'A', 'M', 'D')
+#define CPUID_HYGON1 QCHAR('H', 'y', 'g', 'o')
+#define CPUID_HYGON2 QCHAR('n', 'G', 'e', 'n')
+#define CPUID_HYGON3 QCHAR('u', 'i', 'n', 'e')
#define CPUID_IS(a, b, c, ebx, ecx, edx) \
(!(((ebx) ^ (a)) | ((edx) ^ (b)) | ((ecx) ^ (c))))
@@ -86,6 +89,9 @@ static inline int x86_cpuid_vendor(void)
if (CPUID_IS(CPUID_AMD1, CPUID_AMD2, CPUID_AMD3, ebx, ecx, edx))
return X86_VENDOR_AMD;
+ if (CPUID_IS(CPUID_HYGON1, CPUID_HYGON2, CPUID_HYGON3, ebx, ecx, edx))
+ return X86_VENDOR_HYGON;
+
return X86_VENDOR_UNKNOWN;
}
@@ -127,6 +133,20 @@ static inline void reload_ucode_intel(void) { }
static inline struct microcode_ops *init_intel_microcode(void) { return NULL; }
#endif /* !CONFIG_CPU_SUP_INTEL */
+#ifdef CONFIG_CPU_SUP_HYGON
+void load_ucode_hygon_bsp(struct early_load_data *ed, unsigned int family);
+void load_ucode_hygon_ap(unsigned int family);
+void reload_ucode_hygon(unsigned int cpu);
+struct microcode_ops *init_hygon_microcode(void);
+void exit_hygon_microcode(void);
+#else /* CONFIG_CPU_SUP_HYGON */
+static inline void load_ucode_hygon_bsp(struct early_load_data *ed, unsigned int family) { }
+static inline void load_ucode_hygon_ap(unsigned int family) { }
+static inline void reload_ucode_hygon(unsigned int cpu) { }
+static inline struct microcode_ops *init_hygon_microcode(void) { return NULL; }
+static inline void exit_hygon_microcode(void) { }
+#endif /* !CONFIG_CPU_SUP_HYGON */
+
#define ucode_dbg(fmt, ...) \
({ \
if (IS_ENABLED(CONFIG_MICROCODE_DBG)) \
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-10 6:56 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 6:49 [PATCH v4 0/5] Add support for Hygon family 18h models 4h-8h Fu Hao
2026-07-10 6:50 ` [PATCH v4 1/5] x86/cpu/hygon: Adjust the die_id and logical_die_id for Hygon models 0x4 through 0x8 Fu Hao
2026-07-10 6:51 ` [PATCH v4 2/5] x86/cpu: Get LLC ID for Hygon processor models 0x6 " Fu Hao
2026-07-10 6:52 ` [PATCH v4 3/5] x86/cpu/hygon: Remove Spectral Chicken for Hygon processors Fu Hao
2026-07-10 6:53 ` [PATCH v4 4/5] perf/x86/uncore: Add L3 PMU support for Hygon family 18h model 6h Fu Hao
2026-07-10 6:54 ` [PATCH v4 5/5] x86/microcode/hygon: Add microcode loading support for Hygon processors Fu Hao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox