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 /* Lock to protect tpmi_power_domain_mask and tpmi_cpu_hash */
78 static DEFINE_MUTEX(tpmi_lock);
79
80 static const struct x86_cpu_id tpmi_cpu_ids[] = {
81 X86_MATCH_VFM(INTEL_GRANITERAPIDS_X, NULL),
82 X86_MATCH_VFM(INTEL_ATOM_CRESTMONT_X, NULL),
83 X86_MATCH_VFM(INTEL_ATOM_CRESTMONT, NULL),
84 X86_MATCH_VFM(INTEL_ATOM_DARKMONT_X, NULL),
85 X86_MATCH_VFM(INTEL_GRANITERAPIDS_D, NULL),
86 X86_MATCH_VFM(INTEL_PANTHERCOVE_X, NULL),
87 {}
88 };
89 MODULE_DEVICE_TABLE(x86cpu, tpmi_cpu_ids);
90
91 static DECLARE_HASHTABLE(tpmi_cpu_hash, 8);
92
tpmi_domain_is_valid(struct tpmi_cpu_info * info)93 static bool tpmi_domain_is_valid(struct tpmi_cpu_info *info)
94 {
95 return info->pkg_id < topology_max_packages() &&
96 info->punit_domain_id < MAX_POWER_DOMAINS;
97 }
98
tpmi_get_linux_cpu_number(int package_id,int domain_id,int punit_core_id)99 int tpmi_get_linux_cpu_number(int package_id, int domain_id, int punit_core_id)
100 {
101 struct tpmi_cpu_info *info;
102 int ret = -EINVAL;
103
104 guard(mutex)(&tpmi_lock);
105 hash_for_each_possible(tpmi_cpu_hash, info, hnode, punit_core_id) {
106 if (info->punit_domain_id == domain_id && info->pkg_id == package_id) {
107 ret = info->linux_cpu;
108 break;
109 }
110 }
111
112 return ret;
113 }
114 EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_cpu_number, "INTEL_TPMI_POWER_DOMAIN");
115
tpmi_get_punit_core_number(int cpu_no)116 int tpmi_get_punit_core_number(int cpu_no)
117 {
118 if (cpu_no >= num_possible_cpus())
119 return -EINVAL;
120
121 return per_cpu(tpmi_cpu_info, cpu_no).punit_core_id;
122 }
123 EXPORT_SYMBOL_NS_GPL(tpmi_get_punit_core_number, "INTEL_TPMI_POWER_DOMAIN");
124
tpmi_get_power_domain_id(int cpu_no)125 int tpmi_get_power_domain_id(int cpu_no)
126 {
127 if (cpu_no >= num_possible_cpus())
128 return -EINVAL;
129
130 return per_cpu(tpmi_cpu_info, cpu_no).punit_domain_id;
131 }
132 EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_id, "INTEL_TPMI_POWER_DOMAIN");
133
tpmi_get_power_domain_mask(int cpu_no)134 cpumask_t *tpmi_get_power_domain_mask(int cpu_no)
135 {
136 struct tpmi_cpu_info *info;
137 cpumask_t *mask;
138 int index;
139
140 if (cpu_no >= num_possible_cpus())
141 return NULL;
142
143 info = &per_cpu(tpmi_cpu_info, cpu_no);
144 if (!tpmi_domain_is_valid(info))
145 return NULL;
146
147 index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id;
148 guard(mutex)(&tpmi_lock);
149 mask = &tpmi_power_domain_mask[index];
150
151 return mask;
152 }
153 EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN");
154
tpmi_get_logical_id(unsigned int cpu,struct tpmi_cpu_info * info)155 static int tpmi_get_logical_id(unsigned int cpu, struct tpmi_cpu_info *info)
156 {
157 u64 data;
158 int ret;
159
160 ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
161 if (ret)
162 return ret;
163
164 info->punit_domain_id = FIELD_GET(PM_DOMAIN_ID_MASK, data);
165 if (info->punit_domain_id >= MAX_POWER_DOMAINS)
166 return -EINVAL;
167
168 info->punit_thread_id = FIELD_GET(LP_ID_MASK, data);
169 info->punit_core_id = FIELD_GET(MODULE_ID_MASK, data);
170 info->pkg_id = topology_physical_package_id(cpu);
171 info->linux_cpu = cpu;
172
173 return 0;
174 }
175
tpmi_cpu_online(unsigned int cpu)176 static int tpmi_cpu_online(unsigned int cpu)
177 {
178 struct tpmi_cpu_info *info = &per_cpu(tpmi_cpu_info, cpu);
179 int ret, index;
180
181 /* Don't fail CPU online for some bad mapping of CPUs */
182 ret = tpmi_get_logical_id(cpu, info);
183 if (ret)
184 return 0;
185
186 index = info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id;
187
188 guard(mutex)(&tpmi_lock);
189 cpumask_set_cpu(cpu, &tpmi_power_domain_mask[index]);
190 hash_add(tpmi_cpu_hash, &info->hnode, info->punit_core_id);
191
192 return 0;
193 }
194
tpmi_init(void)195 static int __init tpmi_init(void)
196 {
197 const struct x86_cpu_id *id;
198 u64 data;
199 int ret;
200
201 id = x86_match_cpu(tpmi_cpu_ids);
202 if (!id)
203 return -ENODEV;
204
205 /* Check for MSR 0x54 presence */
206 ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
207 if (ret)
208 return ret;
209
210 tpmi_power_domain_mask = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS),
211 sizeof(*tpmi_power_domain_mask), GFP_KERNEL);
212 if (!tpmi_power_domain_mask)
213 return -ENOMEM;
214
215 ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
216 "platform/x86/tpmi_power_domains:online",
217 tpmi_cpu_online, NULL);
218 if (ret < 0) {
219 kfree(tpmi_power_domain_mask);
220 return ret;
221 }
222
223 tpmi_hp_state = ret;
224
225 return 0;
226 }
module_init(tpmi_init)227 module_init(tpmi_init)
228
229 static void __exit tpmi_exit(void)
230 {
231 cpuhp_remove_state(tpmi_hp_state);
232 kfree(tpmi_power_domain_mask);
233 }
234 module_exit(tpmi_exit)
235
236 MODULE_DESCRIPTION("TPMI Power Domains Mapping");
237 MODULE_LICENSE("GPL");
238