1// SPDX-License-Identifier: GPL-2.0 2/** 3 * @file nmi_timer_int.c 4 * 5 * @remark Copyright 2011 Advanced Micro Devices, Inc. 6 * 7 * @author Robert Richter <robert.richter@amd.com> 8 */ 9 10#include <linux/init.h> 11#include <linux/smp.h> 12#include <linux/errno.h> 13#include <linux/oprofile.h> 14#include <linux/perf_event.h> 15 16#ifdef CONFIG_OPROFILE_NMI_TIMER 17 18static DEFINE_PER_CPU(struct perf_event *, nmi_timer_events); 19static int ctr_running; 20 21static struct perf_event_attr nmi_timer_attr = { 22 .type = PERF_TYPE_HARDWARE, 23 .config = PERF_COUNT_HW_CPU_CYCLES, 24 .size = sizeof(struct perf_event_attr), 25 .pinned = 1, 26 .disabled = 1, 27}; 28 29static void nmi_timer_callback(struct perf_event *event, 30 struct perf_sample_data *data, 31 struct pt_regs *regs) 32{ 33 event->hw.interrupts = 0; /* don't throttle interrupts */ 34 oprofile_add_sample(regs, 0); 35} 36 37static int nmi_timer_start_cpu(int cpu) 38{ 39 struct perf_event *event = per_cpu(nmi_timer_events, cpu); 40 41 if (!event) { 42 event = perf_event_create_kernel_counter(&nmi_timer_attr, cpu, NULL, 43 nmi_timer_callback, NULL); 44 if (IS_ERR(event)) 45 return PTR_ERR(event); 46 per_cpu(nmi_timer_events, cpu) = event; 47 } 48 49 if (event && ctr_running) 50 perf_event_enable(event); 51 52 return 0; 53} 54 55static void nmi_timer_stop_cpu(int cpu) 56{ 57 struct perf_event *event = per_cpu(nmi_timer_events, cpu); 58 59 if (event && ctr_running) 60 perf_event_disable(event); 61} 62 63static int nmi_timer_cpu_online(unsigned int cpu) 64{ 65 nmi_timer_start_cpu(cpu); 66 return 0; 67} 68static int nmi_timer_cpu_predown(unsigned int cpu) 69{ 70 nmi_timer_stop_cpu(cpu); 71 return 0; 72} 73 74static int nmi_timer_start(void) 75{ 76 int cpu; 77 78 get_online_cpus(); 79 ctr_running = 1; 80 for_each_online_cpu(cpu) 81 nmi_timer_start_cpu(cpu); 82 put_online_cpus(); 83 84 return 0; 85} 86 87static void nmi_timer_stop(void) 88{ 89 int cpu; 90 91 get_online_cpus(); 92 for_each_online_cpu(cpu) 93 nmi_timer_stop_cpu(cpu); 94 ctr_running = 0; 95 put_online_cpus(); 96} 97 98static enum cpuhp_state hp_online; 99 100static void nmi_timer_shutdown(void) 101{ 102 struct perf_event *event; 103 int cpu; 104 105 cpuhp_remove_state(hp_online); 106 for_each_possible_cpu(cpu) { 107 event = per_cpu(nmi_timer_events, cpu); 108 if (!event) 109 continue; 110 perf_event_disable(event); 111 per_cpu(nmi_timer_events, cpu) = NULL; 112 perf_event_release_kernel(event); 113 } 114} 115 116static int nmi_timer_setup(void) 117{ 118 int err; 119 u64 period; 120 121 /* clock cycles per tick: */ 122 period = (u64)cpu_khz * 1000; 123 do_div(period, HZ); 124 nmi_timer_attr.sample_period = period; 125 126 err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "oprofile/nmi:online", 127 nmi_timer_cpu_online, nmi_timer_cpu_predown); 128 if (err < 0) { 129 nmi_timer_shutdown(); 130 return err; 131 } 132 hp_online = err; 133 return 0; 134} 135 136int __init op_nmi_timer_init(struct oprofile_operations *ops) 137{ 138 int err = 0; 139 140 err = nmi_timer_setup(); 141 if (err) 142 return err; 143 nmi_timer_shutdown(); /* only check, don't alloc */ 144 145 ops->create_files = NULL; 146 ops->setup = nmi_timer_setup; 147 ops->shutdown = nmi_timer_shutdown; 148 ops->start = nmi_timer_start; 149 ops->stop = nmi_timer_stop; 150 ops->cpu_type = "timer"; 151 152 printk(KERN_INFO "oprofile: using NMI timer interrupt.\n"); 153 154 return 0; 155} 156 157#endif 158