18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/**
38c2ecf20Sopenharmony_ci * @file nmi_timer_int.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * @remark Copyright 2011 Advanced Micro Devices, Inc.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * @author Robert Richter <robert.richter@amd.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/smp.h>
128c2ecf20Sopenharmony_ci#include <linux/errno.h>
138c2ecf20Sopenharmony_ci#include <linux/oprofile.h>
148c2ecf20Sopenharmony_ci#include <linux/perf_event.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#ifdef CONFIG_OPROFILE_NMI_TIMER
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic DEFINE_PER_CPU(struct perf_event *, nmi_timer_events);
198c2ecf20Sopenharmony_cistatic int ctr_running;
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic struct perf_event_attr nmi_timer_attr = {
228c2ecf20Sopenharmony_ci	.type           = PERF_TYPE_HARDWARE,
238c2ecf20Sopenharmony_ci	.config         = PERF_COUNT_HW_CPU_CYCLES,
248c2ecf20Sopenharmony_ci	.size           = sizeof(struct perf_event_attr),
258c2ecf20Sopenharmony_ci	.pinned         = 1,
268c2ecf20Sopenharmony_ci	.disabled       = 1,
278c2ecf20Sopenharmony_ci};
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic void nmi_timer_callback(struct perf_event *event,
308c2ecf20Sopenharmony_ci			       struct perf_sample_data *data,
318c2ecf20Sopenharmony_ci			       struct pt_regs *regs)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	event->hw.interrupts = 0;       /* don't throttle interrupts */
348c2ecf20Sopenharmony_ci	oprofile_add_sample(regs, 0);
358c2ecf20Sopenharmony_ci}
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic int nmi_timer_start_cpu(int cpu)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	struct perf_event *event = per_cpu(nmi_timer_events, cpu);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	if (!event) {
428c2ecf20Sopenharmony_ci		event = perf_event_create_kernel_counter(&nmi_timer_attr, cpu, NULL,
438c2ecf20Sopenharmony_ci							 nmi_timer_callback, NULL);
448c2ecf20Sopenharmony_ci		if (IS_ERR(event))
458c2ecf20Sopenharmony_ci			return PTR_ERR(event);
468c2ecf20Sopenharmony_ci		per_cpu(nmi_timer_events, cpu) = event;
478c2ecf20Sopenharmony_ci	}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	if (event && ctr_running)
508c2ecf20Sopenharmony_ci		perf_event_enable(event);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	return 0;
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic void nmi_timer_stop_cpu(int cpu)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	struct perf_event *event = per_cpu(nmi_timer_events, cpu);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	if (event && ctr_running)
608c2ecf20Sopenharmony_ci		perf_event_disable(event);
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic int nmi_timer_cpu_online(unsigned int cpu)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	nmi_timer_start_cpu(cpu);
668c2ecf20Sopenharmony_ci	return 0;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_cistatic int nmi_timer_cpu_predown(unsigned int cpu)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	nmi_timer_stop_cpu(cpu);
718c2ecf20Sopenharmony_ci	return 0;
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic int nmi_timer_start(void)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	int cpu;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	get_online_cpus();
798c2ecf20Sopenharmony_ci	ctr_running = 1;
808c2ecf20Sopenharmony_ci	for_each_online_cpu(cpu)
818c2ecf20Sopenharmony_ci		nmi_timer_start_cpu(cpu);
828c2ecf20Sopenharmony_ci	put_online_cpus();
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	return 0;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic void nmi_timer_stop(void)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	int cpu;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	get_online_cpus();
928c2ecf20Sopenharmony_ci	for_each_online_cpu(cpu)
938c2ecf20Sopenharmony_ci		nmi_timer_stop_cpu(cpu);
948c2ecf20Sopenharmony_ci	ctr_running = 0;
958c2ecf20Sopenharmony_ci	put_online_cpus();
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic enum cpuhp_state hp_online;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic void nmi_timer_shutdown(void)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	struct perf_event *event;
1038c2ecf20Sopenharmony_ci	int cpu;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	cpuhp_remove_state(hp_online);
1068c2ecf20Sopenharmony_ci	for_each_possible_cpu(cpu) {
1078c2ecf20Sopenharmony_ci		event = per_cpu(nmi_timer_events, cpu);
1088c2ecf20Sopenharmony_ci		if (!event)
1098c2ecf20Sopenharmony_ci			continue;
1108c2ecf20Sopenharmony_ci		perf_event_disable(event);
1118c2ecf20Sopenharmony_ci		per_cpu(nmi_timer_events, cpu) = NULL;
1128c2ecf20Sopenharmony_ci		perf_event_release_kernel(event);
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic int nmi_timer_setup(void)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	int err;
1198c2ecf20Sopenharmony_ci	u64 period;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	/* clock cycles per tick: */
1228c2ecf20Sopenharmony_ci	period = (u64)cpu_khz * 1000;
1238c2ecf20Sopenharmony_ci	do_div(period, HZ);
1248c2ecf20Sopenharmony_ci	nmi_timer_attr.sample_period = period;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "oprofile/nmi:online",
1278c2ecf20Sopenharmony_ci				nmi_timer_cpu_online, nmi_timer_cpu_predown);
1288c2ecf20Sopenharmony_ci	if (err < 0) {
1298c2ecf20Sopenharmony_ci		nmi_timer_shutdown();
1308c2ecf20Sopenharmony_ci		return err;
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci	hp_online = err;
1338c2ecf20Sopenharmony_ci	return 0;
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ciint __init op_nmi_timer_init(struct oprofile_operations *ops)
1378c2ecf20Sopenharmony_ci{
1388c2ecf20Sopenharmony_ci	int err = 0;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	err = nmi_timer_setup();
1418c2ecf20Sopenharmony_ci	if (err)
1428c2ecf20Sopenharmony_ci		return err;
1438c2ecf20Sopenharmony_ci	nmi_timer_shutdown();		/* only check, don't alloc */
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	ops->create_files	= NULL;
1468c2ecf20Sopenharmony_ci	ops->setup		= nmi_timer_setup;
1478c2ecf20Sopenharmony_ci	ops->shutdown		= nmi_timer_shutdown;
1488c2ecf20Sopenharmony_ci	ops->start		= nmi_timer_start;
1498c2ecf20Sopenharmony_ci	ops->stop		= nmi_timer_stop;
1508c2ecf20Sopenharmony_ci	ops->cpu_type		= "timer";
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	printk(KERN_INFO "oprofile: using NMI timer interrupt.\n");
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	return 0;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci#endif
158