cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

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);