1085 lines
29 KiB
C
Executable File
1085 lines
29 KiB
C
Executable File
/*
|
|
* 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 void sec_debug_set_upload_magic(unsigned magic, char *str)
|
|
{
|
|
preempt_disable();
|
|
|
|
*(unsigned int *)SEC_DEBUG_MAGIC_VA = magic;
|
|
*(unsigned int *)(SEC_DEBUG_MAGIC_VA + SZ_4K - 4) = magic;
|
|
|
|
if (str) {
|
|
strncpy((char *)SEC_DEBUG_MAGIC_VA + 4, str, SZ_1K - 4);
|
|
|
|
#ifdef CONFIG_SEC_DEBUG_EXTRA_INFO
|
|
sec_debug_set_extra_info_panic(str);
|
|
sec_debug_finish_extra_info();
|
|
#endif
|
|
}
|
|
|
|
flush_cache_all();
|
|
|
|
preempt_enable();
|
|
|
|
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 void sec_debug_kmsg_dump(struct kmsg_dumper *dumper, enum kmsg_dump_reason reason)
|
|
{
|
|
kmsg_dump_get_buffer(dumper, true, (char *)SEC_DEBUG_DUMPER_LOG_VA, SZ_2K - 4, NULL);
|
|
}
|
|
|
|
static struct kmsg_dumper sec_dumper = {
|
|
.dump = sec_debug_kmsg_dump,
|
|
};
|
|
|
|
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_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 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__);
|
|
|
|
return 0;
|
|
}
|
|
late_initcall(sec_debug_recovery_cause_init);
|
|
|
|
static int __init sec_debug_init(void)
|
|
{
|
|
if (!sec_debug_reserve_ok)
|
|
pr_crit("fatal: %s: memory has not been reserved\n", __func__);
|
|
|
|
/* clear traps info */
|
|
memset((void *)SEC_DEBUG_MAGIC_VA + 4, 0, SZ_1K - 4);
|
|
|
|
sec_debug_set_upload_magic(UPLOAD_MAGIC_PANIC, NULL);
|
|
|
|
kmsg_dump_register(&sec_dumper);
|
|
|
|
return 0;
|
|
}
|
|
early_initcall(sec_debug_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 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");
|
|
|
|
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);
|
|
#ifdef CONFIG_SEC_DEBUG_DTASK
|
|
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);
|
|
#endif /* CONFIG_SEC_DEBUG_DTASK */
|
|
|
|
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();
|
|
|
|
return 0;
|
|
}
|
|
late_initcall(sec_debug_next_init);
|
|
|
|
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 */
|