[PATCH RFC v1 3/8] x86/cpu/topology: Introduce primary core mask

From: Chang S. Bae

Date: Fri Sep 11 2026 - 20:35:50 EST


The microcode loading distinguishes primary SMT threads from sibling
threads to avoid races during updates. Because early loading relies on
this distinction, parallel CPU bringup splits APs into two groups and
brings them up in phases.

Upcoming Intel microcode loading feature expands the loading scope beyond
per-core updates to package-wide or system-wide updates. While CPU0 is
the obvious primary for the system-wide scope, there is currently no
facility to identify per-package primary CPUs for the parallel bringup.

This matters because the parallel bringup logic must determine primary
CPUs before any AP is ever brought up. But topology_logical_package_id()
is not usable at that stage, as logical package IDs are established
during AP bringup itself, as described in:

7af541cee1e0e ("x86/topology: Don't evaluate logical IDs during early boot")

Unlike cpu_primary_thread_mask, there is also no relevant mask that
tracks one primary CPU per package. Thus, introduce cpu_primary_core_mask
to fill this gap.

The mask intentionally includes all threads of the selected core. This
preserves core-domain granularity and avoids coupling with the lower-
level topology details such as SMT primary selection.

Use the mask for the microcode staging path to simplify staging CPU
selection at the moment. Following changes will address the microcode
loading use.

Signed-off-by: Chang S. Bae <chang.seok.bae@xxxxxxxxx>
---
Note:
The staging path itself could have continued using topology_logical_package_id()
as the late loading is only relevant. IOW, the cpu_primary_core_mask
introduction was not required for the staging enablement back then.
---
arch/x86/include/asm/topology.h | 3 +++
arch/x86/kernel/cpu/microcode/intel.c | 14 ++++----------
arch/x86/kernel/cpu/topology.c | 23 ++++++++++++++++++++---
arch/x86/kernel/cpu/topology_common.c | 9 +++++++++
4 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/arch/x86/include/asm/topology.h b/arch/x86/include/asm/topology.h
index 1825691af941..02ad56cfc6f4 100644
--- a/arch/x86/include/asm/topology.h
+++ b/arch/x86/include/asm/topology.h
@@ -234,6 +234,9 @@ static inline unsigned int topology_amd_nodes_per_pkg(void) { return 1; }
extern struct cpumask __cpu_primary_thread_mask;
#define cpu_primary_thread_mask ((const struct cpumask *)&__cpu_primary_thread_mask)

+extern struct cpumask __cpu_primary_core_mask;
+#define cpu_primary_core_mask ((const struct cpumask *)&__cpu_primary_core_mask)
+
/**
* topology_is_primary_thread - Check whether CPU is the primary SMT thread
* @cpu: CPU to check
diff --git a/arch/x86/kernel/cpu/microcode/intel.c b/arch/x86/kernel/cpu/microcode/intel.c
index 1142183c950c..4b5feee14712 100644
--- a/arch/x86/kernel/cpu/microcode/intel.c
+++ b/arch/x86/kernel/cpu/microcode/intel.c
@@ -605,7 +605,6 @@ static int do_stage(u64 mmio_pa)

static void stage_microcode(void)
{
- unsigned int pkg_id = UINT_MAX;
int cpu, err;
u64 mmio_pa;

@@ -619,15 +618,10 @@ static void stage_microcode(void)

/*
* The MMIO address is unique per package, and all the SMT
- * primary threads are online here. Find each MMIO space by
- * their package IDs to avoid duplicate staging.
+ * primary threads are online here. Find unique MMIO space to
+ * avoid duplicate staging.
*/
- for_each_cpu(cpu, cpu_primary_thread_mask) {
- if (topology_logical_package_id(cpu) == pkg_id)
- continue;
-
- pkg_id = topology_logical_package_id(cpu);
-
+ for_each_cpu_and(cpu, cpu_primary_core_mask, cpu_primary_thread_mask) {
err = rdmsrq_on_cpu(cpu, MSR_IA32_MCU_STAGING_MBOX_ADDR, &mmio_pa);
if (WARN_ON_ONCE(err))
return;
@@ -635,7 +629,7 @@ static void stage_microcode(void)
err = do_stage(mmio_pa);
if (err) {
pr_err("Error: staging failed (%d) for CPU%d at package %u.\n",
- err, cpu, pkg_id);
+ err, cpu, topology_logical_package_id(cpu));
return;
}
}
diff --git a/arch/x86/kernel/cpu/topology.c b/arch/x86/kernel/cpu/topology.c
index 4913b64ec592..be6f5a3bd54f 100644
--- a/arch/x86/kernel/cpu/topology.c
+++ b/arch/x86/kernel/cpu/topology.c
@@ -75,12 +75,25 @@ bool arch_match_cpu_phys_id(int cpu, u64 phys_id)
return phys_id == (u64)cpuid_to_apicid[cpu];
}

+static unsigned int __max_cores_per_package __ro_after_init = 1;
+
static void cpu_mark_primary_thread(unsigned int cpu, unsigned int apicid)
{
if (!(apicid & (__max_threads_per_core - 1)))
cpumask_set_cpu(cpu, &__cpu_primary_thread_mask);
}

+/*
+ * Select core 0 of every package and all SMT threads of that core so that the
+ * mask stays at core granularity.
+ */
+static void cpu_mark_primary_core(unsigned int cpu, unsigned int apicid)
+{
+ if (!((apicid >> get_count_order(__max_threads_per_core)) &
+ (__max_cores_per_package - 1)))
+ cpumask_set_cpu(cpu, &__cpu_primary_core_mask);
+}
+
/*
* Convert the APIC ID to a domain level ID by masking out the low bits
* below the domain level @dom.
@@ -505,13 +518,16 @@ void __init topology_init_possible_cpus(void)
pr_info("Max. logical dies: %3u\n", cntb);
pr_info("Max. dies per package: %3u\n", __max_dies_per_package);

- cnta = domain_weight(TOPO_CORE_DOMAIN);
- cntb = domain_weight(TOPO_SMT_DOMAIN);
+ cntb = domain_weight(TOPO_CORE_DOMAIN);
+ __max_cores_per_package = 1U << (get_count_order(cntb) - get_count_order(cnta));
+ pr_info("Max. cores per package:%3u\n", __max_cores_per_package);
+
+ cnta = domain_weight(TOPO_SMT_DOMAIN);
/*
* Can't use order delta here as order(cnta) can be equal
* order(cntb) even if cnta != cntb.
*/
- __max_threads_per_core = DIV_ROUND_UP(cntb, cnta);
+ __max_threads_per_core = DIV_ROUND_UP(cnta, cntb);
pr_info("Max. threads per core: %3u\n", __max_threads_per_core);

firstid = find_first_bit(apic_maps[TOPO_SMT_DOMAIN].map, MAX_LOCAL_APIC);
@@ -545,6 +561,7 @@ void __init topology_init_possible_cpus(void)
continue;

cpu_mark_primary_thread(cpu, apicid);
+ cpu_mark_primary_core(cpu, apicid);
set_cpu_present(cpu, test_bit(apicid, phys_cpu_present_map));
}
}
diff --git a/arch/x86/kernel/cpu/topology_common.c b/arch/x86/kernel/cpu/topology_common.c
index 6845e3c63fbb..dc1d96be8ba5 100644
--- a/arch/x86/kernel/cpu/topology_common.c
+++ b/arch/x86/kernel/cpu/topology_common.c
@@ -20,6 +20,15 @@ EXPORT_SYMBOL_GPL(__amd_nodes_per_pkg);
/* CPUs which are the primary SMT threads */
struct cpumask __cpu_primary_thread_mask __read_mostly;

+/*
+ * CPUs belonging to the primary core of each package
+ *
+ * To preserve core-domain granularity and to avoid the next-level primary
+ * details that cpu_primary_thread_mask can present, the mask includes all
+ * threads of each primary core.
+ */
+struct cpumask __cpu_primary_core_mask __read_mostly;
+
void topology_set_dom(struct topo_scan *tscan, enum x86_topology_domains dom,
unsigned int shift, unsigned int ncpus)
{
--
2.53.0