1c7ab62bfSHuang Rui /* 2c7ab62bfSHuang Rui * Performance events - AMD Processor Power Reporting Mechanism 3c7ab62bfSHuang Rui * 4c7ab62bfSHuang Rui * Copyright (C) 2016 Advanced Micro Devices, Inc. 5c7ab62bfSHuang Rui * 6c7ab62bfSHuang Rui * Author: Huang Rui <ray.huang@amd.com> 7c7ab62bfSHuang Rui * 8c7ab62bfSHuang Rui * This program is free software; you can redistribute it and/or modify 9c7ab62bfSHuang Rui * it under the terms of the GNU General Public License version 2 as 10c7ab62bfSHuang Rui * published by the Free Software Foundation. 11c7ab62bfSHuang Rui */ 12c7ab62bfSHuang Rui 13c7ab62bfSHuang Rui #include <linux/module.h> 14c7ab62bfSHuang Rui #include <linux/slab.h> 15c7ab62bfSHuang Rui #include <linux/perf_event.h> 16c7ab62bfSHuang Rui #include <asm/cpu_device_id.h> 17c7ab62bfSHuang Rui #include "../perf_event.h" 18c7ab62bfSHuang Rui 19c7ab62bfSHuang Rui #define MSR_F15H_CU_PWR_ACCUMULATOR 0xc001007a 20c7ab62bfSHuang Rui #define MSR_F15H_CU_MAX_PWR_ACCUMULATOR 0xc001007b 21c7ab62bfSHuang Rui #define MSR_F15H_PTSC 0xc0010280 22c7ab62bfSHuang Rui 23c7ab62bfSHuang Rui /* Event code: LSB 8 bits, passed in attr->config any other bit is reserved. */ 24c7ab62bfSHuang Rui #define AMD_POWER_EVENT_MASK 0xFFULL 25c7ab62bfSHuang Rui 26c7ab62bfSHuang Rui /* 27c7ab62bfSHuang Rui * Accumulated power status counters. 28c7ab62bfSHuang Rui */ 29c7ab62bfSHuang Rui #define AMD_POWER_EVENTSEL_PKG 1 30c7ab62bfSHuang Rui 31c7ab62bfSHuang Rui /* 32c7ab62bfSHuang Rui * The ratio of compute unit power accumulator sample period to the 33c7ab62bfSHuang Rui * PTSC period. 34c7ab62bfSHuang Rui */ 35c7ab62bfSHuang Rui static unsigned int cpu_pwr_sample_ratio; 36c7ab62bfSHuang Rui 37c7ab62bfSHuang Rui /* Maximum accumulated power of a compute unit. */ 38c7ab62bfSHuang Rui static u64 max_cu_acc_power; 39c7ab62bfSHuang Rui 40c7ab62bfSHuang Rui static struct pmu pmu_class; 41c7ab62bfSHuang Rui 42c7ab62bfSHuang Rui /* 43c7ab62bfSHuang Rui * Accumulated power represents the sum of each compute unit's (CU) power 44c7ab62bfSHuang Rui * consumption. On any core of each CU we read the total accumulated power from 45c7ab62bfSHuang Rui * MSR_F15H_CU_PWR_ACCUMULATOR. cpu_mask represents CPU bit map of all cores 46c7ab62bfSHuang Rui * which are picked to measure the power for the CUs they belong to. 47c7ab62bfSHuang Rui */ 48c7ab62bfSHuang Rui static cpumask_t cpu_mask; 49c7ab62bfSHuang Rui 50c7ab62bfSHuang Rui static void event_update(struct perf_event *event) 51c7ab62bfSHuang Rui { 52c7ab62bfSHuang Rui struct hw_perf_event *hwc = &event->hw; 53c7ab62bfSHuang Rui u64 prev_pwr_acc, new_pwr_acc, prev_ptsc, new_ptsc; 54c7ab62bfSHuang Rui u64 delta, tdelta; 55c7ab62bfSHuang Rui 56c7ab62bfSHuang Rui prev_pwr_acc = hwc->pwr_acc; 57c7ab62bfSHuang Rui prev_ptsc = hwc->ptsc; 58c7ab62bfSHuang Rui rdmsrl(MSR_F15H_CU_PWR_ACCUMULATOR, new_pwr_acc); 59c7ab62bfSHuang Rui rdmsrl(MSR_F15H_PTSC, new_ptsc); 60c7ab62bfSHuang Rui 61c7ab62bfSHuang Rui /* 62c7ab62bfSHuang Rui * Calculate the CU power consumption over a time period, the unit of 63c7ab62bfSHuang Rui * final value (delta) is micro-Watts. Then add it to the event count. 64c7ab62bfSHuang Rui */ 65c7ab62bfSHuang Rui if (new_pwr_acc < prev_pwr_acc) { 66c7ab62bfSHuang Rui delta = max_cu_acc_power + new_pwr_acc; 67c7ab62bfSHuang Rui delta -= prev_pwr_acc; 68c7ab62bfSHuang Rui } else 69c7ab62bfSHuang Rui delta = new_pwr_acc - prev_pwr_acc; 70c7ab62bfSHuang Rui 71c7ab62bfSHuang Rui delta *= cpu_pwr_sample_ratio * 1000; 72c7ab62bfSHuang Rui tdelta = new_ptsc - prev_ptsc; 73c7ab62bfSHuang Rui 74c7ab62bfSHuang Rui do_div(delta, tdelta); 75c7ab62bfSHuang Rui local64_add(delta, &event->count); 76c7ab62bfSHuang Rui } 77c7ab62bfSHuang Rui 78c7ab62bfSHuang Rui static void __pmu_event_start(struct perf_event *event) 79c7ab62bfSHuang Rui { 80c7ab62bfSHuang Rui if (WARN_ON_ONCE(!(event->hw.state & PERF_HES_STOPPED))) 81c7ab62bfSHuang Rui return; 82c7ab62bfSHuang Rui 83c7ab62bfSHuang Rui event->hw.state = 0; 84c7ab62bfSHuang Rui 85c7ab62bfSHuang Rui rdmsrl(MSR_F15H_PTSC, event->hw.ptsc); 86c7ab62bfSHuang Rui rdmsrl(MSR_F15H_CU_PWR_ACCUMULATOR, event->hw.pwr_acc); 87c7ab62bfSHuang Rui } 88c7ab62bfSHuang Rui 89c7ab62bfSHuang Rui static void pmu_event_start(struct perf_event *event, int mode) 90c7ab62bfSHuang Rui { 91c7ab62bfSHuang Rui __pmu_event_start(event); 92c7ab62bfSHuang Rui } 93c7ab62bfSHuang Rui 94c7ab62bfSHuang Rui static void pmu_event_stop(struct perf_event *event, int mode) 95c7ab62bfSHuang Rui { 96c7ab62bfSHuang Rui struct hw_perf_event *hwc = &event->hw; 97c7ab62bfSHuang Rui 98c7ab62bfSHuang Rui /* Mark event as deactivated and stopped. */ 99c7ab62bfSHuang Rui if (!(hwc->state & PERF_HES_STOPPED)) 100c7ab62bfSHuang Rui hwc->state |= PERF_HES_STOPPED; 101c7ab62bfSHuang Rui 102c7ab62bfSHuang Rui /* Check if software counter update is necessary. */ 103c7ab62bfSHuang Rui if ((mode & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) { 104c7ab62bfSHuang Rui /* 105c7ab62bfSHuang Rui * Drain the remaining delta count out of an event 106c7ab62bfSHuang Rui * that we are disabling: 107c7ab62bfSHuang Rui */ 108c7ab62bfSHuang Rui event_update(event); 109c7ab62bfSHuang Rui hwc->state |= PERF_HES_UPTODATE; 110c7ab62bfSHuang Rui } 111c7ab62bfSHuang Rui } 112c7ab62bfSHuang Rui 113c7ab62bfSHuang Rui static int pmu_event_add(struct perf_event *event, int mode) 114c7ab62bfSHuang Rui { 115c7ab62bfSHuang Rui struct hw_perf_event *hwc = &event->hw; 116c7ab62bfSHuang Rui 117c7ab62bfSHuang Rui hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED; 118c7ab62bfSHuang Rui 119c7ab62bfSHuang Rui if (mode & PERF_EF_START) 120c7ab62bfSHuang Rui __pmu_event_start(event); 121c7ab62bfSHuang Rui 122c7ab62bfSHuang Rui return 0; 123c7ab62bfSHuang Rui } 124c7ab62bfSHuang Rui 125c7ab62bfSHuang Rui static void pmu_event_del(struct perf_event *event, int flags) 126c7ab62bfSHuang Rui { 127c7ab62bfSHuang Rui pmu_event_stop(event, PERF_EF_UPDATE); 128c7ab62bfSHuang Rui } 129c7ab62bfSHuang Rui 130c7ab62bfSHuang Rui static int pmu_event_init(struct perf_event *event) 131c7ab62bfSHuang Rui { 132c7ab62bfSHuang Rui u64 cfg = event->attr.config & AMD_POWER_EVENT_MASK; 133c7ab62bfSHuang Rui 134c7ab62bfSHuang Rui /* Only look at AMD power events. */ 135c7ab62bfSHuang Rui if (event->attr.type != pmu_class.type) 136c7ab62bfSHuang Rui return -ENOENT; 137c7ab62bfSHuang Rui 138c7ab62bfSHuang Rui /* Unsupported modes and filters. */ 1392ff40250SAndrew Murray if (event->attr.sample_period) 140c7ab62bfSHuang Rui return -EINVAL; 141c7ab62bfSHuang Rui 142c7ab62bfSHuang Rui if (cfg != AMD_POWER_EVENTSEL_PKG) 143c7ab62bfSHuang Rui return -EINVAL; 144c7ab62bfSHuang Rui 145c7ab62bfSHuang Rui return 0; 146c7ab62bfSHuang Rui } 147c7ab62bfSHuang Rui 148c7ab62bfSHuang Rui static void pmu_event_read(struct perf_event *event) 149c7ab62bfSHuang Rui { 150c7ab62bfSHuang Rui event_update(event); 151c7ab62bfSHuang Rui } 152c7ab62bfSHuang Rui 153c7ab62bfSHuang Rui static ssize_t 154c7ab62bfSHuang Rui get_attr_cpumask(struct device *dev, struct device_attribute *attr, char *buf) 155c7ab62bfSHuang Rui { 156c7ab62bfSHuang Rui return cpumap_print_to_pagebuf(true, buf, &cpu_mask); 157c7ab62bfSHuang Rui } 158c7ab62bfSHuang Rui 159c7ab62bfSHuang Rui static DEVICE_ATTR(cpumask, S_IRUGO, get_attr_cpumask, NULL); 160c7ab62bfSHuang Rui 161c7ab62bfSHuang Rui static struct attribute *pmu_attrs[] = { 162c7ab62bfSHuang Rui &dev_attr_cpumask.attr, 163c7ab62bfSHuang Rui NULL, 164c7ab62bfSHuang Rui }; 165c7ab62bfSHuang Rui 166c7ab62bfSHuang Rui static struct attribute_group pmu_attr_group = { 167c7ab62bfSHuang Rui .attrs = pmu_attrs, 168c7ab62bfSHuang Rui }; 169c7ab62bfSHuang Rui 170c7ab62bfSHuang Rui /* 171c7ab62bfSHuang Rui * Currently it only supports to report the power of each 172c7ab62bfSHuang Rui * processor/package. 173c7ab62bfSHuang Rui */ 174c7ab62bfSHuang Rui EVENT_ATTR_STR(power-pkg, power_pkg, "event=0x01"); 175c7ab62bfSHuang Rui 176c7ab62bfSHuang Rui EVENT_ATTR_STR(power-pkg.unit, power_pkg_unit, "mWatts"); 177c7ab62bfSHuang Rui 178c7ab62bfSHuang Rui /* Convert the count from micro-Watts to milli-Watts. */ 179c7ab62bfSHuang Rui EVENT_ATTR_STR(power-pkg.scale, power_pkg_scale, "1.000000e-3"); 180c7ab62bfSHuang Rui 181c7ab62bfSHuang Rui static struct attribute *events_attr[] = { 182c7ab62bfSHuang Rui EVENT_PTR(power_pkg), 183c7ab62bfSHuang Rui EVENT_PTR(power_pkg_unit), 184c7ab62bfSHuang Rui EVENT_PTR(power_pkg_scale), 185c7ab62bfSHuang Rui NULL, 186c7ab62bfSHuang Rui }; 187c7ab62bfSHuang Rui 188c7ab62bfSHuang Rui static struct attribute_group pmu_events_group = { 189c7ab62bfSHuang Rui .name = "events", 190c7ab62bfSHuang Rui .attrs = events_attr, 191c7ab62bfSHuang Rui }; 192c7ab62bfSHuang Rui 193c7ab62bfSHuang Rui PMU_FORMAT_ATTR(event, "config:0-7"); 194c7ab62bfSHuang Rui 195c7ab62bfSHuang Rui static struct attribute *formats_attr[] = { 196c7ab62bfSHuang Rui &format_attr_event.attr, 197c7ab62bfSHuang Rui NULL, 198c7ab62bfSHuang Rui }; 199c7ab62bfSHuang Rui 200c7ab62bfSHuang Rui static struct attribute_group pmu_format_group = { 201c7ab62bfSHuang Rui .name = "format", 202c7ab62bfSHuang Rui .attrs = formats_attr, 203c7ab62bfSHuang Rui }; 204c7ab62bfSHuang Rui 205c7ab62bfSHuang Rui static const struct attribute_group *attr_groups[] = { 206c7ab62bfSHuang Rui &pmu_attr_group, 207c7ab62bfSHuang Rui &pmu_format_group, 208c7ab62bfSHuang Rui &pmu_events_group, 209c7ab62bfSHuang Rui NULL, 210c7ab62bfSHuang Rui }; 211c7ab62bfSHuang Rui 212c7ab62bfSHuang Rui static struct pmu pmu_class = { 213c7ab62bfSHuang Rui .attr_groups = attr_groups, 214c7ab62bfSHuang Rui /* system-wide only */ 215c7ab62bfSHuang Rui .task_ctx_nr = perf_invalid_context, 216c7ab62bfSHuang Rui .event_init = pmu_event_init, 217c7ab62bfSHuang Rui .add = pmu_event_add, 218c7ab62bfSHuang Rui .del = pmu_event_del, 219c7ab62bfSHuang Rui .start = pmu_event_start, 220c7ab62bfSHuang Rui .stop = pmu_event_stop, 221c7ab62bfSHuang Rui .read = pmu_event_read, 2222ff40250SAndrew Murray .capabilities = PERF_PMU_CAP_NO_EXCLUDE, 223c7ab62bfSHuang Rui }; 224c7ab62bfSHuang Rui 225c6a84daaSAnna-Maria Gleixner static int power_cpu_exit(unsigned int cpu) 226c7ab62bfSHuang Rui { 227c7ab62bfSHuang Rui int target; 228c7ab62bfSHuang Rui 229c7ab62bfSHuang Rui if (!cpumask_test_and_clear_cpu(cpu, &cpu_mask)) 230c6a84daaSAnna-Maria Gleixner return 0; 231c7ab62bfSHuang Rui 232c7ab62bfSHuang Rui /* 233c7ab62bfSHuang Rui * Find a new CPU on the same compute unit, if was set in cpumask 234c7ab62bfSHuang Rui * and still some CPUs on compute unit. Then migrate event and 235c7ab62bfSHuang Rui * context to new CPU. 236c7ab62bfSHuang Rui */ 237c7ab62bfSHuang Rui target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); 238c7ab62bfSHuang Rui if (target < nr_cpumask_bits) { 239c7ab62bfSHuang Rui cpumask_set_cpu(target, &cpu_mask); 240c7ab62bfSHuang Rui perf_pmu_migrate_context(&pmu_class, cpu, target); 241c7ab62bfSHuang Rui } 242c6a84daaSAnna-Maria Gleixner return 0; 243c7ab62bfSHuang Rui } 244c7ab62bfSHuang Rui 245c6a84daaSAnna-Maria Gleixner static int power_cpu_init(unsigned int cpu) 246c7ab62bfSHuang Rui { 247c7ab62bfSHuang Rui int target; 248c7ab62bfSHuang Rui 249c7ab62bfSHuang Rui /* 250c7ab62bfSHuang Rui * 1) If any CPU is set at cpu_mask in the same compute unit, do 251c7ab62bfSHuang Rui * nothing. 252c7ab62bfSHuang Rui * 2) If no CPU is set at cpu_mask in the same compute unit, 2538381f6a0SAnna-Maria Gleixner * set current ONLINE CPU. 254c7ab62bfSHuang Rui * 255c7ab62bfSHuang Rui * Note: if there is a CPU aside of the new one already in the 256c7ab62bfSHuang Rui * sibling mask, then it is also in cpu_mask. 257c7ab62bfSHuang Rui */ 258c7ab62bfSHuang Rui target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); 259c7ab62bfSHuang Rui if (target >= nr_cpumask_bits) 260c7ab62bfSHuang Rui cpumask_set_cpu(cpu, &cpu_mask); 261c6a84daaSAnna-Maria Gleixner return 0; 262c7ab62bfSHuang Rui } 263c7ab62bfSHuang Rui 264c7ab62bfSHuang Rui static const struct x86_cpu_id cpu_match[] = { 265c7ab62bfSHuang Rui { .vendor = X86_VENDOR_AMD, .family = 0x15 }, 266c7ab62bfSHuang Rui {}, 267c7ab62bfSHuang Rui }; 268c7ab62bfSHuang Rui 269c7ab62bfSHuang Rui static int __init amd_power_pmu_init(void) 270c7ab62bfSHuang Rui { 271c6a84daaSAnna-Maria Gleixner int ret; 272c7ab62bfSHuang Rui 273c7ab62bfSHuang Rui if (!x86_match_cpu(cpu_match)) 27440d4071cSXiao Liang return -ENODEV; 275c7ab62bfSHuang Rui 276c7ab62bfSHuang Rui if (!boot_cpu_has(X86_FEATURE_ACC_POWER)) 277c7ab62bfSHuang Rui return -ENODEV; 278c7ab62bfSHuang Rui 279c7ab62bfSHuang Rui cpu_pwr_sample_ratio = cpuid_ecx(0x80000007); 280c7ab62bfSHuang Rui 281c7ab62bfSHuang Rui if (rdmsrl_safe(MSR_F15H_CU_MAX_PWR_ACCUMULATOR, &max_cu_acc_power)) { 282c7ab62bfSHuang Rui pr_err("Failed to read max compute unit power accumulator MSR\n"); 283c7ab62bfSHuang Rui return -ENODEV; 284c7ab62bfSHuang Rui } 285c7ab62bfSHuang Rui 286c7ab62bfSHuang Rui 287c6a84daaSAnna-Maria Gleixner cpuhp_setup_state(CPUHP_AP_PERF_X86_AMD_POWER_ONLINE, 28873c1b41eSThomas Gleixner "perf/x86/amd/power:online", 289c6a84daaSAnna-Maria Gleixner power_cpu_init, power_cpu_exit); 290c7ab62bfSHuang Rui 291c7ab62bfSHuang Rui ret = perf_pmu_register(&pmu_class, "power", -1); 292c7ab62bfSHuang Rui if (WARN_ON(ret)) { 293c7ab62bfSHuang Rui pr_warn("AMD Power PMU registration failed\n"); 294c6a84daaSAnna-Maria Gleixner return ret; 295c7ab62bfSHuang Rui } 296c7ab62bfSHuang Rui 297c7ab62bfSHuang Rui pr_info("AMD Power PMU detected\n"); 298c7ab62bfSHuang Rui return ret; 299c7ab62bfSHuang Rui } 300c7ab62bfSHuang Rui module_init(amd_power_pmu_init); 301c7ab62bfSHuang Rui 302c7ab62bfSHuang Rui static void __exit amd_power_pmu_exit(void) 303c7ab62bfSHuang Rui { 304c6a84daaSAnna-Maria Gleixner cpuhp_remove_state_nocalls(CPUHP_AP_PERF_X86_AMD_POWER_ONLINE); 305c7ab62bfSHuang Rui perf_pmu_unregister(&pmu_class); 306c7ab62bfSHuang Rui } 307c7ab62bfSHuang Rui module_exit(amd_power_pmu_exit); 308c7ab62bfSHuang Rui 309c7ab62bfSHuang Rui MODULE_AUTHOR("Huang Rui <ray.huang@amd.com>"); 310c7ab62bfSHuang Rui MODULE_DESCRIPTION("AMD Processor Power Reporting Mechanism"); 311c7ab62bfSHuang Rui MODULE_LICENSE("GPL v2"); 312