// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Samsung debugging code */ #include #include #include #include #include #include #include #include "../../../kernel/sched/sched.h" #include "../../soc/samsung/debug/debug-snapshot-local.h" #include "sec_debug_internal.h" #include #define PRINT_LINE_MAX 512 static void secdbg_scin_show_sched_info(unsigned int cpu, int count) { unsigned long long task_idx; ssize_t offset = 0; int max_count = dss_get_len_task_log_by_cpu(0); char buf[PRINT_LINE_MAX]; struct dbg_snapshot_log_item *log_item = dbg_snapshot_log_get_item_by_index(DSS_LOG_TASK_ID); struct task_log *task; if (!log_item->entry.enabled) return; if (cpu < 0 || cpu >= DSS_NR_CPUS) { pr_warn("%s: invalid cpu %d\n", __func__, cpu); return; } if (count > max_count) count = max_count; offset += scnprintf(buf + offset, PRINT_LINE_MAX - offset, "Sched info "); task_idx = dss_get_last_task_log_idx(cpu); for_each_item_in_dss_by_cpu(task, cpu, task_idx, count, false) { if (task->time == 0) break; offset += scnprintf(buf + offset, PRINT_LINE_MAX - offset, "[%d]<", task->pid); } pr_auto(ASL5, "%s\n", buf); } struct busy_info { struct list_head node; unsigned long long residency; struct task_struct *tsk; pid_t pid; }; static LIST_HEAD(busy_info_list); static int is_busy; static bool is_busy_info_list; static unsigned long long real_duration; static struct list_head *__create_busy_info(struct task_struct *tsk, unsigned long long residency) { struct busy_info *info; info = kzalloc(sizeof(struct busy_info), GFP_ATOMIC); if (!info) return NULL; info->pid = tsk->pid; if (info->pid == 0) is_busy = 0; info->tsk = tsk; info->residency = residency; return &info->node; } #ifdef SEC_DEBUG_LISTSORT_CONST static int __residency_cmp(void *priv, const struct list_head *a, const struct list_head *b) #else static int __residency_cmp(void *priv, struct list_head *a, struct list_head *b) #endif { struct busy_info *busy_info_a; struct busy_info *busy_info_b; busy_info_a = container_of(a, struct busy_info, node); busy_info_b = container_of(b, struct busy_info, node); if (busy_info_a->residency < busy_info_b->residency) return 1; else if (busy_info_a->residency > busy_info_b->residency) return -1; else return 0; } static void show_callstack(void *info) { secdbg_stra_show_callstack_auto(NULL); } static unsigned long long secdbg_scin_make_busy_task_list(unsigned int cpu, unsigned long long duration) { unsigned long long residency; unsigned long long limit_time = local_clock() - duration * NSEC_PER_SEC; struct list_head *entry; int count = dss_get_len_task_log_by_cpu(0) - 1; unsigned long task_idx = dss_get_last_task_log_idx(cpu); struct busy_info *info; struct task_log *tl; struct task_log *tl_next; struct task_log *task; is_busy_info_list = true; is_busy = 1; tl = dss_get_task_log_by_idx(cpu, task_idx); if (!tl || tl->time == 0) return 0; residency = local_clock() - tl->time; real_duration += residency; entry = __create_busy_info(tl->task, residency); if (!entry) return 0; list_add(entry, &busy_info_list); tl_next = tl; task_idx = task_idx > 0 ? (task_idx - 1) : count; for_each_item_in_dss_by_cpu(task, cpu, task_idx, count, false) { if (task->time == 0 || (task->time > tl_next->time) || (task->time < limit_time)) { break; } residency = tl_next->time - task->time; real_duration += residency; list_for_each_entry(info, &busy_info_list, node) { if (info->pid == task->pid) { info->residency += residency; goto next; } } entry = __create_busy_info(task->task, residency); if (!entry) return real_duration - residency; list_add(entry, &busy_info_list); next: tl_next = task; } return real_duration; } enum sched_class_type { SECDBG_SCHED_NONE, SECDBG_SCHED_IDLE, SECDBG_SCHED_FAIR, SECDBG_SCHED_RT, SECDBG_SCHED_DL, SECDBG_SCHED_STOP, SECDBG_SCHED_MAX }; struct sched_class_info { int type; const struct sched_class *sclass; const char *symname; }; struct sched_class_info sched_class_array[SECDBG_SCHED_MAX] = { [SECDBG_SCHED_IDLE] = {SECDBG_SCHED_IDLE, NULL, "idle_sched_class"}, [SECDBG_SCHED_FAIR] = {SECDBG_SCHED_FAIR, NULL, "fair_sched_class"}, [SECDBG_SCHED_RT] = {SECDBG_SCHED_RT, NULL, "rt_sched_class"}, [SECDBG_SCHED_DL] = {SECDBG_SCHED_DL, NULL, "dl_sched_class"}, [SECDBG_SCHED_STOP] = {SECDBG_SCHED_STOP, NULL, "stop_sched_class"}, }; static int get_current_sclass(void) { int i; char sym[KSYM_SYMBOL_LEN] = {0,}; snprintf(sym, KSYM_SYMBOL_LEN, "%ps", current->sched_class); for (i = SECDBG_SCHED_IDLE; i < SECDBG_SCHED_MAX; i++) { if (!strncmp(sched_class_array[i].symname, sym, KSYM_SYMBOL_LEN)) return i; } return SECDBG_SCHED_NONE; } static const struct sched_class *__get_sclass_address(int type, int base_type, const struct sched_class *base_address) { return base_address + (type - base_type); } static void calculate_address_sclass(int base_type, const struct sched_class *base_address) { int i; for (i = SECDBG_SCHED_IDLE; i < SECDBG_SCHED_MAX; i++) sched_class_array[i].sclass = __get_sclass_address(i, base_type, base_address); } static void initialize_sched_class_array(void) { int type = get_current_sclass(); pr_info("%s: current type:%d\n", __func__, type); if (type != SECDBG_SCHED_NONE) calculate_address_sclass(type, current->sched_class); } static enum sched_class_type get_sched_class(struct task_struct *tsk) { const struct sched_class *class = tsk->sched_class; int type = SECDBG_SCHED_NONE; for (type = SECDBG_SCHED_IDLE; type < SECDBG_SCHED_MAX; type++) { if (class == sched_class_array[type].sclass) return type; } return SECDBG_SCHED_NONE; } static int secdbg_scin_show_busy_task(unsigned int cpu, unsigned long long duration, int count) { unsigned long long real_duration; struct busy_info *info; struct task_struct *main_busy_tsk; ssize_t offset = 0; char buf[PRINT_LINE_MAX]; struct dbg_snapshot_log_item *log_item = dbg_snapshot_log_get_item_by_index(DSS_LOG_TASK_ID); char sched_class_array[] = {'0', 'I', 'F', 'R', 'D', 'S'}; if (is_busy_info_list) return -1; if (cpu < 0 || cpu >= DSS_NR_CPUS) { pr_warn("%s: invalid cpu %d\n", __func__, cpu); return -EINVAL; } /* This needs runqueues with EXPORT_SYMBOL */ pr_auto(ASL5, "CPU%u [CFS util_avg:%lu load_avg:%lu nr_running:%u][RT rt_nr_running:%u][avg_rt util_avg:%lu]\n", cpu, cpu_rq(cpu)->cfs.avg.util_avg, cpu_rq(cpu)->cfs.avg.load_avg, cpu_rq(cpu)->cfs.nr_running, cpu_rq(cpu)->rt.rt_nr_running, cpu_rq(cpu)->avg_rt.util_avg); if (!log_item->entry.enabled) return -1; real_duration = secdbg_scin_make_busy_task_list(cpu, duration); list_sort(NULL, &busy_info_list, __residency_cmp); if (list_empty(&busy_info_list)) return -1; offset += scnprintf(buf + offset, PRINT_LINE_MAX - offset, "CPU Usage: PERIOD(%us)", (unsigned int)(real_duration / NSEC_PER_SEC)); list_for_each_entry(info, &busy_info_list, node) { offset += scnprintf(buf + offset, PRINT_LINE_MAX - offset, " %s:%d[%c,%d](%u%%)", info->tsk->comm, info->tsk->pid, sched_class_array[get_sched_class(info->tsk)], info->tsk->prio, real_duration > 0 ? (unsigned int)((info->residency * 100) / real_duration) : 0); if (--count == 0) break; } pr_auto(ASL5, "%s\n", buf); info = list_first_entry(&busy_info_list, struct busy_info, node); main_busy_tsk = info->tsk; if (!is_busy) { pr_auto(ASL5, "CPU%u is not busy.", cpu); } if (main_busy_tsk->on_cpu && (main_busy_tsk->cpu == cpu)) { smp_call_function_single(cpu, show_callstack, NULL, 1); } else { secdbg_stra_show_callstack_auto(main_busy_tsk); #if IS_ENABLED(CONFIG_SEC_DEBUG_SHOW_USER_STACK) secdbg_send_sig_debuggerd(main_busy_tsk, 2); #endif } return is_busy; } static void secdbg_wqlockup_info_handler(void *ignore, int cpu, unsigned long pool_ts) { pr_info("%s\n", __func__); if (cpu < 0) return; secdbg_scin_show_sched_info(cpu, 10); secdbg_scin_show_busy_task(cpu, jiffies_to_msecs(jiffies - pool_ts) / 1000, 5); } static int __init secdbg_scin_init(void) { pr_info("%s: init\n", __func__); initialize_sched_class_array(); if (IS_ENABLED(CONFIG_SEC_DEBUG_WQ_LOCKUP_INFO)) register_trace_android_vh_wq_lockup_pool(secdbg_wqlockup_info_handler, NULL); return 0; } module_init(secdbg_scin_init); static void __exit secdbg_scin_exit(void) { if (IS_ENABLED(CONFIG_SEC_DEBUG_WQ_LOCKUP_INFO)) unregister_trace_android_vh_wq_lockup_pool(secdbg_wqlockup_info_handler, NULL); } module_exit(secdbg_scin_exit); MODULE_DESCRIPTION("Samsung Debug Sched info driver"); MODULE_LICENSE("GPL v2");