mirror of
https://github.com/physwizz/a155-U-u1.git
synced 2024-11-19 13:27:49 +00:00
1565 lines
38 KiB
C
1565 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2015 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/cdev.h>
|
|
#include <linux/debug_locks.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/init.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pid.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/mm.h>
|
|
#include <linux/sched/rt.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/sched/task.h>
|
|
#include <linux/semaphore.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/workqueue.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/traps.h>
|
|
#include <uapi/linux/sched/types.h>
|
|
|
|
#include <mt-plat/aee.h>
|
|
#if IS_ENABLED(CONFIG_MTK_AEE_IPANIC)
|
|
#include <mt-plat/mboot_params.h>
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_MTK_BOOT)
|
|
#include <mt-plat/mtk_boot_common.h>
|
|
#endif
|
|
#include <mt-plat/mrdump.h>
|
|
|
|
#include "aed/aed.h"
|
|
#include "hang_detect.h"
|
|
#include "hang_unwind.h"
|
|
#include "mrdump/mrdump_private.h"
|
|
#include "mrdump/mrdump_mini.h"
|
|
|
|
#ifndef TASK_STATE_TO_CHAR_STR
|
|
#define TASK_STATE_TO_CHAR_STR "RSDTtZXxKWPNn"
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
#define MAX_HANG_INFO_SIZE (2*1024*1024) /* 2M info */
|
|
#define MAX_STRING_SIZE 256
|
|
#define MEM_BUFFER_DEFAULT_SIZE (3*1024)
|
|
#define MSDC_BUFFER_DEFAULT_SIZE (30*1024)
|
|
static int MaxHangInfoSize = MAX_HANG_INFO_SIZE;
|
|
static char *Hang_Info;
|
|
static int Hang_Info_Size;
|
|
static bool watchdog_thread_exist;
|
|
static bool system_server_exist;
|
|
#endif
|
|
|
|
static bool Hang_first_done;
|
|
static bool hd_detect_enabled;
|
|
static bool hd_zygote_stopped;
|
|
static int hd_timeout = 0x7fffffff;
|
|
static int hang_detect_counter = 0x7fffffff;
|
|
static int dump_bt_done;
|
|
static bool reboot_flag;
|
|
static struct name_list *white_list;
|
|
static struct hang_callback_list *callback_list;
|
|
|
|
#ifdef CONFIG_MTK_HANG_PROC
|
|
static struct proc_dir_entry *pe;
|
|
#endif
|
|
|
|
DECLARE_WAIT_QUEUE_HEAD(dump_bt_start_wait);
|
|
DECLARE_WAIT_QUEUE_HEAD(dump_bt_done_wait);
|
|
DEFINE_RAW_SPINLOCK(white_list_lock);
|
|
DEFINE_RAW_SPINLOCK(callback_list_lock);
|
|
|
|
static void show_status(int flag);
|
|
static void monitor_hang_kick(int lParam);
|
|
static void show_bt_by_pid(int task_pid);
|
|
static void log_hang_info(const char *fmt, ...);
|
|
static bool check_white_list(void);
|
|
static int show_white_list_bt(struct task_struct *p);
|
|
static int find_task_by_name(char *name);
|
|
static int run_callback(void);
|
|
|
|
static void (*p_ldt_disable_aee)(void);
|
|
void monitor_hang_regist_ldt(void (*fn)(void))
|
|
{
|
|
p_ldt_disable_aee = fn;
|
|
}
|
|
EXPORT_SYMBOL_GPL(monitor_hang_regist_ldt);
|
|
|
|
static void reset_hang_info(void)
|
|
{
|
|
Hang_first_done = false;
|
|
}
|
|
|
|
int add_white_list(char *name)
|
|
{
|
|
struct name_list *new_thread;
|
|
struct name_list *pList;
|
|
|
|
raw_spin_lock(&white_list_lock);
|
|
if (!white_list) {
|
|
new_thread = kmalloc(sizeof(struct name_list), GFP_KERNEL);
|
|
if (!new_thread) {
|
|
raw_spin_unlock(&white_list_lock);
|
|
return -1;
|
|
}
|
|
strncpy(new_thread->name, name, TASK_COMM_LEN);
|
|
new_thread->name[TASK_COMM_LEN - 1] = 0;
|
|
new_thread->next = NULL;
|
|
white_list = new_thread;
|
|
raw_spin_unlock(&white_list_lock);
|
|
return 0;
|
|
}
|
|
|
|
pList = white_list;
|
|
while (pList) {
|
|
/*find same thread name*/
|
|
if (strncmp(pList->name, name, TASK_COMM_LEN) == 0) {
|
|
raw_spin_unlock(&white_list_lock);
|
|
return 0;
|
|
}
|
|
pList = pList->next;
|
|
}
|
|
|
|
/*add new thread name*/
|
|
new_thread = kmalloc(sizeof(struct name_list), GFP_KERNEL);
|
|
if (!new_thread) {
|
|
raw_spin_unlock(&white_list_lock);
|
|
return -1;
|
|
}
|
|
|
|
strncpy(new_thread->name, name, TASK_COMM_LEN);
|
|
new_thread->next = white_list;
|
|
white_list = new_thread;
|
|
raw_spin_unlock(&white_list_lock);
|
|
return 0;
|
|
}
|
|
|
|
int del_white_list(char *name)
|
|
{
|
|
struct name_list *pList;
|
|
struct name_list *pList_old;
|
|
|
|
if (!white_list)
|
|
return 0;
|
|
|
|
raw_spin_lock(&white_list_lock);
|
|
pList = pList_old = white_list;
|
|
while (pList) {
|
|
/*find same thread name*/
|
|
if (strncmp(pList->name, name, TASK_COMM_LEN) == 0) {
|
|
if (pList == white_list) {
|
|
white_list = pList->next;
|
|
kfree(pList);
|
|
raw_spin_unlock(&white_list_lock);
|
|
return 0;
|
|
}
|
|
|
|
pList_old->next = pList->next;
|
|
kfree(pList);
|
|
raw_spin_unlock(&white_list_lock);
|
|
return 0;
|
|
}
|
|
pList_old = pList;
|
|
pList = pList->next;
|
|
}
|
|
raw_spin_unlock(&white_list_lock);
|
|
return 0;
|
|
}
|
|
|
|
int register_hang_callback(void (*function_addr)(void))
|
|
{
|
|
struct hang_callback_list *new_callback;
|
|
struct hang_callback_list *pList;
|
|
|
|
raw_spin_lock(&callback_list_lock);
|
|
if (!callback_list) {
|
|
new_callback = kmalloc(sizeof(struct hang_callback_list), GFP_KERNEL);
|
|
if (!new_callback) {
|
|
raw_spin_unlock(&callback_list_lock);
|
|
return -1;
|
|
}
|
|
new_callback->fn = function_addr;
|
|
new_callback->next = NULL;
|
|
callback_list = new_callback;
|
|
raw_spin_unlock(&callback_list_lock);
|
|
return 0;
|
|
}
|
|
|
|
pList = callback_list;
|
|
/*add new thread name*/
|
|
new_callback = kmalloc(sizeof(struct hang_callback_list), GFP_KERNEL);
|
|
if (!new_callback) {
|
|
raw_spin_unlock(&callback_list_lock);
|
|
return -1;
|
|
}
|
|
|
|
new_callback->fn = function_addr;
|
|
new_callback->next = callback_list;
|
|
callback_list = new_callback;
|
|
raw_spin_unlock(&callback_list_lock);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(register_hang_callback);
|
|
|
|
#ifdef CONFIG_MTK_HANG_PROC
|
|
#define SEQ_printf(m, x...) \
|
|
do { \
|
|
if (m) \
|
|
seq_printf(m, x); \
|
|
else \
|
|
pr_debug(x); \
|
|
} while (0)
|
|
|
|
static void monitor_hang_callback_dummy1(void)
|
|
{
|
|
log_hang_info("callback 1 ok\n");
|
|
}
|
|
|
|
static void monitor_hang_callback_dummy2(void)
|
|
{
|
|
log_hang_info("callback 2 ok\n");
|
|
}
|
|
|
|
static int monitor_hang_show(struct seq_file *m, void *v)
|
|
{
|
|
SEQ_printf(m, "show hang_detect_raw\n");
|
|
SEQ_printf(m, "%s", Hang_Info);
|
|
return 0;
|
|
}
|
|
|
|
static int monitor_hang_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, monitor_hang_show, inode->i_private);
|
|
}
|
|
|
|
|
|
static ssize_t monitor_hang_proc_write(struct file *filp, const char *ubuf,
|
|
size_t cnt, loff_t *data)
|
|
{
|
|
char buf[64];
|
|
long val;
|
|
int ret;
|
|
struct task_struct *p;
|
|
|
|
if (cnt >= sizeof(buf))
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(&buf, ubuf, cnt))
|
|
return -EFAULT;
|
|
|
|
buf[cnt] = 0;
|
|
|
|
ret = kstrtoul(buf, 10, (unsigned long *)&val);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (val == 2) {
|
|
reset_hang_info();
|
|
show_status(0);
|
|
|
|
log_hang_info("white list start\n");
|
|
add_white_list("system_server");
|
|
add_white_list("name1");
|
|
add_white_list("name2");
|
|
del_white_list("name1");
|
|
del_white_list("name2");
|
|
check_white_list();
|
|
rcu_read_lock();
|
|
for_each_process(p) {
|
|
if (!strcmp(p->comm, "system_server"))
|
|
break;
|
|
}
|
|
rcu_read_unlock();
|
|
if (show_white_list_bt(p) == 0)
|
|
log_hang_info("white list ok\n");
|
|
del_white_list("system_server");
|
|
log_hang_info("white list done\n");
|
|
|
|
if (find_task_by_name("system_server") == p->pid)
|
|
log_hang_info("find task by name ok\n");
|
|
|
|
register_hang_callback(monitor_hang_callback_dummy1);
|
|
register_hang_callback(monitor_hang_callback_dummy2);
|
|
run_callback();
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static const struct proc_ops monitor_hang_fops = {
|
|
.proc_open = monitor_hang_proc_open,
|
|
.proc_write = monitor_hang_proc_write,
|
|
.proc_read = seq_read,
|
|
.proc_lseek = seq_lseek,
|
|
.proc_release = single_release,
|
|
};
|
|
#endif
|
|
|
|
/******************************************************************************
|
|
* hang detect File operations
|
|
*****************************************************************************/
|
|
static int monitor_hang_open(struct inode *inode, struct file *filp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int monitor_hang_release(struct inode *inode, struct file *filp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int monitor_hang_poll(struct file *file,
|
|
struct poll_table_struct *ptable)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t monitor_hang_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t monitor_hang_write(struct file *filp, const char __user *buf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
char msg[8] = {0};
|
|
|
|
if (count >= 2) {
|
|
pr_info("hang_detect: invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!buf) {
|
|
pr_info("hang_detect: invalid user buf\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_from_user(msg, buf, count)) {
|
|
pr_info("hang_detect: failed to copy from user\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (strncmp(current->comm, "init", 4))
|
|
return -EINVAL;
|
|
|
|
if (msg[0] == '0') {
|
|
hd_detect_enabled = false;
|
|
hd_zygote_stopped = true;
|
|
pr_info("hang_detect: disable by stop cmd\n");
|
|
} else if (msg[0] == '1') {
|
|
if (hd_zygote_stopped) {
|
|
hd_detect_enabled = true;
|
|
hd_zygote_stopped = false;
|
|
pr_info("hang_detect: enable by start cmd\n");
|
|
} else {
|
|
pr_info("hang_detect: zygote running\n");
|
|
}
|
|
} else {
|
|
pr_info("hang_detect: invalid control msg\n");
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static long monitor_hang_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
static long long monitor_status;
|
|
void __user *argp = (void __user *)arg;
|
|
char name[TASK_COMM_LEN] = {0};
|
|
|
|
if (cmd == HANG_KICK) {
|
|
pr_info("hang_detect HANG_KICK ( %d)\n", (int)arg);
|
|
monitor_hang_kick((int)arg);
|
|
return ret;
|
|
}
|
|
|
|
if ((cmd == HANG_SET_SF_STATE) &&
|
|
(!strncmp(current->comm, "surfaceflinger", 10) ||
|
|
!strncmp(current->comm, "SWWatchDog", 10))) {
|
|
if (copy_from_user(&monitor_status, argp, sizeof(long long)))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
} else if (cmd == HANG_GET_SF_STATE) {
|
|
if (copy_to_user(argp, &monitor_status, sizeof(long long)))
|
|
ret = -EFAULT;
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
if (cmd == HANG_SET_REBOOT) {
|
|
reboot_flag = true;
|
|
hang_detect_counter = 5;
|
|
hd_timeout = 5;
|
|
hd_detect_enabled = true;
|
|
pr_info("hang_detect: %s set reboot command.\n", current->comm);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
if (cmd == HANG_ADD_WHITE_LIST) {
|
|
if (copy_from_user(name, argp, TASK_COMM_LEN - 1))
|
|
ret = -EFAULT;
|
|
ret = add_white_list(name);
|
|
pr_info("hang_detect: add white list %s status %d.\n",
|
|
name, ret);
|
|
return ret;
|
|
}
|
|
|
|
if (cmd == HANG_DEL_WHITE_LIST) {
|
|
if (copy_from_user(name, argp, TASK_COMM_LEN - 1))
|
|
ret = -EFAULT;
|
|
ret = del_white_list(name);
|
|
pr_info("hang_detect: del white list %s status %d.\n",
|
|
name, ret);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const struct file_operations Hang_Monitor_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = monitor_hang_open,
|
|
.release = monitor_hang_release,
|
|
.poll = monitor_hang_poll,
|
|
.read = monitor_hang_read,
|
|
.write = monitor_hang_write,
|
|
.unlocked_ioctl = monitor_hang_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = monitor_hang_ioctl,
|
|
#endif
|
|
};
|
|
|
|
static struct miscdevice Hang_Monitor_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "RT_Monitor",
|
|
.fops = &Hang_Monitor_fops,
|
|
};
|
|
|
|
static int find_task_by_name(char *name)
|
|
{
|
|
struct task_struct *task;
|
|
int ret = -1;
|
|
|
|
if (!name)
|
|
return ret;
|
|
rcu_read_lock();
|
|
for_each_process(task) {
|
|
if (task && !strncmp(task->comm, name, strlen(name))) {
|
|
pr_info("[Hang_Detect] %s found pid:%d.\n",
|
|
task->comm, task->pid);
|
|
ret = task->pid;
|
|
break;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
return ret;
|
|
}
|
|
|
|
static void log_hang_info(const char *fmt, ...)
|
|
{
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
unsigned long len;
|
|
va_list ap;
|
|
|
|
if ((Hang_Info_Size + MAX_STRING_SIZE) >=
|
|
(unsigned long)MaxHangInfoSize)
|
|
return;
|
|
|
|
va_start(ap, fmt);
|
|
len = vscnprintf(&Hang_Info[Hang_Info_Size], MAX_STRING_SIZE, fmt, ap);
|
|
va_end(ap);
|
|
Hang_Info_Size += len;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
#ifndef MODULE
|
|
static void buffer_hang_info(const char *buff, unsigned long size)
|
|
{
|
|
if (((unsigned long)Hang_Info_Size + size)
|
|
>= (unsigned long)MaxHangInfoSize)
|
|
return;
|
|
|
|
memcpy(&Hang_Info[Hang_Info_Size], buff, size);
|
|
Hang_Info_Size += size;
|
|
}
|
|
|
|
static void dump_msdc_hang_info(void)
|
|
{
|
|
char *buff_add = NULL;
|
|
unsigned long buff_size = 0;
|
|
|
|
if (get_msdc_aee_buffer) {
|
|
get_msdc_aee_buffer((unsigned long *)&buff_add, &buff_size);
|
|
if (buff_size != 0 && buff_add) {
|
|
if (buff_size > MSDC_BUFFER_DEFAULT_SIZE) {
|
|
buff_add = buff_add + buff_size - MSDC_BUFFER_DEFAULT_SIZE;
|
|
buff_size = MSDC_BUFFER_DEFAULT_SIZE;
|
|
}
|
|
buffer_hang_info(buff_add, buff_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dump_mem_info(void)
|
|
{
|
|
char *buff_add = NULL;
|
|
int buff_size = 0;
|
|
|
|
if (mlog_get_buffer) {
|
|
mlog_get_buffer(&buff_add, &buff_size);
|
|
if (buff_size <= 0 || !buff_add) {
|
|
pr_info("hang_detect: mlog_get_buffer size %d.\n",
|
|
buff_size);
|
|
return;
|
|
}
|
|
|
|
if (buff_size > MEM_BUFFER_DEFAULT_SIZE) {
|
|
buff_add = buff_add + buff_size - MEM_BUFFER_DEFAULT_SIZE;
|
|
buff_size = MEM_BUFFER_DEFAULT_SIZE;
|
|
}
|
|
|
|
buffer_hang_info(buff_add, buff_size);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void trigger_hang_db(void)
|
|
{
|
|
pr_notice("[Hang_Detect] we triger DB.\n");
|
|
|
|
#if IS_ENABLED(CONFIG_MTK_AEE_IPANIC)
|
|
aee_rr_rec_hang_detect_timeout_count(hd_timeout);
|
|
if ((!watchdog_thread_exist & system_server_exist)
|
|
&& reboot_flag == false)
|
|
aee_rr_rec_hang_detect_timeout_count(COUNT_ANDROID_REBOOT);
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_MTK_AEE_IPANIC)
|
|
mrdump_regist_hang_bt(NULL);
|
|
mrdump_common_die(AEE_REBOOT_MODE_HANG_DETECT,
|
|
" Hang Detect", NULL);
|
|
#else
|
|
panic("hang_detect: system blocked");
|
|
#endif
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
|
|
static void get_kernel_bt(struct task_struct *tsk)
|
|
{
|
|
unsigned long stacks[32];
|
|
int nr_entries;
|
|
int i;
|
|
|
|
#ifndef __aarch64__
|
|
nr_entries = stack_trace_save_tsk(tsk, stacks, ARRAY_SIZE(stacks), 0);
|
|
#else
|
|
nr_entries = hang_kernel_trace(tsk, stacks, ARRAY_SIZE(stacks));
|
|
#endif
|
|
for (i = 0; i < nr_entries; i++) {
|
|
log_hang_info("<%lx> %pS\n", (long)stacks[i],
|
|
(void *)stacks[i]);
|
|
hang_log("<%lx> %pS\n", (long)stacks[i],
|
|
(void *)stacks[i]);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static long long nsec_high(unsigned long long nsec)
|
|
{
|
|
if ((long long)nsec < 0) {
|
|
nsec = -nsec;
|
|
do_div(nsec, 1000000);
|
|
return -nsec;
|
|
}
|
|
do_div(nsec, 1000000);
|
|
|
|
return nsec;
|
|
}
|
|
|
|
static unsigned long nsec_low(unsigned long long nsec)
|
|
{
|
|
if ((long long)nsec < 0)
|
|
nsec = -nsec;
|
|
|
|
return do_div(nsec, 1000000);
|
|
}
|
|
|
|
void store_task_info(struct task_struct *p)
|
|
{
|
|
unsigned int state;
|
|
char stat_nam[] = TASK_STATE_TO_CHAR_STR;
|
|
|
|
state = p->state ? __ffs(p->state) + 1 : 0;
|
|
|
|
log_hang_info("%-15.15s %c ", p->comm,
|
|
state < sizeof(stat_nam) - 1 ? stat_nam[state] : '?');
|
|
log_hang_info("%lld.%06ld %d %lu %lu 0x%x 0x%lx %d %d %d ",
|
|
nsec_high(p->se.sum_exec_runtime),
|
|
nsec_low(p->se.sum_exec_runtime),
|
|
task_pid_nr(p), p->nvcsw, p->nivcsw, p->flags,
|
|
(unsigned long)task_thread_info(p)->flags,
|
|
p->tgid, task_pid_nr(rcu_dereference(p->real_parent)),
|
|
task_pid_nr(rcu_dereference(p->parent)));
|
|
#if IS_ENABLED(CONFIG_SCHED_INFO)
|
|
log_hang_info("%llu", p->sched_info.last_arrival);
|
|
#endif
|
|
log_hang_info("\n");
|
|
}
|
|
|
|
void show_thread_info(struct task_struct *p, bool dump_bt)
|
|
{
|
|
unsigned int state;
|
|
char stat_nam[] = TASK_STATE_TO_CHAR_STR;
|
|
|
|
state = p->state ? __ffs(p->state) + 1 : 0;
|
|
|
|
log_hang_info("%-15.15s %c ", p->comm,
|
|
state < sizeof(stat_nam) - 1 ? stat_nam[state] : '?');
|
|
hang_log("%-15.15s %c ", p->comm,
|
|
state < sizeof(stat_nam) - 1 ? stat_nam[state] : '?');
|
|
log_hang_info("%lld.%06ld %d %lu %lu 0x%x 0x%lx %d ",
|
|
nsec_high(p->se.sum_exec_runtime),
|
|
nsec_low(p->se.sum_exec_runtime),
|
|
task_pid_nr(p), p->nvcsw, p->nivcsw, p->flags,
|
|
(unsigned long)task_thread_info(p)->flags,
|
|
p->tgid);
|
|
hang_log("%lld.%06ld %d %lu %lu 0x%x 0x%lx %d ",
|
|
nsec_high(p->se.sum_exec_runtime),
|
|
nsec_low(p->se.sum_exec_runtime),
|
|
task_pid_nr(p), p->nvcsw, p->nivcsw, p->flags,
|
|
(unsigned long)task_thread_info(p)->flags,
|
|
p->tgid);
|
|
#ifdef CONFIG_SCHED_INFO
|
|
log_hang_info("%llu", p->sched_info.last_arrival);
|
|
hang_log("%llu", p->sched_info.last_arrival);
|
|
#endif
|
|
log_hang_info("\n");
|
|
|
|
/* nvscw: voluntary context switch. */
|
|
/* requires a resource that is unavailable. */
|
|
/* nivcsw: involuntary context switch. */
|
|
/* time slice out or when higher-priority thread to run*/
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
if (!strcmp(p->comm, "watchdog"))
|
|
watchdog_thread_exist = true;
|
|
if (!strcmp(p->comm, "system_server"))
|
|
system_server_exist = true;
|
|
#endif
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
if (dump_bt || ((p->state == TASK_RUNNING ||
|
|
p->state & TASK_UNINTERRUPTIBLE) &&
|
|
!strstr(p->comm, "wdtk")))
|
|
/* Catch kernel-space backtrace */
|
|
get_kernel_bt(p);
|
|
#endif
|
|
}
|
|
|
|
static int dump_native_maps(pid_t pid, struct task_struct *current_task)
|
|
{
|
|
struct vm_area_struct *vma;
|
|
int mapcount = 0;
|
|
struct file *file;
|
|
int flags;
|
|
struct mm_struct *mm;
|
|
struct pt_regs *user_ret;
|
|
char tpath[512];
|
|
char *path_p = NULL;
|
|
struct path base_path;
|
|
unsigned long long pgoff = 0;
|
|
|
|
if (!current_task)
|
|
return -ESRCH;
|
|
user_ret = task_pt_regs(current_task);
|
|
|
|
if (!user_mode(user_ret)) {
|
|
pr_info(" %s,%d:%s: in user_mode", __func__, pid,
|
|
current_task->comm);
|
|
return -1;
|
|
}
|
|
|
|
if (!get_task_mm(current_task)) {
|
|
pr_info(" %s,%d:%s: current_task->mm == NULL", __func__, pid,
|
|
current_task->comm);
|
|
return -1;
|
|
}
|
|
|
|
vma = current_task->mm->mmap;
|
|
log_hang_info("Dump native maps files:\n");
|
|
hang_log("Dump native maps files:\n");
|
|
while (vma && (mapcount < current_task->mm->map_count)) {
|
|
file = vma->vm_file;
|
|
flags = vma->vm_flags;
|
|
pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;
|
|
if (file) { /* !!!!!!!!only dump 1st mmaps!!!!!!!!!!!! */
|
|
if (flags & VM_EXEC) {
|
|
/* we only catch code section for reduce
|
|
* maps space
|
|
*/
|
|
base_path = file->f_path;
|
|
path_p = d_path(&base_path, tpath, 512);
|
|
log_hang_info("%08lx-%08lx %c%c%c%c %08llx %s\n",
|
|
vma->vm_start, vma->vm_end,
|
|
flags & VM_READ ? 'r' : '-',
|
|
flags & VM_WRITE ? 'w' : '-',
|
|
flags & VM_EXEC ? 'x' : '-',
|
|
flags & VM_MAYSHARE ? 's' : 'p',
|
|
pgoff, path_p);
|
|
hang_log("%08lx-%08lx %c%c%c%c %08llx %s\n",
|
|
vma->vm_start, vma->vm_end,
|
|
flags & VM_READ ? 'r' : '-',
|
|
flags & VM_WRITE ? 'w' : '-',
|
|
flags & VM_EXEC ? 'x' : '-',
|
|
flags & VM_MAYSHARE ? 's' : 'p',
|
|
pgoff, path_p);
|
|
}
|
|
} else {
|
|
const char *name = hang_arch_vma_name(vma);
|
|
|
|
mm = vma->vm_mm;
|
|
if (!name) {
|
|
if (mm) {
|
|
if (vma->vm_start <= mm->start_brk &&
|
|
vma->vm_end >= mm->brk) {
|
|
name = "[heap]";
|
|
} else if (vma->vm_start <=
|
|
mm->start_stack &&
|
|
vma->vm_end >=
|
|
mm->start_stack) {
|
|
name = "[stack]";
|
|
}
|
|
} else {
|
|
name = "[vdso]";
|
|
}
|
|
}
|
|
|
|
if (flags & VM_EXEC) {
|
|
log_hang_info("%08lx-%08lx %c%c%c%c %08llx %s\n",
|
|
vma->vm_start, vma->vm_end,
|
|
flags & VM_READ ? 'r' : '-',
|
|
flags & VM_WRITE ? 'w' : '-',
|
|
flags & VM_EXEC ? 'x' : '-',
|
|
flags & VM_MAYSHARE ? 's' : 'p', pgoff, name);
|
|
hang_log("%08lx-%08lx %c%c%c%c %08llx %s\n",
|
|
vma->vm_start, vma->vm_end,
|
|
flags & VM_READ ? 'r' : '-',
|
|
flags & VM_WRITE ? 'w' : '-',
|
|
flags & VM_EXEC ? 'x' : '-',
|
|
flags & VM_MAYSHARE ? 's' : 'p', pgoff, name);
|
|
}
|
|
}
|
|
vma = vma->vm_next;
|
|
mapcount++;
|
|
}
|
|
++oops_in_progress; /* sleeping function warn */
|
|
mmput(current_task->mm);
|
|
--oops_in_progress;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static int dump_native_info_by_tid(pid_t tid,
|
|
struct task_struct *current_task)
|
|
{
|
|
struct pt_regs *user_ret;
|
|
struct vm_area_struct *vma;
|
|
unsigned long userstack_start = 0;
|
|
unsigned long userstack_end = 0, length = 0;
|
|
int ret = -1;
|
|
|
|
if (!current_task)
|
|
return -ESRCH;
|
|
user_ret = task_pt_regs(current_task);
|
|
|
|
if (!user_mode(user_ret)) {
|
|
pr_info(" %s,%d:%s,fail in user_mode", __func__, tid,
|
|
current_task->comm);
|
|
return ret;
|
|
}
|
|
|
|
if (!current_task->mm) {
|
|
pr_info(" %s,%d:%s, current_task->mm == NULL", __func__, tid,
|
|
current_task->comm);
|
|
return ret;
|
|
}
|
|
#ifndef __aarch64__ /* 32bit */
|
|
log_hang_info(" pc/lr/sp 0x%08x/0x%08x/0x%08x\n", user_ret->ARM_pc,
|
|
user_ret->ARM_lr, user_ret->ARM_sp);
|
|
hang_log(" pc/lr/sp 0x%08x/0x%08x/0x%08x\n", user_ret->ARM_pc,
|
|
user_ret->ARM_lr, user_ret->ARM_sp);
|
|
log_hang_info("r12-r0 0x%08x/0x%08x/0x%08x/0x%08x\n",
|
|
(long)(user_ret->ARM_ip), (long)(user_ret->ARM_fp),
|
|
(long)(user_ret->ARM_r10), (long)(user_ret->ARM_r9));
|
|
hang_log("r12-r0 0x%08x/0x%08x/0x%08x/0x%08x\n",
|
|
(long)(user_ret->ARM_ip), (long)(user_ret->ARM_fp),
|
|
(long)(user_ret->ARM_r10), (long)(user_ret->ARM_r9));
|
|
log_hang_info("0x%08x/0x%08x/0x%08x/0x%08x/0x%08x\n",
|
|
(long)(user_ret->ARM_r8), (long)(user_ret->ARM_r7),
|
|
(long)(user_ret->ARM_r6), (long)(user_ret->ARM_r5),
|
|
(long)(user_ret->ARM_r4));
|
|
hang_log("0x%08x/0x%08x/0x%08x/0x%08x/0x%08x\n",
|
|
(long)(user_ret->ARM_r8), (long)(user_ret->ARM_r7),
|
|
(long)(user_ret->ARM_r6), (long)(user_ret->ARM_r5),
|
|
(long)(user_ret->ARM_r4));
|
|
log_hang_info("0x%08x/0x%08x/0x%08x/0x%08x\n",
|
|
(long)(user_ret->ARM_r3), (long)(user_ret->ARM_r2),
|
|
(long)(user_ret->ARM_r1), (long)(user_ret->ARM_r0));
|
|
hang_log("0x%08x/0x%08x/0x%08x/0x%08x\n",
|
|
(long)(user_ret->ARM_r3), (long)(user_ret->ARM_r2),
|
|
(long)(user_ret->ARM_r1), (long)(user_ret->ARM_r0));
|
|
|
|
userstack_start = (unsigned long)user_ret->ARM_sp;
|
|
|
|
vma = current_task->mm->mmap;
|
|
while (vma) {
|
|
if (vma->vm_start <= userstack_start &&
|
|
vma->vm_end >= userstack_start) {
|
|
userstack_end = vma->vm_end;
|
|
break;
|
|
}
|
|
vma = vma->vm_next;
|
|
if (vma == current_task->mm->mmap)
|
|
break;
|
|
}
|
|
|
|
if (!userstack_end) {
|
|
pr_info(" %s,%d:%s,userstack_end == 0", __func__,
|
|
tid, current_task->comm);
|
|
return ret;
|
|
}
|
|
length = userstack_end - userstack_start;
|
|
|
|
|
|
/* dump native stack to buffer */
|
|
{
|
|
unsigned long SPStart = 0, SPEnd = 0;
|
|
int tempSpContent[4], copied;
|
|
|
|
SPStart = userstack_start;
|
|
SPEnd = SPStart + length;
|
|
log_hang_info("UserSP_start:%08x,Length:%08x,End:%08x\n",
|
|
SPStart, length, SPEnd);
|
|
hang_log("UserSP_start:%08x,Length:%08x,End:%08x\n",
|
|
SPStart, length, SPEnd);
|
|
while (SPStart < SPEnd) {
|
|
++oops_in_progress; /* sleeping function warn */
|
|
copied =
|
|
access_process_vm(current_task, SPStart,
|
|
&tempSpContent, sizeof(tempSpContent),
|
|
0);
|
|
--oops_in_progress;
|
|
if (copied != sizeof(tempSpContent)) {
|
|
pr_info("access_process_vm SPStart error,sizeof(tempSpContent)=%x\n",
|
|
(unsigned int)sizeof(tempSpContent));
|
|
/* return -EIO; */
|
|
}
|
|
if (tempSpContent[0] != 0 ||
|
|
tempSpContent[1] != 0 ||
|
|
tempSpContent[2] != 0 ||
|
|
tempSpContent[3] != 0) {
|
|
log_hang_info("%08x:%08x %08x %08x %08x\n", SPStart,
|
|
tempSpContent[0],
|
|
tempSpContent[1],
|
|
tempSpContent[2],
|
|
tempSpContent[3]);
|
|
hang_log("%08x:%08x %08x %08x %08x\n", SPStart,
|
|
tempSpContent[0],
|
|
tempSpContent[1],
|
|
tempSpContent[2],
|
|
tempSpContent[3]);
|
|
}
|
|
SPStart += 4 * 4;
|
|
}
|
|
}
|
|
#else /* 64bit, First deal with K64+U64, the last time to deal with K64+U32 */
|
|
/* K64_U32 for current task */
|
|
if (compat_user_mode(user_ret)) { /* K64_U32 for check reg */
|
|
log_hang_info("K64+ U32 pc/lr/sp 0x%16lx/0x%16lx/0x%16lx\n",
|
|
(long)(user_ret->user_regs.pc),
|
|
(long)(user_ret->user_regs.regs[14]),
|
|
(long)(user_ret->user_regs.regs[13]));
|
|
hang_log("K64+ U32 pc/lr/sp 0x%16lx/0x%16lx/0x%16lx\n",
|
|
(long)(user_ret->user_regs.pc),
|
|
(long)(user_ret->user_regs.regs[14]),
|
|
(long)(user_ret->user_regs.regs[13]));
|
|
log_hang_info("r12-r0 0x%lx/0x%lx/0x%lx/0x%lx\n",
|
|
(long)(user_ret->user_regs.regs[12]),
|
|
(long)(user_ret->user_regs.regs[11]),
|
|
(long)(user_ret->user_regs.regs[10]),
|
|
(long)(user_ret->user_regs.regs[9]));
|
|
hang_log("r12-r0 0x%lx/0x%lx/0x%lx/0x%lx\n",
|
|
(long)(user_ret->user_regs.regs[12]),
|
|
(long)(user_ret->user_regs.regs[11]),
|
|
(long)(user_ret->user_regs.regs[10]),
|
|
(long)(user_ret->user_regs.regs[9]));
|
|
log_hang_info("0x%lx/0x%lx/0x%lx/0x%lx/0x%lx\n",
|
|
(long)(user_ret->user_regs.regs[8]),
|
|
(long)(user_ret->user_regs.regs[7]),
|
|
(long)(user_ret->user_regs.regs[6]),
|
|
(long)(user_ret->user_regs.regs[5]),
|
|
(long)(user_ret->user_regs.regs[4]));
|
|
hang_log("0x%lx/0x%lx/0x%lx/0x%lx/0x%lx\n",
|
|
(long)(user_ret->user_regs.regs[8]),
|
|
(long)(user_ret->user_regs.regs[7]),
|
|
(long)(user_ret->user_regs.regs[6]),
|
|
(long)(user_ret->user_regs.regs[5]),
|
|
(long)(user_ret->user_regs.regs[4]));
|
|
log_hang_info("0x%lx/0x%lx/0x%lx/0x%lx\n",
|
|
(long)(user_ret->user_regs.regs[3]),
|
|
(long)(user_ret->user_regs.regs[2]),
|
|
(long)(user_ret->user_regs.regs[1]),
|
|
(long)(user_ret->user_regs.regs[0]));
|
|
hang_log("0x%lx/0x%lx/0x%lx/0x%lx\n",
|
|
(long)(user_ret->user_regs.regs[3]),
|
|
(long)(user_ret->user_regs.regs[2]),
|
|
(long)(user_ret->user_regs.regs[1]),
|
|
(long)(user_ret->user_regs.regs[0]));
|
|
userstack_start = (unsigned long)user_ret->user_regs.regs[13];
|
|
vma = current_task->mm->mmap;
|
|
while (vma) {
|
|
if (vma->vm_start <= userstack_start &&
|
|
vma->vm_end >= userstack_start) {
|
|
userstack_end = vma->vm_end;
|
|
break;
|
|
}
|
|
vma = vma->vm_next;
|
|
if (vma == current_task->mm->mmap)
|
|
break;
|
|
}
|
|
|
|
if (!userstack_end) {
|
|
pr_info("Dump native stack failed:\n");
|
|
return ret;
|
|
}
|
|
|
|
length = userstack_end - userstack_start;
|
|
|
|
/* dump native stack to buffer */
|
|
{
|
|
unsigned long SPStart = 0, SPEnd = 0;
|
|
int tempSpContent[4], copied;
|
|
|
|
SPStart = userstack_start;
|
|
SPEnd = SPStart + length;
|
|
log_hang_info("UserSP_start:%lx,Length:%lx,End:%lx\n",
|
|
SPStart, length, SPEnd);
|
|
hang_log("UserSP_start:%lx,Length:%lx,End:%lx\n",
|
|
SPStart, length, SPEnd);
|
|
while (SPStart < SPEnd) {
|
|
++oops_in_progress; /* sleeping function warn */
|
|
copied = access_process_vm(current_task,
|
|
SPStart, &tempSpContent,
|
|
sizeof(tempSpContent), 0);
|
|
--oops_in_progress;
|
|
if (copied != sizeof(tempSpContent)) {
|
|
pr_info(
|
|
"access_process_vm SPStart error,sizeof(tempSpContent)=%x\n",
|
|
(unsigned int)sizeof(tempSpContent));
|
|
/* return -EIO; */
|
|
}
|
|
if (tempSpContent[0] != 0 ||
|
|
tempSpContent[1] != 0 ||
|
|
tempSpContent[2] != 0 ||
|
|
tempSpContent[3] != 0) {
|
|
log_hang_info("%08lx:%x %x %x %x\n",
|
|
SPStart,
|
|
tempSpContent[0],
|
|
tempSpContent[1],
|
|
tempSpContent[2],
|
|
tempSpContent[3]);
|
|
hang_log("%08lx:%x %x %x %x\n",
|
|
SPStart,
|
|
tempSpContent[0],
|
|
tempSpContent[1],
|
|
tempSpContent[2],
|
|
tempSpContent[3]);
|
|
}
|
|
SPStart += 4 * 4;
|
|
}
|
|
}
|
|
} else { /*K64+U64 */
|
|
userstack_start = (unsigned long)user_ret->user_regs.sp;
|
|
|
|
vma = current_task->mm->mmap;
|
|
while (vma) {
|
|
if (vma->vm_start <= userstack_start &&
|
|
vma->vm_end >= userstack_start) {
|
|
userstack_end = vma->vm_end;
|
|
break;
|
|
}
|
|
vma = vma->vm_next;
|
|
if (vma == current_task->mm->mmap)
|
|
break;
|
|
}
|
|
if (!userstack_end) {
|
|
pr_info("Dump native stack failed:\n");
|
|
return ret;
|
|
}
|
|
|
|
{
|
|
unsigned long tmpfp, tmp, tmpLR;
|
|
int copied, frames;
|
|
unsigned long native_bt[16];
|
|
|
|
native_bt[0] = user_ret->user_regs.pc;
|
|
native_bt[1] = user_ret->user_regs.regs[30];
|
|
tmpfp = user_ret->user_regs.regs[29];
|
|
frames = 2;
|
|
while (tmpfp < userstack_end &&
|
|
tmpfp > userstack_start) {
|
|
++oops_in_progress; /* sleeping function warn */
|
|
copied =
|
|
access_process_vm(current_task,
|
|
(unsigned long)tmpfp, &tmp,
|
|
sizeof(tmp), 0);
|
|
--oops_in_progress;
|
|
if (copied != sizeof(tmp)) {
|
|
pr_info("access_process_vm fp error\n");
|
|
return -EIO;
|
|
}
|
|
++oops_in_progress; /* sleeping function warn */
|
|
copied =
|
|
access_process_vm(current_task,
|
|
(unsigned long)tmpfp + 0x08,
|
|
&tmpLR, sizeof(tmpLR), 0);
|
|
--oops_in_progress;
|
|
if (copied != sizeof(tmpLR)) {
|
|
pr_info("access_process_vm pc error\n");
|
|
return -EIO;
|
|
}
|
|
tmpfp = tmp;
|
|
native_bt[frames] = tmpLR;
|
|
frames++;
|
|
if (frames >= 16)
|
|
break;
|
|
}
|
|
for (copied = 0; copied < frames; copied++) {
|
|
/* #00 pc 000000000006c760
|
|
* /system/lib64/ libc.so (__epoll_pwait+8)
|
|
*/
|
|
log_hang_info("#%d pc %lx\n", copied,
|
|
native_bt[copied]);
|
|
hang_log("#%d pc %lx\n", copied,
|
|
native_bt[copied]);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void show_bt_by_pid(int task_pid)
|
|
{
|
|
struct task_struct *t, *p;
|
|
struct pid *pid;
|
|
#ifdef __aarch64__
|
|
struct pt_regs *user_ret;
|
|
#endif
|
|
int dump_native = 0;
|
|
unsigned int state = 0;
|
|
char stat_nam[] = TASK_STATE_TO_CHAR_STR;
|
|
|
|
pid = find_get_pid(task_pid);
|
|
t = p = get_pid_task(pid, PIDTYPE_PID);
|
|
|
|
if (p != NULL) {
|
|
if (try_get_task_stack(p)) {
|
|
log_hang_info("%s: %d: %s.\n", __func__, task_pid, t->comm);
|
|
hang_log("%s: %d: %s.\n", __func__, task_pid, t->comm);
|
|
#ifndef __aarch64__ /* 32bit */
|
|
if (!strcmp(t->comm, "system_server"))
|
|
dump_native = 1;
|
|
else
|
|
dump_native = 0;
|
|
#else
|
|
user_ret = task_pt_regs(t);
|
|
|
|
if (!user_mode(user_ret)) {
|
|
pr_info(" %s,%d:%s,fail in user_mode", __func__,
|
|
task_pid, t->comm);
|
|
dump_native = 0;
|
|
} else if (!t->mm) {
|
|
pr_info(" %s,%d:%s, current_task->mm == NULL", __func__,
|
|
task_pid, t->comm);
|
|
dump_native = 0;
|
|
} else if (compat_user_mode(user_ret)) {
|
|
/* K64_U32 for check reg */
|
|
if (!strcmp(t->comm, "system_server"))
|
|
dump_native = 1;
|
|
else
|
|
dump_native = 0;
|
|
} else
|
|
dump_native = 1;
|
|
#endif
|
|
if (dump_native == 1)
|
|
/* catch maps to Userthread_maps */
|
|
dump_native_maps(task_pid, p);
|
|
put_task_stack(p);
|
|
} else {
|
|
state = p->state ? __ffs(p->state) + 1 : 0;
|
|
log_hang_info("%s pid %d state %c, flags %d. stack is null.\n",
|
|
t->comm, task_pid, state < sizeof(stat_nam) - 1 ?
|
|
stat_nam[state] : '?', t->flags);
|
|
hang_log("%s pid %d state %c, flags %d. stack is null.\n",
|
|
t->comm, task_pid, state < sizeof(stat_nam) - 1 ?
|
|
stat_nam[state] : '?', t->flags);
|
|
}
|
|
do {
|
|
if (!t)
|
|
break;
|
|
|
|
get_task_struct(t);
|
|
if (try_get_task_stack(t)) {
|
|
pid_t tid = 0;
|
|
|
|
tid = task_pid_vnr(t);
|
|
state = t->state ? __ffs(t->state) + 1 : 0;
|
|
/* catch kernel bt */
|
|
show_thread_info(t, true);
|
|
|
|
log_hang_info("%s sysTid=%d, pid=%d\n", t->comm,
|
|
tid, task_pid);
|
|
hang_log("%s sysTid=%d, pid=%d\n", t->comm,
|
|
tid, task_pid);
|
|
|
|
if (dump_native == 1)
|
|
dump_native_info_by_tid(tid, t);
|
|
|
|
put_task_stack(t);
|
|
}
|
|
put_task_struct(t);
|
|
log_hang_info("-\n");
|
|
} while_each_thread(p, t);
|
|
|
|
put_task_struct(p); /* pairing get_pid_task */
|
|
}
|
|
put_pid(pid);
|
|
}
|
|
|
|
static int show_white_list_bt(struct task_struct *p)
|
|
{
|
|
struct name_list *pList = NULL;
|
|
|
|
if (!white_list)
|
|
return -1;
|
|
|
|
|
|
raw_spin_lock(&white_list_lock);
|
|
pList = white_list;
|
|
while (pList) {
|
|
if (!strcmp(p->comm, pList->name)) {
|
|
raw_spin_unlock(&white_list_lock);
|
|
show_bt_by_pid(p->pid);
|
|
return 0;
|
|
}
|
|
pList = pList->next;
|
|
}
|
|
raw_spin_unlock(&white_list_lock);
|
|
return -1;
|
|
}
|
|
|
|
static int run_callback(void)
|
|
{
|
|
struct hang_callback_list *pList = NULL;
|
|
|
|
if (!callback_list)
|
|
return -1;
|
|
|
|
|
|
raw_spin_lock(&callback_list_lock);
|
|
pList = callback_list;
|
|
while (pList) {
|
|
pList->fn();
|
|
pList = pList->next;
|
|
}
|
|
raw_spin_unlock(&callback_list_lock);
|
|
return -1;
|
|
}
|
|
|
|
static void show_task_info(void)
|
|
{
|
|
struct task_struct *p, *t;
|
|
|
|
rcu_read_lock();
|
|
for_each_process_thread(p, t)
|
|
store_task_info(t);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static void show_task_backtrace(void)
|
|
{
|
|
struct task_struct *p, *t, *system_server_task = NULL;
|
|
struct task_struct *monkey_task = NULL;
|
|
struct task_struct *aee_aed_task = NULL;
|
|
bool first_dump_blocked = false;
|
|
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
watchdog_thread_exist = false;
|
|
system_server_exist = false;
|
|
#endif
|
|
log_hang_info("dump backtrace start: %llu\n", local_clock());
|
|
|
|
if (!strcmp(current->comm, "hang_detect2")) {
|
|
pr_info("hang_detect first dump was blocked\n");
|
|
first_dump_blocked = true;
|
|
}
|
|
#if IS_ENABLED(CONFIG_PROVE_LOCKING)
|
|
if (debug_locks && p_ldt_disable_aee) {
|
|
p_ldt_disable_aee();
|
|
pr_info("hang_detect debug locks off here\n");
|
|
debug_locks_off();
|
|
}
|
|
#endif
|
|
rcu_read_lock();
|
|
for_each_process(p) {
|
|
get_task_struct(p);
|
|
if (Hang_first_done == false) {
|
|
if (!strcmp(p->comm, "system_server"))
|
|
system_server_task = p;
|
|
if (strstr(p->comm, "monkey"))
|
|
monkey_task = p;
|
|
if (!strcmp(p->comm, "aee_aed"))
|
|
aee_aed_task = p;
|
|
}
|
|
/* specify process, need dump maps file and native backtrace */
|
|
if (!first_dump_blocked &&
|
|
(!strcmp(p->comm, "init") ||
|
|
!strcmp(p->comm, "system_server") ||
|
|
!strcmp(p->comm, "vold") ||
|
|
!strcmp(p->comm, "vdc"))) {
|
|
show_bt_by_pid(p->pid);
|
|
put_task_struct(p);
|
|
continue;
|
|
}
|
|
if (!show_white_list_bt(p)) {
|
|
put_task_struct(p);
|
|
continue;
|
|
}
|
|
for_each_thread(p, t) {
|
|
if (t) {
|
|
get_task_struct(t);
|
|
if (try_get_task_stack(t)) {
|
|
show_thread_info(t, false);
|
|
put_task_stack(t);
|
|
}
|
|
put_task_struct(t);
|
|
}
|
|
}
|
|
put_task_struct(p);
|
|
}
|
|
rcu_read_unlock();
|
|
log_hang_info("dump backtrace end: %llu\n", local_clock());
|
|
if (Hang_first_done == false) {
|
|
if (aee_aed_task)
|
|
send_sig(SIGUSR1, aee_aed_task, 1);
|
|
if (system_server_task)
|
|
send_sig(SIGQUIT, system_server_task, 1);
|
|
if (monkey_task)
|
|
send_sig(SIGQUIT, monkey_task, 1);
|
|
}
|
|
}
|
|
|
|
static void show_status(int flag)
|
|
{
|
|
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
#ifndef MODULE
|
|
if (Hang_first_done) { /* the last dump */
|
|
dump_mem_info();
|
|
dump_msdc_hang_info();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_MTK_HANG_PROC)
|
|
show_mem(0, NULL);
|
|
#endif
|
|
show_task_backtrace();
|
|
|
|
if (Hang_first_done) { /* the last dump */
|
|
/* debug_locks = 1; */
|
|
debug_show_all_locks();
|
|
#ifndef MODULE
|
|
show_free_areas(0, NULL);
|
|
#endif
|
|
run_callback();
|
|
|
|
}
|
|
}
|
|
|
|
static int dump_last_thread(void *arg)
|
|
{
|
|
/* unsigned long flags; */
|
|
struct sched_param param = {
|
|
.sched_priority = 99
|
|
};
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
pr_info("[Hang_Detect] dump last thread.\n");
|
|
show_status(1);
|
|
dump_bt_done = 1;
|
|
wake_up_interruptible(&dump_bt_done_wait);
|
|
return 0;
|
|
}
|
|
|
|
static int hang_detect_dump_thread(void *arg)
|
|
{
|
|
/* unsigned long flags; */
|
|
struct sched_param param = {
|
|
.sched_priority = 99
|
|
};
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
msleep(120 * 1000);
|
|
dump_bt_done = 1;
|
|
while (1) {
|
|
wait_event_interruptible(dump_bt_start_wait, dump_bt_done == 0);
|
|
show_status(0);
|
|
dump_bt_done = 1;
|
|
wake_up_interruptible(&dump_bt_done_wait);
|
|
}
|
|
pr_notice("[Hang_Detect] hang_detect dump thread exit.\n");
|
|
return 0;
|
|
}
|
|
|
|
void wake_up_dump(void)
|
|
{
|
|
dump_bt_done = 0;
|
|
wake_up_interruptible(&dump_bt_start_wait);
|
|
if (dump_bt_done != 1)
|
|
wait_event_interruptible_timeout(dump_bt_done_wait,
|
|
dump_bt_done == 1, HZ*10);
|
|
}
|
|
|
|
|
|
static bool check_white_list(void)
|
|
{
|
|
struct name_list *pList = NULL;
|
|
|
|
if (!white_list)
|
|
return true;
|
|
raw_spin_lock(&white_list_lock);
|
|
pList = white_list;
|
|
while (pList) {
|
|
if (find_task_by_name(pList->name) < 0) {
|
|
/* not fond the Task */
|
|
raw_spin_unlock(&white_list_lock);
|
|
return false;
|
|
}
|
|
pList = pList->next;
|
|
}
|
|
raw_spin_unlock(&white_list_lock);
|
|
return true;
|
|
}
|
|
|
|
static int hang_detect_thread(void *arg)
|
|
{
|
|
/* unsigned long flags; */
|
|
struct sched_param param = {
|
|
.sched_priority = 99
|
|
};
|
|
struct task_struct *hd_thread;
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
reset_hang_info();
|
|
msleep(120 * 1000);
|
|
pr_debug("[Hang_Detect] hang_detect thread starts.\n");
|
|
|
|
#ifdef BOOT_UP_HANG
|
|
hd_timeout = 9;
|
|
hang_detect_counter = 9;
|
|
hd_detect_enabled = true;
|
|
#endif
|
|
|
|
while (1) {
|
|
pr_info("[Hang_Detect] hang_detect thread counts down %d:%d, status %d.\n",
|
|
hang_detect_counter, hd_timeout, hd_detect_enabled);
|
|
#ifdef BOOT_UP_HANG
|
|
if (hd_detect_enabled)
|
|
#else
|
|
if (hd_detect_enabled && check_white_list())
|
|
#endif
|
|
{
|
|
|
|
if (hang_detect_counter <= 0) {
|
|
log_hang_info(
|
|
"[Hang_detect]Dump the %d time process bt.\n",
|
|
Hang_first_done ? 2 : 1);
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
if (!Hang_first_done) {
|
|
memset(Hang_Info, 0, MaxHangInfoSize);
|
|
Hang_Info_Size = 0;
|
|
}
|
|
#endif
|
|
if (Hang_first_done == true
|
|
&& dump_bt_done != 1) {
|
|
/* some time dump thread will block in dumping native bt */
|
|
/* so create new thread to dump enough kernel bt */
|
|
hd_thread = kthread_create(
|
|
dump_last_thread,
|
|
NULL, "hang_detect2");
|
|
if (hd_thread)
|
|
wake_up_process(hd_thread);
|
|
if (dump_bt_done != 1)
|
|
wait_event_interruptible_timeout(
|
|
dump_bt_done_wait,
|
|
dump_bt_done == 1,
|
|
HZ*10);
|
|
} else
|
|
wake_up_dump();
|
|
|
|
|
|
if (Hang_first_done == true) {
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
trigger_hang_db();
|
|
#else
|
|
BUG();
|
|
#endif
|
|
} else
|
|
Hang_first_done = true;
|
|
}
|
|
hang_detect_counter--;
|
|
}
|
|
|
|
msleep((HD_INTER) * 1000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void monitor_hang_kick(int lParam)
|
|
{
|
|
if (reboot_flag) {
|
|
pr_info("[Hang_Detect] in reboot flow.\n");
|
|
return;
|
|
}
|
|
|
|
if (lParam == 0) {
|
|
hd_detect_enabled = 0;
|
|
hang_detect_counter = hd_timeout;
|
|
pr_info("[Hang_Detect] hang_detect disabled\n");
|
|
} else if (lParam > 0) {
|
|
/* lParem=0x1000|timeout,only set in aee call when NE in
|
|
* system_server so only change hang_detect_counter when
|
|
* call from AEE
|
|
* Others ioctl, will change
|
|
* hd_detect_enabled & hang_detect_counter
|
|
*/
|
|
if (lParam & 0x1000) {
|
|
hang_detect_counter = hd_timeout =
|
|
((long)(lParam & 0x0fff) + HD_INTER - 1) / (HD_INTER);
|
|
} else {
|
|
hd_detect_enabled = 1;
|
|
hang_detect_counter = hd_timeout =
|
|
((long)lParam + HD_INTER - 1) / (HD_INTER);
|
|
}
|
|
|
|
if (hd_timeout < 3) {
|
|
/* hang detect min timeout is 10 (5min) */
|
|
hang_detect_counter = 3;
|
|
hd_timeout = 3;
|
|
}
|
|
pr_info("[Hang_Detect] hang_detect enabled %d\n", hd_timeout);
|
|
}
|
|
reset_hang_info();
|
|
}
|
|
|
|
int hang_detect_init(void)
|
|
{
|
|
|
|
struct task_struct *hd_thread;
|
|
|
|
pr_debug("[Hang_Detect] Initialize proc\n");
|
|
hd_thread = kthread_create(hang_detect_thread, NULL, "hang_detect");
|
|
if (hd_thread)
|
|
wake_up_process(hd_thread);
|
|
|
|
hd_thread = kthread_create(hang_detect_dump_thread, NULL,
|
|
"hang_detect1");
|
|
if (hd_thread)
|
|
wake_up_process(hd_thread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init monitor_hang_init(void)
|
|
{
|
|
int err = 0;
|
|
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
Hang_Info = kmalloc(MAX_HANG_INFO_SIZE, GFP_KERNEL);
|
|
if (Hang_Info == NULL)
|
|
return 1;
|
|
#if IS_ENABLED(CONFIG_MTK_AEE_IPANIC)
|
|
mrdump_mini_add_hang_raw((unsigned long)Hang_Info,
|
|
MaxHangInfoSize);
|
|
#endif
|
|
#endif
|
|
|
|
err = misc_register(&Hang_Monitor_dev);
|
|
if (unlikely(err)) {
|
|
pr_notice("failed to register Hang_Monitor_dev device!\n");
|
|
return err;
|
|
}
|
|
hang_detect_init();
|
|
mrdump_regist_hang_bt(show_task_info);
|
|
|
|
#ifdef CONFIG_MTK_HANG_PROC
|
|
pe = proc_create("monitor_hang", 0660, NULL, &monitor_hang_fops);
|
|
if (!pe)
|
|
return -ENOMEM;
|
|
#endif
|
|
|
|
return err;
|
|
}
|
|
|
|
static void __exit monitor_hang_exit(void)
|
|
{
|
|
mrdump_regist_hang_bt(NULL);
|
|
misc_deregister(&Hang_Monitor_dev);
|
|
#ifdef CONFIG_MTK_HANG_DETECT_DB
|
|
/* kfree(NULL) is safe */
|
|
kfree(Hang_Info);
|
|
#endif
|
|
}
|
|
|
|
module_init(monitor_hang_init);
|
|
module_exit(monitor_hang_exit);
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("MediaTek MonitorHang Driver");
|
|
MODULE_AUTHOR("MediaTek Inc.");
|