918 lines
25 KiB
C
Executable File
918 lines
25 KiB
C
Executable File
/*
|
|
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* Debug-SnapShot: Debug Framework for Ramdump based debugging method
|
|
* The original code is Exynos-Snapshot for Exynos SoC
|
|
*
|
|
* Author: Hosung Kim <hosung0.kim@samsung.com>
|
|
* Author: Changki Kim <changki.kim@samsung.com>
|
|
*
|
|
* 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/version.h>
|
|
#include <linux/io.h>
|
|
#include <linux/device.h>
|
|
#include <linux/bootmem.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/module.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/sched/clock.h>
|
|
|
|
#include "debug-snapshot-local.h"
|
|
|
|
/* To Support Samsung SoC */
|
|
#include <dt-bindings/soc/samsung/debug-snapshot-table.h>
|
|
#ifdef CONFIG_DEBUG_SNAPSHOT_PMU
|
|
#include <soc/samsung/cal-if.h>
|
|
#endif
|
|
|
|
#include <linux/sec_debug.h>
|
|
|
|
#ifdef CONFIG_SEC_PM_DEBUG
|
|
#include <linux/uaccess.h>
|
|
#include <linux/proc_fs.h>
|
|
|
|
static bool sec_log_full;
|
|
#endif
|
|
|
|
extern void register_hook_logbuf(void (*)(const char *, size_t));
|
|
extern void register_hook_logger(void (*)(const char *, const char *, size_t));
|
|
|
|
struct dbg_snapshot_interface {
|
|
struct dbg_snapshot_log *info_event;
|
|
struct dbg_snapshot_item info_log[DSS_ITEM_MAX_NUM];
|
|
};
|
|
|
|
#ifdef CONFIG_DEBUG_SNAPSHOT_PMU
|
|
struct dbg_snapshot_ops {
|
|
int (*pd_status)(unsigned int id);
|
|
};
|
|
|
|
struct dbg_snapshot_ops dss_ops = {
|
|
.pd_status = cal_pd_status,
|
|
};
|
|
#endif
|
|
|
|
const char *debug_level_val[] = {
|
|
"low",
|
|
"mid",
|
|
};
|
|
|
|
struct dbg_snapshot_item dss_items[] = {
|
|
{"header", {0, 0, 0, false, false}, NULL ,NULL, 0, },
|
|
{"log_kernel", {0, 0, 0, false, false}, NULL ,NULL, 0, },
|
|
{"log_platform", {0, 0, 0, false, false}, NULL ,NULL, 0, },
|
|
{"log_sfr", {0, 0, 0, false, false}, NULL ,NULL, 0, },
|
|
{"log_s2d", {0, 0, 0, true, false}, NULL, NULL, 0, },
|
|
{"log_cachedump", {0, 0, 0, true, false}, NULL, NULL, 0, },
|
|
{"log_etm", {0, 0, 0, true, false}, NULL ,NULL, 0, },
|
|
{"log_bcm", {0, 0, 0, false, false}, NULL ,NULL, 0, },
|
|
{"log_pstore", {0, 0, 0, true, false}, NULL ,NULL, 0, },
|
|
{"log_kevents", {0, 0, 0, false, false}, NULL ,NULL, 0, },
|
|
};
|
|
|
|
/* External interface variable for trace debugging */
|
|
static struct dbg_snapshot_interface dss_info __attribute__ ((used));
|
|
static struct dbg_snapshot_interface *ess_info __attribute__ ((used));
|
|
|
|
struct dbg_snapshot_base dss_base;
|
|
struct dbg_snapshot_base ess_base;
|
|
struct dbg_snapshot_log *dss_log = NULL;
|
|
struct dbg_snapshot_desc dss_desc;
|
|
|
|
void sec_debug_get_kevent_info(struct ess_info_offset *p, int type)
|
|
{
|
|
unsigned long kevent_base_va = (unsigned long)(dss_log->task);
|
|
unsigned long kevent_base_pa = dss_items[dss_desc.kevents_num].entry.paddr;
|
|
|
|
switch (type) {
|
|
case DSS_KEVENT_TASK:
|
|
p->base = kevent_base_pa + (unsigned long)(dss_log->task) - kevent_base_va;
|
|
p->nr = DSS_LOG_MAX_NUM;
|
|
p->size = sizeof(struct __task_log);
|
|
p->per_core = 1;
|
|
break;
|
|
|
|
case DSS_KEVENT_WORK:
|
|
p->base = kevent_base_pa + (unsigned long)(dss_log->work) - kevent_base_va;
|
|
p->nr = DSS_LOG_MAX_NUM;
|
|
p->size = sizeof(struct __work_log);
|
|
p->per_core = 1;
|
|
break;
|
|
|
|
case DSS_KEVENT_IRQ:
|
|
p->base = kevent_base_pa + (unsigned long)(dss_log->irq) - kevent_base_va;
|
|
p->nr = DSS_LOG_MAX_NUM * 2;
|
|
p->size = sizeof(struct __irq_log);
|
|
p->per_core = 1;
|
|
break;
|
|
|
|
case DSS_KEVENT_FREQ:
|
|
p->base = kevent_base_pa + (unsigned long)(dss_log->freq) - kevent_base_va;
|
|
p->nr = DSS_LOG_MAX_NUM;
|
|
p->size = sizeof(struct __freq_log);
|
|
p->per_core = 0;
|
|
break;
|
|
|
|
case DSS_KEVENT_IDLE:
|
|
p->base = kevent_base_pa + (unsigned long)(dss_log->cpuidle) - kevent_base_va;
|
|
p->nr = DSS_LOG_MAX_NUM;
|
|
p->size = sizeof(struct __cpuidle_log);
|
|
p->per_core = 1;
|
|
break;
|
|
|
|
case DSS_KEVENT_THRM:
|
|
p->base = kevent_base_pa + (unsigned long)(dss_log->thermal) - kevent_base_va;
|
|
p->nr = DSS_LOG_MAX_NUM;
|
|
p->size = sizeof(struct __thermal_log);
|
|
p->per_core = 0;
|
|
break;
|
|
|
|
case DSS_KEVENT_ACPM:
|
|
p->base = kevent_base_pa + (unsigned long)(dss_log->acpm) - kevent_base_va;
|
|
p->nr = DSS_LOG_MAX_NUM;
|
|
p->size = sizeof(struct __acpm_log);
|
|
p->per_core = 0;
|
|
break;
|
|
|
|
default:
|
|
p->base = 0;
|
|
p->nr = 0;
|
|
p->size = 0;
|
|
p->per_core = 0;
|
|
break;
|
|
}
|
|
|
|
p->last = sec_debug_get_kevent_index_addr(type);
|
|
}
|
|
|
|
|
|
/* Variable for assigning virtual address base */
|
|
static size_t g_dbg_snapshot_vaddr_base = DSS_FIXED_VIRT_BASE;
|
|
|
|
int dbg_snapshot_set_debug_level(int level)
|
|
{
|
|
int i;
|
|
|
|
if (level > -1 && level < ARRAY_SIZE(debug_level_val)) {
|
|
dss_desc.debug_level = i;
|
|
} else {
|
|
#if !IS_ENABLED(CONFIG_DEBUG_SNAPSHOT_USER_MODE)
|
|
dss_desc.debug_level = DSS_DEBUG_LEVEL_MID;
|
|
#else
|
|
dss_desc.debug_level = DSS_DEBUG_LEVEL_LOW;
|
|
#endif
|
|
}
|
|
dbg_snapshot_set_debug_level_reg();
|
|
return 0;
|
|
}
|
|
|
|
int dbg_snapshot_get_debug_level(void)
|
|
{
|
|
return dss_desc.debug_level;
|
|
}
|
|
|
|
int dbg_snapshot_set_enable(const char *name, int en)
|
|
{
|
|
struct dbg_snapshot_item *item = NULL;
|
|
unsigned long i;
|
|
|
|
if (!strncmp(name, "base", strlen(name))) {
|
|
dss_base.enabled = en;
|
|
pr_info("debug-snapshot: %sabled\n", en ? "en" : "dis");
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
|
if (!strncmp(dss_items[i].name, name, strlen(name))) {
|
|
item = &dss_items[i];
|
|
item->entry.enabled = en;
|
|
item->time = local_clock();
|
|
pr_info("debug-snapshot: item - %s is %sabled\n",
|
|
name, en ? "en" : "dis");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(dbg_snapshot_set_enable);
|
|
|
|
int dbg_snapshot_try_enable(const char *name, unsigned long long duration)
|
|
{
|
|
struct dbg_snapshot_item *item = NULL;
|
|
unsigned long long time;
|
|
unsigned long i;
|
|
int ret = -1;
|
|
|
|
/* If DSS was disabled, just return */
|
|
if (unlikely(!dss_base.enabled) || !dbg_snapshot_get_enable("header"))
|
|
return ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
|
if (!strncmp(dss_items[i].name, name, strlen(name))) {
|
|
item = &dss_items[i];
|
|
|
|
/* We only interest in disabled */
|
|
if (!item->entry.enabled) {
|
|
time = local_clock() - item->time;
|
|
if (time > duration) {
|
|
item->entry.enabled = true;
|
|
ret = 1;
|
|
} else
|
|
ret = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(dbg_snapshot_try_enable);
|
|
|
|
int dbg_snapshot_get_enable(const char *name)
|
|
{
|
|
struct dbg_snapshot_item *item = NULL;
|
|
unsigned long i;
|
|
int ret = 0;
|
|
|
|
if (!strncmp(name, "base", strlen(name)))
|
|
return dss_base.enabled;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
|
if (!strncmp(dss_items[i].name, name, strlen(name))) {
|
|
item = &dss_items[i];
|
|
ret = item->entry.enabled;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(dbg_snapshot_get_enable);
|
|
|
|
static inline int dbg_snapshot_check_eob(struct dbg_snapshot_item *item,
|
|
size_t size)
|
|
{
|
|
size_t max, cur;
|
|
|
|
max = (size_t)(item->head_ptr + item->entry.size);
|
|
cur = (size_t)(item->curr_ptr + size);
|
|
|
|
if (unlikely(cur > max))
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static inline void dbg_snapshot_hook_logger(const char *name,
|
|
const char *buf, size_t size)
|
|
{
|
|
struct dbg_snapshot_item *item = &dss_items[dss_desc.log_platform_num];
|
|
|
|
if (likely(dss_base.enabled && item->entry.enabled)) {
|
|
size_t last_buf;
|
|
|
|
if (unlikely((dbg_snapshot_check_eob(item, size))))
|
|
item->curr_ptr = item->head_ptr;
|
|
|
|
memcpy(item->curr_ptr, buf, size);
|
|
item->curr_ptr += size;
|
|
/* save the address of last_buf to physical address */
|
|
last_buf = (size_t)item->curr_ptr;
|
|
|
|
__raw_writel(item->entry.paddr + (last_buf - item->entry.vaddr),
|
|
dbg_snapshot_get_base_vaddr() + DSS_OFFSET_LAST_PLATFORM_LOGBUF);
|
|
}
|
|
}
|
|
|
|
size_t dbg_snapshot_get_curr_ptr_for_sysrq(void)
|
|
{
|
|
#ifdef CONFIG_SEC_DEBUG_SYSRQ_KMSG
|
|
struct dbg_snapshot_item *item = &dss_items[dss_desc.log_kernel_num];
|
|
|
|
return (size_t)item->curr_ptr;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static inline void dbg_snapshot_hook_logbuf(const char *buf, size_t size)
|
|
{
|
|
struct dbg_snapshot_item *item = &dss_items[dss_desc.log_kernel_num];
|
|
|
|
if (likely(dss_base.enabled && item->entry.enabled)) {
|
|
size_t last_buf;
|
|
|
|
if (dbg_snapshot_check_eob(item, size)) {
|
|
item->curr_ptr = item->head_ptr;
|
|
#ifdef CONFIG_SEC_DEBUG_LAST_KMSG
|
|
*((unsigned long long *)(item->head_ptr + item->entry.size - (size_t)0x08)) = SEC_LKMSG_MAGICKEY;
|
|
#endif
|
|
#ifdef CONFIG_SEC_PM_DEBUG
|
|
if (!sec_log_full)
|
|
sec_log_full = true;
|
|
#endif
|
|
}
|
|
|
|
memcpy(item->curr_ptr, buf, size);
|
|
item->curr_ptr += size;
|
|
/* save the address of last_buf to physical address */
|
|
last_buf = (size_t)item->curr_ptr;
|
|
|
|
__raw_writel(item->entry.paddr + (last_buf - item->entry.vaddr),
|
|
dbg_snapshot_get_base_vaddr() + DSS_OFFSET_LAST_LOGBUF);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_SNAPSHOT_PMU
|
|
static bool dbg_snapshot_check_pmu(struct dbg_snapshot_sfrdump *sfrdump,
|
|
const struct device_node *np)
|
|
{
|
|
int ret = 0, count, i;
|
|
unsigned int val;
|
|
|
|
if (!sfrdump->pwr_mode)
|
|
return true;
|
|
|
|
count = of_property_count_u32_elems(np, "cal-pd-id");
|
|
for (i = 0; i < count; i++) {
|
|
ret = of_property_read_u32_index(np, "cal-pd-id", i, &val);
|
|
if (ret < 0) {
|
|
pr_err("failed to get pd-id - %s\n", sfrdump->name);
|
|
return false;
|
|
}
|
|
ret = dss_ops.pd_status(val);
|
|
if (ret < 0) {
|
|
pr_err("not powered - %s (pd-id: %d)\n", sfrdump->name, i);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void dbg_snapshot_dump_sfr(void)
|
|
{
|
|
struct dbg_snapshot_sfrdump *sfrdump;
|
|
struct dbg_snapshot_item *item = &dss_items[dss_desc.log_sfr_num];
|
|
struct list_head *entry;
|
|
struct device_node *np;
|
|
unsigned int reg, offset, val, size;
|
|
int i, ret;
|
|
static char buf[SZ_64];
|
|
|
|
if (unlikely(!dss_base.enabled || !item->entry.enabled))
|
|
return;
|
|
|
|
if (list_empty(&dss_desc.sfrdump_list)) {
|
|
pr_emerg("debug-snapshot: %s: No information\n", __func__);
|
|
return;
|
|
}
|
|
|
|
list_for_each(entry, &dss_desc.sfrdump_list) {
|
|
sfrdump = list_entry(entry, struct dbg_snapshot_sfrdump, list);
|
|
np = of_node_get(sfrdump->node);
|
|
ret = dbg_snapshot_check_pmu(sfrdump, np);
|
|
if (!ret)
|
|
/* may off */
|
|
continue;
|
|
|
|
for (i = 0; i < sfrdump->num; i++) {
|
|
ret = of_property_read_u32_index(np, "addr", i, ®);
|
|
if (ret < 0) {
|
|
pr_err("debug-snapshot: failed to get address information - %s\n",
|
|
sfrdump->name);
|
|
break;
|
|
}
|
|
if (reg == 0xFFFFFFFF || reg == 0)
|
|
break;
|
|
offset = reg - sfrdump->phy_reg;
|
|
if (reg < offset) {
|
|
pr_err("debug-snapshot: invalid address information - %s: 0x%08x\n",
|
|
sfrdump->name, reg);
|
|
break;
|
|
}
|
|
val = __raw_readl(sfrdump->reg + offset);
|
|
snprintf(buf, SZ_64, "0x%X = 0x%0X\n",reg, val);
|
|
size = (unsigned int)strlen(buf);
|
|
if (unlikely((dbg_snapshot_check_eob(item, size))))
|
|
item->curr_ptr = item->head_ptr;
|
|
memcpy(item->curr_ptr, buf, strlen(buf));
|
|
item->curr_ptr += strlen(buf);
|
|
}
|
|
of_node_put(np);
|
|
pr_info("debug-snapshot: complete to dump %s\n", sfrdump->name);
|
|
}
|
|
|
|
}
|
|
|
|
static int dbg_snapshot_sfr_dump_init(struct device_node *np)
|
|
{
|
|
struct device_node *dump_np;
|
|
struct dbg_snapshot_sfrdump *sfrdump;
|
|
char *dump_str;
|
|
int count, ret, i;
|
|
u32 phy_regs[2];
|
|
|
|
ret = of_property_count_strings(np, "sfr-dump-list");
|
|
if (ret < 0) {
|
|
pr_err("failed to get sfr-dump-list\n");
|
|
return ret;
|
|
}
|
|
count = ret;
|
|
|
|
INIT_LIST_HEAD(&dss_desc.sfrdump_list);
|
|
for (i = 0; i < count; i++) {
|
|
ret = of_property_read_string_index(np, "sfr-dump-list", i,
|
|
(const char **)&dump_str);
|
|
if (ret < 0) {
|
|
pr_err("failed to get sfr-dump-list\n");
|
|
continue;
|
|
}
|
|
|
|
dump_np = of_get_child_by_name(np, dump_str);
|
|
if (!dump_np) {
|
|
pr_err("failed to get %s node, count:%d\n", dump_str, count);
|
|
continue;
|
|
}
|
|
|
|
sfrdump = kzalloc(sizeof(struct dbg_snapshot_sfrdump), GFP_KERNEL);
|
|
if (!sfrdump) {
|
|
pr_err("failed to get memory region of dbg_snapshot_sfrdump\n");
|
|
of_node_put(dump_np);
|
|
continue;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(dump_np, "reg", phy_regs, 2);
|
|
if (ret < 0) {
|
|
pr_err("failed to get register information\n");
|
|
of_node_put(dump_np);
|
|
kfree(sfrdump);
|
|
continue;
|
|
}
|
|
|
|
sfrdump->reg = ioremap(phy_regs[0], phy_regs[1]);
|
|
if (!sfrdump->reg) {
|
|
pr_err("failed to get i/o address %s node\n", dump_str);
|
|
of_node_put(dump_np);
|
|
kfree(sfrdump);
|
|
continue;
|
|
}
|
|
sfrdump->name = dump_str;
|
|
|
|
ret = of_property_count_u32_elems(dump_np, "addr");
|
|
if (ret < 0) {
|
|
pr_err("failed to get addr count\n");
|
|
of_node_put(dump_np);
|
|
kfree(sfrdump);
|
|
continue;
|
|
}
|
|
sfrdump->phy_reg = phy_regs[0];
|
|
sfrdump->num = ret;
|
|
|
|
ret = of_property_count_u32_elems(dump_np, "cal-pd-id");
|
|
if (ret < 0)
|
|
sfrdump->pwr_mode = false;
|
|
else
|
|
sfrdump->pwr_mode = true;
|
|
|
|
sfrdump->node = dump_np;
|
|
list_add(&sfrdump->list, &dss_desc.sfrdump_list);
|
|
|
|
pr_info("success to regsiter %s\n", sfrdump->name);
|
|
of_node_put(dump_np);
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int __init dbg_snapshot_remap(void)
|
|
{
|
|
unsigned long i, j;
|
|
unsigned int enabled_count = 0;
|
|
pgprot_t prot = __pgprot(PROT_NORMAL_NC);
|
|
int page_size, ret;
|
|
struct page *page;
|
|
struct page **pages;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
|
if (dss_items[i].entry.enabled) {
|
|
enabled_count++;
|
|
page_size = dss_items[i].entry.size / PAGE_SIZE;
|
|
pages = kzalloc(sizeof(struct page *) * page_size, GFP_KERNEL);
|
|
page = phys_to_page(dss_items[i].entry.paddr);
|
|
|
|
for (j = 0; j < page_size; j++)
|
|
pages[j] = page++;
|
|
|
|
ret = map_vm_area(&dss_items[i].vm, prot, pages);
|
|
kfree(pages);
|
|
if (ret) {
|
|
pr_err("debug-snapshot: failed to mapping between virt and phys");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dss_items[i].entry.vaddr = (size_t)dss_items[i].vm.addr;
|
|
dss_items[i].head_ptr = (unsigned char *)dss_items[i].entry.vaddr;
|
|
dss_items[i].curr_ptr = (unsigned char *)dss_items[i].entry.vaddr;
|
|
}
|
|
}
|
|
dss_desc.log_cnt = ARRAY_SIZE(dss_items);
|
|
return enabled_count;
|
|
}
|
|
|
|
static int __init dbg_snapshot_init_desc(void)
|
|
{
|
|
unsigned int i, len;
|
|
|
|
/* initialize dss_desc */
|
|
memset((struct dbg_snapshot_desc *)&dss_desc, 0, sizeof(struct dbg_snapshot_desc));
|
|
dss_desc.callstack = CONFIG_DEBUG_SNAPSHOT_CALLSTACK;
|
|
raw_spin_lock_init(&dss_desc.ctrl_lock);
|
|
raw_spin_lock_init(&dss_desc.nmi_lock);
|
|
|
|
for (i = 0; i < (unsigned int)ARRAY_SIZE(dss_items); i++) {
|
|
len = strlen(dss_items[i].name);
|
|
if (!strncmp(dss_items[i].name, "header", len))
|
|
dss_desc.header_num = i;
|
|
else if (!strncmp(dss_items[i].name, "log_kevents", len))
|
|
dss_desc.kevents_num = i;
|
|
else if (!strncmp(dss_items[i].name, "log_kernel", len))
|
|
dss_desc.log_kernel_num = i;
|
|
else if (!strncmp(dss_items[i].name, "log_platform", len))
|
|
dss_desc.log_platform_num = i;
|
|
else if (!strncmp(dss_items[i].name, "log_sfr", len))
|
|
dss_desc.log_sfr_num = i;
|
|
else if (!strncmp(dss_items[i].name, "log_pstore", len))
|
|
dss_desc.log_pstore_num = i;
|
|
}
|
|
|
|
#ifdef CONFIG_S3C2410_WATCHDOG
|
|
dss_desc.no_wdt_dev = false;
|
|
#else
|
|
dss_desc.no_wdt_dev = true;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF_RESERVED_MEM
|
|
static int __init dbg_snapshot_item_reserved_mem_setup(struct reserved_mem *remem)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < (unsigned int)ARRAY_SIZE(dss_items); i++) {
|
|
if (strnstr(remem->name, dss_items[i].name, strlen(remem->name)))
|
|
break;
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(dss_items))
|
|
return -ENODEV;
|
|
|
|
dss_items[i].entry.paddr = remem->base;
|
|
dss_items[i].entry.size = remem->size;
|
|
dss_items[i].entry.enabled = true;
|
|
|
|
dss_items[i].vm.phys_addr = remem->base;
|
|
dss_items[i].vm.addr = (void *)g_dbg_snapshot_vaddr_base;
|
|
dss_items[i].vm.size = remem->size;
|
|
dss_items[i].vm.flags = VM_NO_GUARD;
|
|
g_dbg_snapshot_vaddr_base += remem->size;
|
|
|
|
vm_area_add_early(&dss_items[i].vm);
|
|
|
|
if (strnstr(remem->name, "header", strlen(remem->name))) {
|
|
dss_base.paddr = remem->base;
|
|
dss_base.vaddr = (size_t)dss_items[i].vm.addr;
|
|
ess_base = dss_base;
|
|
dss_base.enabled = false;
|
|
}
|
|
dss_base.size += remem->size;
|
|
return 0;
|
|
}
|
|
|
|
#define DECLARE_DBG_SNAPSHOT_RESERVED_REGION(compat, name) \
|
|
RESERVEDMEM_OF_DECLARE(name, compat#name, dbg_snapshot_item_reserved_mem_setup)
|
|
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", header);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_kernel);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_platform);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_sfr);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_s2d);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_cachedump);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_etm);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_bcm);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_pstore);
|
|
DECLARE_DBG_SNAPSHOT_RESERVED_REGION("debug-snapshot,", log_kevents);
|
|
#endif
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------
|
|
* - dummy data:phy_addr, virtual_addr, buffer_size, magic_key(4K) -
|
|
* ---------------------------------------------------------------------
|
|
* - Cores MMU register(4K) -
|
|
* ---------------------------------------------------------------------
|
|
* - Cores CPU register(4K) -
|
|
* ---------------------------------------------------------------------
|
|
*/
|
|
static int __init dbg_snapshot_output(void)
|
|
{
|
|
unsigned long i, size = 0;
|
|
|
|
pr_info("debug-snapshot physical / virtual memory layout:\n");
|
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
|
if (dss_items[i].entry.enabled)
|
|
pr_info("%-12s: phys:0x%zx / virt:0x%zx / size:0x%zx\n",
|
|
dss_items[i].name,
|
|
dss_items[i].entry.paddr,
|
|
dss_items[i].entry.vaddr,
|
|
dss_items[i].entry.size);
|
|
size += dss_items[i].entry.size;
|
|
}
|
|
|
|
pr_info("total_item_size: %ldKB, dbg_snapshot_log struct size: %dKB\n",
|
|
size / SZ_1K, dbg_snapshot_log_size / SZ_1K);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Header dummy data(4K)
|
|
* -------------------------------------------------------------------------
|
|
* 0 4 8 C
|
|
* -------------------------------------------------------------------------
|
|
* 0 vaddr phy_addr size magic_code
|
|
* 4 Scratch_val logbuf_addr 0 0
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
static void __init dbg_snapshot_fixmap_header(void)
|
|
{
|
|
/* fill 0 to next to header */
|
|
size_t vaddr, paddr, size;
|
|
size_t *addr;
|
|
|
|
vaddr = dss_items[dss_desc.header_num].entry.vaddr;
|
|
paddr = dss_items[dss_desc.header_num].entry.paddr;
|
|
size = dss_items[dss_desc.header_num].entry.size;
|
|
|
|
/* set to confirm debug-snapshot */
|
|
addr = (size_t *)vaddr;
|
|
memcpy(addr, &dss_base, sizeof(struct dbg_snapshot_base));
|
|
|
|
if (!dbg_snapshot_get_enable("header"))
|
|
return;
|
|
|
|
/* initialize kernel event to 0 except only header */
|
|
memset((size_t *)(vaddr + DSS_KEEP_HEADER_SZ), 0, size - DSS_KEEP_HEADER_SZ);
|
|
}
|
|
|
|
static void __init dbg_snapshot_fixmap(void)
|
|
{
|
|
size_t last_buf;
|
|
size_t vaddr, paddr, size;
|
|
unsigned long i;
|
|
|
|
/* fixmap to header first */
|
|
dbg_snapshot_fixmap_header();
|
|
|
|
for (i = 1; i < ARRAY_SIZE(dss_items); i++) {
|
|
if (!dss_items[i].entry.enabled)
|
|
continue;
|
|
|
|
/* assign dss_item information */
|
|
paddr = dss_items[i].entry.paddr;
|
|
vaddr = dss_items[i].entry.vaddr;
|
|
size = dss_items[i].entry.size;
|
|
|
|
if (i == dss_desc.log_kernel_num) {
|
|
/* load last_buf address value(phy) by virt address */
|
|
last_buf = (size_t)__raw_readl(dbg_snapshot_get_base_vaddr() +
|
|
DSS_OFFSET_LAST_LOGBUF);
|
|
/* check physical address offset of kernel logbuf */
|
|
if (last_buf >= dss_items[i].entry.paddr &&
|
|
(last_buf) <= (dss_items[i].entry.paddr + dss_items[i].entry.size)) {
|
|
/* assumed valid address, conversion to virt */
|
|
dss_items[i].curr_ptr = (unsigned char *)(dss_items[i].entry.vaddr +
|
|
(last_buf - dss_items[i].entry.paddr));
|
|
} else {
|
|
/* invalid address, set to first line */
|
|
dss_items[i].curr_ptr = (unsigned char *)vaddr;
|
|
/* initialize logbuf to 0 */
|
|
memset((size_t *)vaddr, 0, size);
|
|
}
|
|
} else if (i == dss_desc.log_platform_num) {
|
|
last_buf = (size_t)__raw_readl(dbg_snapshot_get_base_vaddr() +
|
|
DSS_OFFSET_LAST_PLATFORM_LOGBUF);
|
|
if (last_buf >= dss_items[i].entry.vaddr &&
|
|
(last_buf) <= (dss_items[i].entry.vaddr + dss_items[i].entry.size)) {
|
|
dss_items[i].curr_ptr = (unsigned char *)(last_buf);
|
|
} else {
|
|
dss_items[i].curr_ptr = (unsigned char *)vaddr;
|
|
memset((size_t *)vaddr, 0, size);
|
|
}
|
|
} else {
|
|
/* initialized log to 0 if persist == false */
|
|
if (!dss_items[i].entry.persist)
|
|
memset((size_t *)vaddr, 0, size);
|
|
}
|
|
dss_info.info_log[i - 1].name = kstrdup(dss_items[i].name, GFP_KERNEL);
|
|
dss_info.info_log[i - 1].head_ptr = (unsigned char *)dss_items[i].entry.vaddr;
|
|
dss_info.info_log[i - 1].curr_ptr = NULL;
|
|
dss_info.info_log[i - 1].entry.size = size;
|
|
}
|
|
|
|
dss_log = (struct dbg_snapshot_log *)(dss_items[dss_desc.kevents_num].entry.vaddr);
|
|
|
|
/* set fake translation to virtual address to debug trace */
|
|
dss_info.info_event = dss_log;
|
|
ess_info = &dss_info;
|
|
|
|
/* output the information of debug-snapshot */
|
|
dbg_snapshot_output();
|
|
|
|
#ifdef CONFIG_SEC_DEBUG
|
|
sec_debug_save_last_kmsg(dss_items[dss_desc.log_kernel_num].head_ptr,
|
|
dss_items[dss_desc.log_kernel_num].curr_ptr,
|
|
dss_items[dss_desc.log_kernel_num].entry.size);
|
|
#endif
|
|
}
|
|
|
|
static int dbg_snapshot_init_dt_parse(struct device_node *np)
|
|
{
|
|
int ret = 0;
|
|
struct device_node *sfr_dump_np;
|
|
|
|
if (of_property_read_u32(np, "use_multistage_wdt_irq",
|
|
&dss_desc.multistage_wdt_irq)) {
|
|
dss_desc.multistage_wdt_irq = 0;
|
|
pr_err("debug-snapshot: no support multistage_wdt\n");
|
|
}
|
|
|
|
sfr_dump_np = of_get_child_by_name(np, "dump-info");
|
|
if (!sfr_dump_np) {
|
|
pr_err("debug-snapshot: failed to get dump-info node\n");
|
|
ret = -ENODEV;
|
|
} else {
|
|
#ifdef CONFIG_DEBUG_SNAPSHOT_PMU
|
|
ret = dbg_snapshot_sfr_dump_init(sfr_dump_np);
|
|
if (ret < 0) {
|
|
pr_err("debug-snapshot: failed to register sfr dump node\n");
|
|
ret = -ENODEV;
|
|
of_node_put(sfr_dump_np);
|
|
}
|
|
#endif
|
|
}
|
|
if (ret < 0)
|
|
dbg_snapshot_set_enable("log_sfr", false);
|
|
|
|
of_node_put(np);
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id dss_of_match[] __initconst = {
|
|
{ .compatible = "debug-snapshot-soc",
|
|
.data = dbg_snapshot_init_dt_parse},
|
|
{},
|
|
};
|
|
|
|
static int __init dbg_snapshot_init_dt(void)
|
|
{
|
|
struct device_node *np;
|
|
const struct of_device_id *matched_np;
|
|
dss_initcall_t init_fn;
|
|
|
|
np = of_find_matching_node_and_match(NULL, dss_of_match, &matched_np);
|
|
|
|
if (!np) {
|
|
pr_info("debug-snapshot: couldn't find device tree file of debug-snapshot\n");
|
|
dbg_snapshot_set_enable("log_sfr", false);
|
|
return -ENODEV;
|
|
}
|
|
|
|
init_fn = (dss_initcall_t)matched_np->data;
|
|
return init_fn(np);
|
|
}
|
|
|
|
static int __init dbg_snapshot_init_value(void)
|
|
{
|
|
int val = dbg_snapshot_get_debug_level_reg();
|
|
struct dbg_snapshot_item *item;
|
|
|
|
dbg_snapshot_set_debug_level(val);
|
|
|
|
pr_info("debug-snapshot: debug_level [%s]\n",
|
|
debug_level_val[dss_desc.debug_level]);
|
|
|
|
dbg_snapshot_scratch_reg(DSS_SIGN_SCRATCH);
|
|
|
|
dbg_snapshot_set_sjtag_status();
|
|
|
|
/* copy linux_banner, physical address of
|
|
* kernel log / platform log / kevents to DSS header */
|
|
strncpy(dbg_snapshot_get_base_vaddr() + DSS_OFFSET_LINUX_BANNER,
|
|
linux_banner, strlen(linux_banner));
|
|
|
|
item = &dss_items[dss_desc.log_kernel_num];
|
|
__raw_writel(item->entry.paddr,
|
|
dbg_snapshot_get_base_vaddr() + DSS_OFFSET_KERNEL_LOG);
|
|
|
|
item = &dss_items[dss_desc.log_platform_num];
|
|
__raw_writel(item->entry.paddr,
|
|
dbg_snapshot_get_base_vaddr() + DSS_OFFSET_PLATFORM_LOG);
|
|
|
|
item = &dss_items[dss_desc.kevents_num];
|
|
__raw_writel(item->entry.paddr,
|
|
dbg_snapshot_get_base_vaddr() + DSS_OFFSET_KERNEL_EVENT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init dbg_snapshot_init(void)
|
|
{
|
|
dbg_snapshot_init_desc();
|
|
if (dbg_snapshot_remap() > 0) {
|
|
/*
|
|
* for debugging when we don't know the virtual address of pointer,
|
|
* In just privous the debug buffer, It is added 16byte dummy data.
|
|
* start address(dummy 16bytes)
|
|
* --> @virtual_addr | @phy_addr | @buffer_size | @magic_key(0xDBDBDBDB)
|
|
* And then, the debug buffer is shown.
|
|
*/
|
|
dbg_snapshot_init_log_idx();
|
|
dbg_snapshot_fixmap();
|
|
dbg_snapshot_init_dt();
|
|
dbg_snapshot_init_helper();
|
|
dbg_snapshot_init_utils();
|
|
dbg_snapshot_init_value();
|
|
|
|
dbg_snapshot_set_enable("base", true);
|
|
|
|
register_hook_logbuf(dbg_snapshot_hook_logbuf);
|
|
register_hook_logger(dbg_snapshot_hook_logger);
|
|
} else
|
|
pr_err("debug-snapshot: %s failed\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
early_initcall(dbg_snapshot_init);
|
|
|
|
#ifdef CONFIG_SEC_PM_DEBUG
|
|
static ssize_t sec_log_read_all(struct file *file, char __user *buf,
|
|
size_t len, loff_t *offset)
|
|
{
|
|
loff_t pos = *offset;
|
|
ssize_t count;
|
|
size_t size;
|
|
struct dbg_snapshot_item *item = &dss_items[dss_desc.log_kernel_num];
|
|
|
|
if (sec_log_full)
|
|
size = item->entry.size;
|
|
else
|
|
size = (size_t)(item->curr_ptr - item->head_ptr);
|
|
|
|
if (pos >= size)
|
|
return 0;
|
|
|
|
count = min(len, size);
|
|
|
|
if ((pos + count) > size)
|
|
count = size - pos;
|
|
|
|
if (copy_to_user(buf, item->head_ptr + pos, count))
|
|
return -EFAULT;
|
|
|
|
*offset += count;
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations sec_log_file_ops = {
|
|
.owner = THIS_MODULE,
|
|
.read = sec_log_read_all,
|
|
};
|
|
|
|
static int __init sec_log_late_init(void)
|
|
{
|
|
struct proc_dir_entry *entry;
|
|
struct dbg_snapshot_item *item = &dss_items[dss_desc.log_kernel_num];
|
|
|
|
if (!item->head_ptr)
|
|
return 0;
|
|
|
|
entry = proc_create("sec_log", 0440, NULL, &sec_log_file_ops);
|
|
if (!entry) {
|
|
pr_err("%s: failed to create proc entry\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
proc_set_size(entry, item->entry.size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
late_initcall(sec_log_late_init);
|
|
#endif /* CONFIG_SEC_PM_DEBUG */
|