1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Performance monitoring support for Virtual Processor Area(VPA) based counters
4 *
5 * Copyright (C) 2024 IBM Corporation
6 */
7#define pr_fmt(fmt) "vpa_pmu: " fmt
8
9#include <linux/module.h>
10#include <linux/perf_event.h>
11#include <asm/kvm_ppc.h>
12#include <asm/kvm_book3s_64.h>
13
14#define MODULE_VERS "1.0"
15#define MODULE_NAME "pseries_vpa_pmu"
16
17#define EVENT(_name, _code) enum{_name = _code}
18
19#define VPA_PMU_EVENT_VAR(_id) event_attr_##_id
20#define VPA_PMU_EVENT_PTR(_id) (&event_attr_##_id.attr.attr)
21
22static ssize_t vpa_pmu_events_sysfs_show(struct device *dev,
23 struct device_attribute *attr, char *page)
24{
25 struct perf_pmu_events_attr *pmu_attr;
26
27 pmu_attr = container_of(attr, struct perf_pmu_events_attr, attr);
28
29 return sprintf(buf: page, fmt: "event=0x%02llx\n", pmu_attr->id);
30}
31
32#define VPA_PMU_EVENT_ATTR(_name, _id) \
33 PMU_EVENT_ATTR(_name, VPA_PMU_EVENT_VAR(_id), _id, \
34 vpa_pmu_events_sysfs_show)
35
36EVENT(L1_TO_L2_CS_LAT, 0x1);
37EVENT(L2_TO_L1_CS_LAT, 0x2);
38EVENT(L2_RUNTIME_AGG, 0x3);
39
40VPA_PMU_EVENT_ATTR(l1_to_l2_lat, L1_TO_L2_CS_LAT);
41VPA_PMU_EVENT_ATTR(l2_to_l1_lat, L2_TO_L1_CS_LAT);
42VPA_PMU_EVENT_ATTR(l2_runtime_agg, L2_RUNTIME_AGG);
43
44static struct attribute *vpa_pmu_events_attr[] = {
45 VPA_PMU_EVENT_PTR(L1_TO_L2_CS_LAT),
46 VPA_PMU_EVENT_PTR(L2_TO_L1_CS_LAT),
47 VPA_PMU_EVENT_PTR(L2_RUNTIME_AGG),
48 NULL
49};
50
51static const struct attribute_group vpa_pmu_events_group = {
52 .name = "events",
53 .attrs = vpa_pmu_events_attr,
54};
55
56PMU_FORMAT_ATTR(event, "config:0-31");
57static struct attribute *vpa_pmu_format_attr[] = {
58 &format_attr_event.attr,
59 NULL,
60};
61
62static struct attribute_group vpa_pmu_format_group = {
63 .name = "format",
64 .attrs = vpa_pmu_format_attr,
65};
66
67static const struct attribute_group *vpa_pmu_attr_groups[] = {
68 &vpa_pmu_events_group,
69 &vpa_pmu_format_group,
70 NULL
71};
72
73static int vpa_pmu_event_init(struct perf_event *event)
74{
75 if (event->attr.type != event->pmu->type)
76 return -ENOENT;
77
78 /* it does not support event sampling mode */
79 if (is_sampling_event(event))
80 return -EOPNOTSUPP;
81
82 /* no branch sampling */
83 if (has_branch_stack(event))
84 return -EOPNOTSUPP;
85
86 /* Invalid event code */
87 if ((event->attr.config <= 0) || (event->attr.config > 3))
88 return -EINVAL;
89
90 return 0;
91}
92
93static unsigned long get_counter_data(struct perf_event *event)
94{
95 unsigned int config = event->attr.config;
96 u64 data;
97
98 switch (config) {
99 case L1_TO_L2_CS_LAT:
100 if (event->attach_state & PERF_ATTACH_TASK)
101 data = kvmhv_get_l1_to_l2_cs_time_vcpu();
102 else
103 data = kvmhv_get_l1_to_l2_cs_time();
104 break;
105 case L2_TO_L1_CS_LAT:
106 if (event->attach_state & PERF_ATTACH_TASK)
107 data = kvmhv_get_l2_to_l1_cs_time_vcpu();
108 else
109 data = kvmhv_get_l2_to_l1_cs_time();
110 break;
111 case L2_RUNTIME_AGG:
112 if (event->attach_state & PERF_ATTACH_TASK)
113 data = kvmhv_get_l2_runtime_agg_vcpu();
114 else
115 data = kvmhv_get_l2_runtime_agg();
116 break;
117 default:
118 data = 0;
119 break;
120 }
121
122 return data;
123}
124
125static int vpa_pmu_add(struct perf_event *event, int flags)
126{
127 u64 data;
128
129 kvmhv_set_l2_counters_status(smp_processor_id(), true);
130
131 data = get_counter_data(event);
132 local64_set(&event->hw.prev_count, data);
133
134 return 0;
135}
136
137static void vpa_pmu_read(struct perf_event *event)
138{
139 u64 prev_data, new_data, final_data;
140
141 prev_data = local64_read(&event->hw.prev_count);
142 new_data = get_counter_data(event);
143 final_data = new_data - prev_data;
144
145 local64_add(final_data, &event->count);
146}
147
148static void vpa_pmu_del(struct perf_event *event, int flags)
149{
150 vpa_pmu_read(event);
151
152 /*
153 * Disable vpa counter accumulation
154 */
155 kvmhv_set_l2_counters_status(smp_processor_id(), false);
156}
157
158static struct pmu vpa_pmu = {
159 .module = THIS_MODULE,
160 .task_ctx_nr = perf_sw_context,
161 .name = "vpa_pmu",
162 .event_init = vpa_pmu_event_init,
163 .add = vpa_pmu_add,
164 .del = vpa_pmu_del,
165 .read = vpa_pmu_read,
166 .attr_groups = vpa_pmu_attr_groups,
167 .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT,
168};
169
170static int __init pseries_vpa_pmu_init(void)
171{
172 /*
173 * List of current Linux on Power platforms and
174 * this driver is supported only in PowerVM LPAR
175 * (L1) platform.
176 *
177 * Enabled Linux on Power Platforms
178 * ----------------------------------------
179 * [X] PowerVM LPAR (L1)
180 * [ ] KVM Guest On PowerVM KoP(L2)
181 * [ ] Baremetal(PowerNV)
182 * [ ] KVM Guest On PowerNV
183 */
184 if (!firmware_has_feature(FW_FEATURE_LPAR) || is_kvm_guest())
185 return -ENODEV;
186
187 perf_pmu_register(pmu: &vpa_pmu, name: vpa_pmu.name, type: -1);
188 pr_info("Virtual Processor Area PMU registered.\n");
189
190 return 0;
191}
192
193static void __exit pseries_vpa_pmu_cleanup(void)
194{
195 perf_pmu_unregister(pmu: &vpa_pmu);
196 pr_info("Virtual Processor Area PMU unregistered.\n");
197}
198
199module_init(pseries_vpa_pmu_init);
200module_exit(pseries_vpa_pmu_cleanup);
201MODULE_DESCRIPTION("Perf Driver for pSeries VPA pmu counter");
202MODULE_AUTHOR("Kajol Jain <kjain@linux.ibm.com>");
203MODULE_AUTHOR("Madhavan Srinivasan <maddy@linux.ibm.com>");
204MODULE_LICENSE("GPL");
205

source code of linux/arch/powerpc/perf/vpa-pmu.c