| /* |
| * Copyright (c) 2014-2017 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com |
| * |
| * Samsung TN debugging code |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/input.h> |
| #include <linux/uaccess.h> |
| #include <linux/proc_fs.h> |
| #include <linux/kmsg_dump.h> |
| #include <linux/kallsyms.h> |
| #include <linux/kernel_stat.h> |
| #include <linux/irq.h> |
| #include <linux/tick.h> |
| #include <linux/file.h> |
| #include <linux/sec_class.h> |
| #include <linux/sec_ext.h> |
| #include <linux/sec_debug.h> |
| #include <linux/sec_hard_reset_hook.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/file.h> |
| #include <linux/fdtable.h> |
| #include <linux/mount.h> |
| #include <linux/of_reserved_mem.h> |
| #include <linux/memblock.h> |
| #include <linux/sched/task.h> |
| #include <linux/moduleparam.h> |
| |
| #include <asm/cacheflush.h> |
| #include <asm/stacktrace.h> |
| |
| #include <soc/samsung/exynos-pmu.h> |
| #include <soc/samsung/exynos-powermode.h> |
| #include <linux/soc/samsung/exynos-soc.h> |
| |
| #ifdef CONFIG_SEC_DEBUG |
| |
| /* enable/disable sec_debug feature |
| * level = 0 when enable = 0 && enable_user = 0 |
| * level = 1 when enable = 1 && enable_user = 0 |
| * level = 0x10001 when enable = 1 && enable_user = 1 |
| * The other cases are not considered |
| */ |
| union sec_debug_level_t { |
| struct { |
| u16 kernel_fault; |
| u16 user_fault; |
| } en; |
| u32 uint_val; |
| } sec_debug_level = { .en.kernel_fault = 1, }; |
| |
| module_param_named(enable, sec_debug_level.en.kernel_fault, ushort, 0644); |
| module_param_named(enable_user, sec_debug_level.en.user_fault, ushort, 0644); |
| module_param_named(level, sec_debug_level.uint_val, uint, 0644); |
| |
| static int sec_debug_reserve_ok; |
| |
| static int __debug_sj_lock; |
| |
| int sec_debug_check_sj(void) |
| { |
| if (__debug_sj_lock == 1) |
| /* Locked */ |
| return 1; |
| else if (__debug_sj_lock == 0) |
| /* Unlocked */ |
| return 0; |
| |
| return -1; |
| } |
| |
| static int __init sec_debug_get_sj_status(char *str) |
| { |
| unsigned long val = memparse(str, &str); |
| |
| pr_err("%s: start %lx\n", __func__, val); |
| |
| if (!val) { |
| pr_err("%s: UNLOCKED (%lx)\n", __func__, val); |
| __debug_sj_lock = 0; |
| /* Unlocked or Disabled */ |
| return 1; |
| } else { |
| pr_err("%s: LOCKED (%lx)\n", __func__, val); |
| __debug_sj_lock = 1; |
| /* Locked */ |
| return 1; |
| } |
| } |
| __setup("sec_debug.sjl=", sec_debug_get_sj_status); |
| |
| |
| int sec_debug_get_debug_level(void) |
| { |
| return sec_debug_level.uint_val; |
| } |
| |
| static long __force_upload; |
| |
| static int sec_debug_get_force_upload(void) |
| { |
| /* enabled */ |
| if (__force_upload == 1) |
| return 1; |
| /* disabled */ |
| else if (__force_upload == 0) |
| return 0; |
| |
| return -1; |
| } |
| |
| static int __init sec_debug_force_upload(char *str) |
| { |
| unsigned long val = memparse(str, &str); |
| |
| if (!val) { |
| pr_err("%s: disabled (%lx)\n", __func__, val); |
| __force_upload = 0; |
| /* Unlocked or Disabled */ |
| return 1; |
| } else { |
| pr_err("%s: enabled (%lx)\n", __func__, val); |
| __force_upload = 1; |
| /* Locked */ |
| return 1; |
| } |
| } |
| __setup("androidboot.force_upload=", sec_debug_force_upload); |
| |
| int sec_debug_enter_upload(void) |
| { |
| return sec_debug_get_force_upload(); |
| } |
| |
| static void sec_debug_user_fault_dump(void) |
| { |
| if (sec_debug_level.en.kernel_fault == 1 && |
| sec_debug_level.en.user_fault == 1) |
| panic("User Fault"); |
| } |
| |
| static ssize_t sec_debug_user_fault_write(struct file *file, const char __user *buffer, size_t count, loff_t *offs) |
| { |
| char buf[100]; |
| |
| if (count > sizeof(buf) - 1) |
| return -EINVAL; |
| if (copy_from_user(buf, buffer, count)) |
| return -EFAULT; |
| buf[count] = '\0'; |
| |
| if (strncmp(buf, "dump_user_fault", 15) == 0) |
| sec_debug_user_fault_dump(); |
| |
| return count; |
| } |
| |
| static const struct file_operations sec_debug_user_fault_proc_fops = { |
| .write = sec_debug_user_fault_write, |
| }; |
| |
| static int __init sec_debug_user_fault_init(void) |
| { |
| struct proc_dir_entry *entry; |
| |
| entry = proc_create("user_fault", S_IWUSR | S_IWGRP, NULL, |
| &sec_debug_user_fault_proc_fops); |
| if (!entry) |
| return -ENOMEM; |
| return 0; |
| } |
| device_initcall(sec_debug_user_fault_init); |
| |
| /* layout of SDRAM : First 4KB of DRAM |
| * 0x0: magic (4B) |
| * 0x4~0x3FF: panic string (1020B) |
| * 0x400~0x7FF: panic Extra Info (1KB) |
| * 0x800~0xFFB: panic dumper log (2KB - 4B) |
| * 0xFFC: copy of magic (4B) |
| */ |
| |
| enum sec_debug_upload_magic_t { |
| UPLOAD_MAGIC_INIT = 0x0, |
| UPLOAD_MAGIC_PANIC = 0x66262564, |
| }; |
| |
| enum sec_debug_upload_cause_t { |
| UPLOAD_CAUSE_INIT = 0xCAFEBABE, |
| UPLOAD_CAUSE_KERNEL_PANIC = 0x000000C8, |
| UPLOAD_CAUSE_FORCED_UPLOAD = 0x00000022, |
| UPLOAD_CAUSE_USER_FORCED_UPLOAD = 0x00000074, |
| UPLOAD_CAUSE_CP_ERROR_FATAL = 0x000000CC, |
| UPLOAD_CAUSE_USER_FAULT = 0x0000002F, |
| UPLOAD_CAUSE_HSIC_DISCONNECTED = 0x000000DD, |
| UPLOAD_CAUSE_POWERKEY_LONG_PRESS = 0x00000085, |
| UPLOAD_CAUSE_HARD_RESET = 0x00000066, |
| }; |
| |
| static unsigned long sec_debug_rmem_virt; |
| static unsigned long sec_debug_rmem_phys; |
| static unsigned long sec_debug_rmem_size; |
| |
| static void sec_debug_set_upload_magic(unsigned magic, char *str) |
| { |
| *(unsigned int *)sec_debug_rmem_virt = magic; |
| |
| if (str) { |
| #ifdef CONFIG_SEC_DEBUG_EXTRA_INFO |
| sec_debug_set_extra_info_panic(str); |
| sec_debug_finish_extra_info(); |
| #endif |
| } |
| |
| pr_emerg("sec_debug: set magic code (0x%x)\n", magic); |
| } |
| |
| static void sec_debug_set_upload_cause(enum sec_debug_upload_cause_t type) |
| { |
| exynos_pmu_write(SEC_DEBUG_PANIC_INFORM, type); |
| |
| pr_emerg("sec_debug: set upload cause (0x%x)\n", type); |
| } |
| |
| static int __init sec_debug_magic_setup(struct reserved_mem *rmem) |
| { |
| pr_info("%s: Reserved Mem(0x%llx, 0x%llx) - Success\n", |
| __func__, rmem->base, rmem->size); |
| |
| sec_debug_rmem_phys = rmem->base; |
| sec_debug_rmem_size = rmem->size; |
| |
| sec_debug_reserve_ok = 1; |
| |
| return 0; |
| } |
| RESERVEDMEM_OF_DECLARE(sec_debug_magic, "exynos,sec_debug_magic", sec_debug_magic_setup); |
| |
| #define MAX_RECOVERY_CAUSE_SIZE 256 |
| char recovery_cause[MAX_RECOVERY_CAUSE_SIZE]; |
| unsigned long recovery_cause_offset; |
| |
| static ssize_t show_recovery_cause(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| if (!recovery_cause_offset) |
| return 0; |
| |
| sec_get_param_str(recovery_cause_offset, buf); |
| pr_info("%s: %s\n", __func__, buf); |
| |
| return strlen(buf); |
| } |
| |
| static ssize_t store_recovery_cause(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) |
| { |
| if (!recovery_cause_offset) |
| return 0; |
| |
| if (strlen(buf) > sizeof(recovery_cause)) |
| pr_err("%s: input buffer length is out of range.\n", __func__); |
| |
| snprintf(recovery_cause, sizeof(recovery_cause), "%s:%d ", current->comm, task_pid_nr(current)); |
| if (strlen(recovery_cause) + strlen(buf) >= sizeof(recovery_cause)) { |
| pr_err("%s: input buffer length is out of range.\n", __func__); |
| return count; |
| } |
| strncat(recovery_cause, buf, strlen(buf)); |
| |
| sec_set_param_str(recovery_cause_offset, recovery_cause, sizeof(recovery_cause)); |
| pr_info("%s: %s, count:%d\n", __func__, recovery_cause, (int)count); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(recovery_cause, 0660, show_recovery_cause, store_recovery_cause); |
| |
| void sec_debug_recovery_reboot(void) |
| { |
| char *buf; |
| |
| if (recovery_cause_offset) { |
| if (!recovery_cause[0] || !strlen(recovery_cause)) { |
| buf = "empty caller"; |
| store_recovery_cause(NULL, NULL, buf, strlen(buf)); |
| } |
| } |
| } |
| |
| static int __init sec_debug_recovery_cause_setup(char *str) |
| { |
| recovery_cause_offset = memparse(str, &str); |
| |
| /* If we encounter any problem parsing str ... */ |
| if (!recovery_cause_offset) { |
| pr_err("%s: failed to parse address.\n", __func__); |
| goto out; |
| } |
| |
| pr_info("%s, recovery_cause_offset :%lx\n", __func__, recovery_cause_offset); |
| out: |
| return 0; |
| } |
| __setup("androidboot.recovery_offset=", sec_debug_recovery_cause_setup); |
| |
| static unsigned long fmm_lock_offset; |
| |
| static int __init sec_debug_fmm_lock_offset(char *arg) |
| { |
| fmm_lock_offset = simple_strtoul(arg, NULL, 10); |
| return 0; |
| } |
| |
| early_param("sec_debug.fmm_lock_offset", sec_debug_fmm_lock_offset); |
| |
| static ssize_t store_FMM_lock(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| char lock; |
| |
| sscanf(buf, "%c", &lock); |
| pr_info("%s: store %c in FMM_lock\n", __func__, lock); |
| sec_set_param(fmm_lock_offset, lock); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(FMM_lock, 0220, NULL, store_FMM_lock); |
| |
| static int __init sec_debug_recovery_cause_init(void) |
| { |
| struct device *dev; |
| |
| memset(recovery_cause, 0, MAX_RECOVERY_CAUSE_SIZE); |
| |
| dev = sec_device_create(NULL, "sec_debug"); |
| WARN_ON(!dev); |
| if (IS_ERR(dev)) |
| pr_err("%s:Failed to create devce\n", __func__); |
| |
| if (device_create_file(dev, &dev_attr_recovery_cause) < 0) |
| pr_err("%s: Failed to create device file\n", __func__); |
| |
| if (device_create_file(dev, &dev_attr_FMM_lock) < 0) |
| pr_err("%s: Failed to create device file\n", __func__); |
| |
| return 0; |
| } |
| late_initcall(sec_debug_recovery_cause_init); |
| |
| #ifndef arch_irq_stat_cpu |
| #define arch_irq_stat_cpu(cpu) 0 |
| #endif |
| #ifndef arch_irq_stat |
| #define arch_irq_stat() 0 |
| #endif |
| #ifdef arch_idle_time |
| static cputime64_t get_idle_time(int cpu) |
| { |
| cputime64_t idle; |
| |
| idle = kcpustat_cpu(cpu).cpustat[CPUTIME_IDLE]; |
| if (cpu_online(cpu) && !nr_iowait_cpu(cpu)) |
| idle += arch_idle_time(cpu); |
| return idle; |
| } |
| |
| static cputime64_t get_iowait_time(int cpu) |
| { |
| cputime64_t iowait; |
| |
| iowait = kcpustat_cpu(cpu).cpustat[CPUTIME_IOWAIT]; |
| if (cpu_online(cpu) && nr_iowait_cpu(cpu)) |
| iowait += arch_idle_time(cpu); |
| return iowait; |
| } |
| #else |
| static u64 get_idle_time(int cpu) |
| { |
| u64 idle, idle_time = -1ULL; |
| |
| if (cpu_online(cpu)) |
| idle_time = get_cpu_idle_time_us(cpu, NULL); |
| |
| if (idle_time == -1ULL) |
| /* !NO_HZ or cpu offline so we can rely on cpustat.idle */ |
| idle = kcpustat_cpu(cpu).cpustat[CPUTIME_IDLE]; |
| else |
| idle = idle_time * NSEC_PER_USEC; |
| |
| return idle; |
| } |
| |
| static u64 get_iowait_time(int cpu) |
| { |
| u64 iowait, iowait_time = -1ULL; |
| |
| if (cpu_online(cpu)) |
| iowait_time = get_cpu_iowait_time_us(cpu, NULL); |
| |
| if (iowait_time == -1ULL) |
| /* !NO_HZ or cpu offline so we can rely on cpustat.iowait */ |
| iowait = kcpustat_cpu(cpu).cpustat[CPUTIME_IOWAIT]; |
| else |
| iowait = iowait_time * NSEC_PER_USEC; |
| |
| return iowait; |
| } |
| #endif |
| |
| static void sec_debug_dump_cpu_stat(void) |
| { |
| int i, j; |
| u64 user = 0; |
| u64 nice = 0; |
| u64 system = 0; |
| u64 idle = 0; |
| u64 iowait = 0; |
| u64 irq = 0; |
| u64 softirq = 0; |
| u64 steal = 0; |
| u64 guest = 0; |
| u64 guest_nice = 0; |
| u64 sum = 0; |
| u64 sum_softirq = 0; |
| unsigned int per_softirq_sums[NR_SOFTIRQS] = {0}; |
| |
| char *softirq_to_name[NR_SOFTIRQS] = { "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL", "TASKLET", "SCHED", "HRTIMER", "RCU" }; |
| |
| for_each_possible_cpu(i) { |
| user += kcpustat_cpu(i).cpustat[CPUTIME_USER]; |
| nice += kcpustat_cpu(i).cpustat[CPUTIME_NICE]; |
| system += kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM]; |
| idle += get_idle_time(i); |
| iowait += get_iowait_time(i); |
| irq += kcpustat_cpu(i).cpustat[CPUTIME_IRQ]; |
| softirq += kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ]; |
| steal += kcpustat_cpu(i).cpustat[CPUTIME_STEAL]; |
| guest += kcpustat_cpu(i).cpustat[CPUTIME_GUEST]; |
| guest_nice += kcpustat_cpu(i).cpustat[CPUTIME_GUEST_NICE]; |
| sum += kstat_cpu_irqs_sum(i); |
| sum += arch_irq_stat_cpu(i); |
| |
| for (j = 0; j < NR_SOFTIRQS; j++) { |
| unsigned int softirq_stat = kstat_softirqs_cpu(j, i); |
| |
| per_softirq_sums[j] += softirq_stat; |
| sum_softirq += softirq_stat; |
| } |
| } |
| sum += arch_irq_stat(); |
| |
| pr_info("\n"); |
| pr_info("cpu user:%llu \tnice:%llu \tsystem:%llu \tidle:%llu \tiowait:%llu \tirq:%llu \tsoftirq:%llu \t %llu %llu %llu\n", |
| (unsigned long long)nsec_to_clock_t(user), |
| (unsigned long long)nsec_to_clock_t(nice), |
| (unsigned long long)nsec_to_clock_t(system), |
| (unsigned long long)nsec_to_clock_t(idle), |
| (unsigned long long)nsec_to_clock_t(iowait), |
| (unsigned long long)nsec_to_clock_t(irq), |
| (unsigned long long)nsec_to_clock_t(softirq), |
| (unsigned long long)nsec_to_clock_t(steal), |
| (unsigned long long)nsec_to_clock_t(guest), |
| (unsigned long long)nsec_to_clock_t(guest_nice)); |
| pr_info("-------------------------------------------------------------------------------------------------------------\n"); |
| |
| for_each_possible_cpu(i) { |
| /* Copy values here to work around gcc-2.95.3, gcc-2.96 */ |
| user = kcpustat_cpu(i).cpustat[CPUTIME_USER]; |
| nice = kcpustat_cpu(i).cpustat[CPUTIME_NICE]; |
| system = kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM]; |
| idle = get_idle_time(i); |
| iowait = get_iowait_time(i); |
| irq = kcpustat_cpu(i).cpustat[CPUTIME_IRQ]; |
| softirq = kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ]; |
| steal = kcpustat_cpu(i).cpustat[CPUTIME_STEAL]; |
| guest = kcpustat_cpu(i).cpustat[CPUTIME_GUEST]; |
| guest_nice = kcpustat_cpu(i).cpustat[CPUTIME_GUEST_NICE]; |
| |
| pr_info("cpu%d user:%llu \tnice:%llu \tsystem:%llu \tidle:%llu \tiowait:%llu \tirq:%llu \tsoftirq:%llu \t %llu %llu %llu\n", |
| i, |
| (unsigned long long)nsec_to_clock_t(user), |
| (unsigned long long)nsec_to_clock_t(nice), |
| (unsigned long long)nsec_to_clock_t(system), |
| (unsigned long long)nsec_to_clock_t(idle), |
| (unsigned long long)nsec_to_clock_t(iowait), |
| (unsigned long long)nsec_to_clock_t(irq), |
| (unsigned long long)nsec_to_clock_t(softirq), |
| (unsigned long long)nsec_to_clock_t(steal), |
| (unsigned long long)nsec_to_clock_t(guest), |
| (unsigned long long)nsec_to_clock_t(guest_nice)); |
| } |
| pr_info("-------------------------------------------------------------------------------------------------------------\n"); |
| pr_info("\n"); |
| pr_info("irq : %llu", (unsigned long long)sum); |
| pr_info("-------------------------------------------------------------------------------------------------------------\n"); |
| /* sum again ? it could be updated? */ |
| for_each_irq_nr(j) { |
| unsigned int irq_stat = kstat_irqs(j); |
| |
| if (irq_stat) { |
| pr_info("irq-%-4d : %8u %s\n", j, irq_stat, |
| irq_to_desc(j)->action ? irq_to_desc(j)->action->name ? : "???" : "???"); |
| } |
| } |
| pr_info("-------------------------------------------------------------------------------------------------------------\n"); |
| pr_info("\n"); |
| pr_info("softirq : %llu", (unsigned long long)sum_softirq); |
| pr_info("-------------------------------------------------------------------------------------------------------------\n"); |
| for (i = 0; i < NR_SOFTIRQS; i++) |
| if (per_softirq_sums[i]) |
| pr_info("softirq-%d : %8u %s\n", i, per_softirq_sums[i], softirq_to_name[i]); |
| pr_info("-------------------------------------------------------------------------------------------------------------\n"); |
| } |
| |
| void sec_debug_clear_magic_rambase(void) |
| { |
| /* Clear magic code in normal reboot */ |
| sec_debug_set_upload_magic(UPLOAD_MAGIC_INIT, NULL); |
| } |
| |
| void sec_debug_reboot_handler(void *p) |
| { |
| pr_emerg("sec_debug: %s\n", __func__); |
| |
| /* Clear magic code in normal reboot */ |
| sec_debug_set_upload_magic(UPLOAD_MAGIC_INIT, NULL); |
| |
| if (p != NULL) |
| if (!strcmp(p, "recovery")) |
| sec_debug_recovery_reboot(); |
| } |
| |
| void sec_debug_panic_handler(void *buf, bool dump) |
| { |
| pr_emerg("sec_debug: %s\n", __func__); |
| |
| /* Set upload cause */ |
| sec_debug_set_upload_magic(UPLOAD_MAGIC_PANIC, buf); |
| if (!strncmp(buf, "User Fault", 10)) |
| sec_debug_set_upload_cause(UPLOAD_CAUSE_USER_FAULT); |
| else if (is_hard_reset_occurred()) |
| sec_debug_set_upload_cause(UPLOAD_CAUSE_HARD_RESET); |
| else if (!strncmp(buf, "Crash Key", 9)) |
| sec_debug_set_upload_cause(UPLOAD_CAUSE_FORCED_UPLOAD); |
| else if (!strncmp(buf, "User Crash Key", 14)) |
| sec_debug_set_upload_cause(UPLOAD_CAUSE_USER_FORCED_UPLOAD); |
| else if (!strncmp(buf, "CP Crash", 8)) |
| sec_debug_set_upload_cause(UPLOAD_CAUSE_CP_ERROR_FATAL); |
| else if (!strncmp(buf, "HSIC Disconnected", 17)) |
| sec_debug_set_upload_cause(UPLOAD_CAUSE_HSIC_DISCONNECTED); |
| else |
| sec_debug_set_upload_cause(UPLOAD_CAUSE_KERNEL_PANIC); |
| |
| /* dump debugging info */ |
| if (dump) { |
| sec_debug_dump_cpu_stat(); |
| debug_show_all_locks(); |
| } |
| } |
| |
| void sec_debug_post_panic_handler(void) |
| { |
| hard_reset_delay(); |
| |
| /* reset */ |
| pr_emerg("sec_debug: %s\n", linux_banner); |
| pr_emerg("sec_debug: rebooting...\n"); |
| |
| flush_cache_all(); |
| } |
| |
| #ifdef CONFIG_SEC_DEBUG_FILE_LEAK |
| void sec_debug_print_file_list(void) |
| { |
| int i = 0; |
| unsigned int count = 0; |
| struct file *file = NULL; |
| struct files_struct *files = current->files; |
| const char *p_rootname = NULL; |
| const char *p_filename = NULL; |
| |
| count = files->fdt->max_fds; |
| |
| pr_err("[Opened file list of process %s(PID:%d, TGID:%d) :: %d]\n", |
| current->group_leader->comm, current->pid, current->tgid, count); |
| |
| for (i = 0; i < count; i++) { |
| rcu_read_lock(); |
| file = fcheck_files(files, i); |
| |
| p_rootname = NULL; |
| p_filename = NULL; |
| |
| if (file) { |
| if (file->f_path.mnt && file->f_path.mnt->mnt_root && |
| file->f_path.mnt->mnt_root->d_name.name) |
| p_rootname = file->f_path.mnt->mnt_root->d_name.name; |
| |
| if (file->f_path.dentry && file->f_path.dentry->d_name.name) |
| p_filename = file->f_path.dentry->d_name.name; |
| |
| pr_err("[%04d]%s%s\n", i, p_rootname ? p_rootname : "null", |
| p_filename ? p_filename : "null"); |
| } |
| rcu_read_unlock(); |
| } |
| } |
| |
| void sec_debug_EMFILE_error_proc(unsigned long files_addr) |
| { |
| if (files_addr != (unsigned long)(current->files)) { |
| pr_err("Too many open files Error at %pS\n" |
| "%s(%d) thread of %s process tried fd allocation by proxy.\n" |
| "files_addr = 0x%lx, current->files=0x%p\n", |
| __builtin_return_address(0), |
| current->comm, current->tgid, current->group_leader->comm, |
| files_addr, current->files); |
| return; |
| } |
| |
| pr_err("Too many open files(%d:%s) at %pS\n", |
| current->tgid, current->group_leader->comm, __builtin_return_address(0)); |
| |
| if (!sec_debug_level.en.kernel_fault) |
| return; |
| |
| /* We check EMFILE error in only "system_server","mediaserver" and "surfaceflinger" process.*/ |
| if (!strcmp(current->group_leader->comm, "system_server") || |
| !strcmp(current->group_leader->comm, "mediaserver") || |
| !strcmp(current->group_leader->comm, "surfaceflinger")) { |
| sec_debug_print_file_list(); |
| panic("Too many open files"); |
| } |
| } |
| #endif /* CONFIG_SEC_DEBUG_FILE_LEAK */ |
| |
| static struct sec_debug_next *sdn; |
| static unsigned long sec_debug_next_phys; |
| static unsigned long sec_debug_next_size; |
| |
| void sec_debug_set_task_in_pm_suspend(uint64_t task) |
| { |
| if (sdn) |
| sdn->kernd.task_in_pm_suspend = task; |
| } |
| |
| void sec_debug_set_task_in_sys_reboot(uint64_t task) |
| { |
| if (sdn) |
| sdn->kernd.task_in_sys_reboot = task; |
| } |
| |
| struct watchdogd_info *sec_debug_get_wdd_info(void) |
| { |
| if (sdn) { |
| pr_crit("%s: return right value\n", __func__); |
| |
| return &(sdn->kernd.wddinfo); |
| } |
| |
| pr_crit("%s: return NULL\n", __func__); |
| |
| return NULL; |
| } |
| |
| struct bad_stack_info *sec_debug_get_bs_info(void) |
| { |
| if (sdn) |
| return &sdn->kernd.bsi; |
| |
| return NULL; |
| } |
| |
| void *sec_debug_get_debug_base(int type) |
| { |
| if (sdn) { |
| if (type == SDN_MAP_AUTO_COMMENT) |
| return &(sdn->auto_comment); |
| else if (type == SDN_MAP_EXTRA_INFO) |
| return &(sdn->extra_info); |
| } |
| |
| pr_crit("%s: return NULL\n", __func__); |
| |
| return NULL; |
| } |
| |
| unsigned long sec_debug_get_buf_base(int type) |
| { |
| if (sdn) { |
| return sdn->map.buf[type].base; |
| } |
| |
| pr_crit("%s: return 0\n", __func__); |
| |
| return 0; |
| } |
| |
| unsigned long sec_debug_get_buf_size(int type) |
| { |
| if (sdn) { |
| return sdn->map.buf[type].size; |
| } |
| |
| pr_crit("%s: return 0\n", __func__); |
| |
| return 0; |
| } |
| |
| void secdbg_write_buf(struct outbuf *obuf, int len, const char *fmt, ...) |
| { |
| va_list list; |
| char *base; |
| int rem, ret; |
| |
| base = obuf->buf; |
| base += obuf->index; |
| |
| rem = sizeof(obuf->buf); |
| rem -= obuf->index; |
| |
| if (rem <= 0) |
| return; |
| |
| if ((len > 0) && (len < rem)) |
| rem = len; |
| |
| va_start(list, fmt); |
| ret = vsnprintf(base, rem, fmt, list); |
| if (ret) |
| obuf->index += ret; |
| |
| va_end(list); |
| } |
| |
| void sec_debug_set_task_in_sys_shutdown(uint64_t task) |
| { |
| if (sdn) |
| sdn->kernd.task_in_sys_shutdown = task; |
| } |
| |
| void sec_debug_set_task_in_dev_shutdown(uint64_t task) |
| { |
| if (sdn) |
| sdn->kernd.task_in_dev_shutdown = task; |
| } |
| |
| void sec_debug_set_sysrq_crash(struct task_struct *task) |
| { |
| if (sdn) { |
| sdn->kernd.task_in_sysrq_crash = (uint64_t)task; |
| |
| #ifdef CONFIG_SEC_DEBUG_SYSRQ_KMSG |
| if (task) { |
| if (strcmp(task->comm, "init") == 0) |
| sdn->kernd.sysrq_ptr = sec_debug_get_curr_init_ptr(); |
| else |
| sdn->kernd.sysrq_ptr = dbg_snapshot_get_curr_ptr_for_sysrq(); |
| #endif |
| } |
| } |
| } |
| |
| void sec_debug_set_task_in_soft_lockup(uint64_t task) |
| { |
| if (sdn) |
| sdn->kernd.task_in_soft_lockup = task; |
| } |
| |
| void sec_debug_set_cpu_in_soft_lockup(uint64_t cpu) |
| { |
| if (sdn) |
| sdn->kernd.cpu_in_soft_lockup = cpu; |
| } |
| |
| void sec_debug_set_task_in_hard_lockup(uint64_t task) |
| { |
| if (sdn) |
| sdn->kernd.task_in_hard_lockup = task; |
| } |
| |
| void sec_debug_set_cpu_in_hard_lockup(uint64_t cpu) |
| { |
| if (sdn) |
| sdn->kernd.cpu_in_hard_lockup = cpu; |
| } |
| |
| void sec_debug_set_unfrozen_task(uint64_t task) |
| { |
| if (sdn) |
| sdn->kernd.unfrozen_task = task; |
| } |
| |
| void sec_debug_set_unfrozen_task_count(uint64_t count) |
| { |
| if (sdn) |
| sdn->kernd.unfrozen_task_count = count; |
| } |
| |
| void sec_debug_set_task_in_sync_irq(uint64_t task, unsigned int irq, const char *name, struct irq_desc *desc) |
| { |
| if (sdn) { |
| sdn->kernd.sync_irq_task = task; |
| sdn->kernd.sync_irq_num = irq; |
| sdn->kernd.sync_irq_name = (uint64_t)name; |
| sdn->kernd.sync_irq_desc = (uint64_t)desc; |
| |
| if (desc) { |
| sdn->kernd.sync_irq_threads_active = desc->threads_active.counter; |
| |
| if (desc->action && (desc->action->irq == irq) && desc->action->thread) |
| sdn->kernd.sync_irq_thread = (uint64_t)(desc->action->thread); |
| else |
| sdn->kernd.sync_irq_thread = 0; |
| } |
| } |
| } |
| |
| void sec_debug_set_device_shutdown_timeinfo(uint64_t start, uint64_t end, uint64_t duration, uint64_t func) |
| { |
| if (sdn && func) { |
| if (duration > sdn->kernd.dev_shutdown_duration) { |
| sdn->kernd.dev_shutdown_start = start; |
| sdn->kernd.dev_shutdown_end = end; |
| sdn->kernd.dev_shutdown_duration = duration; |
| sdn->kernd.dev_shutdown_func = func; |
| } |
| } |
| } |
| |
| void sec_debug_clr_device_shutdown_timeinfo(void) |
| { |
| if (sdn) { |
| sdn->kernd.dev_shutdown_start = 0; |
| sdn->kernd.dev_shutdown_end = 0; |
| sdn->kernd.dev_shutdown_duration = 0; |
| sdn->kernd.dev_shutdown_func = 0; |
| } |
| } |
| |
| void sec_debug_set_shutdown_device(const char *fname, const char *dname) |
| { |
| if (sdn) { |
| sdn->kernd.sdi.shutdown_func = (uint64_t)fname; |
| sdn->kernd.sdi.shutdown_device = (uint64_t)dname; |
| } |
| } |
| |
| void sec_debug_set_suspend_device(const char *fname, const char *dname) |
| { |
| if (sdn) { |
| sdn->kernd.sdi.suspend_func = (uint64_t)fname; |
| sdn->kernd.sdi.suspend_device = (uint64_t)dname; |
| } |
| } |
| |
| static void init_ess_info(unsigned int index, char *key) |
| { |
| struct ess_info_offset *p; |
| |
| p = &(sdn->ss_info.item[index]); |
| |
| sec_debug_get_kevent_info(p, index); |
| |
| memset(p->key, 0, SD_ESSINFO_KEY_SIZE); |
| snprintf(p->key, SD_ESSINFO_KEY_SIZE, key); |
| } |
| |
| static void sec_debug_set_essinfo(void) |
| { |
| unsigned int index = 0; |
| |
| memset(&(sdn->ss_info), 0, sizeof(struct sec_debug_ess_info)); |
| |
| init_ess_info(index++, "kevnt-task"); |
| init_ess_info(index++, "kevnt-work"); |
| init_ess_info(index++, "kevnt-irq"); |
| init_ess_info(index++, "kevnt-freq"); |
| init_ess_info(index++, "kevnt-idle"); |
| init_ess_info(index++, "kevnt-thrm"); |
| init_ess_info(index++, "kevnt-acpm"); |
| init_ess_info(index++, "kevnt-mfrq"); |
| |
| for (; index < SD_NR_ESSINFO_ITEMS;) |
| init_ess_info(index++, "empty"); |
| |
| for (index = 0; index < SD_NR_ESSINFO_ITEMS; index++) |
| printk("%s: key: %s offset: %llx nr: %x\n", __func__, |
| sdn->ss_info.item[index].key, |
| sdn->ss_info.item[index].base, |
| sdn->ss_info.item[index].nr); |
| } |
| |
| static void sec_debug_set_taskinfo(void) |
| { |
| sdn->task.stack_size = THREAD_SIZE; |
| sdn->task.start_sp = THREAD_START_SP; |
| sdn->task.irq_stack.pcpu_stack = (uint64_t)&irq_stack_ptr; |
| sdn->task.irq_stack.size = IRQ_STACK_SIZE; |
| sdn->task.irq_stack.start_sp = IRQ_STACK_START_SP; |
| |
| sdn->task.ti.struct_size = sizeof(struct thread_info); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ti.flags, struct thread_info, flags); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.cpu, struct task_struct, cpu); |
| |
| sdn->task.ts.struct_size = sizeof(struct task_struct); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.state, struct task_struct, state); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.exit_state, struct task_struct, |
| exit_state); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.stack, struct task_struct, stack); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.flags, struct task_struct, flags); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.on_cpu, struct task_struct, on_cpu); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.pid, struct task_struct, pid); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.on_rq, struct task_struct, on_rq); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.comm, struct task_struct, comm); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.tasks_next, struct task_struct, |
| tasks.next); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.thread_group_next, |
| struct task_struct, thread_group.next); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.fp, struct task_struct, |
| thread.cpu_context.fp); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.sp, struct task_struct, |
| thread.cpu_context.sp); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.pc, struct task_struct, |
| thread.cpu_context.pc); |
| |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.sched_info__pcount, |
| struct task_struct, sched_info.pcount); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.sched_info__run_delay, |
| struct task_struct, |
| sched_info.run_delay); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.sched_info__last_arrival, |
| struct task_struct, |
| sched_info.last_arrival); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.sched_info__last_queued, |
| struct task_struct, |
| sched_info.last_queued); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.ssdbg_wait__type, |
| struct task_struct, |
| ssdbg_wait.type); |
| SET_MEMBER_TYPE_INFO(&sdn->task.ts.ssdbg_wait__data, |
| struct task_struct, |
| ssdbg_wait.data); |
| |
| sdn->task.init_task = (uint64_t)&init_task; |
| } |
| |
| static void sec_debug_set_spinlockinfo(void) |
| { |
| #ifdef CONFIG_DEBUG_SPINLOCK |
| SET_MEMBER_TYPE_INFO(&sdn->rlock.owner_cpu, struct raw_spinlock, owner_cpu); |
| SET_MEMBER_TYPE_INFO(&sdn->rlock.owner, struct raw_spinlock, owner); |
| sdn->rlock.debug_enabled = 1; |
| #else |
| sdn->rlock.debug_enabled = 0; |
| #endif |
| } |
| |
| static unsigned long kconfig_base; |
| static unsigned long kconfig_size; |
| |
| void sec_debug_set_kconfig(unsigned long base, unsigned long size) |
| { |
| if (!sdn) { |
| pr_info("%s: call before sdn init\n", __func__); |
| } |
| |
| kconfig_base = base; |
| kconfig_size = size; |
| } |
| |
| static void sec_debug_set_kconstants(void) |
| { |
| sdn->kcnst.nr_cpus = NR_CPUS; |
| sdn->kcnst.per_cpu_offset.pa = virt_to_phys(__per_cpu_offset); |
| sdn->kcnst.per_cpu_offset.size = sizeof(__per_cpu_offset[0]); |
| sdn->kcnst.per_cpu_offset.count = sizeof(__per_cpu_offset) / sizeof(__per_cpu_offset[0]); |
| |
| sdn->kcnst.phys_offset = PHYS_OFFSET; |
| sdn->kcnst.phys_mask = PHYS_MASK; |
| sdn->kcnst.page_offset = PAGE_OFFSET; |
| sdn->kcnst.page_mask = PAGE_MASK; |
| sdn->kcnst.page_shift = PAGE_SHIFT; |
| |
| sdn->kcnst.va_bits = VA_BITS; |
| sdn->kcnst.kimage_vaddr = kimage_vaddr; |
| sdn->kcnst.kimage_voffset = kimage_voffset; |
| |
| sdn->kcnst.pa_swapper = (uint64_t)virt_to_phys(init_mm.pgd); |
| sdn->kcnst.pgdir_shift = PGDIR_SHIFT; |
| sdn->kcnst.pud_shift = PUD_SHIFT; |
| sdn->kcnst.pmd_shift = PMD_SHIFT; |
| sdn->kcnst.ptrs_per_pgd = PTRS_PER_PGD; |
| sdn->kcnst.ptrs_per_pud = PTRS_PER_PUD; |
| sdn->kcnst.ptrs_per_pmd = PTRS_PER_PMD; |
| sdn->kcnst.ptrs_per_pte = PTRS_PER_PTE; |
| |
| sdn->kcnst.kconfig_base = kconfig_base; |
| sdn->kcnst.kconfig_size = kconfig_size; |
| |
| sdn->kcnst.pa_text = virt_to_phys(_text); |
| sdn->kcnst.pa_start_rodata = virt_to_phys(__start_rodata); |
| |
| } |
| |
| static void __init sec_debug_init_sdn(struct sec_debug_next *d) |
| { |
| #define clear_sdn_field(__p, __m) memset(&(__p)->__m, 0x0, sizeof((__p)->__m)); |
| |
| clear_sdn_field(d, memtab); |
| clear_sdn_field(d, ksyms); |
| clear_sdn_field(d, kcnst); |
| clear_sdn_field(d, task); |
| clear_sdn_field(d, ss_info); |
| clear_sdn_field(d, rlock); |
| clear_sdn_field(d, kernd); |
| } |
| |
| static int __init sec_debug_next_init(void) |
| { |
| if (!sdn) { |
| pr_info("%s: sdn is not allocated, quit\n", __func__); |
| |
| return -1; |
| } |
| |
| /* set magic */ |
| sdn->magic[0] = SEC_DEBUG_MAGIC0; |
| sdn->magic[1] = SEC_DEBUG_MAGIC1; |
| |
| sdn->version[1] = SEC_DEBUG_KERNEL_UPPER_VERSION << 16; |
| sdn->version[1] += SEC_DEBUG_KERNEL_LOWER_VERSION; |
| |
| /* set member table */ |
| secdbg_base_set_memtab_info(&sdn->memtab); |
| |
| /* set kernel symbols */ |
| sec_debug_set_kallsyms_info(&(sdn->ksyms), SEC_DEBUG_MAGIC1); |
| |
| /* set kernel constants */ |
| sec_debug_set_kconstants(); |
| sec_debug_set_taskinfo(); |
| sec_debug_set_essinfo(); |
| sec_debug_set_spinlockinfo(); |
| |
| sec_debug_set_task_in_pm_suspend((uint64_t)NULL); |
| sec_debug_set_task_in_sys_reboot((uint64_t)NULL); |
| sec_debug_set_task_in_sys_shutdown((uint64_t)NULL); |
| sec_debug_set_task_in_dev_shutdown((uint64_t)NULL); |
| sec_debug_set_sysrq_crash(NULL); |
| sec_debug_set_task_in_soft_lockup((uint64_t)NULL); |
| sec_debug_set_cpu_in_soft_lockup((uint64_t)0); |
| sec_debug_set_task_in_hard_lockup((uint64_t)NULL); |
| sec_debug_set_cpu_in_hard_lockup((uint64_t)0); |
| sec_debug_set_unfrozen_task((uint64_t)NULL); |
| sec_debug_set_unfrozen_task_count((uint64_t)0); |
| sec_debug_set_task_in_sync_irq((uint64_t)NULL, 0, NULL, NULL); |
| sec_debug_clr_device_shutdown_timeinfo(); |
| |
| pr_crit("%s: TEST: %d\n", __func__, sec_debug_get_debug_level()); |
| |
| return 0; |
| } |
| late_initcall(sec_debug_next_init); |
| |
| static int __init sec_debug_nocache_remap(void) |
| { |
| pgprot_t prot = __pgprot(PROT_NORMAL_NC); |
| int page_size, i; |
| struct page *page; |
| struct page **pages; |
| void *addr; |
| |
| if (!sec_debug_rmem_size || !sec_debug_rmem_phys) { |
| pr_err("%s: failed to set nocache pages\n", __func__); |
| |
| return -1; |
| } |
| |
| page_size = (sec_debug_rmem_size + PAGE_SIZE - 1) / PAGE_SIZE; |
| pages = kzalloc(sizeof(struct page *) * page_size, GFP_KERNEL); |
| if (!pages) { |
| pr_err("%s: failed to allocate pages\n", __func__); |
| |
| return -1; |
| } |
| page = phys_to_page(sec_debug_rmem_phys); |
| for (i = 0; i < page_size; i++) |
| pages[i] = page++; |
| |
| addr = vm_map_ram(pages, page_size, -1, prot); |
| if (!addr) { |
| pr_err("%s: failed to mapping between virt and phys\n", __func__); |
| kfree(pages); |
| |
| return -1; |
| } |
| |
| pr_info("%s: virt: 0x%p\n", __func__, addr); |
| sec_debug_rmem_virt = (unsigned long)addr; |
| |
| kfree(pages); |
| |
| memset((void *)sec_debug_rmem_virt, 0, sec_debug_rmem_size); |
| sec_debug_set_upload_magic(UPLOAD_MAGIC_PANIC, NULL); |
| |
| return 0; |
| } |
| early_initcall(sec_debug_nocache_remap); |
| |
| static int __init sec_debug_next_setup(char *str) |
| { |
| unsigned long size = memparse(str, &str); |
| unsigned long base = 0; |
| |
| /* If we encounter any problem parsing str ... */ |
| if (!size || *str != '@' || kstrtoul(str + 1, 0, &base)) { |
| pr_err("%s: failed to parse address.\n", __func__); |
| goto out; |
| } |
| |
| #ifdef CONFIG_NO_BOOTMEM |
| if (memblock_is_region_reserved(base, size) || memblock_reserve(base, size)) { |
| #else |
| if reserve_bootmem(base, size, BOOTMEM_EXCLUSIVE) { |
| #endif |
| /* size is not match with -size and size + sizeof(...) */ |
| pr_err("%s: failed to reserve size:0x%lx at base 0x%lx\n", |
| __func__, size, base); |
| goto out; |
| } |
| |
| sdn = (struct sec_debug_next *)phys_to_virt(base); |
| if (!sdn) { |
| pr_info("%s: fail to init sec debug next buffer\n", __func__); |
| |
| goto out; |
| } |
| sec_debug_init_sdn(sdn); |
| |
| sec_debug_next_phys = base; |
| sec_debug_next_size = size; |
| pr_info("%s: base(virt):0x%lx size:0x%lx\n", __func__, (unsigned long)sdn, size); |
| pr_info("%s: ds size: 0x%lx\n", __func__, round_up(sizeof(struct sec_debug_next), PAGE_SIZE)); |
| |
| out: |
| return 0; |
| } |
| __setup("sec_debug_next=", sec_debug_next_setup); |
| |
| #endif /* CONFIG_SEC_DEBUG */ |