// 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 #include #include #include #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 "); MODULE_AUTHOR("Madhavan Srinivasan "); MODULE_LICENSE("GPL");