18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Performance events - AMD Processor Power Reporting Mechanism 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016 Advanced Micro Devices, Inc. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Huang Rui <ray.huang@amd.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/perf_event.h> 138c2ecf20Sopenharmony_ci#include <asm/cpu_device_id.h> 148c2ecf20Sopenharmony_ci#include "../perf_event.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* Event code: LSB 8 bits, passed in attr->config any other bit is reserved. */ 178c2ecf20Sopenharmony_ci#define AMD_POWER_EVENT_MASK 0xFFULL 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* 208c2ecf20Sopenharmony_ci * Accumulated power status counters. 218c2ecf20Sopenharmony_ci */ 228c2ecf20Sopenharmony_ci#define AMD_POWER_EVENTSEL_PKG 1 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * The ratio of compute unit power accumulator sample period to the 268c2ecf20Sopenharmony_ci * PTSC period. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_cistatic unsigned int cpu_pwr_sample_ratio; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* Maximum accumulated power of a compute unit. */ 318c2ecf20Sopenharmony_cistatic u64 max_cu_acc_power; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic struct pmu pmu_class; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* 368c2ecf20Sopenharmony_ci * Accumulated power represents the sum of each compute unit's (CU) power 378c2ecf20Sopenharmony_ci * consumption. On any core of each CU we read the total accumulated power from 388c2ecf20Sopenharmony_ci * MSR_F15H_CU_PWR_ACCUMULATOR. cpu_mask represents CPU bit map of all cores 398c2ecf20Sopenharmony_ci * which are picked to measure the power for the CUs they belong to. 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_cistatic cpumask_t cpu_mask; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic void event_update(struct perf_event *event) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 468c2ecf20Sopenharmony_ci u64 prev_pwr_acc, new_pwr_acc, prev_ptsc, new_ptsc; 478c2ecf20Sopenharmony_ci u64 delta, tdelta; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci prev_pwr_acc = hwc->pwr_acc; 508c2ecf20Sopenharmony_ci prev_ptsc = hwc->ptsc; 518c2ecf20Sopenharmony_ci rdmsrl(MSR_F15H_CU_PWR_ACCUMULATOR, new_pwr_acc); 528c2ecf20Sopenharmony_ci rdmsrl(MSR_F15H_PTSC, new_ptsc); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci /* 558c2ecf20Sopenharmony_ci * Calculate the CU power consumption over a time period, the unit of 568c2ecf20Sopenharmony_ci * final value (delta) is micro-Watts. Then add it to the event count. 578c2ecf20Sopenharmony_ci */ 588c2ecf20Sopenharmony_ci if (new_pwr_acc < prev_pwr_acc) { 598c2ecf20Sopenharmony_ci delta = max_cu_acc_power + new_pwr_acc; 608c2ecf20Sopenharmony_ci delta -= prev_pwr_acc; 618c2ecf20Sopenharmony_ci } else 628c2ecf20Sopenharmony_ci delta = new_pwr_acc - prev_pwr_acc; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci delta *= cpu_pwr_sample_ratio * 1000; 658c2ecf20Sopenharmony_ci tdelta = new_ptsc - prev_ptsc; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci do_div(delta, tdelta); 688c2ecf20Sopenharmony_ci local64_add(delta, &event->count); 698c2ecf20Sopenharmony_ci} 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cistatic void __pmu_event_start(struct perf_event *event) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(!(event->hw.state & PERF_HES_STOPPED))) 748c2ecf20Sopenharmony_ci return; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci event->hw.state = 0; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci rdmsrl(MSR_F15H_PTSC, event->hw.ptsc); 798c2ecf20Sopenharmony_ci rdmsrl(MSR_F15H_CU_PWR_ACCUMULATOR, event->hw.pwr_acc); 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic void pmu_event_start(struct perf_event *event, int mode) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci __pmu_event_start(event); 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic void pmu_event_stop(struct perf_event *event, int mode) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci /* Mark event as deactivated and stopped. */ 928c2ecf20Sopenharmony_ci if (!(hwc->state & PERF_HES_STOPPED)) 938c2ecf20Sopenharmony_ci hwc->state |= PERF_HES_STOPPED; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci /* Check if software counter update is necessary. */ 968c2ecf20Sopenharmony_ci if ((mode & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) { 978c2ecf20Sopenharmony_ci /* 988c2ecf20Sopenharmony_ci * Drain the remaining delta count out of an event 998c2ecf20Sopenharmony_ci * that we are disabling: 1008c2ecf20Sopenharmony_ci */ 1018c2ecf20Sopenharmony_ci event_update(event); 1028c2ecf20Sopenharmony_ci hwc->state |= PERF_HES_UPTODATE; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic int pmu_event_add(struct perf_event *event, int mode) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci if (mode & PERF_EF_START) 1138c2ecf20Sopenharmony_ci __pmu_event_start(event); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci return 0; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic void pmu_event_del(struct perf_event *event, int flags) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci pmu_event_stop(event, PERF_EF_UPDATE); 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int pmu_event_init(struct perf_event *event) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci u64 cfg = event->attr.config & AMD_POWER_EVENT_MASK; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* Only look at AMD power events. */ 1288c2ecf20Sopenharmony_ci if (event->attr.type != pmu_class.type) 1298c2ecf20Sopenharmony_ci return -ENOENT; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* Unsupported modes and filters. */ 1328c2ecf20Sopenharmony_ci if (event->attr.sample_period) 1338c2ecf20Sopenharmony_ci return -EINVAL; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (cfg != AMD_POWER_EVENTSEL_PKG) 1368c2ecf20Sopenharmony_ci return -EINVAL; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci return 0; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic void pmu_event_read(struct perf_event *event) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci event_update(event); 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic ssize_t 1478c2ecf20Sopenharmony_ciget_attr_cpumask(struct device *dev, struct device_attribute *attr, char *buf) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci return cpumap_print_to_pagebuf(true, buf, &cpu_mask); 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic DEVICE_ATTR(cpumask, S_IRUGO, get_attr_cpumask, NULL); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic struct attribute *pmu_attrs[] = { 1558c2ecf20Sopenharmony_ci &dev_attr_cpumask.attr, 1568c2ecf20Sopenharmony_ci NULL, 1578c2ecf20Sopenharmony_ci}; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic struct attribute_group pmu_attr_group = { 1608c2ecf20Sopenharmony_ci .attrs = pmu_attrs, 1618c2ecf20Sopenharmony_ci}; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci/* 1648c2ecf20Sopenharmony_ci * Currently it only supports to report the power of each 1658c2ecf20Sopenharmony_ci * processor/package. 1668c2ecf20Sopenharmony_ci */ 1678c2ecf20Sopenharmony_ciEVENT_ATTR_STR(power-pkg, power_pkg, "event=0x01"); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ciEVENT_ATTR_STR(power-pkg.unit, power_pkg_unit, "mWatts"); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci/* Convert the count from micro-Watts to milli-Watts. */ 1728c2ecf20Sopenharmony_ciEVENT_ATTR_STR(power-pkg.scale, power_pkg_scale, "1.000000e-3"); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic struct attribute *events_attr[] = { 1758c2ecf20Sopenharmony_ci EVENT_PTR(power_pkg), 1768c2ecf20Sopenharmony_ci EVENT_PTR(power_pkg_unit), 1778c2ecf20Sopenharmony_ci EVENT_PTR(power_pkg_scale), 1788c2ecf20Sopenharmony_ci NULL, 1798c2ecf20Sopenharmony_ci}; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic struct attribute_group pmu_events_group = { 1828c2ecf20Sopenharmony_ci .name = "events", 1838c2ecf20Sopenharmony_ci .attrs = events_attr, 1848c2ecf20Sopenharmony_ci}; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(event, "config:0-7"); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic struct attribute *formats_attr[] = { 1898c2ecf20Sopenharmony_ci &format_attr_event.attr, 1908c2ecf20Sopenharmony_ci NULL, 1918c2ecf20Sopenharmony_ci}; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic struct attribute_group pmu_format_group = { 1948c2ecf20Sopenharmony_ci .name = "format", 1958c2ecf20Sopenharmony_ci .attrs = formats_attr, 1968c2ecf20Sopenharmony_ci}; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic const struct attribute_group *attr_groups[] = { 1998c2ecf20Sopenharmony_ci &pmu_attr_group, 2008c2ecf20Sopenharmony_ci &pmu_format_group, 2018c2ecf20Sopenharmony_ci &pmu_events_group, 2028c2ecf20Sopenharmony_ci NULL, 2038c2ecf20Sopenharmony_ci}; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic struct pmu pmu_class = { 2068c2ecf20Sopenharmony_ci .attr_groups = attr_groups, 2078c2ecf20Sopenharmony_ci /* system-wide only */ 2088c2ecf20Sopenharmony_ci .task_ctx_nr = perf_invalid_context, 2098c2ecf20Sopenharmony_ci .event_init = pmu_event_init, 2108c2ecf20Sopenharmony_ci .add = pmu_event_add, 2118c2ecf20Sopenharmony_ci .del = pmu_event_del, 2128c2ecf20Sopenharmony_ci .start = pmu_event_start, 2138c2ecf20Sopenharmony_ci .stop = pmu_event_stop, 2148c2ecf20Sopenharmony_ci .read = pmu_event_read, 2158c2ecf20Sopenharmony_ci .capabilities = PERF_PMU_CAP_NO_EXCLUDE, 2168c2ecf20Sopenharmony_ci .module = THIS_MODULE, 2178c2ecf20Sopenharmony_ci}; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic int power_cpu_exit(unsigned int cpu) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci int target; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci if (!cpumask_test_and_clear_cpu(cpu, &cpu_mask)) 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci /* 2278c2ecf20Sopenharmony_ci * Find a new CPU on the same compute unit, if was set in cpumask 2288c2ecf20Sopenharmony_ci * and still some CPUs on compute unit. Then migrate event and 2298c2ecf20Sopenharmony_ci * context to new CPU. 2308c2ecf20Sopenharmony_ci */ 2318c2ecf20Sopenharmony_ci target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); 2328c2ecf20Sopenharmony_ci if (target < nr_cpumask_bits) { 2338c2ecf20Sopenharmony_ci cpumask_set_cpu(target, &cpu_mask); 2348c2ecf20Sopenharmony_ci perf_pmu_migrate_context(&pmu_class, cpu, target); 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci return 0; 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic int power_cpu_init(unsigned int cpu) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci int target; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci /* 2448c2ecf20Sopenharmony_ci * 1) If any CPU is set at cpu_mask in the same compute unit, do 2458c2ecf20Sopenharmony_ci * nothing. 2468c2ecf20Sopenharmony_ci * 2) If no CPU is set at cpu_mask in the same compute unit, 2478c2ecf20Sopenharmony_ci * set current ONLINE CPU. 2488c2ecf20Sopenharmony_ci * 2498c2ecf20Sopenharmony_ci * Note: if there is a CPU aside of the new one already in the 2508c2ecf20Sopenharmony_ci * sibling mask, then it is also in cpu_mask. 2518c2ecf20Sopenharmony_ci */ 2528c2ecf20Sopenharmony_ci target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); 2538c2ecf20Sopenharmony_ci if (target >= nr_cpumask_bits) 2548c2ecf20Sopenharmony_ci cpumask_set_cpu(cpu, &cpu_mask); 2558c2ecf20Sopenharmony_ci return 0; 2568c2ecf20Sopenharmony_ci} 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_cistatic const struct x86_cpu_id cpu_match[] = { 2598c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FAM(AMD, 0x15, NULL), 2608c2ecf20Sopenharmony_ci {}, 2618c2ecf20Sopenharmony_ci}; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistatic int __init amd_power_pmu_init(void) 2648c2ecf20Sopenharmony_ci{ 2658c2ecf20Sopenharmony_ci int ret; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci if (!x86_match_cpu(cpu_match)) 2688c2ecf20Sopenharmony_ci return -ENODEV; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci if (!boot_cpu_has(X86_FEATURE_ACC_POWER)) 2718c2ecf20Sopenharmony_ci return -ENODEV; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci cpu_pwr_sample_ratio = cpuid_ecx(0x80000007); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci if (rdmsrl_safe(MSR_F15H_CU_MAX_PWR_ACCUMULATOR, &max_cu_acc_power)) { 2768c2ecf20Sopenharmony_ci pr_err("Failed to read max compute unit power accumulator MSR\n"); 2778c2ecf20Sopenharmony_ci return -ENODEV; 2788c2ecf20Sopenharmony_ci } 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci cpuhp_setup_state(CPUHP_AP_PERF_X86_AMD_POWER_ONLINE, 2828c2ecf20Sopenharmony_ci "perf/x86/amd/power:online", 2838c2ecf20Sopenharmony_ci power_cpu_init, power_cpu_exit); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci ret = perf_pmu_register(&pmu_class, "power", -1); 2868c2ecf20Sopenharmony_ci if (WARN_ON(ret)) { 2878c2ecf20Sopenharmony_ci pr_warn("AMD Power PMU registration failed\n"); 2888c2ecf20Sopenharmony_ci return ret; 2898c2ecf20Sopenharmony_ci } 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci pr_info("AMD Power PMU detected\n"); 2928c2ecf20Sopenharmony_ci return ret; 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_cimodule_init(amd_power_pmu_init); 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cistatic void __exit amd_power_pmu_exit(void) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci cpuhp_remove_state_nocalls(CPUHP_AP_PERF_X86_AMD_POWER_ONLINE); 2998c2ecf20Sopenharmony_ci perf_pmu_unregister(&pmu_class); 3008c2ecf20Sopenharmony_ci} 3018c2ecf20Sopenharmony_cimodule_exit(amd_power_pmu_exit); 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ciMODULE_AUTHOR("Huang Rui <ray.huang@amd.com>"); 3048c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("AMD Processor Power Reporting Mechanism"); 3058c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 306