Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Mapping of TPMI power domains CPU mapping
4 *
5 * Copyright (c) 2024, Intel Corporation.
6 */
7
8#include <linux/bitfield.h>
9#include <linux/cleanup.h>
10#include <linux/cpuhotplug.h>
11#include <linux/cpumask.h>
12#include <linux/errno.h>
13#include <linux/export.h>
14#include <linux/hashtable.h>
15#include <linux/module.h>
16#include <linux/mutex.h>
17#include <linux/overflow.h>
18#include <linux/slab.h>
19#include <linux/topology.h>
20#include <linux/types.h>
21
22#include <asm/cpu_device_id.h>
23#include <asm/intel-family.h>
24#include <asm/msr.h>
25
26#include "tpmi_power_domains.h"
27
28#define MSR_PM_LOGICAL_ID 0x54
29
30/*
31 * Struct of MSR 0x54
32 * [15:11] PM_DOMAIN_ID
33 * [10:3] MODULE_ID (aka IDI_AGENT_ID)
34 * [2:0] LP_ID
35 * For Atom:
36 * [2] Always 0
37 * [1:0] core ID within module
38 * For Core
39 * [2:1] Always 0
40 * [0] thread ID
41 */
42
43#define LP_ID_MASK GENMASK_ULL(2, 0)
44#define MODULE_ID_MASK GENMASK_ULL(10, 3)
45#define PM_DOMAIN_ID_MASK GENMASK_ULL(15, 11)
46
47/**
48 * struct tpmi_cpu_info - Mapping information for a CPU
49 * @hnode: Used to add mapping information to hash list
50 * @linux_cpu: Linux CPU number
51 * @pkg_id: Package ID of this CPU
52 * @punit_thread_id: Punit thread id of this CPU
53 * @punit_core_id: Punit core id
54 * @punit_domain_id: Power domain id from Punit
55 *
56 * Structure to store mapping information for a Linux CPU
57 * to a Punit core, thread and power domain.
58 */
59struct tpmi_cpu_info {
60 struct hlist_node hnode;
61 int linux_cpu;
62 u8 pkg_id;
63 u8 punit_thread_id;
64 u8 punit_core_id;
65 u8 punit_domain_id;
66};
67
68static DEFINE_PER_CPU(struct tpmi_cpu_info, tpmi_cpu_info);
69
70/* The dynamically assigned cpu hotplug state to free later */
71static enum cpuhp_state tpmi_hp_state __read_mostly;
72
73#define MAX_POWER_DOMAINS 8
74
75static cpumask_t *tpmi_power_domain_mask;
76
77static u16 *domain_die_map;
78
79/* Lock to protect tpmi_power_domain_mask and tpmi_cpu_hash */
80static DEFINE_MUTEX(tpmi_lock);
81
82static const struct x86_cpu_id tpmi_cpu_ids[] = {
83 X86_MATCH_VFM(INTEL_GRANITERAPIDS_X, NULL),
84 X86_MATCH_VFM(INTEL_ATOM_CRESTMONT_X, NULL),
85 X86_MATCH_VFM(INTEL_ATOM_CRESTMONT, NULL),
86 X86_MATCH_VFM(INTEL_ATOM_DARKMONT_X, NULL),
87 X86_MATCH_VFM(INTEL_GRANITERAPIDS_D, NULL),
88 X86_MATCH_VFM(INTEL_DIAMONDRAPIDS_X, NULL),
89 {}
90};
91MODULE_DEVICE_TABLE(x86cpu, tpmi_cpu_ids);
92
93static DECLARE_HASHTABLE(tpmi_cpu_hash, 8);
94
95static bool tpmi_domain_is_valid(struct tpmi_cpu_info *info)
96{
97 return info->pkg_id < topology_max_packages() &&
98 info->punit_domain_id < MAX_POWER_DOMAINS;
99}
100
101int tpmi_get_linux_cpu_number(int package_id, int domain_id, int punit_core_id)
102{
103 struct tpmi_cpu_info *info;
104 int ret = -EINVAL;
105
106 guard(mutex)(&tpmi_lock);
107 hash_for_each_possible(tpmi_cpu_hash, info, hnode, punit_core_id) {
108 if (info->punit_domain_id == domain_id && info->pkg_id == package_id) {
109 ret = info->linux_cpu;
110 break;
111 }
112 }
113
114 return ret;
115}
116EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_cpu_number, "INTEL_TPMI_POWER_DOMAIN");
117
118int tpmi_get_punit_core_number(int cpu_no)
119{
120 if (cpu_no >= num_possible_cpus())
121 return -EINVAL;
122
123 return per_cpu(tpmi_cpu_info, cpu_no).punit_core_id;
124}
125EXPORT_SYMBOL_NS_GPL(tpmi_get_punit_core_number, "INTEL_TPMI_POWER_DOMAIN");
126
127int tpmi_get_power_domain_id(int cpu_no)
128{
129 if (cpu_no >= num_possible_cpus())
130 return -EINVAL;
131
132 return per_cpu(tpmi_cpu_info, cpu_no).punit_domain_id;
133}
134EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_id, "INTEL_TPMI_POWER_DOMAIN");
135
136cpumask_t *tpmi_get_power_domain_mask(int cpu_no)
137{
138 struct tpmi_cpu_info *info;
139 cpumask_t *mask;
140 int index;
141
142 if (cpu_no >= num_possible_cpus())
143 return NULL;
144
145 info = &per_cpu(tpmi_cpu_info, cpu_no);
146 if (!tpmi_domain_is_valid(info))
147 return NULL;
148
149 index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id;
150 guard(mutex)(&tpmi_lock);
151 mask = &tpmi_power_domain_mask[index];
152
153 return mask;
154}
155EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN");
156
157int tpmi_get_linux_die_id(int pkg_id, int domain_id)
158{
159 if (pkg_id >= topology_max_packages() || domain_id >= MAX_POWER_DOMAINS)
160 return -EINVAL;
161
162 return domain_die_map[pkg_id * MAX_POWER_DOMAINS + domain_id];
163}
164EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_die_id, "INTEL_TPMI_POWER_DOMAIN");
165
166static int tpmi_get_logical_id(unsigned int cpu, struct tpmi_cpu_info *info)
167{
168 u64 data;
169 int ret;
170
171 ret = rdmsrq_safe(MSR_PM_LOGICAL_ID, &data);
172 if (ret)
173 return ret;
174
175 info->punit_domain_id = FIELD_GET(PM_DOMAIN_ID_MASK, data);
176 if (info->punit_domain_id >= MAX_POWER_DOMAINS)
177 return -EINVAL;
178
179 info->punit_thread_id = FIELD_GET(LP_ID_MASK, data);
180 info->punit_core_id = FIELD_GET(MODULE_ID_MASK, data);
181 info->pkg_id = topology_logical_package_id(cpu);
182 info->linux_cpu = cpu;
183
184 return 0;
185}
186
187static int tpmi_cpu_online(unsigned int cpu)
188{
189 struct tpmi_cpu_info *info = &per_cpu(tpmi_cpu_info, cpu);
190 int ret, index;
191
192 /* Don't fail CPU online for some bad mapping of CPUs */
193 ret = tpmi_get_logical_id(cpu, info);
194 if (ret)
195 return 0;
196
197 index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id;
198
199 guard(mutex)(&tpmi_lock);
200 cpumask_set_cpu(cpu, &tpmi_power_domain_mask[index]);
201 hash_add(tpmi_cpu_hash, &info->hnode, info->punit_core_id);
202
203 domain_die_map[info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id] =
204 topology_die_id(cpu);
205
206 return 0;
207}
208
209static int __init tpmi_init(void)
210{
211 const struct x86_cpu_id *id;
212 u64 data;
213 int ret;
214
215 id = x86_match_cpu(tpmi_cpu_ids);
216 if (!id)
217 return -ENODEV;
218
219 /* Check for MSR 0x54 presence */
220 ret = rdmsrq_safe(MSR_PM_LOGICAL_ID, &data);
221 if (ret)
222 return ret;
223
224 tpmi_power_domain_mask = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS),
225 sizeof(*tpmi_power_domain_mask), GFP_KERNEL);
226 if (!tpmi_power_domain_mask)
227 return -ENOMEM;
228
229 domain_die_map = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS),
230 sizeof(*domain_die_map), GFP_KERNEL);
231 if (!domain_die_map) {
232 ret = -ENOMEM;
233 goto free_domain_mask;
234 }
235
236 ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
237 "platform/x86/tpmi_power_domains:online",
238 tpmi_cpu_online, NULL);
239 if (ret < 0)
240 goto free_domain_map;
241
242 tpmi_hp_state = ret;
243
244 return 0;
245
246free_domain_map:
247 kfree(domain_die_map);
248
249free_domain_mask:
250 kfree(tpmi_power_domain_mask);
251
252 return ret;
253}
254module_init(tpmi_init)
255
256static void __exit tpmi_exit(void)
257{
258 cpuhp_remove_state(tpmi_hp_state);
259 kfree(tpmi_power_domain_mask);
260 kfree(domain_die_map);
261}
262module_exit(tpmi_exit)
263
264MODULE_DESCRIPTION("TPMI Power Domains Mapping");
265MODULE_LICENSE("GPL");