Files
kernel_common_drivers/drivers/debug/debug_ftrace_ramoops.c
T
Hanjie Lin 908c1c3c19 debug: fix iotrace break gki issue [2/3]
PD#SWPL-94447

Problem:
iotrace break gki10

Solution:
modify iotrace still builtin but not break gki10
also enabled DEBUG_PRINTK

Verify:
sc2

Signed-off-by: Hanjie Lin <hanjie.lin@amlogic.com>
Change-Id: Iffc6a21f28de871b7289ca671c2c226c40e9a66a
2022-10-15 03:28:18 -07:00

668 lines
16 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.
*
*/
#include <linux/kernel.h>
#include <linux/compiler.h>
#include <linux/irqflags.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/ftrace.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/amlogic/debug_ftrace_ramoops.h>
#include <../../../fs/pstore/internal.h>
#include <linux/trace_clock.h>
#include <linux/percpu.h>
#include <linux/moduleparam.h>
#include <linux/pstore_ram.h>
#include <linux/io.h>
#include <linux/printk.h>
#include <linux/uaccess.h>
#include <linux/rbtree.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
static DEFINE_PER_CPU(int, en);
#define IRQ_D 1
#define MAX_DETECT_REG 10
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];
struct rb_root reg_addr_root;
struct reg_addr {
struct rb_node node;
unsigned long phys_addr;
unsigned long virt_addr;
unsigned int size;
};
static int reg_check_panic;
core_param(reg_check_panic, reg_check_panic, int, 0644);
unsigned int dump_iomap;
core_param(dump_iomap, dump_iomap, uint, 0664);
unsigned int ramoops_ftrace_en;
int ramoops_io_en;
core_param(ramoops_io_en, ramoops_io_en, int, 0664);
int ramoops_io_dump;
core_param(ramoops_io_dump, ramoops_io_dump, int, 0664);
static int ramoops_io_skip = 1;
core_param(ramoops_io_skip, ramoops_io_skip, int, 0664);
static int dump_phys_addr;
core_param(dump_phys_addr, dump_phys_addr, int, 0644);
const char *record_name[] = {
"NULL",
"IO-FUNC",
"IO-R",
"IO-W",
"IO-R-E",
"IO-W-E",
"IO-TAG",
"IO-SCHED-SWITCH",
"IO-SMC-IN",
"IO-SMC-OUT",
"IO-SMC-NORET-IN",
"IO-CLK-EN",
"IO-CLK-DIS",
"IO-POWER-ON",
"IO-POWER-OFF",
};
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(const char *ptr, const struct kernel_param *kp)
{
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_init();
return 0;
}
static int check_reg_show(char *ptr, const struct kernel_param *kp)
{
unsigned int i = 0;
if (!check_reg[i])
pr_info("No check reg\n");
while (i < MAX_DETECT_REG && check_reg[i]) {
pr_info("check_reg[%u]:0x%x\n", i, check_reg[i]);
i++;
}
return 0;
}
static const struct kernel_param_ops check_reg_ops = {
.set = check_reg_setup,
.get = check_reg_show
};
core_param_cb(check_reg, &check_reg_ops, NULL, 0644);
static int check_mask_setup(const char *ptr, const struct kernel_param *kp)
{
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;
}
static int check_mask_show(char *ptr, const struct kernel_param *kp)
{
unsigned int i = 0;
if (!check_mask[i])
pr_info("No check mask\n");
while (i < MAX_DETECT_REG && check_mask[i]) {
pr_info("check_mask[%u]:0x%x\n", i, check_mask[i]);
i++;
}
return 0;
}
static const struct kernel_param_ops check_mask_ops = {
.set = check_mask_setup,
.get = check_mask_show
};
core_param_cb(check_mask, &check_mask_ops, NULL, 0644);
DEFINE_SPINLOCK(rbtree_lock);
static int reg_addr_insert(struct rb_root *root, struct reg_addr *new)
{
struct rb_node **link = &root->rb_node, *parent = NULL;
/* Figure out where to put new node */
while (*link) {
struct reg_addr *this = container_of(*link, struct reg_addr, node);
int result = new->virt_addr - this->virt_addr;
parent = *link;
if (result < 0)
link = &((*link)->rb_left);
else if (result > 0)
link = &((*link)->rb_right);
else
return 0;
}
/* Add new node and rebalance tree */
rb_link_node(&new->node, parent, link);
rb_insert_color(&new->node, root);
return 1;
}
void save_iomap_info(unsigned long virt_addr, unsigned long phys_addr, unsigned int size)
{
struct reg_addr *new;
unsigned long flags;
if (!dump_phys_addr)
return;
new = kmalloc(sizeof(*new), GFP_KERNEL);
if (new) {
new->virt_addr = virt_addr;
new->phys_addr = phys_addr;
new->size = size;
spin_lock_irqsave(&rbtree_lock, flags);
reg_addr_insert(&reg_addr_root, new);
spin_unlock_irqrestore(&rbtree_lock, flags);
}
}
EXPORT_SYMBOL(save_iomap_info);
void delete_iomap_info(unsigned long addr)
{
struct rb_node *node = NULL;
struct reg_addr *this = NULL;
unsigned long flags;
if (!dump_phys_addr)
return;
for (node = rb_first(&reg_addr_root); node; node = rb_next(node)) {
this = container_of(node, struct reg_addr, node);
if (addr == this->virt_addr) {
spin_lock_irqsave(&rbtree_lock, flags);
rb_erase(&this->node, &reg_addr_root);
spin_unlock_irqrestore(&rbtree_lock, flags);
kfree(this);
return;
}
}
}
EXPORT_SYMBOL(delete_iomap_info);
void notrace pstore_io_rw_dump(struct pstore_ftrace_record *rec,
struct seq_file *s)
{
unsigned long sec = 0, us = 0;
unsigned long long time = rec->time;
unsigned int cpu = pstore_ftrace_decode_cpu(rec);
do_div(time, 1000);
us = (unsigned long)do_div(time, 1000000);
sec = (unsigned long)time;
seq_printf(s, "[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %s%08lx-%8lx> <%ps <- %pS>\n",
sec, us, cpu, rec->in_irq, rec->pid, rec->comm,
record_name[rec->flag], rec->phys_addr ? "p:" : "", rec->val1,
(rec->flag == PSTORE_FLAG_IO_W || rec->flag == PSTORE_FLAG_IO_TAG) ?
rec->val2 : 0, (void *)rec->ip, (void *)rec->parent_ip);
}
void notrace pstore_sched_switch_dump(struct pstore_ftrace_record *rec,
struct seq_file *s)
{
unsigned long sec = 0, us = 0;
unsigned long long time = rec->time;
unsigned int cpu = pstore_ftrace_decode_cpu(rec);
do_div(time, 1000);
us = (unsigned long)do_div(time, 1000000);
sec = (unsigned long)time;
seq_printf(s, "[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %lu:%s>\n",
sec, us, cpu, rec->in_irq, rec->pid, rec->comm,
record_name[rec->flag], rec->val1, (char *)&rec->val2);
}
void notrace pstore_clk_pd_dump(struct pstore_ftrace_record *rec,
struct seq_file *s)
{
unsigned long sec = 0, us = 0;
unsigned long long time = rec->time;
unsigned int cpu = pstore_ftrace_decode_cpu(rec);
do_div(time, 1000);
us = (unsigned long)do_div(time, 1000000);
sec = (unsigned long)time;
seq_printf(s, "[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %s> <%ps <- %pS>\n",
sec, us, cpu, rec->in_irq, rec->pid, rec->comm, record_name[rec->flag],
rec->name, (void *)rec->ip, (void *)rec->parent_ip);
}
void notrace pstore_ftrace_dump(struct pstore_ftrace_record *rec,
struct seq_file *s)
{
switch (rec->flag & PSTORE_FLAG_MASK) {
case PSTORE_FLAG_IO_R:
case PSTORE_FLAG_IO_W:
case PSTORE_FLAG_IO_W_END:
case PSTORE_FLAG_IO_R_END:
case PSTORE_FLAG_IO_TAG:
case PSTORE_FLAG_IO_SMC_IN:
case PSTORE_FLAG_IO_SMC_OUT:
case PSTORE_FLAG_IO_SMC_NORET_IN:
pstore_io_rw_dump(rec, s);
break;
case PSTORE_FLAG_IO_SCHED_SWITCH:
pstore_sched_switch_dump(rec, s);
break;
case PSTORE_FLAG_CLK_ENABLE:
case PSTORE_FLAG_CLK_DISABLE:
case PSTORE_FLAG_PD_POWER_ON:
case PSTORE_FLAG_PD_POWER_OFF:
pstore_clk_pd_dump(rec, s);
break;
default:
seq_printf(s, "Unknown Msg:%x\n", rec->flag);
}
}
void notrace __pstore_sched_switch_dump(struct pstore_ftrace_record *rec)
{
unsigned long sec = 0, us = 0;
unsigned long long time = rec->time;
unsigned int cpu = pstore_ftrace_decode_cpu(rec);
do_div(time, 1000);
us = (unsigned long)do_div(time, 1000000);
sec = (unsigned long)time;
pr_info("[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %lu:%s>\n",
sec, us, cpu, rec->in_irq, rec->pid, rec->comm,
record_name[rec->flag], rec->val1, (char *)&rec->val2);
}
void notrace __pstore_clk_pd_dump(struct pstore_ftrace_record *rec)
{
unsigned long sec = 0, us = 0;
unsigned long long time = rec->time;
unsigned int cpu = pstore_ftrace_decode_cpu(rec);
do_div(time, 1000);
us = (unsigned long)do_div(time, 1000000);
sec = (unsigned long)time;
pr_info("[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %s> <%ps <- %pS>\n",
sec, us, cpu, rec->in_irq, rec->pid, rec->comm, record_name[rec->flag],
rec->name, (void *)rec->ip, (void *)rec->parent_ip);
}
static unsigned long virt_convert_phys_addr(unsigned long virt_addr)
{
struct rb_node *node;
struct reg_addr *this, *tmp = NULL;
unsigned long phys_addr;
static unsigned long last_virt_addr, last_phys_addr;
if ((virt_addr & PAGE_MASK) == (last_virt_addr & PAGE_MASK))
return (last_phys_addr & PAGE_MASK) + (virt_addr & ~PAGE_MASK);
for (node = rb_first(&reg_addr_root); node; node = rb_next(node)) {
this = container_of(node, struct reg_addr, node);
if (virt_addr >= this->virt_addr && virt_addr < this->virt_addr + this->size) {
tmp = this;
phys_addr = virt_addr - tmp->virt_addr + tmp->phys_addr;
last_virt_addr = virt_addr;
last_phys_addr = phys_addr;
return phys_addr;
}
}
/*No corresponding physical address found*/
return -1;
}
void notrace __pstore_io_save(unsigned long reg, unsigned long val,
unsigned long parent, unsigned int flag,
unsigned long *irq_flag)
{
int cpu;
unsigned long ret;
unsigned long flags;
struct pstore_ftrace_record rec = {};
struct pstore_record record = {
.type = PSTORE_TYPE_FTRACE,
.buf = (char *)&rec,
.size = sizeof(rec),
.psi = psinfo,
};
if (!ramoops_ftrace_en || !ramoops_io_en)
return;
if ((flag == PSTORE_FLAG_IO_R || flag == PSTORE_FLAG_IO_W) && IRQ_D)
local_irq_save(*irq_flag);
if (flag == PSTORE_FLAG_IO_W_END)
reg_check_func();
switch (ramoops_io_skip) {
case 1:
rec.ip = CALLER_ADDR1;
rec.parent_ip = CALLER_ADDR2;
break;
case 2:
rec.ip = CALLER_ADDR2;
rec.parent_ip = CALLER_ADDR3;
break;
case 3:
rec.ip = CALLER_ADDR3;
rec.parent_ip = CALLER_ADDR4;
break;
default:
rec.ip = CALLER_ADDR0;
rec.parent_ip = parent;
break;
}
rec.flag = flag;
rec.in_irq = !!in_irq();
rec.phys_addr = 0;
switch (rec.flag) {
case PSTORE_FLAG_CLK_ENABLE:
case PSTORE_FLAG_CLK_DISABLE:
case PSTORE_FLAG_PD_POWER_ON:
case PSTORE_FLAG_PD_POWER_OFF:
strscpy(rec.name, (char *)val, sizeof(rec.name));
break;
default:
rec.val1 = reg;
rec.val2 = val;
break;
}
if (dump_phys_addr) {
switch (rec.flag) {
case PSTORE_FLAG_IO_R:
case PSTORE_FLAG_IO_R_END:
case PSTORE_FLAG_IO_W:
case PSTORE_FLAG_IO_W_END:
spin_lock_irqsave(&rbtree_lock, flags);
ret = virt_convert_phys_addr(reg);
spin_unlock_irqrestore(&rbtree_lock, flags);
if (ret != -1) {
rec.val1 = ret;
rec.phys_addr = 1;
}
break;
default:
break;
}
}
cpu = raw_smp_processor_id();
if (unlikely(oops_in_progress) || unlikely(per_cpu(en, cpu))) {
if ((flag == PSTORE_FLAG_IO_R || flag == PSTORE_FLAG_IO_W) && IRQ_D)
local_irq_restore(*irq_flag);
return;
}
per_cpu(en, cpu) = 1;
pstore_ftrace_encode_cpu(&rec, cpu);
strscpy(rec.comm, current->comm, sizeof(rec.comm));
rec.pid = current->pid;
rec.time = trace_clock_local();
psinfo->write(&record);
per_cpu(en, cpu) = 0;
if ((flag == PSTORE_FLAG_IO_R_END || flag == PSTORE_FLAG_IO_W_END) &&
IRQ_D)
local_irq_restore(*irq_flag);
}
static void notrace __pstore_io_rw_dump(struct pstore_ftrace_record *rec)
{
unsigned long sec = 0, us = 0;
unsigned long long time = rec->time;
unsigned int cpu = pstore_ftrace_decode_cpu(rec);
do_div(time, 1000);
us = (unsigned long)do_div(time, 1000000);
sec = (unsigned long)time;
pr_info("[%04ld.%06ld@%d %d] <%5d-%6s> <%6s %s%08lx-%8lx> <%pS <- %pS>\n",
sec, us, cpu, rec->in_irq, rec->pid, rec->comm,
record_name[rec->flag], rec->phys_addr ? "p:" : "", rec->val1,
(rec->flag == PSTORE_FLAG_IO_W || rec->flag == PSTORE_FLAG_IO_TAG) ?
rec->val2 : 0, (void *)rec->ip, (void *)rec->parent_ip);
}
static void notrace __pstore_ftrace_dump_old(struct pstore_ftrace_record *rec)
{
switch (rec->flag & PSTORE_FLAG_MASK) {
case PSTORE_FLAG_FUNC:
break;
case PSTORE_FLAG_IO_R:
case PSTORE_FLAG_IO_W:
case PSTORE_FLAG_IO_W_END:
case PSTORE_FLAG_IO_R_END:
case PSTORE_FLAG_IO_TAG:
case PSTORE_FLAG_IO_SMC_IN:
case PSTORE_FLAG_IO_SMC_OUT:
case PSTORE_FLAG_IO_SMC_NORET_IN:
__pstore_io_rw_dump(rec);
break;
case PSTORE_FLAG_IO_SCHED_SWITCH:
__pstore_sched_switch_dump(rec);
break;
case PSTORE_FLAG_CLK_ENABLE:
case PSTORE_FLAG_CLK_DISABLE:
case PSTORE_FLAG_PD_POWER_ON:
case PSTORE_FLAG_PD_POWER_OFF:
__pstore_clk_pd_dump(rec);
break;
default:
pr_err("Unknown Msg:%x\n", rec->flag);
}
}
static char reboot_mode[16];
static int __init reboot_mode_setup(char *s)
{
if (s)
snprintf(reboot_mode, sizeof(reboot_mode), "%s", s);
return 0;
}
__setup("reboot_mode=", reboot_mode_setup);
static void dump_reg_compatible(void)
{
#ifdef CONFIG_ARM64
u64 reg[20];
#else
u32 reg[20];
#endif
int reg_size, i;
const char *string;
struct device_node *node, *tmp_node;
for_each_node_with_property(node, "reg") {
tmp_node = node;
while (tmp_node) {
if (!of_property_read_string(tmp_node, "compatible", &string))
break;
tmp_node = tmp_node->parent;
}
pr_info("%s:\n", string);
#ifdef CONFIG_ARM64
reg_size = of_property_read_variable_u64_array(node, "reg", reg, 0, 20);
for (i = 0; i + 1 < reg_size; i += 2)
pr_info("0x%llx_0x%llx\n", reg[i], reg[i + 1]);
#else
reg_size = of_property_read_variable_u32_array(node, "reg", reg, 0, 20);
for (i = 0; i + 1 < reg_size; i += 2)
pr_info("0x%x_0x%x\n", reg[i], reg[i + 1]);
#endif
}
}
void notrace pstore_ftrace_dump_old(struct persistent_ram_zone *prz)
{
struct pstore_ftrace_record *rec;
void *rec_end;
rec = (struct pstore_ftrace_record *)prz->old_log;
rec_end = (void *)rec + prz->old_log_size;
pr_info("ramoops_io_dump=%d, buffer=%px ftrace_old_log=%px, size=%u, reboot_mode=%s\n",
ramoops_io_dump, prz->buffer,
rec, (unsigned int)prz->old_log_size, reboot_mode);
if (!strcmp(reboot_mode, "cold_boot"))
return;
if (!strcmp(reboot_mode, "normal"))
return;
if (!ramoops_io_dump)
return;
if (!persistent_ram_old_size(prz))
return;
if (dump_phys_addr)
dump_reg_compatible();
rec = (void *)rec + prz->old_log_size % sizeof(*rec);
while ((void *)rec < rec_end) {
__pstore_ftrace_dump_old(rec);
rec++;
}
}