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