mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
ba1bb225eb
PD#SWPL-179411 Problem: BUG: sleeping function called from invalid context at kernel/sched/completion.c in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 1, name: swapper/0 Preemption disabled at: [<c0428468>] queue_stop_cpus_work+0x60/0x1bc [<c030dc24>] show_stack+0x28/0x2c [<c105e624>] dump_stack_lvl+0x48/0x54 [<c0371660>] ___might_sleep+0x1c4/0x1e8 [<c10621b8>] wait_for_completion+0x28/0x58 [<c0428180>] stop_machine_cpuslocked+0x10c/0x208 [<c106ac28>] patch_text+0x20/0x28 [<c0501254>] __jump_label_update+0x7c/0xec [<c05002b8>] static_key_enable_cpuslocked+0x8c/0x100 [<c050034c>] static_key_enable+0x20/0x28 [<c0441acc>] tracepoint_add_func+0x38c/0x400 [<c0441c00>] tracepoint_probe_register+0x54/0x6c [<c0db6974>] ftrace_ramoops_init+0x288/0x2fc [<c163cb30>] aml_iotrace_init+0x46c/0x550 [<c03020a0>] do_one_initcall+0x104/0x364 [<c1601f90>] do_initcall_level+0xa0/0x15c [<c1601ec0>] do_initcalls+0x58/0x88 [<c1601d04>] kernel_init_freeable+0x114/0x160 [<c105fe24>] kernel_init+0x20/0x1c4 [<c0300134>] ret_from_fork+0x14/0x40 Solution: add preempt protections at hooks Verify: t5w Change-Id: Ib6a13e104aeb697e84e331152f6ea41ef6d11b35 Signed-off-by: song.han <song.han@amlogic.com>
500 lines
12 KiB
C
500 lines
12 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* drivers/amlogic/debug/debug_ftrace_ramoops.c
|
|
*
|
|
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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 SKIP_LOCKUP_CHECK
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/irqflags.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/pm_domain.h>
|
|
#include <linux/workqueue.h>
|
|
#include <trace/hooks/sched.h>
|
|
#include <linux/amlogic/aml_iotrace.h>
|
|
#include <linux/amlogic/gki_module.h>
|
|
#include <trace/events/rwmmio.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/scs.h>
|
|
|
|
static DEFINE_PER_CPU(int, en);
|
|
|
|
#define IRQ_D 1
|
|
#define MAX_DETECT_REG 10
|
|
#define MAX_STACK_DEPTH 6
|
|
|
|
/*
|
|
* bit0: skip vdec-core,vdec irqthread read/write
|
|
* bit1: skip frc tasklet read/write
|
|
* bit2: skip vsync isr read/write
|
|
* bit3: skip amvecm isr read/write
|
|
* bit4: skip usb isr read/write
|
|
*/
|
|
|
|
#define IO_BLACKLIST_VDEC 0x1
|
|
#define IO_BLACKLIST_FRC 0x2
|
|
#define IO_BLACKLIST_VSYNC 0x4
|
|
#define IO_BLACKLIST_AMVECM 0x8
|
|
#define IO_BLACKLIST_USB 0x10
|
|
|
|
static int ramoops_io_blacklist = IO_BLACKLIST_AMVECM;
|
|
|
|
static int ramoops_io_blacklist_setup(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
if (kstrtoint(buf, 0, &ramoops_io_blacklist)) {
|
|
pr_err("ramoops_io_blacklist error: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
__setup("ramoops_io_blacklist=", ramoops_io_blacklist_setup);
|
|
|
|
static unsigned int check_reg[MAX_DETECT_REG];
|
|
static unsigned int check_mask[MAX_DETECT_REG];
|
|
static unsigned int *virt_addr[MAX_DETECT_REG];
|
|
unsigned long old_val_reg[MAX_DETECT_REG];
|
|
|
|
DEFINE_PER_CPU(bool, frc_iotrace_cut);
|
|
EXPORT_PER_CPU_SYMBOL_GPL(frc_iotrace_cut);
|
|
|
|
DEFINE_PER_CPU(bool, vsync_iotrace_cut);
|
|
EXPORT_PER_CPU_SYMBOL_GPL(vsync_iotrace_cut);
|
|
|
|
DEFINE_PER_CPU(bool, amvecm_iotrace_cut);
|
|
EXPORT_PER_CPU_SYMBOL_GPL(amvecm_iotrace_cut);
|
|
|
|
DEFINE_PER_CPU(bool, usb_iotrace_cut);
|
|
EXPORT_PER_CPU_SYMBOL_GPL(usb_iotrace_cut);
|
|
|
|
#if 0
|
|
static struct delayed_work find_vdec_pid_work;
|
|
static pid_t vdec_core_pid, vdec_irqthread_pid0, vdec_irqthread_pid1;
|
|
static int retry_times;
|
|
|
|
static void find_vdec_pid(struct work_struct *work)
|
|
{
|
|
struct task_struct *task;
|
|
|
|
for_each_process(task) {
|
|
if (!task->mm) {
|
|
if (!vdec_core_pid && !strncmp(task->comm, "vdec-core", 16))
|
|
vdec_core_pid = task->pid;
|
|
if (!vdec_irqthread_pid0 && strstr(task->comm, "vdec-0"))
|
|
vdec_irqthread_pid0 = task->pid;
|
|
if (!vdec_irqthread_pid1 && strstr(task->comm, "vdec-1"))
|
|
vdec_irqthread_pid1 = task->pid;
|
|
}
|
|
}
|
|
|
|
if (!vdec_core_pid || !vdec_irqthread_pid0 || !vdec_irqthread_pid1) {
|
|
if (retry_times++ > 3)
|
|
//cancel_delayed_work(&find_vdec_pid_work);
|
|
else
|
|
queue_delayed_work(system_wq, &find_vdec_pid_work, retry_times * 10 * HZ);
|
|
} else {
|
|
//cancel_delayed_work(&find_vdec_pid_work);
|
|
}
|
|
}
|
|
|
|
static bool is_vdec_pid(void)
|
|
{
|
|
if (current->pid == vdec_core_pid || current->pid == vdec_irqthread_pid0 ||
|
|
current->pid == vdec_irqthread_pid1)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static bool is_in_frc_tasklet(void)
|
|
{
|
|
unsigned int cpu = raw_smp_processor_id();
|
|
|
|
return per_cpu(frc_iotrace_cut, cpu);
|
|
}
|
|
|
|
static bool is_in_vsync_isr(void)
|
|
{
|
|
unsigned int cpu = raw_smp_processor_id();
|
|
|
|
return per_cpu(vsync_iotrace_cut, cpu);
|
|
}
|
|
|
|
static bool is_in_amvecm_isr(void)
|
|
{
|
|
unsigned int cpu = raw_smp_processor_id();
|
|
|
|
return per_cpu(amvecm_iotrace_cut, cpu);
|
|
}
|
|
|
|
static bool is_in_usb_isr(void)
|
|
{
|
|
unsigned int cpu = raw_smp_processor_id();
|
|
|
|
return per_cpu(usb_iotrace_cut, cpu);
|
|
}
|
|
|
|
static int reg_check_panic;
|
|
static bool reg_check_flag;
|
|
|
|
static int reg_check_panic_setup(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
if (kstrtoint(buf, 0, ®_check_panic)) {
|
|
pr_err("reg_check_panic error: %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
__setup("reg_check_panic=", reg_check_panic_setup);
|
|
|
|
void reg_check_init(void)
|
|
{
|
|
int i;
|
|
unsigned int *virt_tmp[MAX_DETECT_REG] = {NULL};
|
|
|
|
memcpy(virt_tmp, virt_addr, sizeof(virt_addr));
|
|
|
|
for (i = 0; i < MAX_DETECT_REG; i++)
|
|
rcu_assign_pointer(virt_addr[i], NULL);
|
|
|
|
synchronize_rcu();
|
|
|
|
for (i = 0; i < MAX_DETECT_REG; i++) {
|
|
if (virt_tmp[i])
|
|
iounmap(virt_tmp[i]);
|
|
else
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < MAX_DETECT_REG; i++) {
|
|
if (check_reg[i]) {
|
|
virt_addr[i] = (unsigned int *)ioremap(check_reg[i], sizeof(unsigned long));
|
|
if (!virt_addr[i]) {
|
|
pr_err("Unable to map reg 0x%x\n", check_reg[i]);
|
|
return;
|
|
}
|
|
pr_info("reg 0x%x has been mapped to 0x%px\n", check_reg[i], virt_addr[i]);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void reg_check_func(void)
|
|
{
|
|
unsigned int val;
|
|
unsigned long tmp;
|
|
unsigned int i = 0;
|
|
unsigned int *tmp_addr;
|
|
|
|
rcu_read_lock();
|
|
while (i < MAX_DETECT_REG && virt_addr[i]) {
|
|
tmp_addr = rcu_dereference(virt_addr[i]);
|
|
if (old_val_reg[i] != -1) {
|
|
val = *tmp_addr;
|
|
if ((val & check_mask[i]) != (old_val_reg[i] & check_mask[i])) {
|
|
tmp = old_val_reg[i];
|
|
old_val_reg[i] = val;
|
|
pr_err("phys_addr:0x%x new_val=0x%x old_val=0x%lx\n",
|
|
check_reg[i], val, tmp);
|
|
if (!reg_check_panic)
|
|
dump_stack();
|
|
else
|
|
panic("reg_check_panic");
|
|
}
|
|
} else {
|
|
old_val_reg[i] = *tmp_addr;
|
|
}
|
|
i++;
|
|
}
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static int check_reg_setup(char *ptr)
|
|
{
|
|
char *str_entry;
|
|
char *str = (char *)ptr;
|
|
unsigned int tmp;
|
|
unsigned int i = 0, ret;
|
|
|
|
do {
|
|
str_entry = strsep(&str, ",");
|
|
if (str_entry) {
|
|
ret = kstrtou32(str_entry, 16, &tmp);
|
|
if (ret)
|
|
return -1;
|
|
pr_info("check_reg: 0x%x\n", tmp);
|
|
check_reg[i] = tmp;
|
|
old_val_reg[i++] = -1;
|
|
}
|
|
} while (str_entry && i < MAX_DETECT_REG);
|
|
|
|
reg_check_flag = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
__setup("check_reg=", check_reg_setup);
|
|
|
|
static int check_mask_setup(char *ptr)
|
|
{
|
|
char *str_entry;
|
|
char *str = (char *)ptr;
|
|
unsigned int tmp;
|
|
unsigned int i = 0, ret;
|
|
|
|
do {
|
|
str_entry = strsep(&str, ",");
|
|
if (str_entry) {
|
|
ret = kstrtou32(str_entry, 16, &tmp);
|
|
if (ret)
|
|
return -1;
|
|
pr_info("check_mask: 0x%x\n", tmp);
|
|
check_mask[i++] = tmp;
|
|
}
|
|
} while (str_entry && i < MAX_DETECT_REG);
|
|
|
|
return 0;
|
|
}
|
|
|
|
__setup("check_mask=", check_mask_setup);
|
|
|
|
#define REG_MAX_NUM 5
|
|
static struct resource gic_mem[REG_MAX_NUM];
|
|
|
|
static char *gic_compatible[] = {
|
|
"arm,gic-400",
|
|
"arm,arm11mp-gic",
|
|
"arm,arm1176jzf-devchip-gic",
|
|
"arm,cortex-a15-gic",
|
|
"arm,cortex-a9-gic",
|
|
"arm,cortex-a7-gic",
|
|
"arm,p1390"
|
|
};
|
|
|
|
static int gic_reg_num;
|
|
|
|
static void aml_ramoops_filter_dt(void)
|
|
{
|
|
struct device_node *node_gic = NULL;
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(gic_compatible); i++) {
|
|
node_gic = of_find_compatible_node(NULL, NULL, gic_compatible[i]);
|
|
if (node_gic)
|
|
break;
|
|
}
|
|
if (node_gic) {
|
|
for (i = 0; i < REG_MAX_NUM; i++) {
|
|
ret = of_address_to_resource(node_gic, i, &gic_mem[i]);
|
|
if (ret)
|
|
break;
|
|
}
|
|
gic_reg_num = i;
|
|
}
|
|
}
|
|
|
|
static int is_filter_reg(unsigned int reg)
|
|
{
|
|
int i;
|
|
|
|
/* filter the gic register. */
|
|
for (i = 0; i < gic_reg_num; i++)
|
|
if (reg >= gic_mem[i].start && reg <= gic_mem[i].end)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __nocfi pstore_io_save(unsigned long reg, unsigned long val, unsigned int flag,
|
|
unsigned long *irq_flags)
|
|
{
|
|
struct iotrace_record rec = {
|
|
.type = flag,
|
|
};
|
|
|
|
int cpu = raw_smp_processor_id();
|
|
|
|
if (!ramoops_ftrace_en || !(ramoops_trace_mask & TRACE_MASK_IO))
|
|
return;
|
|
|
|
#if 0
|
|
if (ramoops_io_blacklist) {
|
|
if ((ramoops_io_blacklist & IO_BLACKLIST_VDEC && is_vdec_pid() && in_task()) ||
|
|
(ramoops_io_blacklist & IO_BLACKLIST_FRC && (is_in_frc_tasklet() &&
|
|
!in_irq())) ||
|
|
(ramoops_io_blacklist & IO_BLACKLIST_VSYNC && is_in_vsync_isr()) ||
|
|
(ramoops_io_blacklist & IO_BLACKLIST_AMVECM && is_in_amvecm_isr()) ||
|
|
(ramoops_io_blacklist & IO_BLACKLIST_USB && is_in_usb_isr()))
|
|
return;
|
|
}
|
|
#else
|
|
if (ramoops_io_blacklist) {
|
|
if ((ramoops_io_blacklist & IO_BLACKLIST_FRC && (is_in_frc_tasklet() &&
|
|
!in_irq())) ||
|
|
(ramoops_io_blacklist & IO_BLACKLIST_VSYNC && is_in_vsync_isr()) ||
|
|
(ramoops_io_blacklist & IO_BLACKLIST_AMVECM && is_in_amvecm_isr()) ||
|
|
(ramoops_io_blacklist & IO_BLACKLIST_USB && is_in_usb_isr()))
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ((flag == RECORD_TYPE_IO_R || flag == RECORD_TYPE_IO_W) && IRQ_D)
|
|
local_irq_save(*irq_flags);
|
|
|
|
rec.reg = (unsigned int)page_to_phys(vmalloc_to_page((const void *)reg)) +
|
|
offset_in_page(reg);
|
|
rec.reg_val = (unsigned int)val;
|
|
|
|
if (reg_check_flag && flag == RECORD_TYPE_IO_W_END)
|
|
reg_check_func();
|
|
|
|
if (ramoops_io_stack) {
|
|
unsigned long stack_entries[MAX_STACK_DEPTH];
|
|
|
|
stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 2 + ramoops_io_skip);
|
|
|
|
if (flag == RECORD_TYPE_IO_W || flag == RECORD_TYPE_IO_R) {
|
|
rec.ip = stack_entries[0];
|
|
rec.parent_ip = stack_entries[1];
|
|
} else {
|
|
rec.ip = stack_entries[4];
|
|
rec.parent_ip = stack_entries[5];
|
|
}
|
|
}
|
|
|
|
cpu = raw_smp_processor_id();
|
|
if (unlikely(oops_in_progress) || unlikely(per_cpu(en, cpu))) {
|
|
if ((flag == RECORD_TYPE_IO_R || flag == RECORD_TYPE_IO_W) && IRQ_D)
|
|
local_irq_restore(*irq_flags);
|
|
return;
|
|
}
|
|
|
|
per_cpu(en, cpu) = 1;
|
|
|
|
if (!is_filter_reg(rec.reg))
|
|
aml_pstore_write(AML_PSTORE_TYPE_IO, (void *)&rec, irqs_disabled_flags(*irq_flags),
|
|
flag);
|
|
|
|
per_cpu(en, cpu) = 0;
|
|
|
|
if ((flag == RECORD_TYPE_IO_R_END || flag == RECORD_TYPE_IO_W_END) &&
|
|
IRQ_D)
|
|
local_irq_restore(*irq_flags);
|
|
|
|
}
|
|
EXPORT_SYMBOL(pstore_io_save);
|
|
|
|
#ifdef CONFIG_ANDROID_VENDOR_HOOKS
|
|
static void schedule_hook(void *data, struct task_struct *prev, struct task_struct *next,
|
|
struct rq *rq)
|
|
{
|
|
struct iotrace_record rec = {
|
|
.type = RECORD_TYPE_SCHED_SWITCH,
|
|
.curr_pid = prev->pid,
|
|
.next_pid = next->pid,
|
|
};
|
|
|
|
if (!ramoops_ftrace_en || !(ramoops_trace_mask & TRACE_MASK_SCHED))
|
|
return;
|
|
|
|
strscpy(rec.curr_comm, prev->comm, sizeof(rec.curr_comm));
|
|
strscpy(rec.next_comm, next->comm, sizeof(rec.next_comm));
|
|
|
|
aml_pstore_write(AML_PSTORE_TYPE_SCHED, &rec, irqs_disabled(), 0);
|
|
}
|
|
|
|
static DEFINE_PER_CPU(unsigned long, irqflag);
|
|
|
|
static void __nocfi rwmmio_write_hook(void *data, unsigned long caller_addr,
|
|
u64 val, u8 width, volatile void __iomem *addr)
|
|
{
|
|
if (!ramoops_ftrace_en)
|
|
return;
|
|
|
|
preempt_iotrace_enter();
|
|
pstore_ftrace_io_wr((unsigned long)addr, val, per_cpu(irqflag, raw_smp_processor_id()));
|
|
}
|
|
|
|
static void __nocfi rwmmio_post_write_hook(void *data, unsigned long caller_addr,
|
|
u64 val, u8 width, volatile void __iomem *addr)
|
|
{
|
|
if (!ramoops_ftrace_en)
|
|
return;
|
|
|
|
if (unlikely(!iotrace_count()))
|
|
return;
|
|
|
|
pstore_ftrace_io_wr_end((unsigned long)addr, val, per_cpu(irqflag, raw_smp_processor_id()));
|
|
preempt_iotrace_exit();
|
|
}
|
|
|
|
static void __nocfi rwmmio_read_hook(void *data, unsigned long caller_addr,
|
|
u8 width, const volatile void __iomem *addr)
|
|
{
|
|
if (!ramoops_ftrace_en)
|
|
return;
|
|
|
|
preempt_iotrace_enter();
|
|
pstore_ftrace_io_rd((unsigned long)addr, per_cpu(irqflag, raw_smp_processor_id()));
|
|
}
|
|
|
|
static void __nocfi rwmmio_post_read_hook(void *data, unsigned long caller_addr,
|
|
u64 val, u8 width, const volatile void __iomem *addr)
|
|
{
|
|
if (!ramoops_ftrace_en)
|
|
return;
|
|
|
|
if (unlikely(!iotrace_count()))
|
|
return;
|
|
|
|
pstore_ftrace_io_rd_end((unsigned long)addr, val, per_cpu(irqflag, raw_smp_processor_id()));
|
|
preempt_iotrace_exit();
|
|
}
|
|
#endif
|
|
|
|
int ftrace_ramoops_init(void)
|
|
{
|
|
if (reg_check_flag)
|
|
reg_check_init();
|
|
|
|
aml_ramoops_filter_dt();
|
|
|
|
#ifdef CONFIG_ANDROID_VENDOR_HOOKS
|
|
register_trace_android_rvh_schedule(schedule_hook, NULL);
|
|
|
|
register_trace_rwmmio_write(rwmmio_write_hook, NULL);
|
|
register_trace_rwmmio_post_write(rwmmio_post_write_hook, NULL);
|
|
|
|
register_trace_rwmmio_read(rwmmio_read_hook, NULL);
|
|
register_trace_rwmmio_post_read(rwmmio_post_read_hook, NULL);
|
|
#endif
|
|
#if 0
|
|
INIT_DELAYED_WORK(&find_vdec_pid_work, find_vdec_pid);
|
|
queue_delayed_work(system_wq, &find_vdec_pid_work, 10 * HZ);
|
|
#endif
|
|
return 0;
|
|
}
|