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. */ 139c7ab62bfSHuang Rui if (event->attr.exclude_user || 140c7ab62bfSHuang Rui event->attr.exclude_kernel || 141c7ab62bfSHuang Rui event->attr.exclude_hv || 142c7ab62bfSHuang Rui event->attr.exclude_idle || 143c7ab62bfSHuang Rui event->attr.exclude_host || 144c7ab62bfSHuang Rui event->attr.exclude_guest || 145c7ab62bfSHuang Rui /* no sampling */ 146c7ab62bfSHuang Rui event->attr.sample_period) 147c7ab62bfSHuang Rui return -EINVAL; 148c7ab62bfSHuang Rui 149c7ab62bfSHuang Rui if (cfg != AMD_POWER_EVENTSEL_PKG) 150c7ab62bfSHuang Rui return -EINVAL; 151c7ab62bfSHuang Rui 152c7ab62bfSHuang Rui return 0; 153c7ab62bfSHuang Rui } 154c7ab62bfSHuang Rui 155c7ab62bfSHuang Rui static void pmu_event_read(struct perf_event *event) 156c7ab62bfSHuang Rui { 157c7ab62bfSHuang Rui event_update(event); 158c7ab62bfSHuang Rui } 159c7ab62bfSHuang Rui 160c7ab62bfSHuang Rui static ssize_t 161c7ab62bfSHuang Rui get_attr_cpumask(struct device *dev, struct device_attribute *attr, char *buf) 162c7ab62bfSHuang Rui { 163c7ab62bfSHuang Rui return cpumap_print_to_pagebuf(true, buf, &cpu_mask); 164c7ab62bfSHuang Rui } 165c7ab62bfSHuang Rui 166c7ab62bfSHuang Rui static DEVICE_ATTR(cpumask, S_IRUGO, get_attr_cpumask, NULL); 167c7ab62bfSHuang Rui 168c7ab62bfSHuang Rui static struct attribute *pmu_attrs[] = { 169c7ab62bfSHuang Rui &dev_attr_cpumask.attr, 170c7ab62bfSHuang Rui NULL, 171c7ab62bfSHuang Rui }; 172c7ab62bfSHuang Rui 173c7ab62bfSHuang Rui static struct attribute_group pmu_attr_group = { 174c7ab62bfSHuang Rui .attrs = pmu_attrs, 175c7ab62bfSHuang Rui }; 176c7ab62bfSHuang Rui 177c7ab62bfSHuang Rui /* 178c7ab62bfSHuang Rui * Currently it only supports to report the power of each 179c7ab62bfSHuang Rui * processor/package. 180c7ab62bfSHuang Rui */ 181c7ab62bfSHuang Rui EVENT_ATTR_STR(power-pkg, power_pkg, "event=0x01"); 182c7ab62bfSHuang Rui 183c7ab62bfSHuang Rui EVENT_ATTR_STR(power-pkg.unit, power_pkg_unit, "mWatts"); 184c7ab62bfSHuang Rui 185c7ab62bfSHuang Rui /* Convert the count from micro-Watts to milli-Watts. */ 186c7ab62bfSHuang Rui EVENT_ATTR_STR(power-pkg.scale, power_pkg_scale, "1.000000e-3"); 187c7ab62bfSHuang Rui 188c7ab62bfSHuang Rui static struct attribute *events_attr[] = { 189c7ab62bfSHuang Rui EVENT_PTR(power_pkg), 190c7ab62bfSHuang Rui EVENT_PTR(power_pkg_unit), 191c7ab62bfSHuang Rui EVENT_PTR(power_pkg_scale), 192c7ab62bfSHuang Rui NULL, 193c7ab62bfSHuang Rui }; 194c7ab62bfSHuang Rui 195c7ab62bfSHuang Rui static struct attribute_group pmu_events_group = { 196c7ab62bfSHuang Rui .name = "events", 197c7ab62bfSHuang Rui .attrs = events_attr, 198c7ab62bfSHuang Rui }; 199c7ab62bfSHuang Rui 200c7ab62bfSHuang Rui PMU_FORMAT_ATTR(event, "config:0-7"); 201c7ab62bfSHuang Rui 202c7ab62bfSHuang Rui static struct attribute *formats_attr[] = { 203c7ab62bfSHuang Rui &format_attr_event.attr, 204c7ab62bfSHuang Rui NULL, 205c7ab62bfSHuang Rui }; 206c7ab62bfSHuang Rui 207c7ab62bfSHuang Rui static struct attribute_group pmu_format_group = { 208c7ab62bfSHuang Rui .name = "format", 209c7ab62bfSHuang Rui .attrs = formats_attr, 210c7ab62bfSHuang Rui }; 211c7ab62bfSHuang Rui 212c7ab62bfSHuang Rui static const struct attribute_group *attr_groups[] = { 213c7ab62bfSHuang Rui &pmu_attr_group, 214c7ab62bfSHuang Rui &pmu_format_group, 215c7ab62bfSHuang Rui &pmu_events_group, 216c7ab62bfSHuang Rui NULL, 217c7ab62bfSHuang Rui }; 218c7ab62bfSHuang Rui 219c7ab62bfSHuang Rui static struct pmu pmu_class = { 220c7ab62bfSHuang Rui .attr_groups = attr_groups, 221c7ab62bfSHuang Rui /* system-wide only */ 222c7ab62bfSHuang Rui .task_ctx_nr = perf_invalid_context, 223c7ab62bfSHuang Rui .event_init = pmu_event_init, 224c7ab62bfSHuang Rui .add = pmu_event_add, 225c7ab62bfSHuang Rui .del = pmu_event_del, 226c7ab62bfSHuang Rui .start = pmu_event_start, 227c7ab62bfSHuang Rui .stop = pmu_event_stop, 228c7ab62bfSHuang Rui .read = pmu_event_read, 229c7ab62bfSHuang Rui }; 230c7ab62bfSHuang Rui 231c7ab62bfSHuang Rui static void power_cpu_exit(int cpu) 232c7ab62bfSHuang Rui { 233c7ab62bfSHuang Rui int target; 234c7ab62bfSHuang Rui 235c7ab62bfSHuang Rui if (!cpumask_test_and_clear_cpu(cpu, &cpu_mask)) 236c7ab62bfSHuang Rui return; 237c7ab62bfSHuang Rui 238c7ab62bfSHuang Rui /* 239c7ab62bfSHuang Rui * Find a new CPU on the same compute unit, if was set in cpumask 240c7ab62bfSHuang Rui * and still some CPUs on compute unit. Then migrate event and 241c7ab62bfSHuang Rui * context to new CPU. 242c7ab62bfSHuang Rui */ 243c7ab62bfSHuang Rui target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); 244c7ab62bfSHuang Rui if (target < nr_cpumask_bits) { 245c7ab62bfSHuang Rui cpumask_set_cpu(target, &cpu_mask); 246c7ab62bfSHuang Rui perf_pmu_migrate_context(&pmu_class, cpu, target); 247c7ab62bfSHuang Rui } 248c7ab62bfSHuang Rui } 249c7ab62bfSHuang Rui 250c7ab62bfSHuang Rui static void power_cpu_init(int cpu) 251c7ab62bfSHuang Rui { 252c7ab62bfSHuang Rui int target; 253c7ab62bfSHuang Rui 254c7ab62bfSHuang Rui /* 255c7ab62bfSHuang Rui * 1) If any CPU is set at cpu_mask in the same compute unit, do 256c7ab62bfSHuang Rui * nothing. 257c7ab62bfSHuang Rui * 2) If no CPU is set at cpu_mask in the same compute unit, 2588381f6a0SAnna-Maria Gleixner * set current ONLINE CPU. 259c7ab62bfSHuang Rui * 260c7ab62bfSHuang Rui * Note: if there is a CPU aside of the new one already in the 261c7ab62bfSHuang Rui * sibling mask, then it is also in cpu_mask. 262c7ab62bfSHuang Rui */ 263c7ab62bfSHuang Rui target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); 264c7ab62bfSHuang Rui if (target >= nr_cpumask_bits) 265c7ab62bfSHuang Rui cpumask_set_cpu(cpu, &cpu_mask); 266c7ab62bfSHuang Rui } 267c7ab62bfSHuang Rui 268c7ab62bfSHuang Rui static int 269c7ab62bfSHuang Rui power_cpu_notifier(struct notifier_block *self, unsigned long action, void *hcpu) 270c7ab62bfSHuang Rui { 271c7ab62bfSHuang Rui unsigned int cpu = (long)hcpu; 272c7ab62bfSHuang Rui 273c7ab62bfSHuang Rui switch (action & ~CPU_TASKS_FROZEN) { 274c7ab62bfSHuang Rui case CPU_DOWN_FAILED: 2758381f6a0SAnna-Maria Gleixner case CPU_ONLINE: 276c7ab62bfSHuang Rui power_cpu_init(cpu); 277c7ab62bfSHuang Rui break; 278c7ab62bfSHuang Rui case CPU_DOWN_PREPARE: 279c7ab62bfSHuang Rui power_cpu_exit(cpu); 280c7ab62bfSHuang Rui break; 281c7ab62bfSHuang Rui default: 282c7ab62bfSHuang Rui break; 283c7ab62bfSHuang Rui } 284c7ab62bfSHuang Rui 285c7ab62bfSHuang Rui return NOTIFY_OK; 286c7ab62bfSHuang Rui } 287c7ab62bfSHuang Rui 288c7ab62bfSHuang Rui static struct notifier_block power_cpu_notifier_nb = { 289c7ab62bfSHuang Rui .notifier_call = power_cpu_notifier, 290c7ab62bfSHuang Rui .priority = CPU_PRI_PERF, 291c7ab62bfSHuang Rui }; 292c7ab62bfSHuang Rui 293c7ab62bfSHuang Rui static const struct x86_cpu_id cpu_match[] = { 294c7ab62bfSHuang Rui { .vendor = X86_VENDOR_AMD, .family = 0x15 }, 295c7ab62bfSHuang Rui {}, 296c7ab62bfSHuang Rui }; 297c7ab62bfSHuang Rui 298c7ab62bfSHuang Rui static int __init amd_power_pmu_init(void) 299c7ab62bfSHuang Rui { 300c7ab62bfSHuang Rui int cpu, target, ret; 301c7ab62bfSHuang Rui 302c7ab62bfSHuang Rui if (!x86_match_cpu(cpu_match)) 303c7ab62bfSHuang Rui return 0; 304c7ab62bfSHuang Rui 305c7ab62bfSHuang Rui if (!boot_cpu_has(X86_FEATURE_ACC_POWER)) 306c7ab62bfSHuang Rui return -ENODEV; 307c7ab62bfSHuang Rui 308c7ab62bfSHuang Rui cpu_pwr_sample_ratio = cpuid_ecx(0x80000007); 309c7ab62bfSHuang Rui 310c7ab62bfSHuang Rui if (rdmsrl_safe(MSR_F15H_CU_MAX_PWR_ACCUMULATOR, &max_cu_acc_power)) { 311c7ab62bfSHuang Rui pr_err("Failed to read max compute unit power accumulator MSR\n"); 312c7ab62bfSHuang Rui return -ENODEV; 313c7ab62bfSHuang Rui } 314c7ab62bfSHuang Rui 315c7ab62bfSHuang Rui cpu_notifier_register_begin(); 316c7ab62bfSHuang Rui 317c7ab62bfSHuang Rui /* Choose one online core of each compute unit. */ 318c7ab62bfSHuang Rui for_each_online_cpu(cpu) { 319c7ab62bfSHuang Rui target = cpumask_first(topology_sibling_cpumask(cpu)); 320c7ab62bfSHuang Rui if (!cpumask_test_cpu(target, &cpu_mask)) 321c7ab62bfSHuang Rui cpumask_set_cpu(target, &cpu_mask); 322c7ab62bfSHuang Rui } 323c7ab62bfSHuang Rui 324c7ab62bfSHuang Rui ret = perf_pmu_register(&pmu_class, "power", -1); 325c7ab62bfSHuang Rui if (WARN_ON(ret)) { 326c7ab62bfSHuang Rui pr_warn("AMD Power PMU registration failed\n"); 327c7ab62bfSHuang Rui goto out; 328c7ab62bfSHuang Rui } 329c7ab62bfSHuang Rui 330c7ab62bfSHuang Rui __register_cpu_notifier(&power_cpu_notifier_nb); 331c7ab62bfSHuang Rui 332c7ab62bfSHuang Rui pr_info("AMD Power PMU detected\n"); 333c7ab62bfSHuang Rui 334c7ab62bfSHuang Rui out: 335c7ab62bfSHuang Rui cpu_notifier_register_done(); 336c7ab62bfSHuang Rui 337c7ab62bfSHuang Rui return ret; 338c7ab62bfSHuang Rui } 339c7ab62bfSHuang Rui module_init(amd_power_pmu_init); 340c7ab62bfSHuang Rui 341c7ab62bfSHuang Rui static void __exit amd_power_pmu_exit(void) 342c7ab62bfSHuang Rui { 343c7ab62bfSHuang Rui cpu_notifier_register_begin(); 344c7ab62bfSHuang Rui __unregister_cpu_notifier(&power_cpu_notifier_nb); 345c7ab62bfSHuang Rui cpu_notifier_register_done(); 346c7ab62bfSHuang Rui 347c7ab62bfSHuang Rui perf_pmu_unregister(&pmu_class); 348c7ab62bfSHuang Rui } 349c7ab62bfSHuang Rui module_exit(amd_power_pmu_exit); 350c7ab62bfSHuang Rui 351c7ab62bfSHuang Rui MODULE_AUTHOR("Huang Rui <ray.huang@amd.com>"); 352c7ab62bfSHuang Rui MODULE_DESCRIPTION("AMD Processor Power Reporting Mechanism"); 353c7ab62bfSHuang Rui MODULE_LICENSE("GPL v2"); 354