| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Copyright (c) 2019 MediaTek Inc. | 
 |  */ | 
 |  | 
 | /* include <asm/percpu.h> */ | 
 | #include <trace/events/sched.h> | 
 | #include <linux/module.h> | 
 | #include <trace/events/irq.h> | 
 | #include <trace/events/power.h> | 
 |  | 
 | #include "interface.h" | 
 | #include "met_drv.h" | 
 | #include "cpu_pmu.h" | 
 | #include "switch.h" | 
 | #include "sampler.h" | 
 | #include "met_kernel_symbol.h" | 
 | /* #include "trace.h" */ | 
 |  | 
 | /* | 
 |  * IRQ_TIRGGER and CPU_IDLE_TRIGGER | 
 |  */ | 
 | /* #define IRQ_TRIGGER */ | 
 | /* #define CPU_IDLE_TRIGGER */ | 
 |  | 
 | static DEFINE_PER_CPU(unsigned int, first_log); | 
 |  | 
 | #ifdef __aarch64__ | 
 | /* #include <asm/compat.h> */ | 
 | #include <linux/compat.h> | 
 | #endif | 
 |  | 
 | noinline void mt_switch(struct task_struct *prev, struct task_struct *next) | 
 | { | 
 | 	int cpu; | 
 | 	int prev_state = 0, next_state = 0; | 
 |  | 
 | #ifdef __aarch64__ | 
 | 	prev_state = !(is_compat_thread(task_thread_info(prev))); | 
 | 	next_state = !(is_compat_thread(task_thread_info(next))); | 
 | #endif | 
 |  | 
 | 	cpu = smp_processor_id(); | 
 | 	if (per_cpu(first_log, cpu)) { | 
 | 		MET_TRACE("%d, %d, %d, %d\n", prev->pid, prev_state, next->pid, next_state); | 
 | 		per_cpu(first_log, cpu) = 0; | 
 | 	} | 
 | 	else if (prev_state != next_state) | 
 | 		MET_TRACE("%d, %d, %d, %d\n", prev->pid, prev_state, next->pid, next_state); | 
 | } | 
 |  | 
 |  | 
 | #if 0 /* move to kernel space */ | 
 | MET_DEFINE_PROBE(sched_switch, | 
 | 		 TP_PROTO(bool preempt, struct task_struct *prev, struct task_struct *next)) | 
 | { | 
 | 	/* speedup sched_switch callback handle */ | 
 | 	if (met_switch.mode == 0) | 
 | 		return; | 
 |  | 
 | 	if (met_switch.mode & MT_SWITCH_EVENT_TIMER) | 
 | 		met_event_timer_notify(); | 
 |  | 
 | 	if (met_switch.mode & MT_SWITCH_64_32BIT) | 
 | 		mt_switch(prev, next); | 
 |  | 
 | 	if (met_switch.mode & MT_SWITCH_SCHEDSWITCH) { | 
 | 		if (get_pmu_profiling_version() == 1) | 
 | 			cpupmu_polling(0, smp_processor_id()); | 
 | #ifdef MET_SUPPORT_CPUPMU_V2 | 
 | 		else if (get_pmu_profiling_version() == 2) | 
 | 			cpupmu_polling_v2(0, smp_processor_id()); | 
 | #endif | 
 | 	} | 
 | } | 
 | #endif | 
 |  | 
 | void met_sched_switch(struct task_struct *prev, struct task_struct *next) | 
 | { | 
 | 	/* speedup sched_switch callback handle */ | 
 | 	if (met_switch.mode == 0) | 
 | 		return; | 
 |  | 
 | 	if (met_switch.mode & MT_SWITCH_EVENT_TIMER) | 
 | 		met_event_timer_notify(); | 
 |  | 
 | 	if (met_switch.mode & MT_SWITCH_64_32BIT) | 
 | 		mt_switch(prev, next); | 
 |  | 
 | 	/* met_perf_cpupmu_status: 0: stop, others: polling */ | 
 | 	if ((met_switch.mode & MT_SWITCH_SCHEDSWITCH) && met_perf_cpupmu_status) | 
 | 		met_perf_cpupmu_polling(0, smp_processor_id()); | 
 | } | 
 |  | 
 | #ifdef IRQ_TRIGGER | 
 | MET_DEFINE_PROBE(irq_handler_entry, TP_PROTO(int irq, struct irqaction *action)) | 
 | { | 
 | 	if (met_switch.mode & MT_SWITCH_EVENT_TIMER) { | 
 | 		met_event_timer_notify(); | 
 | 		return; | 
 | 	} | 
 | } | 
 | #endif | 
 |  | 
 | #ifdef CPU_IDLE_TRIGGER | 
 | MET_DEFINE_PROBE(cpu_idle, TP_PROTO(unsigned int state, unsigned int cpu_id)) | 
 | { | 
 | 	if (met_switch.mode & MT_SWITCH_EVENT_TIMER) { | 
 | 		met_event_timer_notify(); | 
 | 		return; | 
 | 	} | 
 | } | 
 | #endif | 
 |  | 
 | #ifdef MET_ANYTIME | 
 | /* | 
 |  * create related subfs file node | 
 |  */ | 
 |  | 
 | static ssize_t default_on_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) | 
 | { | 
 | 	return snprintf(buf, PAGE_SIZE, "1\n"); | 
 | } | 
 |  | 
 | static struct kobj_attribute default_on_attr = __ATTR(default_on, 0664, default_on_show, NULL); | 
 | static struct kobject *kobj_cpu; | 
 | #endif | 
 |  | 
 | static int met_switch_create_subfs(struct kobject *parent) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	/* register tracepoints */ | 
 | #if 0 | 
 | 	if (MET_REGISTER_TRACE(sched_switch)) { | 
 | 		pr_debug("can not register callback of sched_switch\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 | #else | 
 | 	if (met_export_api_symbol->met_reg_switch) | 
 | 		ret = met_export_api_symbol->met_reg_switch(); | 
 | #endif | 
 | #ifdef CPU_IDLE_TRIGGER | 
 | 	if (MET_REGISTER_TRACE(cpu_idle)) { | 
 | 		pr_debug("can not register callback of irq_handler_entry\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 | #endif | 
 | #ifdef IRQ_TRIGGER | 
 | 	if (MET_REGISTER_TRACE(irq_handler_entry)) { | 
 | 		pr_debug("can not register callback of irq_handler_entry\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 | #endif | 
 |  | 
 | #ifdef MET_ANYTIME | 
 | 	/* | 
 | 	 * to create default_on file node | 
 | 	 * let user space can know we can support MET default on | 
 | 	 */ | 
 | 	kobj_cpu = parent; | 
 | 	ret = sysfs_create_file(kobj_cpu, &default_on_attr.attr); | 
 | 	if (ret != 0) { | 
 | 		pr_debug("Failed to create default_on in sysfs\n"); | 
 | 		return -1; | 
 | 	} | 
 | #endif | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 |  | 
 | static void met_switch_delete_subfs(void) | 
 | { | 
 | #ifdef MET_ANYTIME | 
 | 	if (kobj_cpu != NULL) { | 
 | 		sysfs_remove_file(kobj_cpu, &default_on_attr.attr); | 
 | 		kobj_cpu = NULL; | 
 | 	} | 
 | #endif | 
 | #ifdef IRQ_TRIGGER | 
 | 	MET_UNREGISTER_TRACE(irq_handler_entry); | 
 | #endif | 
 | #ifdef CPU_IDLE_TRIGGER | 
 | 	MET_UNREGISTER_TRACE(cpu_idle); | 
 | #endif | 
 | #if 0 | 
 | 	MET_UNREGISTER_TRACE(sched_switch); | 
 | #else | 
 | 	if (met_export_api_symbol->met_unreg_switch) | 
 | 		met_export_api_symbol->met_unreg_switch(); | 
 | #endif | 
 |  | 
 | } | 
 |  | 
 |  | 
 | static void (*cpu_timed_polling)(unsigned long long stamp, int cpu); | 
 | /* static void (*cpu_tagged_polling)(unsigned long long stamp, int cpu); */ | 
 |  | 
 | static void met_switch_start(void) | 
 | { | 
 | 	int cpu; | 
 |  | 
 | 	if (met_switch.mode & MT_SWITCH_SCHEDSWITCH) { | 
 | 		cpu_timed_polling = met_cpupmu.timed_polling; | 
 | 		/* cpu_tagged_polling = met_cpupmu.tagged_polling; */ | 
 | 		met_cpupmu.timed_polling = NULL; | 
 | 		/* met_cpupmu.tagged_polling = NULL; */ | 
 | 	} | 
 |  | 
 | 	for_each_possible_cpu(cpu) { | 
 | 		per_cpu(first_log, cpu) = 1; | 
 | 	} | 
 |  | 
 | } | 
 |  | 
 |  | 
 | static void met_switch_stop(void) | 
 | { | 
 | 	int cpu; | 
 |  | 
 | 	if (met_switch.mode & MT_SWITCH_SCHEDSWITCH) { | 
 | 		met_cpupmu.timed_polling = cpu_timed_polling; | 
 | 		/* met_cpupmu.tagged_polling = cpu_tagged_polling; */ | 
 | 	} | 
 |  | 
 | 	for_each_possible_cpu(cpu) { | 
 | 		per_cpu(first_log, cpu) = 0; | 
 | 	} | 
 |  | 
 | } | 
 |  | 
 |  | 
 | static int met_switch_process_argument(const char *arg, int len) | 
 | { | 
 | 	unsigned int value = 0; | 
 | 	/*ex: mxitem is 0x0005, max value should be (5-1) + (5-2) = 0x100 + 0x11 = 7 */ | 
 | 	unsigned int max_value = ((MT_SWITCH_MX_ITEM * 2) - 3); | 
 |  | 
 |  | 
 | 	if (met_parse_num(arg, &value, len) < 0) | 
 | 		goto arg_switch_exit; | 
 |  | 
 | 	if ((value < 1) || (value > max_value)) | 
 | 		goto arg_switch_exit; | 
 |  | 
 | 	met_switch.mode = value; | 
 | 	return 0; | 
 |  | 
 | arg_switch_exit: | 
 | 	met_switch.mode = 0; | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static const char header[] = | 
 | 	"met-info [000] 0.0: met_switch_header: prev_pid,prev_state,next_pid,next_state\n"; | 
 |  | 
 | static const char help[] = | 
 | "  --switch=mode                         mode:0x1 - output CPUPMU whenever sched_switch\n" | 
 | "                                        mode:0x2 - output Aarch 32/64 state whenever state changed (no CPUPMU)\n" | 
 | "                                        mode:0x4 - force output count at tag_start/tag_end\n" | 
 | "                                        mode:0x8 - task switch timer\n" | 
 | "                                        mode:0xF - mode 0x1 + 0x2 + 04 + 08\n"; | 
 |  | 
 | static int met_switch_print_help(char *buf, int len) | 
 | { | 
 | 	return snprintf(buf, PAGE_SIZE, help); | 
 | } | 
 |  | 
 | static int met_switch_print_header(char *buf, int len) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	ret = | 
 | 	    snprintf(buf, PAGE_SIZE, "met-info [000] 0.0: mp_cpu_switch_base: %d\n", | 
 | 		     met_switch.mode); | 
 | 	if (met_switch.mode & MT_SWITCH_64_32BIT) | 
 | 		ret += snprintf(buf + ret, PAGE_SIZE, header); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 |  | 
 | struct metdevice met_switch = { | 
 | 	.name = "switch", | 
 | 	.type = MET_TYPE_PMU, | 
 | 	.create_subfs = met_switch_create_subfs, | 
 | 	.delete_subfs = met_switch_delete_subfs, | 
 | 	.start = met_switch_start, | 
 | 	.stop = met_switch_stop, | 
 | 	.process_argument = met_switch_process_argument, | 
 | 	.print_help = met_switch_print_help, | 
 | 	.print_header = met_switch_print_header, | 
 | }; |