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 22 static 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(page, "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 36 EVENT(L1_TO_L2_CS_LAT, 0x1); 37 EVENT(L2_TO_L1_CS_LAT, 0x2); 38 EVENT(L2_RUNTIME_AGG, 0x3); 39 40 VPA_PMU_EVENT_ATTR(l1_to_l2_lat, L1_TO_L2_CS_LAT); 41 VPA_PMU_EVENT_ATTR(l2_to_l1_lat, L2_TO_L1_CS_LAT); 42 VPA_PMU_EVENT_ATTR(l2_runtime_agg, L2_RUNTIME_AGG); 43 44 static 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 51 static const struct attribute_group vpa_pmu_events_group = { 52 .name = "events", 53 .attrs = vpa_pmu_events_attr, 54 }; 55 56 PMU_FORMAT_ATTR(event, "config:0-31"); 57 static struct attribute *vpa_pmu_format_attr[] = { 58 &format_attr_event.attr, 59 NULL, 60 }; 61 62 static struct attribute_group vpa_pmu_format_group = { 63 .name = "format", 64 .attrs = vpa_pmu_format_attr, 65 }; 66 67 static const struct attribute_group *vpa_pmu_attr_groups[] = { 68 &vpa_pmu_events_group, 69 &vpa_pmu_format_group, 70 NULL 71 }; 72 73 static 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 93 static 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 125 static 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 137 static 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 148 static 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 158 static struct pmu vpa_pmu = { 159 .task_ctx_nr = perf_sw_context, 160 .name = "vpa_pmu", 161 .event_init = vpa_pmu_event_init, 162 .add = vpa_pmu_add, 163 .del = vpa_pmu_del, 164 .read = vpa_pmu_read, 165 .attr_groups = vpa_pmu_attr_groups, 166 .capabilities = PERF_PMU_CAP_NO_EXCLUDE | PERF_PMU_CAP_NO_INTERRUPT, 167 }; 168 169 static int __init pseries_vpa_pmu_init(void) 170 { 171 /* 172 * List of current Linux on Power platforms and 173 * this driver is supported only in PowerVM LPAR 174 * (L1) platform. 175 * 176 * Enabled Linux on Power Platforms 177 * ---------------------------------------- 178 * [X] PowerVM LPAR (L1) 179 * [ ] KVM Guest On PowerVM KoP(L2) 180 * [ ] Baremetal(PowerNV) 181 * [ ] KVM Guest On PowerNV 182 */ 183 if (!firmware_has_feature(FW_FEATURE_LPAR) || is_kvm_guest()) 184 return -ENODEV; 185 186 perf_pmu_register(&vpa_pmu, vpa_pmu.name, -1); 187 pr_info("Virtual Processor Area PMU registered.\n"); 188 189 return 0; 190 } 191 192 static void __exit pseries_vpa_pmu_cleanup(void) 193 { 194 perf_pmu_unregister(&vpa_pmu); 195 pr_info("Virtual Processor Area PMU unregistered.\n"); 196 } 197 198 module_init(pseries_vpa_pmu_init); 199 module_exit(pseries_vpa_pmu_cleanup); 200 MODULE_DESCRIPTION("Perf Driver for pSeries VPA pmu counter"); 201 MODULE_AUTHOR("Kajol Jain <kjain@linux.ibm.com>"); 202 MODULE_AUTHOR("Madhavan Srinivasan <maddy@linux.ibm.com>"); 203 MODULE_LICENSE("GPL"); 204