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