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