204 lines
4.9 KiB
C
204 lines
4.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Performance monitoring support for Virtual Processor Area(VPA) based counters
|
|
*
|
|
* Copyright (C) 2024 IBM Corporation
|
|
*/
|
|
#define pr_fmt(fmt) "vpa_pmu: " fmt
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/perf_event.h>
|
|
#include <asm/kvm_ppc.h>
|
|
#include <asm/kvm_book3s_64.h>
|
|
|
|
#define MODULE_VERS "1.0"
|
|
#define MODULE_NAME "pseries_vpa_pmu"
|
|
|
|
#define EVENT(_name, _code) enum{_name = _code}
|
|
|
|
#define VPA_PMU_EVENT_VAR(_id) event_attr_##_id
|
|
#define VPA_PMU_EVENT_PTR(_id) (&event_attr_##_id.attr.attr)
|
|
|
|
static ssize_t vpa_pmu_events_sysfs_show(struct device *dev,
|
|
struct device_attribute *attr, char *page)
|
|
{
|
|
struct perf_pmu_events_attr *pmu_attr;
|
|
|
|
pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr);
|
|
|
|
return sprintf(page, "event=0x%02llx\n", pmu_attr->id);
|
|
}
|
|
|
|
#define VPA_PMU_EVENT_ATTR(_name, _id) \
|
|
PMU_EVENT_ATTR(_name, VPA_PMU_EVENT_VAR(_id), _id, \
|
|
vpa_pmu_events_sysfs_show)
|
|
|
|
EVENT(L1_TO_L2_CS_LAT, 0x1);
|
|
EVENT(L2_TO_L1_CS_LAT, 0x2);
|
|
EVENT(L2_RUNTIME_AGG, 0x3);
|
|
|
|
VPA_PMU_EVENT_ATTR(l1_to_l2_lat, L1_TO_L2_CS_LAT);
|
|
VPA_PMU_EVENT_ATTR(l2_to_l1_lat, L2_TO_L1_CS_LAT);
|
|
VPA_PMU_EVENT_ATTR(l2_runtime_agg, L2_RUNTIME_AGG);
|
|
|
|
static struct attribute *vpa_pmu_events_attr[] = {
|
|
VPA_PMU_EVENT_PTR(L1_TO_L2_CS_LAT),
|
|
VPA_PMU_EVENT_PTR(L2_TO_L1_CS_LAT),
|
|
VPA_PMU_EVENT_PTR(L2_RUNTIME_AGG),
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group vpa_pmu_events_group = {
|
|
.name = "events",
|
|
.attrs = vpa_pmu_events_attr,
|
|
};
|
|
|
|
PMU_FORMAT_ATTR(event, "config:0-31");
|
|
static struct attribute *vpa_pmu_format_attr[] = {
|
|
&format_attr_event.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group vpa_pmu_format_group = {
|
|
.name = "format",
|
|
.attrs = vpa_pmu_format_attr,
|
|
};
|
|
|
|
static const struct attribute_group *vpa_pmu_attr_groups[] = {
|
|
&vpa_pmu_events_group,
|
|
&vpa_pmu_format_group,
|
|
NULL
|
|
};
|
|
|
|
static int vpa_pmu_event_init(struct perf_event *event)
|
|
{
|
|
if (event->attr.type != event->pmu->type)
|
|
return -ENOENT;
|
|
|
|
/* it does not support event sampling mode */
|
|
if (is_sampling_event(event))
|
|
return -EOPNOTSUPP;
|
|
|
|
/* no branch sampling */
|
|
if (has_branch_stack(event))
|
|
return -EOPNOTSUPP;
|
|
|
|
/* Invalid event code */
|
|
if ((event->attr.config <= 0) || (event->attr.config > 3))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long get_counter_data(struct perf_event *event)
|
|
{
|
|
unsigned int config = event->attr.config;
|
|
u64 data;
|
|
|
|
switch (config) {
|
|
case L1_TO_L2_CS_LAT:
|
|
if (event->attach_state & PERF_ATTACH_TASK)
|
|
data = kvmhv_get_l1_to_l2_cs_time_vcpu();
|
|
else
|
|
data = kvmhv_get_l1_to_l2_cs_time();
|
|
break;
|
|
case L2_TO_L1_CS_LAT:
|
|
if (event->attach_state & PERF_ATTACH_TASK)
|
|
data = kvmhv_get_l2_to_l1_cs_time_vcpu();
|
|
else
|
|
data = kvmhv_get_l2_to_l1_cs_time();
|
|
break;
|
|
case L2_RUNTIME_AGG:
|
|
if (event->attach_state & PERF_ATTACH_TASK)
|
|
data = kvmhv_get_l2_runtime_agg_vcpu();
|
|
else
|
|
data = kvmhv_get_l2_runtime_agg();
|
|
break;
|
|
default:
|
|
data = 0;
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
static int vpa_pmu_add(struct perf_event *event, int flags)
|
|
{
|
|
u64 data;
|
|
|
|
kvmhv_set_l2_counters_status(smp_processor_id(), true);
|
|
|
|
data = get_counter_data(event);
|
|
local64_set(&event->hw.prev_count, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vpa_pmu_read(struct perf_event *event)
|
|
{
|
|
u64 prev_data, new_data, final_data;
|
|
|
|
prev_data = local64_read(&event->hw.prev_count);
|
|
new_data = get_counter_data(event);
|
|
final_data = new_data - prev_data;
|
|
|
|
local64_add(final_data, &event->count);
|
|
}
|
|
|
|
static void vpa_pmu_del(struct perf_event *event, int flags)
|
|
{
|
|
vpa_pmu_read(event);
|
|
|
|
/*
|
|
* Disable vpa counter accumulation
|
|
*/
|
|
kvmhv_set_l2_counters_status(smp_processor_id(), false);
|
|
}
|
|
|
|
static struct pmu vpa_pmu = {
|
|
.task_ctx_nr = perf_sw_context,
|
|
.name = "vpa_pmu",
|
|
.event_init = vpa_pmu_event_init,
|
|
.add = vpa_pmu_add,
|
|
.del = vpa_pmu_del,
|
|
.read = vpa_pmu_read,
|
|
.attr_groups = vpa_pmu_attr_groups,
|
|
.capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT,
|
|
};
|
|
|
|
static int __init pseries_vpa_pmu_init(void)
|
|
{
|
|
/*
|
|
* List of current Linux on Power platforms and
|
|
* this driver is supported only in PowerVM LPAR
|
|
* (L1) platform.
|
|
*
|
|
* Enabled Linux on Power Platforms
|
|
* ----------------------------------------
|
|
* [X] PowerVM LPAR (L1)
|
|
* [ ] KVM Guest On PowerVM KoP(L2)
|
|
* [ ] Baremetal(PowerNV)
|
|
* [ ] KVM Guest On PowerNV
|
|
*/
|
|
if (!firmware_has_feature(FW_FEATURE_LPAR) || is_kvm_guest())
|
|
return -ENODEV;
|
|
|
|
perf_pmu_register(&vpa_pmu, vpa_pmu.name, -1);
|
|
pr_info("Virtual Processor Area PMU registered.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit pseries_vpa_pmu_cleanup(void)
|
|
{
|
|
perf_pmu_unregister(&vpa_pmu);
|
|
pr_info("Virtual Processor Area PMU unregistered.\n");
|
|
}
|
|
|
|
module_init(pseries_vpa_pmu_init);
|
|
module_exit(pseries_vpa_pmu_cleanup);
|
|
MODULE_DESCRIPTION("Perf Driver for pSeries VPA pmu counter");
|
|
MODULE_AUTHOR("Kajol Jain <kjain@linux.ibm.com>");
|
|
MODULE_AUTHOR("Madhavan Srinivasan <maddy@linux.ibm.com>");
|
|
MODULE_LICENSE("GPL");
|