1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Platform energy and frequency attributes driver |
4 | * |
5 | * This driver creates a sys file at /sys/firmware/papr/ which encapsulates a |
6 | * directory structure containing files in keyword - value pairs that specify |
7 | * energy and frequency configuration of the system. |
8 | * |
9 | * The format of exposing the sysfs information is as follows: |
10 | * /sys/firmware/papr/energy_scale_info/ |
11 | * |-- <id>/ |
12 | * |-- desc |
13 | * |-- value |
14 | * |-- value_desc (if exists) |
15 | * |-- <id>/ |
16 | * |-- desc |
17 | * |-- value |
18 | * |-- value_desc (if exists) |
19 | * |
20 | * Copyright 2022 IBM Corp. |
21 | */ |
22 | |
23 | #include <asm/hvcall.h> |
24 | #include <asm/machdep.h> |
25 | #include <asm/firmware.h> |
26 | |
27 | #include "pseries.h" |
28 | |
29 | /* |
30 | * Flag attributes to fetch either all or one attribute from the HCALL |
31 | * flag = BE(0) => fetch all attributes with firstAttributeId = 0 |
32 | * flag = BE(1) => fetch a single attribute with firstAttributeId = id |
33 | */ |
34 | #define ESI_FLAGS_ALL 0 |
35 | #define ESI_FLAGS_SINGLE (1ull << 63) |
36 | |
37 | #define KOBJ_MAX_ATTRS 3 |
38 | |
39 | #define ESI_HDR_SIZE sizeof(struct h_energy_scale_info_hdr) |
40 | #define ESI_ATTR_SIZE sizeof(struct energy_scale_attribute) |
41 | #define CURR_MAX_ESI_ATTRS 8 |
42 | |
43 | struct energy_scale_attribute { |
44 | __be64 id; |
45 | __be64 val; |
46 | u8 desc[64]; |
47 | u8 value_desc[64]; |
48 | } __packed; |
49 | |
50 | struct h_energy_scale_info_hdr { |
51 | __be64 num_attrs; |
52 | __be64 array_offset; |
53 | u8 ; |
54 | } __packed; |
55 | |
56 | struct papr_attr { |
57 | u64 id; |
58 | struct kobj_attribute kobj_attr; |
59 | }; |
60 | |
61 | struct papr_group { |
62 | struct attribute_group pg; |
63 | struct papr_attr pgattrs[KOBJ_MAX_ATTRS]; |
64 | }; |
65 | |
66 | static struct papr_group *papr_groups; |
67 | /* /sys/firmware/papr */ |
68 | static struct kobject *papr_kobj; |
69 | /* /sys/firmware/papr/energy_scale_info */ |
70 | static struct kobject *esi_kobj; |
71 | |
72 | /* |
73 | * Energy modes can change dynamically hence making a new hcall each time the |
74 | * information needs to be retrieved |
75 | */ |
76 | static int papr_get_attr(u64 id, struct energy_scale_attribute *esi) |
77 | { |
78 | int esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * ESI_ATTR_SIZE); |
79 | int ret, max_esi_attrs = CURR_MAX_ESI_ATTRS; |
80 | struct energy_scale_attribute *curr_esi; |
81 | struct h_energy_scale_info_hdr *hdr; |
82 | char *buf; |
83 | |
84 | buf = kmalloc(esi_buf_size, GFP_KERNEL); |
85 | if (buf == NULL) |
86 | return -ENOMEM; |
87 | |
88 | retry: |
89 | ret = plpar_hcall_norets(H_GET_ENERGY_SCALE_INFO, ESI_FLAGS_SINGLE, |
90 | id, virt_to_phys(buf), |
91 | esi_buf_size); |
92 | |
93 | /* |
94 | * If the hcall fails with not enough memory for either the |
95 | * header or data, attempt to allocate more |
96 | */ |
97 | if (ret == H_PARTIAL || ret == H_P4) { |
98 | char *temp_buf; |
99 | |
100 | max_esi_attrs += 4; |
101 | esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * max_esi_attrs); |
102 | |
103 | temp_buf = krealloc(buf, esi_buf_size, GFP_KERNEL); |
104 | if (temp_buf) { |
105 | buf = temp_buf; |
106 | } else { |
107 | ret = -ENOMEM; |
108 | goto out_buf; |
109 | } |
110 | |
111 | goto retry; |
112 | } |
113 | |
114 | if (ret != H_SUCCESS) { |
115 | pr_warn("hcall failed: H_GET_ENERGY_SCALE_INFO" ); |
116 | ret = -EIO; |
117 | goto out_buf; |
118 | } |
119 | |
120 | hdr = (struct h_energy_scale_info_hdr *) buf; |
121 | curr_esi = (struct energy_scale_attribute *) |
122 | (buf + be64_to_cpu(hdr->array_offset)); |
123 | |
124 | if (esi_buf_size < |
125 | be64_to_cpu(hdr->array_offset) + (be64_to_cpu(hdr->num_attrs) |
126 | * sizeof(struct energy_scale_attribute))) { |
127 | ret = -EIO; |
128 | goto out_buf; |
129 | } |
130 | |
131 | *esi = *curr_esi; |
132 | |
133 | out_buf: |
134 | kfree(buf); |
135 | |
136 | return ret; |
137 | } |
138 | |
139 | /* |
140 | * Extract and export the description of the energy scale attributes |
141 | */ |
142 | static ssize_t desc_show(struct kobject *kobj, |
143 | struct kobj_attribute *kobj_attr, |
144 | char *buf) |
145 | { |
146 | struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr, |
147 | kobj_attr); |
148 | struct energy_scale_attribute esi; |
149 | int ret; |
150 | |
151 | ret = papr_get_attr(id: pattr->id, esi: &esi); |
152 | if (ret) |
153 | return ret; |
154 | |
155 | return sysfs_emit(buf, fmt: "%s\n" , esi.desc); |
156 | } |
157 | |
158 | /* |
159 | * Extract and export the numeric value of the energy scale attributes |
160 | */ |
161 | static ssize_t val_show(struct kobject *kobj, |
162 | struct kobj_attribute *kobj_attr, |
163 | char *buf) |
164 | { |
165 | struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr, |
166 | kobj_attr); |
167 | struct energy_scale_attribute esi; |
168 | int ret; |
169 | |
170 | ret = papr_get_attr(id: pattr->id, esi: &esi); |
171 | if (ret) |
172 | return ret; |
173 | |
174 | return sysfs_emit(buf, fmt: "%llu\n" , be64_to_cpu(esi.val)); |
175 | } |
176 | |
177 | /* |
178 | * Extract and export the value description in string format of the energy |
179 | * scale attributes |
180 | */ |
181 | static ssize_t val_desc_show(struct kobject *kobj, |
182 | struct kobj_attribute *kobj_attr, |
183 | char *buf) |
184 | { |
185 | struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr, |
186 | kobj_attr); |
187 | struct energy_scale_attribute esi; |
188 | int ret; |
189 | |
190 | ret = papr_get_attr(id: pattr->id, esi: &esi); |
191 | if (ret) |
192 | return ret; |
193 | |
194 | return sysfs_emit(buf, fmt: "%s\n" , esi.value_desc); |
195 | } |
196 | |
197 | static struct papr_ops_info { |
198 | const char *attr_name; |
199 | ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *kobj_attr, |
200 | char *buf); |
201 | } ops_info[KOBJ_MAX_ATTRS] = { |
202 | { "desc" , desc_show }, |
203 | { "value" , val_show }, |
204 | { "value_desc" , val_desc_show }, |
205 | }; |
206 | |
207 | static void add_attr(u64 id, int index, struct papr_attr *attr) |
208 | { |
209 | attr->id = id; |
210 | sysfs_attr_init(&attr->kobj_attr.attr); |
211 | attr->kobj_attr.attr.name = ops_info[index].attr_name; |
212 | attr->kobj_attr.attr.mode = 0444; |
213 | attr->kobj_attr.show = ops_info[index].show; |
214 | } |
215 | |
216 | static int add_attr_group(u64 id, struct papr_group *pg, bool show_val_desc) |
217 | { |
218 | int i; |
219 | |
220 | for (i = 0; i < KOBJ_MAX_ATTRS; i++) { |
221 | if (!strcmp(ops_info[i].attr_name, "value_desc" ) && |
222 | !show_val_desc) { |
223 | continue; |
224 | } |
225 | add_attr(id, index: i, attr: &pg->pgattrs[i]); |
226 | pg->pg.attrs[i] = &pg->pgattrs[i].kobj_attr.attr; |
227 | } |
228 | |
229 | return sysfs_create_group(kobj: esi_kobj, grp: &pg->pg); |
230 | } |
231 | |
232 | |
233 | static int __init papr_init(void) |
234 | { |
235 | int esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * ESI_ATTR_SIZE); |
236 | int ret, idx, i, max_esi_attrs = CURR_MAX_ESI_ATTRS; |
237 | struct h_energy_scale_info_hdr *esi_hdr; |
238 | struct energy_scale_attribute *esi_attrs; |
239 | uint64_t num_attrs; |
240 | char *esi_buf; |
241 | |
242 | if (!firmware_has_feature(FW_FEATURE_LPAR) || |
243 | !firmware_has_feature(FW_FEATURE_ENERGY_SCALE_INFO)) { |
244 | return -ENXIO; |
245 | } |
246 | |
247 | esi_buf = kmalloc(esi_buf_size, GFP_KERNEL); |
248 | if (esi_buf == NULL) |
249 | return -ENOMEM; |
250 | /* |
251 | * hcall( |
252 | * uint64 H_GET_ENERGY_SCALE_INFO, // Get energy scale info |
253 | * uint64 flags, // Per the flag request |
254 | * uint64 firstAttributeId, // The attribute id |
255 | * uint64 bufferAddress, // Guest physical address of the output buffer |
256 | * uint64 bufferSize); // The size in bytes of the output buffer |
257 | */ |
258 | retry: |
259 | |
260 | ret = plpar_hcall_norets(H_GET_ENERGY_SCALE_INFO, ESI_FLAGS_ALL, 0, |
261 | virt_to_phys(esi_buf), esi_buf_size); |
262 | |
263 | /* |
264 | * If the hcall fails with not enough memory for either the |
265 | * header or data, attempt to allocate more |
266 | */ |
267 | if (ret == H_PARTIAL || ret == H_P4) { |
268 | char *temp_esi_buf; |
269 | |
270 | max_esi_attrs += 4; |
271 | esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * max_esi_attrs); |
272 | |
273 | temp_esi_buf = krealloc(esi_buf, esi_buf_size, GFP_KERNEL); |
274 | if (temp_esi_buf) |
275 | esi_buf = temp_esi_buf; |
276 | else |
277 | return -ENOMEM; |
278 | |
279 | goto retry; |
280 | } |
281 | |
282 | if (ret != H_SUCCESS) { |
283 | pr_warn("hcall failed: H_GET_ENERGY_SCALE_INFO, ret: %d\n" , ret); |
284 | goto out_free_esi_buf; |
285 | } |
286 | |
287 | esi_hdr = (struct h_energy_scale_info_hdr *) esi_buf; |
288 | num_attrs = be64_to_cpu(esi_hdr->num_attrs); |
289 | esi_attrs = (struct energy_scale_attribute *) |
290 | (esi_buf + be64_to_cpu(esi_hdr->array_offset)); |
291 | |
292 | if (esi_buf_size < |
293 | be64_to_cpu(esi_hdr->array_offset) + |
294 | (num_attrs * sizeof(struct energy_scale_attribute))) { |
295 | goto out_free_esi_buf; |
296 | } |
297 | |
298 | papr_groups = kcalloc(num_attrs, sizeof(*papr_groups), GFP_KERNEL); |
299 | if (!papr_groups) |
300 | goto out_free_esi_buf; |
301 | |
302 | papr_kobj = kobject_create_and_add(name: "papr" , parent: firmware_kobj); |
303 | if (!papr_kobj) { |
304 | pr_warn("kobject_create_and_add papr failed\n" ); |
305 | goto out_papr_groups; |
306 | } |
307 | |
308 | esi_kobj = kobject_create_and_add(name: "energy_scale_info" , parent: papr_kobj); |
309 | if (!esi_kobj) { |
310 | pr_warn("kobject_create_and_add energy_scale_info failed\n" ); |
311 | goto out_kobj; |
312 | } |
313 | |
314 | /* Allocate the groups before registering */ |
315 | for (idx = 0; idx < num_attrs; idx++) { |
316 | papr_groups[idx].pg.attrs = kcalloc(KOBJ_MAX_ATTRS + 1, |
317 | sizeof(*papr_groups[idx].pg.attrs), |
318 | GFP_KERNEL); |
319 | if (!papr_groups[idx].pg.attrs) |
320 | goto out_pgattrs; |
321 | |
322 | papr_groups[idx].pg.name = kasprintf(GFP_KERNEL, fmt: "%lld" , |
323 | be64_to_cpu(esi_attrs[idx].id)); |
324 | if (papr_groups[idx].pg.name == NULL) |
325 | goto out_pgattrs; |
326 | } |
327 | |
328 | for (idx = 0; idx < num_attrs; idx++) { |
329 | bool show_val_desc = true; |
330 | |
331 | /* Do not add the value desc attr if it does not exist */ |
332 | if (strnlen(p: esi_attrs[idx].value_desc, |
333 | maxlen: sizeof(esi_attrs[idx].value_desc)) == 0) |
334 | show_val_desc = false; |
335 | |
336 | if (add_attr_group(be64_to_cpu(esi_attrs[idx].id), |
337 | pg: &papr_groups[idx], |
338 | show_val_desc)) { |
339 | pr_warn("Failed to create papr attribute group %s\n" , |
340 | papr_groups[idx].pg.name); |
341 | idx = num_attrs; |
342 | goto out_pgattrs; |
343 | } |
344 | } |
345 | |
346 | kfree(esi_buf); |
347 | return 0; |
348 | out_pgattrs: |
349 | for (i = 0; i < idx ; i++) { |
350 | kfree(papr_groups[i].pg.attrs); |
351 | kfree(papr_groups[i].pg.name); |
352 | } |
353 | kobject_put(kobj: esi_kobj); |
354 | out_kobj: |
355 | kobject_put(kobj: papr_kobj); |
356 | out_papr_groups: |
357 | kfree(papr_groups); |
358 | out_free_esi_buf: |
359 | kfree(esi_buf); |
360 | |
361 | return -ENOMEM; |
362 | } |
363 | |
364 | machine_device_initcall(pseries, papr_init); |
365 | |