diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 0693f074ed41..dc7f3ddc1989 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -520,6 +520,15 @@ config QCOM_CPU_VENDOR_HOOKS having scope to upstream. If unsure, say N. +config QCOM_HUNG_TASK_ENH + tristate "QTI Hung Task Enhancement" + depends on ARCH_QCOM + help + Add options at sysfs to enhance khungtask, allow user to + select and monitor tasks in black/white list mode. Also + processes in iowait for specific situation will be monitored + to avoid devices long time no response. + config QCOM_MINIDUMP tristate "QCOM Minidump Support" depends on QCOM_SMEM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 6e59b85d1f6e..f20426583532 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -53,3 +53,4 @@ minidump-y += msm_minidump.o minidump_log.o minidump-$(CONFIG_QCOM_MINIDUMP_PANIC_DUMP) += minidump_memory.o obj-$(CONFIG_QCOM_VA_MINIDUMP) += qcom_va_minidump.o obj-$(CONFIG_QCOM_LOGBUF_VENDOR_HOOKS) += qcom_logbuf_vh.o +obj-$(CONFIG_QCOM_HUNG_TASK_ENH) += hung_task_enh.o diff --git a/drivers/soc/qcom/hung_task_enh.c b/drivers/soc/qcom/hung_task_enh.c new file mode 100644 index 000000000000..5b1f0fbd1252 --- /dev/null +++ b/drivers/soc/qcom/hung_task_enh.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include + +#define DEFAULT_MAX_IOWAIT_TASK 5 + +enum { + TASK_DEFAULT = 0, + TASK_IN_WHITELIST, + TASK_IN_BLACKLIST, +}; + +enum { + HUNG_TASK_MODE_WHITELIST = 0, + HUNG_TASK_MODE_BLACKLIST, +}; + +/** + * struct hung_task_enh_data + * @read_pid: PID of the process will be read + * @global_detect_mode: The global detect mode switch, 0-Whitelist/1-Blacklist + * @curr_iowait_task_cnt: Count the number of iowait processes in one scan cycle + * @max_iowait_task_cnt: Max number of iowait processes + * @curr_iowait_timeout_cnt: Count how many times the iowait condition is met + * @max_iowait_timeout_cnt: Max times of the iowait condition is met + * @ctl_table_hdr: hung_task_enh ctl table header + */ +struct hung_task_enh_data { + int read_pid; + int global_detect_mode; + int curr_iowait_task_cnt; + int max_iowait_task_cnt; + int curr_iowait_timeout_cnt; + int max_iowait_timeout_cnt; + struct ctl_table_header *ctl_table_hdr; +}; + +static struct hung_task_enh_data hung_task_enh; + +void qcom_before_check_tasks(void *ignore, struct task_struct *t, unsigned long timeout, + bool *need_check) +{ + struct walt_task_struct *wts = (struct walt_task_struct *) t->android_vendor_data1; + + if ((hung_task_enh.global_detect_mode == HUNG_TASK_MODE_WHITELIST && + wts->hung_detect_status != TASK_IN_WHITELIST) || + (hung_task_enh.global_detect_mode == HUNG_TASK_MODE_BLACKLIST && + wts->hung_detect_status == TASK_IN_BLACKLIST)) { + *need_check = false; + return; + } + + *need_check = true; + + if (unlikely(t->in_iowait) && (t->__state == TASK_UNINTERRUPTIBLE || + t->__state == TASK_STOPPED || t->__state == TASK_TRACED) && + t->last_switch_time != 0 && + time_is_before_jiffies(t->last_switch_time + timeout * HZ) && + (t->mm != NULL && t == t->group_leader)) + hung_task_enh.curr_iowait_task_cnt++; +} + +void qcom_check_tasks_done(void *ignore, void *extra) +{ + if (hung_task_enh.curr_iowait_task_cnt >= hung_task_enh.max_iowait_task_cnt) + hung_task_enh.curr_iowait_timeout_cnt++; + else + hung_task_enh.curr_iowait_timeout_cnt = 0; + + if (hung_task_enh.max_iowait_timeout_cnt != 0 && + hung_task_enh.curr_iowait_timeout_cnt >= hung_task_enh.max_iowait_timeout_cnt) + panic("Detect IO wait too long time for multiple tasks!\n"); + + hung_task_enh.curr_iowait_task_cnt = 0; +} + +static DEFINE_MUTEX(readpid_mutex); +static int read_pid_handler(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int ret; + + mutex_lock(&readpid_mutex); + ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + mutex_unlock(&readpid_mutex); + + return ret; +} + +static int hung_task_handler(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int ret; + struct task_struct *task; + struct walt_task_struct *wts; + int pid_and_val[2] = {-1, -1}; + + struct ctl_table tmp = { + .data = &pid_and_val, + .maxlen = sizeof(pid_and_val), + .mode = table->mode, + }; + + mutex_lock(&readpid_mutex); + + if (!write) { + task = get_pid_task(find_vpid(hung_task_enh.read_pid), + PIDTYPE_PID); + if (!task) { + ret = -ENOENT; + goto unlock_mutex; + } + wts = (struct walt_task_struct *) task->android_vendor_data1; + pid_and_val[0] = hung_task_enh.read_pid; + pid_and_val[1] = wts->hung_detect_status; + ret = proc_dointvec(&tmp, write, buffer, lenp, ppos); + goto put_task; + } + + ret = proc_dointvec(&tmp, write, buffer, lenp, ppos); + if (ret) + goto unlock_mutex; + + if (pid_and_val[0] <= 0 || pid_and_val[1] < 0 || pid_and_val[1] > 1) { + ret = -ENOENT; + goto unlock_mutex; + } + + task = get_pid_task(find_vpid(pid_and_val[0]), PIDTYPE_PID); + if (!task) { + ret = -ENOENT; + goto unlock_mutex; + } + + wts = (struct walt_task_struct *) task->android_vendor_data1; + if (pid_and_val[1] == 1) { + if (hung_task_enh.global_detect_mode == HUNG_TASK_MODE_WHITELIST) + wts->hung_detect_status = TASK_IN_WHITELIST; + else + wts->hung_detect_status = TASK_IN_BLACKLIST; + } else { + wts->hung_detect_status = TASK_DEFAULT; + } + +put_task: + put_task_struct(task); +unlock_mutex: + mutex_unlock(&readpid_mutex); + + return ret; +} + +struct ctl_table hung_task_table[] = { + { + .procname = "global_detect_mode", + .data = &hung_task_enh.global_detect_mode, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, + { + .procname = "per_task_detect_mode", + .data = (int *) 0, + .maxlen = sizeof(unsigned int) * 2, + .mode = 0644, + .proc_handler = hung_task_handler, + }, + { + .procname = "max_iowait_timeout_cnt", + .data = &hung_task_enh.max_iowait_timeout_cnt, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_INT_MAX, + }, + { + .procname = "max_iowait_task_cnt", + .data = &hung_task_enh.max_iowait_task_cnt, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ONE, + .extra2 = SYSCTL_INT_MAX, + }, + { + .procname = "read_pid", + .data = &hung_task_enh.read_pid, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = read_pid_handler, + .extra1 = SYSCTL_ONE, + .extra2 = SYSCTL_INT_MAX, + }, + { } +}; + +struct ctl_table hung_task_base_table[] = { + { + .procname = "hung_task_enh", + .mode = 0555, + .child = hung_task_table, + }, + { } +}; + +static int __init hung_task_enh_init(void) +{ + int ret; + + hung_task_enh.max_iowait_task_cnt = DEFAULT_MAX_IOWAIT_TASK; + + ret = register_trace_android_vh_check_uninterruptible_tasks( + qcom_before_check_tasks, NULL); + if (ret) + return ret; + + ret = register_trace_android_vh_check_uninterruptible_tasks_dn( + qcom_check_tasks_done, NULL); + if (ret) { + unregister_trace_android_vh_check_uninterruptible_tasks( + qcom_before_check_tasks, NULL); + return ret; + } + + hung_task_enh.ctl_table_hdr = register_sysctl_table( + hung_task_base_table); + if (!hung_task_enh.ctl_table_hdr) { + unregister_trace_android_vh_check_uninterruptible_tasks( + qcom_before_check_tasks, NULL); + unregister_trace_android_vh_check_uninterruptible_tasks_dn( + qcom_check_tasks_done, NULL); + return -ENOMEM; + } + + return ret; +} +late_initcall(hung_task_enh_init); + +static void __exit hung_task_enh_exit(void) +{ + unregister_sysctl_table(hung_task_enh.ctl_table_hdr); + unregister_trace_android_vh_check_uninterruptible_tasks( + qcom_before_check_tasks, NULL); + unregister_trace_android_vh_check_uninterruptible_tasks_dn( + qcom_check_tasks_done, NULL); +} +module_exit(hung_task_enh_exit); + +MODULE_DESCRIPTION("QCOM Hung Task Enhancement"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/sched/walt.h b/include/linux/sched/walt.h index 85eda2491683..0eab97e74b21 100644 --- a/include/linux/sched/walt.h +++ b/include/linux/sched/walt.h @@ -138,6 +138,7 @@ struct walt_task_struct { int prev_cpu; int new_cpu; u8 enqueue_after_migration; + u8 hung_detect_status; }; #define wts_to_ts(wts) ({ \