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