1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * intel_rapl_tpmi: Intel RAPL driver via TPMI interface |
4 | * |
5 | * Copyright (c) 2023, Intel Corporation. |
6 | * All Rights Reserved. |
7 | * |
8 | */ |
9 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
10 | |
11 | #include <linux/auxiliary_bus.h> |
12 | #include <linux/io.h> |
13 | #include <linux/intel_tpmi.h> |
14 | #include <linux/intel_rapl.h> |
15 | #include <linux/module.h> |
16 | #include <linux/slab.h> |
17 | |
18 | #define TPMI_RAPL_MAJOR_VERSION 0 |
19 | #define TPMI_RAPL_MINOR_VERSION 1 |
20 | |
21 | /* 1 header + 10 registers + 5 reserved. 8 bytes for each. */ |
22 | #define TPMI_RAPL_DOMAIN_SIZE 128 |
23 | |
24 | enum tpmi_rapl_domain_type { |
25 | TPMI_RAPL_DOMAIN_INVALID, |
26 | TPMI_RAPL_DOMAIN_SYSTEM, |
27 | TPMI_RAPL_DOMAIN_PACKAGE, |
28 | TPMI_RAPL_DOMAIN_RESERVED, |
29 | TPMI_RAPL_DOMAIN_MEMORY, |
30 | TPMI_RAPL_DOMAIN_MAX, |
31 | }; |
32 | |
33 | enum tpmi_rapl_register { |
34 | , |
35 | TPMI_RAPL_REG_UNIT, |
36 | TPMI_RAPL_REG_PL1, |
37 | TPMI_RAPL_REG_PL2, |
38 | TPMI_RAPL_REG_PL3, |
39 | TPMI_RAPL_REG_PL4, |
40 | TPMI_RAPL_REG_RESERVED, |
41 | TPMI_RAPL_REG_ENERGY_STATUS, |
42 | TPMI_RAPL_REG_PERF_STATUS, |
43 | TPMI_RAPL_REG_POWER_INFO, |
44 | TPMI_RAPL_REG_DOMAIN_INFO, |
45 | TPMI_RAPL_REG_INTERRUPT, |
46 | TPMI_RAPL_REG_MAX = 15, |
47 | }; |
48 | |
49 | struct tpmi_rapl_package { |
50 | struct rapl_if_priv priv; |
51 | struct intel_tpmi_plat_info *tpmi_info; |
52 | struct rapl_package *rp; |
53 | void __iomem *base; |
54 | struct list_head node; |
55 | }; |
56 | |
57 | static LIST_HEAD(tpmi_rapl_packages); |
58 | static DEFINE_MUTEX(tpmi_rapl_lock); |
59 | |
60 | static struct powercap_control_type *tpmi_control_type; |
61 | |
62 | static int tpmi_rapl_read_raw(int id, struct reg_action *ra) |
63 | { |
64 | if (!ra->reg.mmio) |
65 | return -EINVAL; |
66 | |
67 | ra->value = readq(addr: ra->reg.mmio); |
68 | |
69 | ra->value &= ra->mask; |
70 | return 0; |
71 | } |
72 | |
73 | static int tpmi_rapl_write_raw(int id, struct reg_action *ra) |
74 | { |
75 | u64 val; |
76 | |
77 | if (!ra->reg.mmio) |
78 | return -EINVAL; |
79 | |
80 | val = readq(addr: ra->reg.mmio); |
81 | |
82 | val &= ~ra->mask; |
83 | val |= ra->value; |
84 | |
85 | writeq(val, addr: ra->reg.mmio); |
86 | return 0; |
87 | } |
88 | |
89 | static struct tpmi_rapl_package *trp_alloc(int pkg_id) |
90 | { |
91 | struct tpmi_rapl_package *trp; |
92 | int ret; |
93 | |
94 | mutex_lock(&tpmi_rapl_lock); |
95 | |
96 | if (list_empty(head: &tpmi_rapl_packages)) { |
97 | tpmi_control_type = powercap_register_control_type(NULL, name: "intel-rapl" , NULL); |
98 | if (IS_ERR(ptr: tpmi_control_type)) { |
99 | ret = PTR_ERR(ptr: tpmi_control_type); |
100 | goto err_unlock; |
101 | } |
102 | } |
103 | |
104 | trp = kzalloc(sizeof(*trp), GFP_KERNEL); |
105 | if (!trp) { |
106 | ret = -ENOMEM; |
107 | goto err_del_powercap; |
108 | } |
109 | |
110 | list_add(new: &trp->node, head: &tpmi_rapl_packages); |
111 | |
112 | mutex_unlock(lock: &tpmi_rapl_lock); |
113 | return trp; |
114 | |
115 | err_del_powercap: |
116 | if (list_empty(head: &tpmi_rapl_packages)) |
117 | powercap_unregister_control_type(instance: tpmi_control_type); |
118 | err_unlock: |
119 | mutex_unlock(lock: &tpmi_rapl_lock); |
120 | return ERR_PTR(error: ret); |
121 | } |
122 | |
123 | static void trp_release(struct tpmi_rapl_package *trp) |
124 | { |
125 | mutex_lock(&tpmi_rapl_lock); |
126 | list_del(entry: &trp->node); |
127 | |
128 | if (list_empty(head: &tpmi_rapl_packages)) |
129 | powercap_unregister_control_type(instance: tpmi_control_type); |
130 | |
131 | kfree(objp: trp); |
132 | mutex_unlock(lock: &tpmi_rapl_lock); |
133 | } |
134 | |
135 | /* |
136 | * Bit 0 of TPMI_RAPL_REG_DOMAIN_INFO indicates if the current package is a domain |
137 | * root or not. Only domain root packages can enumerate System (Psys) Domain. |
138 | */ |
139 | #define TPMI_RAPL_DOMAIN_ROOT BIT(0) |
140 | |
141 | static int parse_one_domain(struct tpmi_rapl_package *trp, u32 offset) |
142 | { |
143 | u8 tpmi_domain_version; |
144 | enum rapl_domain_type domain_type; |
145 | enum tpmi_rapl_domain_type tpmi_domain_type; |
146 | enum tpmi_rapl_register reg_index; |
147 | enum rapl_domain_reg_id reg_id; |
148 | int tpmi_domain_size, tpmi_domain_flags; |
149 | u64 tpmi_domain_header = readq(addr: trp->base + offset); |
150 | u64 tpmi_domain_info; |
151 | |
152 | /* Domain Parent bits are ignored for now */ |
153 | tpmi_domain_version = tpmi_domain_header & 0xff; |
154 | tpmi_domain_type = tpmi_domain_header >> 8 & 0xff; |
155 | tpmi_domain_size = tpmi_domain_header >> 16 & 0xff; |
156 | tpmi_domain_flags = tpmi_domain_header >> 32 & 0xffff; |
157 | |
158 | if (tpmi_domain_version == TPMI_VERSION_INVALID) { |
159 | pr_warn(FW_BUG "Invalid version\n" ); |
160 | return -ENODEV; |
161 | } |
162 | |
163 | if (TPMI_MAJOR_VERSION(tpmi_domain_version) != TPMI_RAPL_MAJOR_VERSION) { |
164 | pr_warn(FW_BUG "Unsupported major version:%ld\n" , |
165 | TPMI_MAJOR_VERSION(tpmi_domain_version)); |
166 | return -ENODEV; |
167 | } |
168 | |
169 | if (TPMI_MINOR_VERSION(tpmi_domain_version) > TPMI_RAPL_MINOR_VERSION) |
170 | pr_info("Ignore: Unsupported minor version:%ld\n" , |
171 | TPMI_MINOR_VERSION(tpmi_domain_version)); |
172 | |
173 | /* Domain size: in unit of 128 Bytes */ |
174 | if (tpmi_domain_size != 1) { |
175 | pr_warn(FW_BUG "Invalid Domain size %d\n" , tpmi_domain_size); |
176 | return -EINVAL; |
177 | } |
178 | |
179 | /* Unit register and Energy Status register are mandatory for each domain */ |
180 | if (!(tpmi_domain_flags & BIT(TPMI_RAPL_REG_UNIT)) || |
181 | !(tpmi_domain_flags & BIT(TPMI_RAPL_REG_ENERGY_STATUS))) { |
182 | pr_warn(FW_BUG "Invalid Domain flag 0x%x\n" , tpmi_domain_flags); |
183 | return -EINVAL; |
184 | } |
185 | |
186 | switch (tpmi_domain_type) { |
187 | case TPMI_RAPL_DOMAIN_PACKAGE: |
188 | domain_type = RAPL_DOMAIN_PACKAGE; |
189 | break; |
190 | case TPMI_RAPL_DOMAIN_SYSTEM: |
191 | if (!(tpmi_domain_flags & BIT(TPMI_RAPL_REG_DOMAIN_INFO))) { |
192 | pr_warn(FW_BUG "System domain must support Domain Info register\n" ); |
193 | return -ENODEV; |
194 | } |
195 | tpmi_domain_info = readq(addr: trp->base + offset + TPMI_RAPL_REG_DOMAIN_INFO * 8); |
196 | if (!(tpmi_domain_info & TPMI_RAPL_DOMAIN_ROOT)) |
197 | return 0; |
198 | domain_type = RAPL_DOMAIN_PLATFORM; |
199 | break; |
200 | case TPMI_RAPL_DOMAIN_MEMORY: |
201 | domain_type = RAPL_DOMAIN_DRAM; |
202 | break; |
203 | default: |
204 | pr_warn(FW_BUG "Unsupported Domain type %d\n" , tpmi_domain_type); |
205 | return -EINVAL; |
206 | } |
207 | |
208 | if (trp->priv.regs[domain_type][RAPL_DOMAIN_REG_UNIT].mmio) { |
209 | pr_warn(FW_BUG "Duplicate Domain type %d\n" , tpmi_domain_type); |
210 | return -EINVAL; |
211 | } |
212 | |
213 | reg_index = TPMI_RAPL_REG_HEADER; |
214 | while (++reg_index != TPMI_RAPL_REG_MAX) { |
215 | if (!(tpmi_domain_flags & BIT(reg_index))) |
216 | continue; |
217 | |
218 | switch (reg_index) { |
219 | case TPMI_RAPL_REG_UNIT: |
220 | reg_id = RAPL_DOMAIN_REG_UNIT; |
221 | break; |
222 | case TPMI_RAPL_REG_PL1: |
223 | reg_id = RAPL_DOMAIN_REG_LIMIT; |
224 | trp->priv.limits[domain_type] |= BIT(POWER_LIMIT1); |
225 | break; |
226 | case TPMI_RAPL_REG_PL2: |
227 | reg_id = RAPL_DOMAIN_REG_PL2; |
228 | trp->priv.limits[domain_type] |= BIT(POWER_LIMIT2); |
229 | break; |
230 | case TPMI_RAPL_REG_PL4: |
231 | reg_id = RAPL_DOMAIN_REG_PL4; |
232 | trp->priv.limits[domain_type] |= BIT(POWER_LIMIT4); |
233 | break; |
234 | case TPMI_RAPL_REG_ENERGY_STATUS: |
235 | reg_id = RAPL_DOMAIN_REG_STATUS; |
236 | break; |
237 | case TPMI_RAPL_REG_PERF_STATUS: |
238 | reg_id = RAPL_DOMAIN_REG_PERF; |
239 | break; |
240 | case TPMI_RAPL_REG_POWER_INFO: |
241 | reg_id = RAPL_DOMAIN_REG_INFO; |
242 | break; |
243 | default: |
244 | continue; |
245 | } |
246 | trp->priv.regs[domain_type][reg_id].mmio = trp->base + offset + reg_index * 8; |
247 | } |
248 | |
249 | return 0; |
250 | } |
251 | |
252 | static int intel_rapl_tpmi_probe(struct auxiliary_device *auxdev, |
253 | const struct auxiliary_device_id *id) |
254 | { |
255 | struct tpmi_rapl_package *trp; |
256 | struct intel_tpmi_plat_info *info; |
257 | struct resource *res; |
258 | u32 offset; |
259 | int ret; |
260 | |
261 | info = tpmi_get_platform_data(auxdev); |
262 | if (!info) |
263 | return -ENODEV; |
264 | |
265 | trp = trp_alloc(pkg_id: info->package_id); |
266 | if (IS_ERR(ptr: trp)) |
267 | return PTR_ERR(ptr: trp); |
268 | |
269 | if (tpmi_get_resource_count(auxdev) > 1) { |
270 | dev_err(&auxdev->dev, "does not support multiple resources\n" ); |
271 | ret = -EINVAL; |
272 | goto err; |
273 | } |
274 | |
275 | res = tpmi_get_resource_at_index(auxdev, index: 0); |
276 | if (!res) { |
277 | dev_err(&auxdev->dev, "can't fetch device resource info\n" ); |
278 | ret = -EIO; |
279 | goto err; |
280 | } |
281 | |
282 | trp->base = devm_ioremap_resource(dev: &auxdev->dev, res); |
283 | if (IS_ERR(ptr: trp->base)) { |
284 | ret = PTR_ERR(ptr: trp->base); |
285 | goto err; |
286 | } |
287 | |
288 | for (offset = 0; offset < resource_size(res); offset += TPMI_RAPL_DOMAIN_SIZE) { |
289 | ret = parse_one_domain(trp, offset); |
290 | if (ret) |
291 | goto err; |
292 | } |
293 | |
294 | trp->tpmi_info = info; |
295 | trp->priv.type = RAPL_IF_TPMI; |
296 | trp->priv.read_raw = tpmi_rapl_read_raw; |
297 | trp->priv.write_raw = tpmi_rapl_write_raw; |
298 | trp->priv.control_type = tpmi_control_type; |
299 | |
300 | /* RAPL TPMI I/F is per physical package */ |
301 | trp->rp = rapl_find_package_domain(id: info->package_id, priv: &trp->priv, id_is_cpu: false); |
302 | if (trp->rp) { |
303 | dev_err(&auxdev->dev, "Domain for Package%d already exists\n" , info->package_id); |
304 | ret = -EEXIST; |
305 | goto err; |
306 | } |
307 | |
308 | trp->rp = rapl_add_package(id: info->package_id, priv: &trp->priv, id_is_cpu: false); |
309 | if (IS_ERR(ptr: trp->rp)) { |
310 | dev_err(&auxdev->dev, "Failed to add RAPL Domain for Package%d, %ld\n" , |
311 | info->package_id, PTR_ERR(trp->rp)); |
312 | ret = PTR_ERR(ptr: trp->rp); |
313 | goto err; |
314 | } |
315 | |
316 | rapl_package_add_pmu(rp: trp->rp); |
317 | |
318 | auxiliary_set_drvdata(auxdev, data: trp); |
319 | |
320 | return 0; |
321 | err: |
322 | trp_release(trp); |
323 | return ret; |
324 | } |
325 | |
326 | static void intel_rapl_tpmi_remove(struct auxiliary_device *auxdev) |
327 | { |
328 | struct tpmi_rapl_package *trp = auxiliary_get_drvdata(auxdev); |
329 | |
330 | rapl_package_remove_pmu(rp: trp->rp); |
331 | rapl_remove_package(rp: trp->rp); |
332 | trp_release(trp); |
333 | } |
334 | |
335 | static const struct auxiliary_device_id intel_rapl_tpmi_ids[] = { |
336 | {.name = "intel_vsec.tpmi-rapl" }, |
337 | { } |
338 | }; |
339 | |
340 | MODULE_DEVICE_TABLE(auxiliary, intel_rapl_tpmi_ids); |
341 | |
342 | static struct auxiliary_driver intel_rapl_tpmi_driver = { |
343 | .probe = intel_rapl_tpmi_probe, |
344 | .remove = intel_rapl_tpmi_remove, |
345 | .id_table = intel_rapl_tpmi_ids, |
346 | }; |
347 | |
348 | module_auxiliary_driver(intel_rapl_tpmi_driver) |
349 | |
350 | MODULE_IMPORT_NS("INTEL_TPMI" ); |
351 | |
352 | MODULE_DESCRIPTION("Intel RAPL TPMI Driver" ); |
353 | MODULE_LICENSE("GPL" ); |
354 | |