// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Hypervisor supplied "gpci" ("get performance counter info") performance
* counter support
*
* Author: Cody P Schafer <cody@linux.vnet.ibm.com>
* Copyright 2014 IBM Corporation.
*/
#define pr_fmt(fmt) "hv-gpci: " fmt
#include <linux/init.h>
#include <linux/perf_event.h>
#include <asm/firmware.h>
#include <asm/hvcall.h>
#include <asm/io.h>
#include "hv-gpci.h"
#include "hv-common.h"
/*
* Example usage:
* perf stat -e 'hv_gpci/counter_info_version=3,offset=0,length=8,
* secondary_index=0,starting_index=0xffffffff,request=0x10/' ...
*/
/* u32 */
EVENT_DEFINE_RANGE_FORMAT(request, config, 0, 31);
/* u32 */
/*
* Note that starting_index, phys_processor_idx, sibling_part_id,
* hw_chip_id, partition_id all refer to the same bit range. They
* are basically aliases for the starting_index. The specific alias
* used depends on the event. See REQUEST_IDX_KIND in hv-gpci-requests.h
*/
EVENT_DEFINE_RANGE_FORMAT(starting_index, config, 32, 63);
EVENT_DEFINE_RANGE_FORMAT_LITE(phys_processor_idx, config, 32, 63);
EVENT_DEFINE_RANGE_FORMAT_LITE(sibling_part_id, config, 32, 63);
EVENT_DEFINE_RANGE_FORMAT_LITE(hw_chip_id, config, 32, 63);
EVENT_DEFINE_RANGE_FORMAT_LITE(partition_id, config, 32, 63);
/* u16 */
EVENT_DEFINE_RANGE_FORMAT(secondary_index, config1, 0, 15);
/* u8 */
EVENT_DEFINE_RANGE_FORMAT(counter_info_version, config1, 16, 23);
/* u8, bytes of data (1-8) */
EVENT_DEFINE_RANGE_FORMAT(length, config1, 24, 31);
/* u32, byte offset */
EVENT_DEFINE_RANGE_FORMAT(offset, config1, 32, 63);
static cpumask_t hv_gpci_cpumask;
static struct attribute *format_attrs[] = {
&format_attr_request.attr,
&format_attr_starting_index.attr,
&format_attr_phys_processor_idx.attr,
&format_attr_sibling_part_id.attr,
&format_attr_hw_chip_id.attr,
&format_attr_partition_id.attr,
&format_attr_secondary_index.attr,
&format_attr_counter_info_version.attr,
&format_attr_offset.attr,
&format_attr_length.attr,
NULL,
};
static const struct attribute_group format_group = {
.name = "format",
.attrs = format_attrs,
};
static struct attribute_group event_group = {
.name = "events",
/* .attrs is set in init */
};
#define HV_CAPS_ATTR(_name, _format) \
static ssize_t _name##_show(struct device *dev, \
struct device_attribute *attr, \
char *page) \
{ \
struct hv_perf_caps caps; \
unsigned long hret = hv_perf_caps_get(&caps); \
if (hret) \
return -EIO; \
\
return sprintf(page, _format, caps._name); \
} \
static struct device_attribute hv_caps_attr_##_name = __ATTR_RO(_name)
static ssize_t kernel_version_show(struct device *dev,
struct device_attribute *attr,
char *page)
{
return sprintf(page, "0x%x\n", COUNTER_INFO_VERSION_CURRENT);
}
static ssize_t cpumask_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return cpumap_print_to_pagebuf(true, buf, &hv_gpci_cpumask);
}
/* Interface attribute array index to store system information */
#define INTERFACE_PROCESSOR_BUS_TOPOLOGY_ATTR 6
#define INTERFACE_PROCESSOR_CONFIG_ATTR 7
#define INTERFACE_AFFINITY_DOMAIN_VIA_VP_ATTR 8
#define INTERFACE_AFFINITY_DOMAIN_VIA_DOM_ATTR 9
#define INTERFACE_AFFINITY_DOMAIN_VIA_PAR_ATTR 10
#define INTERFACE_NULL_ATTR 11
/* Counter request value to retrieve system information */
enum {
PROCESSOR_BUS_TOPOLOGY,
PROCESSOR_CONFIG,
AFFINITY_DOMAIN_VIA_VP, /* affinity domain via virtual processor */
AFFINITY_DOMAIN_VIA_DOM, /* affinity domain via domain */
AFFINITY_DOMAIN_VIA_PAR, /* affinity domain via partition */
};
static int sysinfo_counter_request[] = {
[PROCESSOR_BUS_TOPOLOGY] = 0xD0,
[PROCESSOR_CONFIG] = 0x90,
[AFFINITY_DOMAIN_VIA_VP] = 0xA0,
[AFFINITY_DOMAIN_VIA_DOM] = 0xB0,
[AFFINITY_DOMAIN_VIA_PAR] = 0xB1,
};
static DEFINE_PER_CPU(char, hv_gpci_reqb[HGPCI_REQ_BUFFER_SIZE]) __aligned(sizeof(uint64_t));
static unsigned long systeminfo_gpci_request(u32 req, u32 starting_index,
u16 secondary_index, char *buf,
size_t *n, struct hv_gpci_request_buffer *arg)
{
unsigned long ret;
size_t i, j;
arg->params.counter_request = cpu_to_be32(req);
arg->params.starting_index = cpu_to_be32(starting_index);
arg->params.secondary_index = cpu_to_be16(secondary_index);
ret = plpar_hcall_norets(H_GET_PERF_COUNTER_INFO,
virt_to_phys(arg), HGPCI_REQ_BUFFER_SIZE);
/*
* ret value as 'H_PARAMETER' corresponds to 'GEN_BUF_TOO_SMALL',
* which means that the current buffer size cannot accommodate
* all the information and a partial buffer returned.
* hcall fails incase of ret value other than H_SUCCESS or H_PARAMETER.
*
* ret value as H_AUTHORITY implies that partition is not permitted to retrieve
* performance information, and required to set
* "Enable Performance Information Collection" option.
*/
if (ret ==