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 return -ENOMEM; 108 109 goto retry; 110 } 111 112 if (ret != H_SUCCESS) { 113 pr_warn("hcall failed: H_GET_ENERGY_SCALE_INFO"); 114 ret = -EIO; 115 goto out_buf; 116 } 117 118 hdr = (struct h_energy_scale_info_hdr *) buf; 119 curr_esi = (struct energy_scale_attribute *) 120 (buf + be64_to_cpu(hdr->array_offset)); 121 122 if (esi_buf_size < 123 be64_to_cpu(hdr->array_offset) + (be64_to_cpu(hdr->num_attrs) 124 * sizeof(struct energy_scale_attribute))) { 125 ret = -EIO; 126 goto out_buf; 127 } 128 129 *esi = *curr_esi; 130 131 out_buf: 132 kfree(buf); 133 134 return ret; 135 } 136 137 /* 138 * Extract and export the description of the energy scale attributes 139 */ 140 static ssize_t desc_show(struct kobject *kobj, 141 struct kobj_attribute *kobj_attr, 142 char *buf) 143 { 144 struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr, 145 kobj_attr); 146 struct energy_scale_attribute esi; 147 int ret; 148 149 ret = papr_get_attr(pattr->id, &esi); 150 if (ret) 151 return ret; 152 153 return sysfs_emit(buf, "%s\n", esi.desc); 154 } 155 156 /* 157 * Extract and export the numeric value of the energy scale attributes 158 */ 159 static ssize_t val_show(struct kobject *kobj, 160 struct kobj_attribute *kobj_attr, 161 char *buf) 162 { 163 struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr, 164 kobj_attr); 165 struct energy_scale_attribute esi; 166 int ret; 167 168 ret = papr_get_attr(pattr->id, &esi); 169 if (ret) 170 return ret; 171 172 return sysfs_emit(buf, "%llu\n", be64_to_cpu(esi.val)); 173 } 174 175 /* 176 * Extract and export the value description in string format of the energy 177 * scale attributes 178 */ 179 static ssize_t val_desc_show(struct kobject *kobj, 180 struct kobj_attribute *kobj_attr, 181 char *buf) 182 { 183 struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr, 184 kobj_attr); 185 struct energy_scale_attribute esi; 186 int ret; 187 188 ret = papr_get_attr(pattr->id, &esi); 189 if (ret) 190 return ret; 191 192 return sysfs_emit(buf, "%s\n", esi.value_desc); 193 } 194 195 static struct papr_ops_info { 196 const char *attr_name; 197 ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *kobj_attr, 198 char *buf); 199 } ops_info[KOBJ_MAX_ATTRS] = { 200 { "desc", desc_show }, 201 { "value", val_show }, 202 { "value_desc", val_desc_show }, 203 }; 204 205 static void add_attr(u64 id, int index, struct papr_attr *attr) 206 { 207 attr->id = id; 208 sysfs_attr_init(&attr->kobj_attr.attr); 209 attr->kobj_attr.attr.name = ops_info[index].attr_name; 210 attr->kobj_attr.attr.mode = 0444; 211 attr->kobj_attr.show = ops_info[index].show; 212 } 213 214 static int add_attr_group(u64 id, struct papr_group *pg, bool show_val_desc) 215 { 216 int i; 217 218 for (i = 0; i < KOBJ_MAX_ATTRS; i++) { 219 if (!strcmp(ops_info[i].attr_name, "value_desc") && 220 !show_val_desc) { 221 continue; 222 } 223 add_attr(id, i, &pg->pgattrs[i]); 224 pg->pg.attrs[i] = &pg->pgattrs[i].kobj_attr.attr; 225 } 226 227 return sysfs_create_group(esi_kobj, &pg->pg); 228 } 229 230 231 static int __init papr_init(void) 232 { 233 int esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * ESI_ATTR_SIZE); 234 int ret, idx, i, max_esi_attrs = CURR_MAX_ESI_ATTRS; 235 struct h_energy_scale_info_hdr *esi_hdr; 236 struct energy_scale_attribute *esi_attrs; 237 uint64_t num_attrs; 238 char *esi_buf; 239 240 if (!firmware_has_feature(FW_FEATURE_LPAR) || 241 !firmware_has_feature(FW_FEATURE_ENERGY_SCALE_INFO)) { 242 return -ENXIO; 243 } 244 245 esi_buf = kmalloc(esi_buf_size, GFP_KERNEL); 246 if (esi_buf == NULL) 247 return -ENOMEM; 248 /* 249 * hcall( 250 * uint64 H_GET_ENERGY_SCALE_INFO, // Get energy scale info 251 * uint64 flags, // Per the flag request 252 * uint64 firstAttributeId, // The attribute id 253 * uint64 bufferAddress, // Guest physical address of the output buffer 254 * uint64 bufferSize); // The size in bytes of the output buffer 255 */ 256 retry: 257 258 ret = plpar_hcall_norets(H_GET_ENERGY_SCALE_INFO, ESI_FLAGS_ALL, 0, 259 virt_to_phys(esi_buf), esi_buf_size); 260 261 /* 262 * If the hcall fails with not enough memory for either the 263 * header or data, attempt to allocate more 264 */ 265 if (ret == H_PARTIAL || ret == H_P4) { 266 char *temp_esi_buf; 267 268 max_esi_attrs += 4; 269 esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * max_esi_attrs); 270 271 temp_esi_buf = krealloc(esi_buf, esi_buf_size, GFP_KERNEL); 272 if (temp_esi_buf) 273 esi_buf = temp_esi_buf; 274 else 275 return -ENOMEM; 276 277 goto retry; 278 } 279 280 if (ret != H_SUCCESS) { 281 pr_warn("hcall failed: H_GET_ENERGY_SCALE_INFO, ret: %d\n", ret); 282 goto out_free_esi_buf; 283 } 284 285 esi_hdr = (struct h_energy_scale_info_hdr *) esi_buf; 286 num_attrs = be64_to_cpu(esi_hdr->num_attrs); 287 esi_attrs = (struct energy_scale_attribute *) 288 (esi_buf + be64_to_cpu(esi_hdr->array_offset)); 289 290 if (esi_buf_size < 291 be64_to_cpu(esi_hdr->array_offset) + 292 (num_attrs * sizeof(struct energy_scale_attribute))) { 293 goto out_free_esi_buf; 294 } 295 296 papr_groups = kcalloc(num_attrs, sizeof(*papr_groups), GFP_KERNEL); 297 if (!papr_groups) 298 goto out_free_esi_buf; 299 300 papr_kobj = kobject_create_and_add("papr", firmware_kobj); 301 if (!papr_kobj) { 302 pr_warn("kobject_create_and_add papr failed\n"); 303 goto out_papr_groups; 304 } 305 306 esi_kobj = kobject_create_and_add("energy_scale_info", papr_kobj); 307 if (!esi_kobj) { 308 pr_warn("kobject_create_and_add energy_scale_info failed\n"); 309 goto out_kobj; 310 } 311 312 /* Allocate the groups before registering */ 313 for (idx = 0; idx < num_attrs; idx++) { 314 papr_groups[idx].pg.attrs = kcalloc(KOBJ_MAX_ATTRS + 1, 315 sizeof(*papr_groups[idx].pg.attrs), 316 GFP_KERNEL); 317 if (!papr_groups[idx].pg.attrs) 318 goto out_pgattrs; 319 320 papr_groups[idx].pg.name = kasprintf(GFP_KERNEL, "%lld", 321 be64_to_cpu(esi_attrs[idx].id)); 322 if (papr_groups[idx].pg.name == NULL) 323 goto out_pgattrs; 324 } 325 326 for (idx = 0; idx < num_attrs; idx++) { 327 bool show_val_desc = true; 328 329 /* Do not add the value desc attr if it does not exist */ 330 if (strnlen(esi_attrs[idx].value_desc, 331 sizeof(esi_attrs[idx].value_desc)) == 0) 332 show_val_desc = false; 333 334 if (add_attr_group(be64_to_cpu(esi_attrs[idx].id), 335 &papr_groups[idx], 336 show_val_desc)) { 337 pr_warn("Failed to create papr attribute group %s\n", 338 papr_groups[idx].pg.name); 339 idx = num_attrs; 340 goto out_pgattrs; 341 } 342 } 343 344 kfree(esi_buf); 345 return 0; 346 out_pgattrs: 347 for (i = 0; i < idx ; i++) { 348 kfree(papr_groups[i].pg.attrs); 349 kfree(papr_groups[i].pg.name); 350 } 351 kobject_put(esi_kobj); 352 out_kobj: 353 kobject_put(papr_kobj); 354 out_papr_groups: 355 kfree(papr_groups); 356 out_free_esi_buf: 357 kfree(esi_buf); 358 359 return -ENOMEM; 360 } 361 362 machine_device_initcall(pseries, papr_init); 363