kernel_samsung_a53x/drivers/samsung/debug/sec_debug_sched_info.c
2024-06-15 16:02:09 -03:00

345 lines
8.7 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung debugging code
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/list_sort.h>
#include <linux/sec_debug.h>
#include <soc/samsung/debug-snapshot.h>
#include <soc/samsung/debug-snapshot-log.h>
#include "../../../kernel/sched/sched.h"
#include "../../soc/samsung/debug/debug-snapshot-local.h"
#include "sec_debug_internal.h"
#include <trace/hooks/wqlockup.h>
#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");