Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Platform energy and frequency attributes driver
0004  *
0005  * This driver creates a sys file at /sys/firmware/papr/ which encapsulates a
0006  * directory structure containing files in keyword - value pairs that specify
0007  * energy and frequency configuration of the system.
0008  *
0009  * The format of exposing the sysfs information is as follows:
0010  * /sys/firmware/papr/energy_scale_info/
0011  *  |-- <id>/
0012  *    |-- desc
0013  *    |-- value
0014  *    |-- value_desc (if exists)
0015  *  |-- <id>/
0016  *    |-- desc
0017  *    |-- value
0018  *    |-- value_desc (if exists)
0019  *
0020  * Copyright 2022 IBM Corp.
0021  */
0022 
0023 #include <asm/hvcall.h>
0024 #include <asm/machdep.h>
0025 #include <asm/firmware.h>
0026 
0027 #include "pseries.h"
0028 
0029 /*
0030  * Flag attributes to fetch either all or one attribute from the HCALL
0031  * flag = BE(0) => fetch all attributes with firstAttributeId = 0
0032  * flag = BE(1) => fetch a single attribute with firstAttributeId = id
0033  */
0034 #define ESI_FLAGS_ALL       0
0035 #define ESI_FLAGS_SINGLE    (1ull << 63)
0036 
0037 #define KOBJ_MAX_ATTRS      3
0038 
0039 #define ESI_HDR_SIZE        sizeof(struct h_energy_scale_info_hdr)
0040 #define ESI_ATTR_SIZE       sizeof(struct energy_scale_attribute)
0041 #define CURR_MAX_ESI_ATTRS  8
0042 
0043 struct energy_scale_attribute {
0044     __be64 id;
0045     __be64 val;
0046     u8 desc[64];
0047     u8 value_desc[64];
0048 } __packed;
0049 
0050 struct h_energy_scale_info_hdr {
0051     __be64 num_attrs;
0052     __be64 array_offset;
0053     u8 data_header_version;
0054 } __packed;
0055 
0056 struct papr_attr {
0057     u64 id;
0058     struct kobj_attribute kobj_attr;
0059 };
0060 
0061 struct papr_group {
0062     struct attribute_group pg;
0063     struct papr_attr pgattrs[KOBJ_MAX_ATTRS];
0064 };
0065 
0066 static struct papr_group *papr_groups;
0067 /* /sys/firmware/papr */
0068 static struct kobject *papr_kobj;
0069 /* /sys/firmware/papr/energy_scale_info */
0070 static struct kobject *esi_kobj;
0071 
0072 /*
0073  * Energy modes can change dynamically hence making a new hcall each time the
0074  * information needs to be retrieved
0075  */
0076 static int papr_get_attr(u64 id, struct energy_scale_attribute *esi)
0077 {
0078     int esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * ESI_ATTR_SIZE);
0079     int ret, max_esi_attrs = CURR_MAX_ESI_ATTRS;
0080     struct energy_scale_attribute *curr_esi;
0081     struct h_energy_scale_info_hdr *hdr;
0082     char *buf;
0083 
0084     buf = kmalloc(esi_buf_size, GFP_KERNEL);
0085     if (buf == NULL)
0086         return -ENOMEM;
0087 
0088 retry:
0089     ret = plpar_hcall_norets(H_GET_ENERGY_SCALE_INFO, ESI_FLAGS_SINGLE,
0090                  id, virt_to_phys(buf),
0091                  esi_buf_size);
0092 
0093     /*
0094      * If the hcall fails with not enough memory for either the
0095      * header or data, attempt to allocate more
0096      */
0097     if (ret == H_PARTIAL || ret == H_P4) {
0098         char *temp_buf;
0099 
0100         max_esi_attrs += 4;
0101         esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * max_esi_attrs);
0102 
0103         temp_buf = krealloc(buf, esi_buf_size, GFP_KERNEL);
0104         if (temp_buf)
0105             buf = temp_buf;
0106         else
0107             return -ENOMEM;
0108 
0109         goto retry;
0110     }
0111 
0112     if (ret != H_SUCCESS) {
0113         pr_warn("hcall failed: H_GET_ENERGY_SCALE_INFO");
0114         ret = -EIO;
0115         goto out_buf;
0116     }
0117 
0118     hdr = (struct h_energy_scale_info_hdr *) buf;
0119     curr_esi = (struct energy_scale_attribute *)
0120         (buf + be64_to_cpu(hdr->array_offset));
0121 
0122     if (esi_buf_size <
0123         be64_to_cpu(hdr->array_offset) + (be64_to_cpu(hdr->num_attrs)
0124         * sizeof(struct energy_scale_attribute))) {
0125         ret = -EIO;
0126         goto out_buf;
0127     }
0128 
0129     *esi = *curr_esi;
0130 
0131 out_buf:
0132     kfree(buf);
0133 
0134     return ret;
0135 }
0136 
0137 /*
0138  * Extract and export the description of the energy scale attributes
0139  */
0140 static ssize_t desc_show(struct kobject *kobj,
0141               struct kobj_attribute *kobj_attr,
0142               char *buf)
0143 {
0144     struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr,
0145                            kobj_attr);
0146     struct energy_scale_attribute esi;
0147     int ret;
0148 
0149     ret = papr_get_attr(pattr->id, &esi);
0150     if (ret)
0151         return ret;
0152 
0153     return sysfs_emit(buf, "%s\n", esi.desc);
0154 }
0155 
0156 /*
0157  * Extract and export the numeric value of the energy scale attributes
0158  */
0159 static ssize_t val_show(struct kobject *kobj,
0160              struct kobj_attribute *kobj_attr,
0161              char *buf)
0162 {
0163     struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr,
0164                            kobj_attr);
0165     struct energy_scale_attribute esi;
0166     int ret;
0167 
0168     ret = papr_get_attr(pattr->id, &esi);
0169     if (ret)
0170         return ret;
0171 
0172     return sysfs_emit(buf, "%llu\n", be64_to_cpu(esi.val));
0173 }
0174 
0175 /*
0176  * Extract and export the value description in string format of the energy
0177  * scale attributes
0178  */
0179 static ssize_t val_desc_show(struct kobject *kobj,
0180                   struct kobj_attribute *kobj_attr,
0181                   char *buf)
0182 {
0183     struct papr_attr *pattr = container_of(kobj_attr, struct papr_attr,
0184                            kobj_attr);
0185     struct energy_scale_attribute esi;
0186     int ret;
0187 
0188     ret = papr_get_attr(pattr->id, &esi);
0189     if (ret)
0190         return ret;
0191 
0192     return sysfs_emit(buf, "%s\n", esi.value_desc);
0193 }
0194 
0195 static struct papr_ops_info {
0196     const char *attr_name;
0197     ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *kobj_attr,
0198             char *buf);
0199 } ops_info[KOBJ_MAX_ATTRS] = {
0200     { "desc", desc_show },
0201     { "value", val_show },
0202     { "value_desc", val_desc_show },
0203 };
0204 
0205 static void add_attr(u64 id, int index, struct papr_attr *attr)
0206 {
0207     attr->id = id;
0208     sysfs_attr_init(&attr->kobj_attr.attr);
0209     attr->kobj_attr.attr.name = ops_info[index].attr_name;
0210     attr->kobj_attr.attr.mode = 0444;
0211     attr->kobj_attr.show = ops_info[index].show;
0212 }
0213 
0214 static int add_attr_group(u64 id, struct papr_group *pg, bool show_val_desc)
0215 {
0216     int i;
0217 
0218     for (i = 0; i < KOBJ_MAX_ATTRS; i++) {
0219         if (!strcmp(ops_info[i].attr_name, "value_desc") &&
0220             !show_val_desc) {
0221             continue;
0222         }
0223         add_attr(id, i, &pg->pgattrs[i]);
0224         pg->pg.attrs[i] = &pg->pgattrs[i].kobj_attr.attr;
0225     }
0226 
0227     return sysfs_create_group(esi_kobj, &pg->pg);
0228 }
0229 
0230 
0231 static int __init papr_init(void)
0232 {
0233     int esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * ESI_ATTR_SIZE);
0234     int ret, idx, i, max_esi_attrs = CURR_MAX_ESI_ATTRS;
0235     struct h_energy_scale_info_hdr *esi_hdr;
0236     struct energy_scale_attribute *esi_attrs;
0237     uint64_t num_attrs;
0238     char *esi_buf;
0239 
0240     if (!firmware_has_feature(FW_FEATURE_LPAR) ||
0241         !firmware_has_feature(FW_FEATURE_ENERGY_SCALE_INFO)) {
0242         return -ENXIO;
0243     }
0244 
0245     esi_buf = kmalloc(esi_buf_size, GFP_KERNEL);
0246     if (esi_buf == NULL)
0247         return -ENOMEM;
0248     /*
0249      * hcall(
0250      * uint64 H_GET_ENERGY_SCALE_INFO,  // Get energy scale info
0251      * uint64 flags,            // Per the flag request
0252      * uint64 firstAttributeId, // The attribute id
0253      * uint64 bufferAddress,    // Guest physical address of the output buffer
0254      * uint64 bufferSize);      // The size in bytes of the output buffer
0255      */
0256 retry:
0257 
0258     ret = plpar_hcall_norets(H_GET_ENERGY_SCALE_INFO, ESI_FLAGS_ALL, 0,
0259                  virt_to_phys(esi_buf), esi_buf_size);
0260 
0261     /*
0262      * If the hcall fails with not enough memory for either the
0263      * header or data, attempt to allocate more
0264      */
0265     if (ret == H_PARTIAL || ret == H_P4) {
0266         char *temp_esi_buf;
0267 
0268         max_esi_attrs += 4;
0269         esi_buf_size = ESI_HDR_SIZE + (CURR_MAX_ESI_ATTRS * max_esi_attrs);
0270 
0271         temp_esi_buf = krealloc(esi_buf, esi_buf_size, GFP_KERNEL);
0272         if (temp_esi_buf)
0273             esi_buf = temp_esi_buf;
0274         else
0275             return -ENOMEM;
0276 
0277         goto retry;
0278     }
0279 
0280     if (ret != H_SUCCESS) {
0281         pr_warn("hcall failed: H_GET_ENERGY_SCALE_INFO, ret: %d\n", ret);
0282         goto out_free_esi_buf;
0283     }
0284 
0285     esi_hdr = (struct h_energy_scale_info_hdr *) esi_buf;
0286     num_attrs = be64_to_cpu(esi_hdr->num_attrs);
0287     esi_attrs = (struct energy_scale_attribute *)
0288             (esi_buf + be64_to_cpu(esi_hdr->array_offset));
0289 
0290     if (esi_buf_size <
0291         be64_to_cpu(esi_hdr->array_offset) +
0292         (num_attrs * sizeof(struct energy_scale_attribute))) {
0293         goto out_free_esi_buf;
0294     }
0295 
0296     papr_groups = kcalloc(num_attrs, sizeof(*papr_groups), GFP_KERNEL);
0297     if (!papr_groups)
0298         goto out_free_esi_buf;
0299 
0300     papr_kobj = kobject_create_and_add("papr", firmware_kobj);
0301     if (!papr_kobj) {
0302         pr_warn("kobject_create_and_add papr failed\n");
0303         goto out_papr_groups;
0304     }
0305 
0306     esi_kobj = kobject_create_and_add("energy_scale_info", papr_kobj);
0307     if (!esi_kobj) {
0308         pr_warn("kobject_create_and_add energy_scale_info failed\n");
0309         goto out_kobj;
0310     }
0311 
0312     /* Allocate the groups before registering */
0313     for (idx = 0; idx < num_attrs; idx++) {
0314         papr_groups[idx].pg.attrs = kcalloc(KOBJ_MAX_ATTRS + 1,
0315                         sizeof(*papr_groups[idx].pg.attrs),
0316                         GFP_KERNEL);
0317         if (!papr_groups[idx].pg.attrs)
0318             goto out_pgattrs;
0319 
0320         papr_groups[idx].pg.name = kasprintf(GFP_KERNEL, "%lld",
0321                          be64_to_cpu(esi_attrs[idx].id));
0322         if (papr_groups[idx].pg.name == NULL)
0323             goto out_pgattrs;
0324     }
0325 
0326     for (idx = 0; idx < num_attrs; idx++) {
0327         bool show_val_desc = true;
0328 
0329         /* Do not add the value desc attr if it does not exist */
0330         if (strnlen(esi_attrs[idx].value_desc,
0331                 sizeof(esi_attrs[idx].value_desc)) == 0)
0332             show_val_desc = false;
0333 
0334         if (add_attr_group(be64_to_cpu(esi_attrs[idx].id),
0335                    &papr_groups[idx],
0336                    show_val_desc)) {
0337             pr_warn("Failed to create papr attribute group %s\n",
0338                 papr_groups[idx].pg.name);
0339             idx = num_attrs;
0340             goto out_pgattrs;
0341         }
0342     }
0343 
0344     kfree(esi_buf);
0345     return 0;
0346 out_pgattrs:
0347     for (i = 0; i < idx ; i++) {
0348         kfree(papr_groups[i].pg.attrs);
0349         kfree(papr_groups[i].pg.name);
0350     }
0351     kobject_put(esi_kobj);
0352 out_kobj:
0353     kobject_put(papr_kobj);
0354 out_papr_groups:
0355     kfree(papr_groups);
0356 out_free_esi_buf:
0357     kfree(esi_buf);
0358 
0359     return -ENOMEM;
0360 }
0361 
0362 machine_device_initcall(pseries, papr_init);