2
0
mirror of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2025-09-04 20:19:47 +08:00
linux/arch/x86/kernel/cpu/topology_amd.c
K Prateek Nayak c2415c407a x86/cpu/topology: Use initial APIC ID from XTOPOLOGY leaf on AMD/HYGON
Prior to the topology parsing rewrite and the switchover to the new parsing
logic for AMD processors in

  c749ce393b ("x86/cpu: Use common topology code for AMD"),

the initial_apicid on these platforms was:

- First initialized to the LocalApicId from CPUID leaf 0x1 EBX[31:24].

- Then overwritten by the ExtendedLocalApicId in CPUID leaf 0xb
  EDX[31:0] on processors that supported topoext.

With the new parsing flow introduced in

  f7fb3b2dd9 ("x86/cpu: Provide an AMD/HYGON specific topology parser"),

parse_8000_001e() now unconditionally overwrites the initial_apicid already
parsed during cpu_parse_topology_ext().

Although this has not been a problem on baremetal platforms, on virtualized AMD
guests that feature more than 255 cores, QEMU zeros out the CPUID leaf
0x8000001e on CPUs with CoreID > 255 to prevent collision of these IDs in
EBX[7:0] which can only represent a maximum of 255 cores [1].

This results in the following FW_BUG being logged when booting a guest
with more than 255 cores:

    [Firmware Bug]: CPU 512: APIC ID mismatch. CPUID: 0x0000 APIC: 0x0200

AMD64 Architecture Programmer's Manual Volume 2: System Programming Pub.
24593 Rev. 3.42 [2] Section 16.12 "x2APIC_ID" mentions the Extended
Enumeration leaf 0xb (Fn0000_000B_EDX[31:0])(which was later superseded by the
extended leaf 0x80000026) provides the full x2APIC ID under all circumstances
unlike the one reported by CPUID leaf 0x8000001e EAX which depends on the mode
in which APIC is configured.

Rely on the APIC ID parsed during cpu_parse_topology_ext() from CPUID leaf
0x80000026 or 0xb and only use the APIC ID from leaf 0x8000001e if
cpu_parse_topology_ext() failed (has_topoext is false).

On platforms that support the 0xb leaf (Zen2 or later, AMD guests on
QEMU) or the extended leaf 0x80000026 (Zen4 or later), the
initial_apicid is now set to the value parsed from EDX[31:0].

On older AMD/Hygon platforms that do not support the 0xb leaf but support the
TOPOEXT extension (families 0x15, 0x16, 0x17[Zen1], and Hygon), retain current
behavior where the initial_apicid is set using the 0x8000001e leaf.

Issue debugged by Naveen N Rao (AMD) <naveen@kernel.org> and Sairaj Kodilkar
<sarunkod@amd.com>.

  [ bp: Massage commit message. ]

Fixes: c749ce393b ("x86/cpu: Use common topology code for AMD")
Suggested-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: K Prateek Nayak <kprateek.nayak@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Tested-by: Naveen N Rao (AMD) <naveen@kernel.org>
Cc: stable@vger.kernel.org
Link: https://github.com/qemu/qemu/commit/35ac5dfbcaa4b [1]
Link: https://bugzilla.kernel.org/show_bug.cgi?id=206537 [2]
Link: https://lore.kernel.org/20250825075732.10694-2-kprateek.nayak@amd.com
2025-08-27 11:31:11 +02:00

227 lines
6.2 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <linux/cpu.h>
#include <asm/apic.h>
#include <asm/memtype.h>
#include <asm/msr.h>
#include <asm/processor.h>
#include "cpu.h"
static bool parse_8000_0008(struct topo_scan *tscan)
{
struct {
// ecx
u32 cpu_nthreads : 8, // Number of physical threads - 1
: 4, // Reserved
apicid_coreid_len : 4, // Number of thread core ID bits (shift) in APIC ID
perf_tsc_len : 2, // Performance time-stamp counter size
: 14; // Reserved
} ecx;
unsigned int sft;
if (tscan->c->extended_cpuid_level < 0x80000008)
return false;
cpuid_leaf_reg(0x80000008, CPUID_ECX, &ecx);
/* If the thread bits are 0, then get the shift value from ecx.cpu_nthreads */
sft = ecx.apicid_coreid_len;
if (!sft)
sft = get_count_order(ecx.cpu_nthreads + 1);
/*
* cpu_nthreads describes the number of threads in the package
* sft is the number of APIC ID bits per package
*
* As the number of actual threads per core is not described in
* this leaf, just set the CORE domain shift and let the later
* parsers set SMT shift. Assume one thread per core by default
* which is correct if there are no other CPUID leafs to parse.
*/
topology_update_dom(tscan, TOPO_SMT_DOMAIN, 0, 1);
topology_set_dom(tscan, TOPO_CORE_DOMAIN, sft, ecx.cpu_nthreads + 1);
return true;
}
static void store_node(struct topo_scan *tscan, u16 nr_nodes, u16 node_id)
{
/*
* Starting with Fam 17h the DIE domain could probably be used to
* retrieve the node info on AMD/HYGON. Analysis of CPUID dumps
* suggests it's the topmost bit(s) of the CPU cores area, but
* that's guess work and neither enumerated nor documented.
*
* Up to Fam 16h this does not work at all and the legacy node ID
* has to be used.
*/
tscan->amd_nodes_per_pkg = nr_nodes;
tscan->amd_node_id = node_id;
}
static bool parse_8000_001e(struct topo_scan *tscan, bool has_topoext)
{
struct {
// eax
u32 ext_apic_id : 32; // Extended APIC ID
// ebx
u32 core_id : 8, // Unique per-socket logical core unit ID
core_nthreads : 8, // #Threads per core (zero-based)
: 16; // Reserved
// ecx
u32 node_id : 8, // Node (die) ID of invoking logical CPU
nnodes_per_socket : 3, // #nodes in invoking logical CPU's package/socket
: 21; // Reserved
// edx
u32 : 32; // Reserved
} leaf;
if (!boot_cpu_has(X86_FEATURE_TOPOEXT))
return false;
cpuid_leaf(0x8000001e, &leaf);
/*
* If leaf 0xb/0x26 is available, then the APIC ID and the domain
* shifts are set already.
*/
if (!has_topoext) {
tscan->c->topo.initial_apicid = leaf.ext_apic_id;
/*
* Leaf 0x8000008 sets the CORE domain shift but not the
* SMT domain shift. On CPUs with family >= 0x17, there
* might be hyperthreads.
*/
if (tscan->c->x86 >= 0x17) {
/* Update the SMT domain, but do not propagate it. */
unsigned int nthreads = leaf.core_nthreads + 1;
topology_update_dom(tscan, TOPO_SMT_DOMAIN,
get_count_order(nthreads), nthreads);
}
}
store_node(tscan, leaf.nnodes_per_socket + 1, leaf.node_id);
if (tscan->c->x86_vendor == X86_VENDOR_AMD) {
if (tscan->c->x86 == 0x15)
tscan->c->topo.cu_id = leaf.core_id;
cacheinfo_amd_init_llc_id(tscan->c, leaf.node_id);
} else {
/*
* Package ID is ApicId[6..] on certain Hygon CPUs. See
* commit e0ceeae708ce for explanation. The topology info
* is screwed up: The package shift is always 6 and the
* node ID is bit [4:5].
*/
if (!boot_cpu_has(X86_FEATURE_HYPERVISOR) && tscan->c->x86_model <= 0x3) {
topology_set_dom(tscan, TOPO_CORE_DOMAIN, 6,
tscan->dom_ncpus[TOPO_CORE_DOMAIN]);
}
cacheinfo_hygon_init_llc_id(tscan->c);
}
return true;
}
static void parse_fam10h_node_id(struct topo_scan *tscan)
{
union {
struct {
u64 node_id : 3,
nodes_per_pkg : 3,
unused : 58;
};
u64 msr;
} nid;
if (!boot_cpu_has(X86_FEATURE_NODEID_MSR))
return;
rdmsrq(MSR_FAM10H_NODE_ID, nid.msr);
store_node(tscan, nid.nodes_per_pkg + 1, nid.node_id);
tscan->c->topo.llc_id = nid.node_id;
}
static void legacy_set_llc(struct topo_scan *tscan)
{
unsigned int apicid = tscan->c->topo.initial_apicid;
/* If none of the parsers set LLC ID then use the die ID for it. */
if (tscan->c->topo.llc_id == BAD_APICID)
tscan->c->topo.llc_id = apicid >> tscan->dom_shifts[TOPO_CORE_DOMAIN];
}
static void topoext_fixup(struct topo_scan *tscan)
{
struct cpuinfo_x86 *c = tscan->c;
u64 msrval;
/* Try to re-enable TopologyExtensions if switched off by BIOS */
if (cpu_has(c, X86_FEATURE_TOPOEXT) || c->x86_vendor != X86_VENDOR_AMD ||
c->x86 != 0x15 || c->x86_model < 0x10 || c->x86_model > 0x6f)
return;
if (msr_set_bit(0xc0011005, 54) <= 0)
return;
rdmsrq(0xc0011005, msrval);
if (msrval & BIT_64(54)) {
set_cpu_cap(c, X86_FEATURE_TOPOEXT);
pr_info_once(FW_INFO "CPU: Re-enabling disabled Topology Extensions Support.\n");
}
}
static void parse_topology_amd(struct topo_scan *tscan)
{
bool has_topoext = false;
/*
* If the extended topology leaf 0x8000_001e is available
* try to get SMT, CORE, TILE, and DIE shifts from extended
* CPUID leaf 0x8000_0026 on supported processors first. If
* extended CPUID leaf 0x8000_0026 is not supported, try to
* get SMT and CORE shift from leaf 0xb first, then try to
* get the CORE shift from leaf 0x8000_0008.
*/
if (cpu_feature_enabled(X86_FEATURE_TOPOEXT))
has_topoext = cpu_parse_topology_ext(tscan);
if (cpu_feature_enabled(X86_FEATURE_AMD_HTR_CORES))
tscan->c->topo.cpu_type = cpuid_ebx(0x80000026);
if (!has_topoext && !parse_8000_0008(tscan))
return;
/* Prefer leaf 0x8000001e if available */
if (parse_8000_001e(tscan, has_topoext))
return;
/* Try the NODEID MSR */
parse_fam10h_node_id(tscan);
}
void cpu_parse_topology_amd(struct topo_scan *tscan)
{
tscan->amd_nodes_per_pkg = 1;
topoext_fixup(tscan);
parse_topology_amd(tscan);
legacy_set_llc(tscan);
if (tscan->amd_nodes_per_pkg > 1)
set_cpu_cap(tscan->c, X86_FEATURE_AMD_DCM);
}
void cpu_topology_fixup_amd(struct topo_scan *tscan)
{
struct cpuinfo_x86 *c = tscan->c;
/*
* Adjust the core_id relative to the node when there is more than
* one node.
*/
if (tscan->c->x86 < 0x17 && tscan->amd_nodes_per_pkg > 1)
c->topo.core_id %= tscan->dom_ncpus[TOPO_CORE_DOMAIN] / tscan->amd_nodes_per_pkg;
}