aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/arch/x86/kernel/cpu/topology_amd.c
blob: 1a8b3ad493afef8eeeea65fe5dba8673517f1240 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// SPDX-License-Identifier: GPL-2.0
#include <linux/cpu.h>

#include <asm/apic.h>
#include <asm/memtype.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);

	topology_set_dom(tscan, TOPO_SMT_DOMAIN, sft, ecx.cpu_nthreads + 1);
	return true;
}

static void store_node(struct topo_scan *tscan, unsigned int 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_0xb)
{
	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);

	tscan->c->topo.initial_apicid = leaf.ext_apic_id;

	/*
	 * If leaf 0xb is available, then SMT shift is set already. If not
	 * take it from ecx.threads_per_core and use topo_update_dom() -
	 * topology_set_dom() would propagate and overwrite the already
	 * propagated CORE level.
	 */
	if (!has_0xb) {
		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 bool parse_fam10h_node_id(struct topo_scan *tscan)
{
	struct {
		union {
			u64	node_id		:  3,
				nodes_per_pkg	:  3,
				unused		: 58;
			u64	msr;
		};
	} nid;

	if (!boot_cpu_has(X86_FEATURE_NODEID_MSR))
		return false;

	rdmsrl(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;
	return true;
}

static void legacy_set_llc(struct topo_scan *tscan)
{
	unsigned int apicid = tscan->c->topo.initial_apicid;

	/* parse_8000_0008() set everything up except llc_id */
	tscan->c->topo.llc_id = apicid >> tscan->dom_shifts[TOPO_CORE_DOMAIN];
}

static void parse_topology_amd(struct topo_scan *tscan)
{
	bool has_0xb = false;

	/*
	 * If the extended topology leaf 0x8000_001e is available
	 * 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_0xb = cpu_parse_topology_ext(tscan);

	if (!has_0xb && !parse_8000_0008(tscan))
		return;

	/* Prefer leaf 0x8000001e if available */
	if (parse_8000_001e(tscan, has_0xb))
		return;

	/* Try the NODEID MSR */
	if (parse_fam10h_node_id(tscan))
		return;

	legacy_set_llc(tscan);
}

void cpu_parse_topology_amd(struct topo_scan *tscan)
{
	tscan->amd_nodes_per_pkg = 1;
	parse_topology_amd(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;
}