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 data_header_version; 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(pattr->id, &esi); 152 if (ret) 153 return ret; 154 155 return sysfs_emit(buf, "%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(pattr->id, &esi); 171 if (ret) 172 return ret; 173 174 return sysfs_emit(buf, "%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(pattr->id, &esi); 191 if (ret) 192 return ret; 193 194 return sysfs_emit(buf, "%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, i, &pg->pgattrs[i]); 226 pg->pg.attrs[i] = &pg->pgattrs[i].kobj_attr.attr; 227 } 228 229 return sysfs_create_group(esi_kobj, &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("papr", 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("energy_scale_info", 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, "%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(esi_attrs[idx].value_desc, 333 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 &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(esi_kobj); 354 out_kobj: 355 kobject_put(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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.