diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 4dbdc87e809ce0a06c967d56838aa1f26e6b38a7..e7cd80bb876126bbef7031efcb4143aa1163249b 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -122,4 +122,6 @@ source "drivers/staging/hilog/Kconfig" source "drivers/staging/hievent/Kconfig" +source "drivers/staging/hungtask/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index b33dfbdcce7e32c754edc97a73e24fea8afa7f52..dfa144064b947d0878e158fcaa707337145a3173 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -51,3 +51,4 @@ obj-$(CONFIG_WFX) += wfx/ obj-y += hikey9xx/ obj-$(CONFIG_HILOG) += hilog/ obj-$(CONFIG_HIEVENT) += hievent/ +obj-$(CONFIG_DFX_HUNGTASK) += hungtask/ diff --git a/drivers/staging/hungtask/Kconfig b/drivers/staging/hungtask/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..c7b43fa6eb62056b3e693e96dcef0ed452db08d2 --- /dev/null +++ b/drivers/staging/hungtask/Kconfig @@ -0,0 +1,13 @@ +config DFX_HUNGTASK + bool "DFX hungtask" + depends on DETECT_HUNG_TASK + default n + help + Base DFX hungtask module + +config DFX_HUNGTASK_USER + bool "DFX hungtask user watchdog module" + depends on DFX_HUNGTASK + default n + help + DFX hungtask user watchdog module \ No newline at end of file diff --git a/drivers/staging/hungtask/Makefile b/drivers/staging/hungtask/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..24951f2cf42cab6f4c0b664da9e6723d0a5ae986 --- /dev/null +++ b/drivers/staging/hungtask/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_DFX_HUNGTASK) += hungtask_base.o +obj-$(CONFIG_DFX_HUNGTASK_USER) += hungtask_user.o diff --git a/drivers/staging/hungtask/hungtask_base.c b/drivers/staging/hungtask/hungtask_base.c new file mode 100644 index 0000000000000000000000000000000000000000..740a5d1e257824cb41cd89dadf7456c52381602f --- /dev/null +++ b/drivers/staging/hungtask/hungtask_base.c @@ -0,0 +1,1041 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Huawei Technologies Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "hungtask_base " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DFX_ZEROHUNG +#include +#endif +#include +#include "hungtask_user.h" + +static struct rb_root list_tasks = RB_ROOT; +static DEFINE_SPINLOCK(list_tasks_lock); +static struct hlist_head whitelist[WHITELIST_LEN]; +static struct whitelist_item whitetmplist[WHITELIST_LEN]; +static bool whitelist_empty = true; +static int remove_cnt; +static struct task_item *remove_list[MAX_REMOVE_LIST_NUM + 1]; +static unsigned long __read_mostly hungtask_timeout_secs = + CONFIG_DEFAULT_HUNG_TASK_TIMEOUT; +static int did_panic; +static unsigned int hungtask_enable = HT_DISABLE; +static unsigned int whitelist_type = WHITE_LIST; +static int whitelist_dump_cnt = DEFAULT_WHITE_DUMP_CNT; +static int whitelist_panic_cnt = DEFAULT_WHITE_PANIC_CNT; +static int appspawn_pid; +static int dump_and_upload; +static int time_since_upload; +static int hung_task_must_panic; +static int report_zrhung_id; +static struct task_hung_upload upload; +static int do_refresh; +static char frozen_buf[FROZEN_BUF_LEN]; +static int frozen_used; +static bool frozed_head; +static unsigned long cur_heartbeat; +static struct work_struct send_work; +static char report_buf_text[REPORT_MSGLENGTH]; + +bool hashlist_find(struct hlist_head *head, int count, pid_t tgid) +{ + struct hashlist_node *hnode = NULL; + + if (count <= 0) + return false; + if (hlist_empty(&head[tgid % count])) + return false; + hlist_for_each_entry(hnode, &head[tgid % count], list) { + if (hnode->pid == tgid) + return true; + } + return false; +} + +void hashlist_clear(struct hlist_head *head, int count) +{ + int i = 0; + struct hlist_node *n = NULL; + struct hashlist_node *hnode = NULL; + + for (i = 0; i < count; i++) { + hlist_for_each_entry_safe(hnode, n, &head[i], list) { + hlist_del(&hnode->list); + kfree(hnode); + hnode = NULL; + } + } + for (i = 0; i < count; i++) + INIT_HLIST_HEAD(&head[i]); +} + +bool hashlist_insert(struct hlist_head *head, int count, pid_t tgid) +{ + struct hashlist_node *hnode = NULL; + + if (hashlist_find(head, count, tgid)) + return false; + hnode = kmalloc(sizeof(struct hashlist_node), GFP_ATOMIC); + if (!hnode) + return false; + INIT_HLIST_NODE(&hnode->list); + hnode->pid = tgid; + hlist_add_head(&hnode->list, &head[tgid % count]); + return true; +} + +static bool rcu_lock_break(struct task_struct *g, struct task_struct *t) +{ + bool can_cont = false; + + get_task_struct(g); + get_task_struct(t); + rcu_read_unlock(); + cond_resched(); + rcu_read_lock(); + can_cont = pid_alive(g) && pid_alive(t); + put_task_struct(t); + put_task_struct(g); + return can_cont; +} + +static bool rcu_break(int *max_count, int *batch_count, + struct task_struct *g, + struct task_struct *t) +{ + if (!(*max_count)--) + return true; + if (!--(*batch_count)) { + *batch_count = HUNG_TASK_BATCHING; + if (!rcu_lock_break(g, t)) + return true; + } + return false; +} + +static pid_t get_pid_by_name(const char *name) +{ + int max_count = PID_MAX_LIMIT; + int batch_count = HUNG_TASK_BATCHING; + struct task_struct *g = NULL; + struct task_struct *t = NULL; + int pid = 0; + + rcu_read_lock(); + do_each_thread(g, t) { + if (rcu_break(&max_count, &batch_count, g, t)) + goto unlock; + if (!strncmp(t->comm, name, TASK_COMM_LEN)) { + pid = t->tgid; + goto unlock; + } + } while_each_thread(g, t); + +unlock: + rcu_read_unlock(); + return pid; +} + +static unsigned int get_task_type(pid_t pid, pid_t tgid, struct task_struct *parent) +{ + unsigned int flag = TASK_TYPE_IGNORE; + /* check tgid of it's parent as PPID */ + if (parent) { + pid_t ppid = parent->tgid; + + if (ppid == PID_KTHREAD) + flag |= TASK_TYPE_KERNEL; + else if (ppid == appspawn_pid) + flag |= TASK_TYPE_APP; + else if (ppid == PID_INIT) + flag |= TASK_TYPE_NATIVE; + } + if (!whitelist_empty && hashlist_find(whitelist, WHITELIST_LEN, tgid)) + flag |= TASK_TYPE_WHITE | TASK_TYPE_JANK; + + return flag; +} + +static void refresh_appspawn_pids(void) +{ + int max_count = PID_MAX_LIMIT; + int batch_count = HUNG_TASK_BATCHING; + struct task_struct *g = NULL; + struct task_struct *t = NULL; + + rcu_read_lock(); + do_each_thread(g, t) { + if (rcu_break(&max_count, &batch_count, g, t)) + goto unlock; + if (!strncmp(t->comm, "appspawn", TASK_COMM_LEN)) + appspawn_pid = t->tgid; + } while_each_thread(g, t); +unlock: + rcu_read_unlock(); +} + +static void refresh_task_type(pid_t pid, int task_type) +{ + struct task_item *item = NULL; + struct rb_node *p = NULL; + + spin_lock(&list_tasks_lock); + for (p = rb_first(&list_tasks); p; p = rb_next(p)) { + item = rb_entry(p, struct task_item, node); + if (item->tgid == pid) + item->task_type = task_type; + } + spin_unlock(&list_tasks_lock); +} + +static void refresh_whitelist_pids(void) +{ + int i; + + hashlist_clear(whitelist, WHITELIST_LEN); + for (i = 0; i < WHITELIST_LEN; i++) { + if (!strlen(whitetmplist[i].name)) + continue; + whitetmplist[i].pid = + get_pid_by_name(whitetmplist[i].name); + if (!whitetmplist[i].pid) + continue; + refresh_task_type(whitetmplist[i].pid, + TASK_TYPE_WHITE | TASK_TYPE_JANK); + if (hashlist_insert(whitelist, WHITELIST_LEN, + whitetmplist[i].pid)) + pr_info("whitelist[%d]-%s-%d\n", i, + whitetmplist[i].name, whitetmplist[i].pid); + else + pr_info("can't find %s\n", whitetmplist[i].name); + } + refresh_appspawn_pids(); +} + +static struct task_item *find_task(pid_t pid, struct rb_root *root) +{ + struct rb_node **p = &root->rb_node; + struct task_item *cur = NULL; + struct rb_node *parent = NULL; + + while (*p) { + parent = *p; + cur = rb_entry(parent, struct task_item, node); + if (!cur) + return NULL; + if (pid < cur->pid) + p = &(*p)->rb_left; + else if (pid > cur->pid) + p = &(*p)->rb_right; + else + return cur; + } + return NULL; +} + +static bool insert_task(struct task_item *item, struct rb_root *root) +{ + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct task_item *cur = NULL; + + while (*p) { + parent = *p; + + cur = rb_entry(parent, struct task_item, node); + if (!cur) + return false; + if (item->pid < cur->pid) { + p = &(*p)->rb_left; + } else if (item->pid > cur->pid) { + p = &(*p)->rb_right; + } else { + pr_info("insert pid=%d,tgid=%d,name=%s,type=%d fail\n", + item->pid, item->tgid, + item->name, item->task_type); + return false; + } + } + rb_link_node(&item->node, parent, p); + rb_insert_color(&item->node, root); + return true; +} + +void show_block_task(struct task_item *taskitem, struct task_struct *p) +{ + unsigned long last_arrival; + unsigned long last_queued; + +#ifdef CONFIG_SCHED_INFO + last_arrival = p->sched_info.last_arrival; + last_queued = p->sched_info.last_queued; +#else + last_arrival = 0; + last_queued = 0; +#endif /* CONFIG_SCHED_INFO */ + if (unlikely(p->flags & PF_FROZEN)) { + if (taskitem) + pr_err("name=%s,PID=%d,tgid=%d,tgname=%s," + "FROZEN for %ds,type=%d,la%lu/lq%lu\n", + p->comm, p->pid, p->tgid, + p->group_leader->comm, + taskitem->d_state_time * HEARTBEAT_TIME, + taskitem->task_type, + last_arrival, last_queued); + else + pr_err("name=%s,PID=%d,tgid=%d,tgname=%s," + "just FROZE,la%lu/lq%lu\n", + p->comm, p->pid, p->tgid, + p->group_leader->comm, + last_arrival, last_queued); + } else { + if (taskitem) + pr_err("name=%s,PID=%d,tgid=%d,prio=%d,cpu=%d,tgname=%s," + "type=%d,blocked for %ds,la%lu/lq%lu\n", + taskitem->name, taskitem->pid, p->tgid, p->prio, + task_cpu(p), p->group_leader->comm, taskitem->task_type, + taskitem->d_state_time * HEARTBEAT_TIME, + last_arrival, last_queued); + else + pr_err("name=%s,PID=%d,tgid=%d,prio=%d,cpu=%d," + "tgname=%s,la%lu/lq%lu\n", + p->comm, p->pid, p->tgid, p->prio, task_cpu(p), + p->group_leader->comm, + last_arrival, last_queued); + + sched_show_task(p); + } +} + +void htbase_show_state_filter(unsigned long state_filter) +{ + struct task_struct *g = NULL; + struct task_struct *p = NULL; + struct task_item *taskitem = NULL; + +#if BITS_PER_LONG == 32 + pr_info(" task PC stack pid father\n"); +#else + pr_info(" task PC stack pid father\n"); +#endif + rcu_read_lock(); + for_each_process_thread(g, p) { + /* + * reset the NMI-timeout, listing all files on a slow + * console might take a lot of time: + */ + touch_nmi_watchdog(); + if ((p->state == TASK_RUNNING) || (p->state & state_filter)) { + spin_lock(&list_tasks_lock); + taskitem = find_task(p->pid, &list_tasks); + spin_unlock(&list_tasks_lock); + show_block_task(taskitem, p); + } + } + touch_all_softlockup_watchdogs(); + rcu_read_unlock(); + /* Show locks if hungtask happen */ + if ((state_filter == TASK_UNINTERRUPTIBLE) || !state_filter) + debug_show_all_locks(); +} + +void hungtask_show_state_filter(unsigned long state_filter) +{ + pr_err("BinderChain_SysRq start\n"); + htbase_show_state_filter(state_filter); + pr_err("BinderChain_SysRq end\n"); +} + +void do_dump_task(struct task_struct *task) +{ + sched_show_task(task); + debug_show_held_locks(task); +} + +void do_show_task(struct task_struct *task, unsigned int flag, int d_state_time) +{ + pr_err("%s, flag=%d\n", __func__, flag); + rcu_read_lock(); + if (!pid_alive(task)) { + rcu_read_unlock(); + return; + } + if (flag & (FLAG_DUMP_WHITE | FLAG_DUMP_APP)) { + int cnt = 0; + + trace_sched_process_hang(task); + cnt = d_state_time; + pr_err("INFO: task %s:%d tgid:%d blocked for %ds in %s\n", + task->comm, task->pid, task->tgid, + (HEARTBEAT_TIME * cnt), + (flag & FLAG_DUMP_WHITE) ? "whitelist" : "applist"); + pr_err(" %s %s %.*s\n", + print_tainted(), init_utsname()->release, + (int)strcspn(init_utsname()->version, " "), + init_utsname()->version); + do_dump_task(task); + touch_nmi_watchdog(); + if (flag & FLAG_DUMP_WHITE && (!dump_and_upload)) { + dump_and_upload++; + upload.pid = task->pid; + upload.tgid = task->tgid; + upload.duration = d_state_time; + memset(upload.name, 0, sizeof(upload.name)); + strncpy(upload.name, task->comm, sizeof(task->comm)); + upload.flag = flag; + if (task->flags & PF_FROZEN) + upload.flag = (upload.flag | FLAG_PF_FROZEN); + } + } + rcu_read_unlock(); +} + +static void do_panic(void) +{ + if (sysctl_hung_task_panic) { + trigger_all_cpu_backtrace(); + panic("hungtask: blocked tasks"); + } +} + +static void create_taskitem(struct task_item *taskitem, + struct task_struct *task) +{ + taskitem->pid = task->pid; + taskitem->tgid = task->tgid; + memset(taskitem->name, 0, sizeof(taskitem->name)); + strncpy(taskitem->name, task->comm, sizeof(task->comm)); + taskitem->switch_count = task->nvcsw + task->nivcsw; + taskitem->dump_wa = 0; /* whitelist or applist task dump times */ + taskitem->panic_wa = 0; /* whitelist or applist task panic times */ + taskitem->d_state_time = -1; + taskitem->isdone_wa = true; /* if task in white or app dealed */ +} + +static bool refresh_task(struct task_item *taskitem, struct task_struct *task) +{ + bool is_called = false; + + if (taskitem->switch_count != (task->nvcsw + task->nivcsw)) { + taskitem->switch_count = task->nvcsw + task->nivcsw; + is_called = true; + return is_called; + } + if (taskitem->task_type & TASK_TYPE_WHITE) { + taskitem->isdone_wa = false; + taskitem->dump_wa++; + taskitem->panic_wa++; + } + taskitem->d_state_time++; + if (task->flags & PF_FROZEN) + taskitem->task_type |= TASK_TYPE_FROZEN; + return is_called; +} + +static void remove_list_tasks(struct task_item *item) +{ + rb_erase(&item->node, &list_tasks); + kfree(item); +} + +static void shrink_process_item(struct task_item *item, bool *is_finish) +{ + if (remove_cnt >= MAX_REMOVE_LIST_NUM) { + int i; + + remove_list[remove_cnt++] = item; + for (i = 0; i < remove_cnt; i++) + remove_list_tasks(remove_list[i]); + remove_cnt = 0; + *is_finish = false; + } else { + remove_list[remove_cnt++] = item; + } +} + +static void shrink_list_tasks(void) +{ + int i; + bool is_finish = false; + struct rb_node *n = NULL; + struct task_item *item = NULL; + + spin_lock(&list_tasks_lock); + while (!is_finish) { + is_finish = true; + for (n = rb_first(&list_tasks); n != NULL; n = rb_next(n)) { + item = rb_entry(n, struct task_item, node); + if (!item) + continue; + if (item->isdone_wa) { + shrink_process_item(item, &is_finish); + if (!is_finish) + break; + } + } + } + for (i = 0; i < remove_cnt; i++) + remove_list_tasks(remove_list[i]); + remove_cnt = 0; + spin_unlock(&list_tasks_lock); +} + +static void check_parameters(void) +{ + if ((whitelist_dump_cnt < 0) || + (whitelist_dump_cnt > DEFAULT_WHITE_DUMP_CNT)) + whitelist_dump_cnt = DEFAULT_WHITE_DUMP_CNT; + if ((whitelist_panic_cnt <= 0) || + (whitelist_panic_cnt > DEFAULT_WHITE_PANIC_CNT)) + whitelist_panic_cnt = DEFAULT_WHITE_PANIC_CNT; +} + +static void send_work_handler(struct work_struct *data) +{ +#ifdef CONFIG_DFX_ZEROHUNG + zrhung_send_event(HUNGTASK_DOMAIN, HUNGTASK_NAME, + report_buf_text); +#endif +} + +static void htbase_report_zrhung_event(const char *report_buf_tag) +{ + htbase_show_state_filter(TASK_UNINTERRUPTIBLE); + pr_err("%s end\n", report_buf_tag); + schedule_work(&send_work); + report_zrhung_id++; +} + +static void htbase_report_zrhung(unsigned int event) +{ + bool report_load = false; + char report_buf_tag[REPORT_MSGLENGTH] = {0}; + char report_name[TASK_COMM_LEN + 1] = {0}; + int report_pid = 0; + int report_hungtime = 0; + int report_tasktype = 0; + + if (!event) + return; + if (event & HUNGTASK_EVENT_WHITELIST) { + snprintf(report_buf_tag, sizeof(report_buf_tag), + "hungtask_whitelist_%d", report_zrhung_id); + strncpy(report_name, upload.name, TASK_COMM_LEN); + report_pid = upload.pid; + report_tasktype = TASK_TYPE_WHITE; + report_hungtime = whitelist_dump_cnt * HEARTBEAT_TIME; + report_load = true; + } else { + pr_err("No such event report to zerohung!"); + } + pr_err("%s start\n", report_buf_tag); + if (event & HUNGTASK_EVENT_WHITELIST) + pr_err("report HUNGTASK_EVENT_WHITELIST to zrhung\n"); + if (upload.flag & FLAG_PF_FROZEN) + snprintf(report_buf_text, sizeof(report_buf_text), + "Task %s(%s) pid %d type %d blocked %ds.", + report_name, "FROZEN", report_pid, report_tasktype, report_hungtime); + else + snprintf(report_buf_text, sizeof(report_buf_text), + "Task %s pid %d type %d blocked %ds.", + report_name, report_pid, report_tasktype, report_hungtime); + if (report_load) + htbase_report_zrhung_event(report_buf_tag); +} + +static int print_frozen_list_item(int pid) +{ + int tmp; + + if (!frozed_head) { + tmp = snprintf(frozen_buf, FROZEN_BUF_LEN, "%s", "FROZEN Pid:"); + if (tmp < 0) + return -1; + frozen_used += min(tmp, FROZEN_BUF_LEN - 1); + frozed_head = true; + } + tmp = snprintf(frozen_buf + frozen_used, FROZEN_BUF_LEN - frozen_used, "%d,", + pid); + if (tmp < 0) + return -1; + frozen_used += min(tmp, FROZEN_BUF_LEN - frozen_used - 1); + return frozen_used; +} + +int dump_task_wa(struct task_item *item, int dump_cnt, + struct task_struct *task, unsigned int flag) +{ + int ret = 0; + + if ((item->d_state_time > TWO_MINUTES) && + (item->d_state_time % TWO_MINUTES != 0)) + return ret; + if ((item->d_state_time > HUNG_TEN_MINUTES) && + (item->d_state_time % HUNG_TEN_MINUTES != 0)) + return ret; + if ((item->d_state_time > HUNG_ONE_HOUR) && + (item->d_state_time % HUNG_ONE_HOUR != 0)) + return ret; + if (dump_cnt && (item->dump_wa > dump_cnt)) { + item->dump_wa = 1; + if (!dump_and_upload && task->flags & PF_FROZEN) { + int tmp = print_frozen_list_item(item->pid); + if (tmp < 0) + return ret; + if (tmp >= FROZEN_BUF_LEN - 1) { + pr_err("%s", frozen_buf); + memset(frozen_buf, 0, sizeof(frozen_buf)); + frozen_used = 0; + frozed_head = false; + print_frozen_list_item(item->pid); + } + } else if (!dump_and_upload) { + pr_err("Ready to dump a task %s\n", item->name); + do_show_task(task, flag, item->d_state_time); + ret++; + } + } + return ret; +} + +static void update_panic_task(struct task_item *item) +{ + if (upload.pid != 0) + return; + + upload.pid = item->pid; + upload.tgid = item->tgid; + memset(upload.name, 0, sizeof(upload.name)); + strncpy(upload.name, item->name, sizeof(item->name)); +} + +static void deal_task(struct task_item *item, struct task_struct *task, bool is_called) +{ + int any_dumped_num = 0; + + if (is_called) { + item->dump_wa = 1; + item->panic_wa = 1; + item->d_state_time = 0; + return; + } + if (item->task_type & TASK_TYPE_WHITE) + any_dumped_num = dump_task_wa(item, whitelist_dump_cnt, task, + FLAG_DUMP_WHITE); + if (!is_called && (item->task_type & TASK_TYPE_WHITE)) { + if (whitelist_panic_cnt && item->panic_wa > whitelist_panic_cnt) { + pr_err("Task %s is causing panic\n", item->name); + update_panic_task(item); + item->panic_wa = 0; + hung_task_must_panic++; + } else { + item->isdone_wa = false; + } + } + if (item->isdone_wa) + remove_list_tasks(item); +} + +static bool check_conditions(struct task_struct *task, unsigned int task_type) +{ + bool no_check = true; + + if (task->flags & PF_FROZEN) + return no_check; + if (task_type & TASK_TYPE_WHITE && + (whitelist_dump_cnt || whitelist_panic_cnt)) + no_check = false; + return no_check; +} + +static void htbase_check_one_task(struct task_struct *t) +{ + unsigned int task_type = TASK_TYPE_IGNORE; + unsigned long switch_count = t->nvcsw + t->nivcsw; + struct task_item *taskitem = NULL; + bool is_called = false; + + if (unlikely(!switch_count)) { + pr_info("skip one's switch_count is zero\n"); + return; + } + + taskitem = find_task(t->pid, &list_tasks); + if (taskitem) { + if (check_conditions(t, taskitem->task_type)) + return; + is_called = refresh_task(taskitem, t); + } else { + task_type = get_task_type(t->pid, t->tgid, t->real_parent); + if (check_conditions(t, task_type)) + return; + taskitem = kmalloc(sizeof(*taskitem), GFP_ATOMIC); + if (!taskitem) { + pr_err("kmalloc failed"); + return; + } + memset(taskitem, 0, sizeof(*taskitem)); + taskitem->task_type = task_type; + create_taskitem(taskitem, t); + is_called = refresh_task(taskitem, t); + insert_task(taskitem, &list_tasks); + } + deal_task(taskitem, t, is_called); +} + +static void htbase_pre_process(void) +{ + htbase_set_timeout_secs(sysctl_hung_task_timeout_secs); + cur_heartbeat++; + if ((cur_heartbeat % REFRESH_INTERVAL) == 0) + do_refresh = 1; + else + do_refresh = 0; + if (do_refresh || (cur_heartbeat < TIME_REFRESH_PIDS)) { + refresh_whitelist_pids(); + check_parameters(); + } +} + +static void htbase_post_process(void) +{ + struct rb_node *n = NULL; + unsigned int hungevent = 0; + + if (frozen_used) { + pr_err("%s", frozen_buf); + memset(frozen_buf, 0, sizeof(frozen_buf)); + frozen_used = 0; + frozed_head = false; + } + if (dump_and_upload == HUNG_TASK_UPLOAD_ONCE) { + hungevent |= HUNGTASK_EVENT_WHITELIST; + dump_and_upload++; + } + if (dump_and_upload > 0) { + time_since_upload++; + if (time_since_upload > (whitelist_panic_cnt - whitelist_dump_cnt)) { + dump_and_upload = 0; + time_since_upload = 0; + } + } + if (hung_task_must_panic) { + htbase_show_state_filter(TASK_UNINTERRUPTIBLE); + hung_task_must_panic = 0; + pr_err("Task %s:%d blocked for %ds is causing panic\n", + upload.name, upload.pid, + whitelist_panic_cnt * HEARTBEAT_TIME); + do_panic(); + } + htuser_post_process_userlist(); + shrink_list_tasks(); + for (n = rb_first(&list_tasks); n != NULL; n = rb_next(n)) { + struct task_item *item = rb_entry(n, struct task_item, node); + item->isdone_wa = true; + } + + if (hungevent) + htbase_report_zrhung(hungevent); +} + +void htbase_check_tasks(unsigned long timeout) +{ + int max_count = PID_MAX_LIMIT; + int batch_count = HUNG_TASK_BATCHING; + struct task_struct *g = NULL; + struct task_struct *t = NULL; + + if (!hungtask_enable) + return; + if (test_taint(TAINT_DIE) || did_panic) { + pr_err("already in doing panic\n"); + return; + } + + htbase_pre_process(); + rcu_read_lock(); + for_each_process_thread(g, t) { + if (!max_count--) + goto unlock; + if (!--batch_count) { + batch_count = HUNG_TASK_BATCHING; + if (!rcu_lock_break(g, t)) + goto unlock; + } + if ((t->state == TASK_UNINTERRUPTIBLE) || + (t->state == TASK_KILLABLE)) + htbase_check_one_task(t); + } +unlock: + rcu_read_unlock(); + htbase_post_process(); +} + +static ssize_t htbase_enable_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + if (hungtask_enable) + return snprintf(buf, ENABLE_SHOW_LEN, "on\n"); + else + return snprintf(buf, ENABLE_SHOW_LEN, "off\n"); +} + +static ssize_t htbase_enable_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char tmp[6]; /* only storage "on" "off" "kick" and enter */ + size_t len; + char *p = NULL; + + if (!buf) + return -EINVAL; + if ((count < 2) || (count > (sizeof(tmp) - 1))) { + pr_err("string too long or too short\n"); + return -EINVAL; + } + + p = memchr(buf, '\n', count); + len = p ? (size_t)(p - buf) : count; + memset(tmp, 0, sizeof(tmp)); + strncpy(tmp, buf, len); + if (!strncmp(tmp, "on", strlen(tmp))) { + hungtask_enable = HT_ENABLE; + pr_info("set hungtask_enable to enable\n"); + } else if (!strncmp(tmp, "off", strlen(tmp))) { + hungtask_enable = HT_DISABLE; + pr_info("set hungtask_enable to disable\n"); + } else { + pr_err("only accept on or off\n"); + } + return (ssize_t) count; +} + +static ssize_t htbase_monitorlist_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + int i; + char *start = buf; + char all_buf[WHITELIST_STORE_LEN - 20]; /* exclude extra header len 20*/ + unsigned long len = 0; + + memset(all_buf, 0, sizeof(all_buf)); + for (i = 0; i < WHITELIST_LEN; i++) { + if (whitetmplist[i].pid > 0) { + len += snprintf(all_buf + len, sizeof(all_buf) - len, + "%s-%d,", whitetmplist[i].name, whitetmplist[i].pid); + if (!(len < sizeof(all_buf))) { + len = sizeof(all_buf) - 1; + break; + } + } + } + if (len > 0) + all_buf[len] = 0; + if (whitelist_type == WHITE_LIST) + buf += snprintf(buf, WHITELIST_STORE_LEN, "whitelist:[%s]\n", all_buf); + else if (whitelist_type == BLACK_LIST) + buf += snprintf(buf, WHITELIST_STORE_LEN, "blacklist:[%s]\n", all_buf); + else + buf += snprintf(buf, WHITELIST_STORE_LEN, "\n"); + return buf - start; +} + +static void htbase_monitorlist_update(char **cur) +{ + int index = 0; + char *token = NULL; + + hashlist_clear(whitelist, WHITELIST_LEN); + memset(whitetmplist, 0, sizeof(whitetmplist)); + /* generate the new whitelist */ + for (; ; ) { + token = strsep(cur, ","); + if (token && strlen(token)) { + strncpy(whitetmplist[index].name, token, TASK_COMM_LEN); + if (strlen(whitetmplist[index].name) > 0) + whitelist_empty = false; + index++; + if (index >= WHITELIST_LEN) + break; + } + if (!(*cur)) + break; + } +} + +/* + * monitorlist_store - Called when 'write/echo' method is + * used on entry '/sys/kernel/hungtask/monitorlist'. + */ +static ssize_t htbase_monitorlist_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + size_t len; + char *p = NULL; + char all_buf[WHITELIST_STORE_LEN]; + char *cur = all_buf; + + + if ((n < 2) || (n > (sizeof(all_buf) - 1))) { + pr_err("whitelist input string illegal\n"); + return -EINVAL; + } + if (!buf) + return -EINVAL; + /* + * input format: + * write /sys/kernel/hungtask/monitorlist "whitelist, + * system_server,surfaceflinger" + */ + p = memchr(buf, '\n', n); + len = p ? (size_t)(p - buf) : n; /* exclude the '\n' */ + + memset(all_buf, 0, sizeof(all_buf)); + len = len > WHITELIST_STORE_LEN ? WHITELIST_STORE_LEN : len; + strncpy(all_buf, buf, len); + p = strsep(&cur, ","); + if (!cur) { + pr_err("string is not correct\n"); + return -EINVAL; + } + if (!strncmp(p, "whitelist", n)) { + whitelist_type = WHITE_LIST; + } else { + if (!strncmp(p, "blacklist", n)) + pr_err("blacklist is not support\n"); + else + pr_err("wrong list type is set\n"); + return -EINVAL; + } + if (!strlen(cur)) { + pr_err("at least one process need to be set\n"); + return -EINVAL; + } + pr_err("whitelist is %s\n", cur); + + htbase_monitorlist_update(&cur); + /* check again in case user input "whitelist,,,,,," */ + if (whitelist_empty) { + pr_err("at least one process need to be set\n"); + return -EINVAL; + } + return (ssize_t) n; +} + +/* used for sysctl at "/proc/sys/kernel/hung_task_timeout_secs" */ +void htbase_set_timeout_secs(unsigned long new_hungtask_timeout_secs) +{ + if ((new_hungtask_timeout_secs > CONFIG_DEFAULT_HUNG_TASK_TIMEOUT) || + (new_hungtask_timeout_secs % HEARTBEAT_TIME)) + return; + hungtask_timeout_secs = new_hungtask_timeout_secs; + /* + * if user change panic timeout value, we sync it to dump value + * defaultly, user can set it diffrently + */ + whitelist_panic_cnt = (int)(hungtask_timeout_secs / HEARTBEAT_TIME); + if (whitelist_panic_cnt > THIRTY_SECONDS) + whitelist_dump_cnt = whitelist_panic_cnt / HT_DUMP_IN_PANIC_LOOSE; + else + whitelist_dump_cnt = whitelist_panic_cnt / HT_DUMP_IN_PANIC_STRICT; +} + +void htbase_set_panic(int new_did_panic) +{ + did_panic = new_did_panic; +} + +static struct kobj_attribute timeout_attribute = { + .attr = { + .name = "enable", + .mode = 0640, + }, + .show = htbase_enable_show, + .store = htbase_enable_store, +}; + +static struct kobj_attribute monitorlist_attr = { + .attr = { + .name = "monitorlist", + .mode = 0640, + }, + .show = htbase_monitorlist_show, + .store = htbase_monitorlist_store, +}; + +#ifdef CONFIG_DFX_HUNGTASK_USER +static struct kobj_attribute userlist_attr = { + .attr = { + .name = "userlist", + .mode = 0640, + }, + .show = htuser_list_show, + .store = htuser_list_store, +}; +#endif + +static struct attribute *attrs[] = { + &timeout_attribute.attr, + &monitorlist_attr.attr, +#ifdef CONFIG_DFX_HUNGTASK_USER + &userlist_attr.attr, +#endif + NULL +}; + +static struct attribute_group hungtask_attr_group = { + .attrs = attrs, +}; + +static struct kobject *hungtask_kobj; +int htbase_create_sysfs(void) +{ + int i; + int ret; + + /* sleep 1000ms and wait /sys/kernel ready */ + while (!kernel_kobj) + msleep(1000); + + /* Create kobject named "hungtask" located at /sys/kernel/huangtask */ + hungtask_kobj = kobject_create_and_add("hungtask", kernel_kobj); + if (!hungtask_kobj) + return -ENOMEM; + ret = sysfs_create_group(hungtask_kobj, &hungtask_attr_group); + if (ret) + kobject_put(hungtask_kobj); + + for (i = 0; i < WHITELIST_LEN; i++) + INIT_HLIST_HEAD(&whitelist[i]); + memset(whitetmplist, 0, sizeof(whitetmplist)); + + INIT_WORK(&send_work, send_work_handler); + + return ret; +} diff --git a/drivers/staging/hungtask/hungtask_user.c b/drivers/staging/hungtask/hungtask_user.c new file mode 100644 index 0000000000000000000000000000000000000000..7070ba197d9bdc234963ccfc54137168fcfa054a --- /dev/null +++ b/drivers/staging/hungtask/hungtask_user.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Huawei Technologies Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "hungtask_user " fmt + +#include +#include +#include +#include +#include + +#include + +#define CMD_MIN_LEN 3 +#define CMD_MAX_LEN 20 +#define USERLIST_NUM 10 +#define MAX_USER_TIMEOUT 120 +#define MAX_SHOW_LEN 512 + +struct user_item { + pid_t pid; + int cur_cnt; + int panic_cnt; +}; + +static struct user_item userlist[USERLIST_NUM]; +static int userlist_count; +static DEFINE_SPINLOCK(userlist_lock); +static bool is_registered; +static bool need_panic; +static bool need_dump; +static int block_time; +static int block_pid; + +static void htuser_show_task(int pid) +{ + struct task_struct *p = NULL; + + p = pid_task(find_vpid(pid), PIDTYPE_PID); + if (p == NULL) { + pr_err("can not find pid %d\n", pid); + return; + } + + if (p->flags & PF_FROZEN) { + pr_info("process %d is frozen\n", pid); + return; + } + if (p->state == TASK_UNINTERRUPTIBLE) { + pr_err("UserList_KernelStack start\n"); + sched_show_task(p); + pr_err("UserList_KernelStack end\n"); + } +} + +static void htuser_list_insert(int pid, int count) +{ + spin_lock(&userlist_lock); + if (userlist_count >= USERLIST_NUM) { + pr_err("list is full\n"); + spin_unlock(&userlist_lock); + return; + } + userlist[userlist_count].pid = pid; + userlist[userlist_count].cur_cnt = 0; + userlist[userlist_count].panic_cnt = count; + userlist_count++; + spin_unlock(&userlist_lock); +} + +static int htuser_list_remove(int pid) +{ + int i; + + spin_lock(&userlist_lock); + for (i = 0; i < userlist_count; i++) { + if (userlist[i].pid == pid) { + if (i == userlist_count - 1) { + memset(&userlist[i], 0, sizeof(userlist[i])); + } else { + int len = sizeof(userlist[0]) * (userlist_count - i - 1); + memmove(&userlist[i], &userlist[i + 1], len); + } + userlist_count--; + spin_unlock(&userlist_lock); + return 0; + } + } + spin_unlock(&userlist_lock); + return -ENOENT; +} + +static void htuser_list_update(void) +{ + int i; + + need_panic = false; + need_dump = false; + spin_lock(&userlist_lock); + for (i = 0; i < userlist_count; i++) { + userlist[i].cur_cnt++; + if ((userlist[i].cur_cnt >= userlist[i].panic_cnt) || + (userlist[i].cur_cnt == userlist[i].panic_cnt / 2)) { + htuser_show_task(userlist[i].pid); + pr_err("process %d not scheduled for %ds\n", + userlist[i].pid, + userlist[i].cur_cnt * HEARTBEAT_TIME); + } + if (userlist[i].cur_cnt == userlist[i].panic_cnt) { + need_dump = true; + need_panic = true; + block_time = userlist[i].cur_cnt * HEARTBEAT_TIME; + block_pid = userlist[i].pid; + } + } + spin_unlock(&userlist_lock); +} + +static void htuser_list_kick(int pid) +{ + int i; + + spin_lock(&userlist_lock); + for (i = 0; i < userlist_count; i++) { + if (userlist[i].pid == pid) { + userlist[i].cur_cnt = 0; + spin_unlock(&userlist_lock); + return; + } + } + spin_unlock(&userlist_lock); +} + +void htuser_post_process_userlist(void) +{ + htuser_list_update(); + if (need_dump) { + pr_err("print all cpu stack and D state stack\n"); + hungtask_show_state_filter(TASK_UNINTERRUPTIBLE); + } + if (need_panic) + panic("UserList Process %d blocked for %ds causing panic", block_pid, block_time); +} + +static int htuser_process_notifier(struct notifier_block *self, + unsigned long cmd, void *v) +{ + struct task_struct *task = v; + + if (task == NULL) + return NOTIFY_OK; + + if ((task->tgid == task->pid) && (!htuser_list_remove(task->tgid))) + pr_err("remove success due to process %d die\n", task->tgid); + + return NOTIFY_OK; +} + +static struct notifier_block htuser_process_notify = { + .notifier_call = htuser_process_notifier, +}; + +ssize_t htuser_list_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int i; + char tmp[MAX_SHOW_LEN] = {0}; + int len = 0; + + len += snprintf(tmp + len, MAX_SHOW_LEN - len, + " Pid Current(sec) Expired(sec)\n"); + + spin_lock(&userlist_lock); + for (i = 0; i < userlist_count; i++) { + len += snprintf(tmp + len, MAX_SHOW_LEN - len, + "%5d %5d %5d", userlist[i].pid, + userlist[i].cur_cnt * HEARTBEAT_TIME, + userlist[i].panic_cnt * HEARTBEAT_TIME); + if (len >= MAX_SHOW_LEN) { + len = MAX_SHOW_LEN - 1; + break; + } + } + spin_unlock(&userlist_lock); + pr_info("%s\n", tmp); + strncpy(buf, tmp, len); + + return len; +} + +static int htuser_list_store_on(char *tmp, size_t len, int pid) +{ + unsigned long sec = 0; + + if (kstrtoul(tmp + 3, 10, &sec)) { + pr_err("invalid timeout value\n"); + return -EINVAL; + } + if ((sec > MAX_USER_TIMEOUT) || !sec) { + pr_err("invalid timeout value, should be in 0-%d\n", MAX_USER_TIMEOUT); + return -EINVAL; + } + if (sec % HEARTBEAT_TIME) { + pr_err("invalid timeout value, should be devided by %d\n", HEARTBEAT_TIME); + return -EINVAL; + } + pr_info("process %d set to enable, timeout=%ld\n", pid, sec); + htuser_list_insert(pid, sec / HEARTBEAT_TIME); + if (!is_registered) { + profile_event_register(PROFILE_TASK_EXIT, + &htuser_process_notify); + is_registered = true; + } + + return 0; +} + +ssize_t htuser_list_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char tmp[CMD_MAX_LEN]; /* on/off/kick */ + size_t len; + char *p = NULL; + int pid = current->tgid; + int uid = current->cred->euid.val; + + if (uid >= 10000) + pr_err("non-system process %d(uid=%d) can not be added to hungtask userlist\n", + pid, uid); + if ((count < CMD_MIN_LEN) || (count > CMD_MAX_LEN)) { + pr_err("string too long or too short\n"); + return -EINVAL; + } + if (!buf) + return -EINVAL; + + memset(tmp, 0, sizeof(tmp)); + p = memchr(buf, '\n', count); + len = p ? (size_t)(p - buf) : count; + strncpy(tmp, buf, len); + + if (strncmp(tmp, "on", CMD_MIN_LEN) == 0) { + if (htuser_list_store_on(tmp, len, pid)) + return -EINVAL; + } else if (unlikely(strncmp(tmp, "off", CMD_MIN_LEN) == 0)) { + pr_info("process %d set to disable\n", pid); + if (!htuser_list_remove(pid)) + pr_err("remove success duet to process %d call off\n", pid); + } else if (likely(strncmp(tmp, "kick", CMD_MIN_LEN) == 0)) { + pr_info("process %d is kicked\n", pid); + htuser_list_kick(pid); + } else { + pr_err("only accept on off or kick\n"); + } + return (ssize_t)count; +} + diff --git a/drivers/staging/hungtask/hungtask_user.h b/drivers/staging/hungtask/hungtask_user.h new file mode 100644 index 0000000000000000000000000000000000000000..3cd655cac2d5dbcb547b15f0f4a133979b8739af --- /dev/null +++ b/drivers/staging/hungtask/hungtask_user.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Huawei Technologies Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef DFX_HUNGTASK_USER_H +#define DFX_HUNGTASK_USER_H + +#include + +#ifdef CONFIG_DFX_HUNGTASK_USER +void htuser_post_process_userlist(void); +ssize_t htuser_list_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); +ssize_t htuser_list_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +#else +static inline void htuser_post_process_userlist(void) +{ +} + +static inline ssize_t htuser_list_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} +static inline ssize_t htuser_list_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return 0; +} + +#endif + +#endif /* DFX_HUNGTASK_USER_H */ diff --git a/include/dfx/hungtask_base.h b/include/dfx/hungtask_base.h new file mode 100644 index 0000000000000000000000000000000000000000..5c280b5b21b5626be6229f5b7151b1b3130bf40a --- /dev/null +++ b/include/dfx/hungtask_base.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Huawei Technologies Co., Ltd. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef DFX_HUNGTASK_BASE_H +#define DFX_HUNGTASK_BASE_H + +#include +#include +#include + +#define ENABLE_SHOW_LEN 8 +#define WHITELIST_STORE_LEN 400 +#define WHITELIST_LEN 61 +#define WHITE_LIST 1 +#define BLACK_LIST 2 +#define HT_ENABLE 1 +#define HT_DISABLE 0 +#define HEARTBEAT_TIME 3 +#define MAX_LOOP_NUM (CONFIG_DEFAULT_HUNG_TASK_TIMEOUT / HEARTBEAT_TIME) +#define ONE_MINUTE (60 / HEARTBEAT_TIME) +#define ONE_AND_HALF_MINUTE (90 / HEARTBEAT_TIME) +#define TWO_MINUTES (120 / HEARTBEAT_TIME) +#define THREE_MINUTES (180 / HEARTBEAT_TIME) +#define TWENTY_SECONDS (21 / HEARTBEAT_TIME) +#define THIRTY_SECONDS (30 / HEARTBEAT_TIME) +#define HUNG_ONE_HOUR (3600 / HEARTBEAT_TIME) +#define HUNG_TEN_MINUTES (600 / HEARTBEAT_TIME) +#define HUNGTASK_REPORT_TIMECOST TWENTY_SECONDS +#define HT_DUMP_IN_PANIC_LOOSE 5 +#define HT_DUMP_IN_PANIC_STRICT 2 +#define REFRESH_INTERVAL THREE_MINUTES +#define FLAG_DUMP_WHITE (1 << 0) +#define FLAG_DUMP_APP (1 << 1) +#define FLAG_DUMP_NOSCHEDULE (1 << 2) +#define FLAG_DUMP_JANK (1 << 3) +#define FLAG_PANIC (1 << 4) +#define FLAG_PF_FROZEN (1 << 6) +#define TASK_TYPE_IGNORE 0 +#define TASK_TYPE_WHITE (1 << 0) +#define TASK_TYPE_APP (1 << 1) +#define TASK_TYPE_JANK (1 << 2) +#define TASK_TYPE_KERNEL (1 << 3) +#define TASK_TYPE_NATIVE (1 << 4) +#define TASK_TYPE_FROZEN (1 << 6) +#define PID_INIT 1 +#define PID_KTHREAD 2 +#define DEFAULT_WHITE_DUMP_CNT MAX_LOOP_NUM +#define DEFAULT_WHITE_PANIC_CNT MAX_LOOP_NUM +#define HUNG_TASK_UPLOAD_ONCE 1 +#define FROZEN_BUF_LEN 1024 +#define MAX_REMOVE_LIST_NUM 200 +#define HUNGTASK_DOMAIN "KERNEL_VENDOR" +#define HUNGTASK_NAME "HUNGTASK" +#define INIT_FREEZE_NAME "INIT_FREEZE" +#define HUNG_TASK_BATCHING 1024 +#define TIME_REFRESH_PIDS 20 +#define PID_ERROR (-1) +#define HUNGTASK_EVENT_WHITELIST 1 +#define REPORT_MSGLENGTH 200 + +struct task_item { + struct rb_node node; + pid_t pid; + pid_t tgid; + char name[TASK_COMM_LEN + 1]; + unsigned long switch_count; + unsigned int task_type; + int dump_wa; + int panic_wa; + int dump_jank; + int d_state_time; + bool isdone_wa; +}; + +struct hashlist_node { + pid_t pid; + struct hlist_node list; +}; + +struct whitelist_item { + pid_t pid; + char name[TASK_COMM_LEN + 1]; +}; + +struct task_hung_upload { + char name[TASK_COMM_LEN + 1]; + pid_t pid; + pid_t tgid; + unsigned int flag; + int duration; +}; + +extern unsigned long sysctl_hung_task_timeout_secs; +extern unsigned int sysctl_hung_task_panic; + +void do_dump_task(struct task_struct *task); +int dump_task_wa(struct task_item *item, int dump_cnt, + struct task_struct *task, unsigned int flag); +void do_show_task(struct task_struct *task, unsigned int flag, int d_state_time); +void hungtask_show_state_filter(unsigned long state_filter); +int htbase_create_sysfs(void); +void htbase_set_panic(int new_did_panic); +void htbase_set_timeout_secs(unsigned long new_hungtask_timeout_secs); +void htbase_check_tasks(unsigned long timeout); +bool hashlist_find(struct hlist_head *head, int count, pid_t tgid); +void hashlist_clear(struct hlist_head *head, int count); +bool hashlist_insert(struct hlist_head *head, int count, pid_t tgid); + +#endif /* DFX_HUNGTASK_BASE_H */ diff --git a/kernel/hung_task.c b/kernel/hung_task.c index 396ebaebea3fea3578fe295c73cf041530a91d93..82cd7297c9c1dbf6f1e056e0bfe16dc4386a74bc 100644 --- a/kernel/hung_task.c +++ b/kernel/hung_task.c @@ -24,6 +24,10 @@ #include +#ifdef CONFIG_DFX_HUNGTASK +#include +#endif + /* * The number of tasks checked: */ @@ -51,9 +55,11 @@ unsigned long __read_mostly sysctl_hung_task_check_interval_secs; int __read_mostly sysctl_hung_task_warnings = 10; static int __read_mostly did_panic; +#ifndef CONFIG_DFX_HUNGTASK static bool hung_task_show_lock; static bool hung_task_call_panic; static bool hung_task_show_all_bt; +#endif static struct task_struct *watchdog_task; @@ -76,7 +82,9 @@ static int hung_task_panic(struct notifier_block *this, unsigned long event, void *ptr) { did_panic = 1; - +#ifdef CONFIG_DFX_HUNGTASK + htbase_set_panic(did_panic); +#endif return NOTIFY_DONE; } @@ -84,6 +92,7 @@ static struct notifier_block panic_block = { .notifier_call = hung_task_panic, }; +#ifndef CONFIG_DFX_HUNGTASK static void check_hung_task(struct task_struct *t, unsigned long timeout) { unsigned long switch_count = t->nvcsw + t->nivcsw; @@ -212,6 +221,7 @@ static void check_hung_uninterruptible_tasks(unsigned long timeout) if (hung_task_call_panic) panic("hung_task: blocked tasks"); } +#endif static long hung_timeout_jiffies(unsigned long last_checked, unsigned long timeout) @@ -235,7 +245,9 @@ int proc_dohung_task_timeout_secs(struct ctl_table *table, int write, goto out; wake_up_process(watchdog_task); - +#ifdef CONFIG_DFX_HUNGTASK + htbase_set_timeout_secs(sysctl_hung_task_timeout_secs); +#endif out: return ret; } @@ -280,18 +292,26 @@ static int watchdog(void *dummy) set_user_nice(current, 0); for ( ; ; ) { +#ifdef CONFIG_DFX_HUNGTASK + unsigned long timeout = HEARTBEAT_TIME; +#else unsigned long timeout = sysctl_hung_task_timeout_secs; +#endif unsigned long interval = sysctl_hung_task_check_interval_secs; long t; if (interval == 0) interval = timeout; - interval = min_t(unsigned long, interval, timeout); - t = hung_timeout_jiffies(hung_last_checked, interval); + timeout = min_t(unsigned long, interval, timeout); + t = hung_timeout_jiffies(hung_last_checked, timeout); if (t <= 0) { if (!atomic_xchg(&reset_hung_task, 0) && !hung_detector_suspended) +#ifdef CONFIG_DFX_HUNGTASK + htbase_check_tasks(timeout); +#else check_hung_uninterruptible_tasks(timeout); +#endif hung_last_checked = jiffies; continue; } @@ -303,6 +323,13 @@ static int watchdog(void *dummy) static int __init hung_task_init(void) { +#ifdef CONFIG_DFX_HUNGTASK + int ret = 0; + + ret = htbase_create_sysfs(); + if (ret) + pr_err("hungtask: create_sysfs_hungtask fail"); +#endif atomic_notifier_chain_register(&panic_notifier_list, &panic_block); /* Disable hung task detector on suspend */