xref: /linux/drivers/platform/x86/intel/tpmi_power_domains.c (revision 4663747812d1a272312d1b95cbd128f0cdb329f2)
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  */
59 struct 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 
68 static DEFINE_PER_CPU(struct tpmi_cpu_info, tpmi_cpu_info);
69 
70 /* The dynamically assigned cpu hotplug state to free later */
71 static enum cpuhp_state tpmi_hp_state __read_mostly;
72 
73 #define MAX_POWER_DOMAINS	8
74 
75 static cpumask_t *tpmi_power_domain_mask;
76 
77 static u16 *domain_die_map;
78 
79 /* Lock to protect tpmi_power_domain_mask and tpmi_cpu_hash */
80 static DEFINE_MUTEX(tpmi_lock);
81 
82 static 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_PANTHERCOVE_X,	NULL),
89 	{}
90 };
91 MODULE_DEVICE_TABLE(x86cpu, tpmi_cpu_ids);
92 
93 static DECLARE_HASHTABLE(tpmi_cpu_hash, 8);
94 
tpmi_domain_is_valid(struct tpmi_cpu_info * info)95 static 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 
tpmi_get_linux_cpu_number(int package_id,int domain_id,int punit_core_id)101 int 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 }
116 EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_cpu_number, "INTEL_TPMI_POWER_DOMAIN");
117 
tpmi_get_punit_core_number(int cpu_no)118 int 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 }
125 EXPORT_SYMBOL_NS_GPL(tpmi_get_punit_core_number, "INTEL_TPMI_POWER_DOMAIN");
126 
tpmi_get_power_domain_id(int cpu_no)127 int 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 }
134 EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_id, "INTEL_TPMI_POWER_DOMAIN");
135 
tpmi_get_power_domain_mask(int cpu_no)136 cpumask_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 }
155 EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN");
156 
tpmi_get_linux_die_id(int pkg_id,int domain_id)157 int 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 }
164 EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_die_id, "INTEL_TPMI_POWER_DOMAIN");
165 
tpmi_get_logical_id(unsigned int cpu,struct tpmi_cpu_info * info)166 static 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_physical_package_id(cpu);
182 	info->linux_cpu = cpu;
183 
184 	return 0;
185 }
186 
tpmi_cpu_online(unsigned int cpu)187 static 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 
tpmi_init(void)209 static 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 
246 free_domain_map:
247 	kfree(domain_die_map);
248 
249 free_domain_mask:
250 	kfree(tpmi_power_domain_mask);
251 
252 	return ret;
253 }
module_init(tpmi_init)254 module_init(tpmi_init)
255 
256 static 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 }
262 module_exit(tpmi_exit)
263 
264 MODULE_DESCRIPTION("TPMI Power Domains Mapping");
265 MODULE_LICENSE("GPL");
266