soc: qcom: Add minidump collection on panic

Add minidump collection for cpu runqueues, on panic.

Change-Id: Ib9d313c25cb53547fff075587b3fdd9297a74756
Signed-off-by: Neeraj Upadhyay <neeraju@codeaurora.org>
This commit is contained in:
Neeraj Upadhyay 2020-07-05 04:16:25 +05:30 • committed by Gerrit - the friendly Code Review server
commit f8a6a88e57
2 changed files with 262 additions and 0 deletions

View file

@ -495,6 +495,13 @@ config QCOM_MINIDUMP_FTRACE
This allows dumping ftrace buffer content as part of
minidump dumps.
config QCOM_MINIDUMP_PANIC_DUMP
bool "QCOM Minidump Panic dumps Support"
depends on QGKI && QCOM_MINIDUMP
help
This enables various dumps collection in minidump table,
on panic.
config MINIDUMP_MAX_ENTRIES
int "Minidump Maximum num of entries"
default 200

View file

@ -5,10 +5,13 @@
#include <linux/cache.h>
#include <linux/freezer.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kallsyms.h>
#include <linux/rbtree.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/thread_info.h>
#include <soc/qcom/minidump.h>
@ -18,12 +21,23 @@
#include <asm/stacktrace.h>
#include <linux/mm.h>
#include <linux/ratelimit.h>
#include <linux/notifier.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/sched/task.h>
#include <linux/suspend.h>
#include <linux/vmalloc.h>
#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP
#include <linux/bits.h>
#include <linux/sched/prio.h>
#include <linux/seq_buf.h>
#include <asm/memory.h>
#include "../../../kernel/sched/sched.h"
#endif
#ifdef CONFIG_QCOM_DYN_MINIDUMP_STACK
#include <trace/events/sched.h>
@ -63,6 +77,14 @@ static char *md_ftrace_buf_addr;
static size_t md_ftrace_buf_current;
#endif
#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP
#define MD_RUNQUEUE_PAGES 8
static bool md_in_oops_handler;
static struct seq_buf *md_runq_seq_buf;
static md_align_offset;
#endif
static void __init register_log_buf(void)
{
char **log_bufp;
@ -537,6 +559,235 @@ static void md_register_trace_buf(void)
}
#endif
#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP
static void md_dump_align(void)
{
int tab_offset = md_align_offset;
while (tab_offset--)
seq_buf_printf(md_runq_seq_buf, " | ");
seq_buf_printf(md_runq_seq_buf, " |--");
}
static void md_dump_task_info(struct task_struct *task, char *status,
struct task_struct *curr)
{
struct sched_entity *se;
md_dump_align();
if (!task) {
seq_buf_printf(md_runq_seq_buf, "%s : None(0)\n", status);
return;
}
se = &task->se;
if (task == curr) {
seq_buf_printf(md_runq_seq_buf,
"[status: curr] pid: %d comm: %s preempt: %#x\n",
task_pid_nr(task), task->comm,
task->thread_info.preempt_count);
return;
}
seq_buf_printf(md_runq_seq_buf,
"[status: %s] pid: %d tsk: %#lx comm: %s stack: %#lx",
status, task_pid_nr(task),
(unsigned long)task,
task->comm,
(unsigned long)task->stack);
seq_buf_printf(md_runq_seq_buf,
" prio: %d aff: %*pb",
task->prio, cpumask_pr_args(&task->cpus_mask));
#ifdef CONFIG_SCHED_WALT
seq_buf_printf(md_runq_seq_buf, " enq: %lu wake: %lu sleep: %lu",
task->wts.last_enqueued_ts, task->wts.last_wake_ts,
task->wts.last_sleep_ts);
#endif
seq_buf_printf(md_runq_seq_buf,
" vrun: %lu arr: %lu sum_ex: %lu\n",
(unsigned long)se->vruntime,
(unsigned long)se->exec_start,
(unsigned long)se->sum_exec_runtime);
}
static void md_dump_cfs_rq(struct cfs_rq *cfs, struct task_struct *curr);
static void md_dump_cgroup_state(char *status, struct sched_entity *se_p,
struct task_struct *curr)
{
struct task_struct *task;
struct cfs_rq *my_q = NULL;
unsigned int nr_running;
if (!se_p) {
md_dump_task_info(NULL, status, NULL);
return;
}
#ifdef CONFIG_FAIR_GROUP_SCHED
my_q = se_p->my_q;
#endif
if (!my_q) {
task = container_of(se_p, struct task_struct, se);
md_dump_task_info(task, status, curr);
return;
}
nr_running = my_q->nr_running;
md_dump_align();
seq_buf_printf(md_runq_seq_buf, "%s: %d process is grouping\n",
status, nr_running);
md_align_offset++;
md_dump_cfs_rq(my_q, curr);
md_align_offset--;
}
static void md_dump_cfs_node_func(struct rb_node *node,
struct task_struct *curr)
{
struct sched_entity *se_p = container_of(node, struct sched_entity,
run_node);
md_dump_cgroup_state("pend", se_p, curr);
}
static void md_rb_walk_cfs(struct rb_root_cached *rb_root_cached_p,
struct task_struct *curr)
{
int max_walk = 200; /* Bail out, in case of loop */
struct rb_node *leftmost = rb_root_cached_p->rb_leftmost;
struct rb_root *root = &rb_root_cached_p->rb_root;
struct rb_node *rb_node = rb_first(root);
if (!leftmost)
return;
while (rb_node && max_walk--) {
md_dump_cfs_node_func(rb_node, curr);
rb_node = rb_next(rb_node);
}
}
static void md_dump_cfs_rq(struct cfs_rq *cfs, struct task_struct *curr)
{
struct rb_root_cached *rb_root_cached_p = &cfs->tasks_timeline;
md_dump_cgroup_state("curr", cfs->curr, curr);
md_dump_cgroup_state("next", cfs->next, curr);
md_dump_cgroup_state("last", cfs->last, curr);
md_dump_cgroup_state("skip", cfs->skip, curr);
md_rb_walk_cfs(rb_root_cached_p, curr);
}
static void md_dump_rt_rq(struct rt_rq *rt_rq, struct task_struct *curr)
{
struct rt_prio_array *array = &rt_rq->active;
struct sched_rt_entity *rt_se;
int idx;
/* Lifted most of the below code from dump_throttled_rt_tasks() */
if (bitmap_empty(array->bitmap, MAX_RT_PRIO))
return;
idx = sched_find_first_bit(array->bitmap);
while (idx < MAX_RT_PRIO) {
list_for_each_entry(rt_se, array->queue + idx, run_list) {
struct task_struct *p;
#ifdef CONFIG_RT_GROUP_SCHED
if (rt_se->my_q)
continue;
#endif
p = container_of(rt_se, struct task_struct, rt);
md_dump_task_info(p, "pend", curr);
}
idx = find_next_bit(array->bitmap, MAX_RT_PRIO, idx + 1);
}
}
static void md_dump_runqueues(void)
{
int cpu;
struct rq *rq;
struct rt_rq *rt;
struct cfs_rq *cfs;
if (!md_runq_seq_buf)
return;
for_each_possible_cpu(cpu) {
rq = cpu_rq(cpu);
rt = &rq->rt;
cfs = &rq->cfs;
seq_buf_printf(md_runq_seq_buf,
"CPU%d %d process is running\n",
cpu, rq->nr_running);
md_dump_task_info(cpu_curr(cpu), "curr", NULL);
seq_buf_printf(md_runq_seq_buf,
"CFS %d process is pending\n",
cfs->nr_running);
md_dump_cfs_rq(cfs, cpu_curr(cpu));
seq_buf_printf(md_runq_seq_buf,
"RT %d process is pending\n",
rt->rt_nr_running);
md_dump_rt_rq(rt, cpu_curr(cpu));
seq_buf_printf(md_runq_seq_buf, "\n");
}
}
static int md_panic_handler(struct notifier_block *this,
unsigned long event, void *ptr)
{
if (md_in_oops_handler)
return NOTIFY_DONE;
md_in_oops_handler = true;
md_dump_runqueues();
md_in_oops_handler = false;
return NOTIFY_DONE;
}
static struct notifier_block md_panic_blk = {
.notifier_call = md_panic_handler,
.priority = INT_MAX,
};
void md_register_panic_entries(void)
{
char *runq_buf;
struct md_region md_entry;
struct seq_buf *runq_seq_buf;
runq_buf = kzalloc(MD_RUNQUEUE_PAGES * PAGE_SIZE, GFP_KERNEL);
if (!runq_buf)
return;
runq_seq_buf = kzalloc(sizeof(*runq_seq_buf), GFP_KERNEL);
if (!runq_seq_buf)
goto err_seq_buf;
strlcpy(md_entry.name, "KRUNQUEUE", sizeof(md_entry.name));
md_entry.virt_addr = (uintptr_t)runq_buf;
md_entry.phys_addr = virt_to_phys(runq_buf);
md_entry.size = MD_RUNQUEUE_PAGES * PAGE_SIZE;
if (msm_minidump_add_region(&md_entry) < 0) {
pr_err("Failed to add runq buffer entry in Minidump\n");
goto err_runq_reg;
}
seq_buf_init(runq_seq_buf, runq_buf, MD_RUNQUEUE_PAGES * PAGE_SIZE);
/* Complete registration before populating data */
smp_mb();
WRITE_ONCE(md_runq_seq_buf, runq_seq_buf);
return;
err_runq_reg:
kfree(runq_seq_buf);
err_seq_buf:
kfree(runq_buf);
}
#endif
static int __init msm_minidump_log_init(void)
{
register_kernel_sections();
@ -549,6 +800,10 @@ static int __init msm_minidump_log_init(void)
register_log_buf();
#ifdef CONFIG_QCOM_MINIDUMP_FTRACE
md_register_trace_buf();
#endif
#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP
md_register_panic_entries();
atomic_notifier_chain_register(&panic_notifier_list, &md_panic_blk);
#endif
return 0;
}