1186 lines
30 KiB
C
1186 lines
30 KiB
C
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||
|
/*
|
||
|
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
|
||
|
* http://www.samsung.com
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/kallsyms.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of_reserved_mem.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/arm-smccc.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/proc_fs.h>
|
||
|
#include <linux/console.h>
|
||
|
#include <linux/android_debug_symbols.h>
|
||
|
#include <linux/mutex.h>
|
||
|
|
||
|
#include <soc/samsung/exynos-smc.h>
|
||
|
#include <soc/samsung/exynos-pmu-if.h>
|
||
|
#include "debug-snapshot-local.h"
|
||
|
|
||
|
#if IS_ENABLED(CONFIG_SEC_PM_DEBUG)
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/proc_fs.h>
|
||
|
|
||
|
static bool sec_log_full;
|
||
|
#endif /* CONFIG_SEC_PM_DEBUG */
|
||
|
|
||
|
#define DSS_VERSION 2
|
||
|
#define DSS_MAGIC 0x0D550D55
|
||
|
#define DSS_BL_MAGIC 0x1234ABCD
|
||
|
#define LOGGER_LEVEL_PREFIX (2)
|
||
|
|
||
|
struct dbg_snapshot_bl_item {
|
||
|
char name[SZ_16];
|
||
|
unsigned long paddr;
|
||
|
unsigned long size;
|
||
|
};
|
||
|
|
||
|
struct dbg_snapshot_bl {
|
||
|
unsigned int magic;
|
||
|
unsigned int item_count;
|
||
|
struct dbg_snapshot_bl_item item[DSS_MAX_BL_SIZE];
|
||
|
unsigned int checksum;
|
||
|
};
|
||
|
|
||
|
struct dbg_snapshot_interface {
|
||
|
struct dbg_snapshot_log *info_event;
|
||
|
};
|
||
|
|
||
|
static struct dbg_snapshot_bl *dss_bl;
|
||
|
static struct dbg_snapshot_item dss_items[] = {
|
||
|
[DSS_ITEM_HEADER_ID] = {DSS_ITEM_HEADER, {0, 0, 0, true}, true, NULL ,NULL},
|
||
|
[DSS_ITEM_KERNEL_ID] = {DSS_ITEM_KERNEL, {0, 0, 0, false}, true, NULL ,NULL},
|
||
|
[DSS_ITEM_PLATFORM_ID] = {DSS_ITEM_PLATFORM, {0, 0, 0, false}, true, NULL ,NULL},
|
||
|
[DSS_ITEM_KEVENTS_ID] = {DSS_ITEM_KEVENTS, {0, 0, 0, false}, false, NULL ,NULL},
|
||
|
[DSS_ITEM_S2D_ID] = {DSS_ITEM_S2D, {0, 0, 0, false}, false, NULL, NULL},
|
||
|
[DSS_ITEM_ARRDUMP_RESET_ID] = {DSS_ITEM_ARRDUMP_RESET, {0, 0, 0, false}, false, NULL, NULL},
|
||
|
[DSS_ITEM_ARRDUMP_PANIC_ID] = {DSS_ITEM_ARRDUMP_PANIC, {0, 0, 0, false}, false, NULL, NULL},
|
||
|
[DSS_ITEM_FIRST_ID] = {DSS_ITEM_FIRST, {0, 0, 0, false}, false, NULL, NULL},
|
||
|
[DSS_ITEM_BACKTRACE_ID] = {DSS_ITEM_BACKTRACE, {0, 0, 0, false}, false, NULL, NULL},
|
||
|
};
|
||
|
|
||
|
/* External interface variable for trace debugging */
|
||
|
static struct dbg_snapshot_interface dss_info __attribute__ ((used));
|
||
|
static void (*hook_bootstat)(const char *buf);
|
||
|
char *last_kmsg;
|
||
|
int last_kmsg_size;
|
||
|
EXPORT_SYMBOL_GPL(last_kmsg);
|
||
|
EXPORT_SYMBOL_GPL(last_kmsg_size);
|
||
|
|
||
|
static char *dss_kmsg;
|
||
|
static size_t dss_kmsg_valid_data_offset;
|
||
|
static size_t dss_kmsg_size;
|
||
|
static DEFINE_MUTEX(dss_kmsg_mutex);
|
||
|
|
||
|
static struct dbg_snapshot_base *dss_base;
|
||
|
static struct dbg_snapshot_base *ess_base;
|
||
|
struct dbg_snapshot_log *dss_log;
|
||
|
struct dbg_snapshot_kevents dss_kevents;
|
||
|
struct dbg_snapshot_desc dss_desc;
|
||
|
|
||
|
static void make_checksum_for_bl_item(struct dbg_snapshot_bl *item_info)
|
||
|
{
|
||
|
int i;
|
||
|
unsigned int *data = (unsigned int *)item_info->item;
|
||
|
|
||
|
item_info->checksum = 0;
|
||
|
for (i = 0; i < (int)sizeof(item_info->item) / (int)sizeof(unsigned int); i++)
|
||
|
item_info->checksum ^= data[i];
|
||
|
}
|
||
|
|
||
|
int dbg_snapshot_add_bl_item_info(const char *name, unsigned long paddr,
|
||
|
unsigned long size)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
if (!paddr || !size) {
|
||
|
dev_err(dss_desc.dev, "Invalid address(%s) or size(%s)\n",
|
||
|
paddr, size);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!dss_bl || dss_bl->item_count >= DSS_MAX_BL_SIZE) {
|
||
|
dev_err(dss_desc.dev, "Item list full or null!\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < dss_bl->item_count; i++) {
|
||
|
if (!strncmp(dss_bl->item[i].name, name,
|
||
|
strlen(dss_bl->item[i].name))) {
|
||
|
dev_err(dss_desc.dev, "Already has %s item\n", name);
|
||
|
return -EEXIST;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
memcpy(dss_bl->item[dss_bl->item_count].name, name, strlen(name) + 1);
|
||
|
dss_bl->item[dss_bl->item_count].paddr = paddr;
|
||
|
dss_bl->item[dss_bl->item_count].size = size;
|
||
|
dss_bl->item_count++;
|
||
|
make_checksum_for_bl_item(dss_bl);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_add_bl_item_info);
|
||
|
|
||
|
void __iomem *dbg_snapshot_get_header_vaddr(void)
|
||
|
{
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_HEADER))
|
||
|
return (void __iomem *)(dss_items[DSS_ITEM_HEADER_ID].entry.vaddr);
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_header_vaddr);
|
||
|
|
||
|
unsigned long dbg_snapshot_get_header_paddr(void)
|
||
|
{
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_HEADER))
|
||
|
return (unsigned long)dss_items[DSS_ITEM_HEADER_ID].entry.paddr;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_header_paddr);
|
||
|
|
||
|
unsigned int dbg_snapshot_get_val_offset(unsigned int offset)
|
||
|
{
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_HEADER))
|
||
|
return __raw_readl(dbg_snapshot_get_header_vaddr() + offset);
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_val_offset);
|
||
|
|
||
|
void dbg_snapshot_set_val_offset(unsigned int val, unsigned int offset)
|
||
|
{
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_HEADER))
|
||
|
__raw_writel(val, dbg_snapshot_get_header_vaddr() + offset);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_set_val_offset);
|
||
|
|
||
|
u64 dbg_snapshot_get_val64_offset(unsigned int offset)
|
||
|
{
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_HEADER))
|
||
|
return readq_relaxed(dbg_snapshot_get_header_vaddr() + offset);
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_val64_offset);
|
||
|
|
||
|
void dbg_snapshot_set_val64_offset(u64 val, unsigned int offset)
|
||
|
{
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_HEADER))
|
||
|
writeq_relaxed(val, dbg_snapshot_get_header_vaddr() + offset);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_set_val64_offset);
|
||
|
|
||
|
static void dbg_snapshot_set_linux_banner(void)
|
||
|
{
|
||
|
void __iomem *header = dbg_snapshot_get_header_vaddr();
|
||
|
void *banner_addr = android_debug_symbol(ADS_LINUX_BANNER);
|
||
|
|
||
|
if (!header || IS_ERR(banner_addr))
|
||
|
return;
|
||
|
|
||
|
strscpy(header + DSS_OFFSET_LINUX_BANNER, banner_addr, SZ_512);
|
||
|
}
|
||
|
|
||
|
void dbg_snapshot_set_str_offset(unsigned int offset,
|
||
|
size_t len, const char *fmt, ...)
|
||
|
{
|
||
|
char *buf = (char *)(dbg_snapshot_get_header_vaddr() + offset);
|
||
|
va_list args;
|
||
|
|
||
|
va_start(args, fmt);
|
||
|
vsnprintf(buf, len, fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
EXPORT_SYMBOL(dbg_snapshot_set_str_offset);
|
||
|
|
||
|
static void dbg_snapshot_set_sjtag_status(void)
|
||
|
{
|
||
|
#ifdef SMC_CMD_GET_SJTAG_STATUS
|
||
|
struct arm_smccc_res res;
|
||
|
arm_smccc_smc(SMC_CMD_GET_SJTAG_STATUS, 0x3, 0, 0, 0, 0, 0, 0, &res);
|
||
|
dss_desc.sjtag_status = res.a0;
|
||
|
dev_info(dss_desc.dev, "SJTAG is %sabled\n",
|
||
|
dss_desc.sjtag_status ? "en" : "dis");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
int dbg_snapshot_get_sjtag_status(void)
|
||
|
{
|
||
|
return dss_desc.sjtag_status;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_sjtag_status);
|
||
|
|
||
|
void dbg_snapshot_scratch_reg(unsigned int val)
|
||
|
{
|
||
|
if (!dss_desc.scratch_offset || !dss_desc.scratch_bit)
|
||
|
return;
|
||
|
|
||
|
exynos_pmu_update(dss_desc.scratch_offset,
|
||
|
BIT(dss_desc.scratch_bit),
|
||
|
!!val ? BIT(dss_desc.scratch_bit) : 0);
|
||
|
}
|
||
|
|
||
|
void dbg_snapshot_scratch_clear(void)
|
||
|
{
|
||
|
dbg_snapshot_scratch_reg(DSS_SIGN_RESET);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_scratch_clear);
|
||
|
|
||
|
bool dbg_snapshot_is_scratch(void)
|
||
|
{
|
||
|
unsigned int val;
|
||
|
int ret;
|
||
|
|
||
|
if (!dss_desc.scratch_offset || !dss_desc.scratch_bit)
|
||
|
return false;;
|
||
|
|
||
|
ret = exynos_pmu_read(dss_desc.scratch_offset, &val);
|
||
|
if (ret)
|
||
|
return false;
|
||
|
|
||
|
return val & BIT(dss_desc.scratch_bit);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_is_scratch);
|
||
|
|
||
|
void dbg_snapshot_set_debug_test_buffer_addr(u64 paddr, unsigned int cpu)
|
||
|
{
|
||
|
void __iomem *header = dbg_snapshot_get_header_vaddr();
|
||
|
|
||
|
if (header)
|
||
|
__raw_writeq(paddr, header + DSS_OFFSET_DEBUG_TEST_BUFFER(cpu));
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_set_debug_test_buffer_addr);
|
||
|
|
||
|
u64 dbg_snapshot_get_debug_test_buffer_addr(unsigned int cpu)
|
||
|
{
|
||
|
void __iomem *header = dbg_snapshot_get_header_vaddr();
|
||
|
|
||
|
return header ? __raw_readq(header + DSS_OFFSET_DEBUG_TEST_BUFFER(cpu)) : 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_debug_test_buffer_addr);
|
||
|
|
||
|
int dbg_snapshot_get_dpm_none_dump_mode(void)
|
||
|
{
|
||
|
unsigned int val;
|
||
|
void __iomem *header = dbg_snapshot_get_header_vaddr();
|
||
|
|
||
|
if (header) {
|
||
|
val = __raw_readl(header + DSS_OFFSET_NONE_DPM_DUMP_MODE);
|
||
|
if ((val & GENMASK(31, 16)) == DSS_SIGN_MAGIC)
|
||
|
return (val & GENMASK(15, 0));
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_dpm_none_dump_mode);
|
||
|
|
||
|
void dbg_snapshot_set_dpm_none_dump_mode(unsigned int mode)
|
||
|
{
|
||
|
void __iomem *header = dbg_snapshot_get_header_vaddr();
|
||
|
|
||
|
if (header) {
|
||
|
if (mode)
|
||
|
mode |= DSS_SIGN_MAGIC;
|
||
|
else
|
||
|
mode = 0;
|
||
|
|
||
|
__raw_writel(mode, header + DSS_OFFSET_NONE_DPM_DUMP_MODE);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_set_dpm_none_dump_mode);
|
||
|
|
||
|
void dbg_snapshot_set_qd_entry(unsigned long address)
|
||
|
{
|
||
|
void __iomem *header = dbg_snapshot_get_header_vaddr();
|
||
|
|
||
|
if (header)
|
||
|
__raw_writeq((unsigned long)(virt_to_phys)((void *)address),
|
||
|
header + DSS_OFFSET_QD_ENTRY);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_set_qd_entry);
|
||
|
|
||
|
struct dbg_snapshot_item *dbg_snapshot_get_item(const char *name)
|
||
|
{
|
||
|
unsigned long i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
||
|
if (dss_items[i].name &&
|
||
|
!strncmp(dss_items[i].name, name, strlen(name)))
|
||
|
return &dss_items[i];
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
struct dbg_snapshot_item *dbg_snapshot_get_item_by_device_node(struct device_node *np)
|
||
|
{
|
||
|
unsigned long i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
||
|
if (!!of_device_is_compatible(np, dss_items[i].name))
|
||
|
return &dss_items[i];
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
unsigned int dbg_snapshot_get_item_size(const char *name)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = dbg_snapshot_get_item(name);
|
||
|
|
||
|
return item && item->entry.enabled ? item->entry.size : 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_item_size);
|
||
|
|
||
|
unsigned long dbg_snapshot_get_item_vaddr(const char *name)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = dbg_snapshot_get_item(name);
|
||
|
|
||
|
return item && item->entry.enabled ? item->entry.vaddr : 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_item_vaddr);
|
||
|
|
||
|
unsigned int dbg_snapshot_get_item_paddr(const char *name)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = dbg_snapshot_get_item(name);
|
||
|
|
||
|
return item && item->entry.enabled ? item->entry.paddr : 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_item_paddr);
|
||
|
|
||
|
int dbg_snapshot_get_item_enable(const char *name)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = dbg_snapshot_get_item(name);
|
||
|
|
||
|
return item && item->entry.enabled ? item->entry.enabled : 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_item_enable);
|
||
|
|
||
|
void dbg_snapshot_set_item_enable(const char *name, int en)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = NULL;
|
||
|
|
||
|
if (!name || !dss_dpm.enabled_debug || dss_dpm.dump_mode == NONE_DUMP)
|
||
|
return;
|
||
|
|
||
|
/* This is default for debug-mode */
|
||
|
item = dbg_snapshot_get_item(name);
|
||
|
if (item) {
|
||
|
item->entry.enabled = en;
|
||
|
pr_info("item - %s is %sabled\n", name, en ? "en" : "dis");
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_set_item_enable);
|
||
|
|
||
|
static void dbg_snapshot_set_enable(int en)
|
||
|
{
|
||
|
dss_base->enabled = en;
|
||
|
dev_info(dss_desc.dev, "%sabled\n", en ? "en" : "dis");
|
||
|
}
|
||
|
|
||
|
int dbg_snapshot_get_enable(void)
|
||
|
{
|
||
|
return dss_base && dss_base->enabled;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_enable);
|
||
|
|
||
|
void *dbg_snapshot_get_item_by_index(int index)
|
||
|
{
|
||
|
if (index < 0 || index > ARRAY_SIZE(dss_items))
|
||
|
return NULL;
|
||
|
|
||
|
return &dss_items[index];
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_item_by_index);
|
||
|
|
||
|
int dbg_snapshot_get_num_items(void)
|
||
|
{
|
||
|
return ARRAY_SIZE(dss_items);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_num_items);
|
||
|
|
||
|
static inline int dbg_snapshot_check_eob(struct dbg_snapshot_item *item,
|
||
|
size_t size)
|
||
|
{
|
||
|
size_t max = (size_t)(item->head_ptr + item->entry.size);
|
||
|
size_t cur = (size_t)(item->curr_ptr + size);
|
||
|
|
||
|
return (unlikely(cur > max)) ? -1 : 0;
|
||
|
}
|
||
|
|
||
|
void dbg_snapshot_hook_logger(const char *buf, size_t size,
|
||
|
unsigned int id)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = dbg_snapshot_get_item_by_index(id);
|
||
|
size_t last_buf;
|
||
|
u32 offset = 0;
|
||
|
static char *logger_buffer = NULL;
|
||
|
|
||
|
if (!dbg_snapshot_get_enable() || !item->entry.enabled)
|
||
|
return;
|
||
|
|
||
|
if (id == DSS_ITEM_KERNEL_ID) {
|
||
|
offset = DSS_OFFSET_LAST_LOGBUF;
|
||
|
} else if (id == DSS_ITEM_PLATFORM_ID) {
|
||
|
offset = DSS_OFFSET_LAST_PLATFORM_LOGBUF;
|
||
|
} else if (id == DSS_ITEM_FIRST_ID) {
|
||
|
if (dbg_snapshot_check_eob(item, size)) {
|
||
|
item->entry.enabled = false;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
if (dbg_snapshot_check_eob(item, size)) {
|
||
|
if (id == DSS_ITEM_KERNEL_ID)
|
||
|
dbg_snapshot_set_val_offset(1, DSS_OFFSET_CHECK_EOB);
|
||
|
item->curr_ptr = item->head_ptr;
|
||
|
#if IS_ENABLED(CONFIG_SEC_PM_DEBUG)
|
||
|
sec_log_full = true;
|
||
|
#endif /* CONFIG_SEC_PM_DEBUG */
|
||
|
}
|
||
|
|
||
|
if (unlikely(!logger_buffer && id == DSS_ITEM_PLATFORM_ID))
|
||
|
if (size == LOGGER_LEVEL_PREFIX ||
|
||
|
(buf[0] == '\n' && buf[1] == '['))
|
||
|
logger_buffer = (char*)buf;
|
||
|
|
||
|
/* save the address of last_buf to physical address */
|
||
|
|
||
|
last_buf = (size_t)item->curr_ptr + size;
|
||
|
if (offset)
|
||
|
dbg_snapshot_set_val_offset(item->entry.paddr +
|
||
|
(last_buf - item->entry.vaddr),
|
||
|
offset);
|
||
|
memcpy(item->curr_ptr, buf, size);
|
||
|
item->curr_ptr += size;
|
||
|
|
||
|
/* SEC_BOOTSTAT */
|
||
|
if (id != DSS_ITEM_PLATFORM_ID)
|
||
|
return;
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_SEC_BOOTSTAT) && size > 2 &&
|
||
|
strncmp(buf, "!@", 2) == 0) {
|
||
|
char _buf[SZ_128];
|
||
|
size_t count = size < SZ_128 ? size : SZ_128 - 1;
|
||
|
memcpy(_buf, buf, count);
|
||
|
_buf[count] = '\0';
|
||
|
pr_info("%s\n", _buf);
|
||
|
|
||
|
if ((hook_bootstat) && size > 6 &&
|
||
|
strncmp(_buf, "!@Boot", 6) == 0)
|
||
|
hook_bootstat(_buf);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void register_hook_bootstat(void (*func)(const char *buf))
|
||
|
{
|
||
|
if (!func)
|
||
|
return;
|
||
|
|
||
|
hook_bootstat = func;
|
||
|
pr_info("%s done!\n", __func__);
|
||
|
}
|
||
|
EXPORT_SYMBOL(register_hook_bootstat);
|
||
|
|
||
|
void dbg_snapshot_output(void)
|
||
|
{
|
||
|
unsigned long i, size = 0;
|
||
|
|
||
|
dev_info(dss_desc.dev, "debug-snapshot physical / virtual memory layout:\n");
|
||
|
for (i = 0; i < ARRAY_SIZE(dss_items); i++) {
|
||
|
if (!dss_items[i].entry.paddr)
|
||
|
continue;
|
||
|
pr_info("%-16s: phys:0x%zx / virt:0x%zx / size:0x%zx / en:%d\n",
|
||
|
dss_items[i].name,
|
||
|
dss_items[i].entry.paddr,
|
||
|
dss_items[i].entry.vaddr,
|
||
|
dss_items[i].entry.size,
|
||
|
dss_items[i].entry.enabled);
|
||
|
size += dss_items[i].entry.size;
|
||
|
}
|
||
|
|
||
|
dev_info(dss_desc.dev, "total_item_size: %ldKB\n", size / SZ_1K);
|
||
|
}
|
||
|
|
||
|
static void dbg_snapshot_init_desc(struct device *dev)
|
||
|
{
|
||
|
u32 val;
|
||
|
|
||
|
/* initialize dss_desc */
|
||
|
memset((void *)&dss_desc, 0, sizeof(struct dbg_snapshot_desc));
|
||
|
raw_spin_lock_init(&dss_desc.ctrl_lock);
|
||
|
dss_desc.dev = dev;
|
||
|
dbg_snapshot_set_sjtag_status();
|
||
|
|
||
|
if (!of_property_read_u32(dev->of_node, "panic_to_wdt", &val))
|
||
|
dss_desc.panic_to_wdt = val;
|
||
|
|
||
|
if (!of_property_read_u32(dev->of_node, "last_kmsg", &val))
|
||
|
dss_desc.last_kmsg = val;
|
||
|
|
||
|
if (!of_property_read_u32(dev->of_node, "hold-key", &val))
|
||
|
dss_desc.hold_key = val;
|
||
|
|
||
|
if (!of_property_read_u32(dev->of_node, "trigger-key", &val))
|
||
|
dss_desc.trigger_key = val;
|
||
|
|
||
|
if (!of_property_read_u32(dev->of_node, "scratch-offset", &val))
|
||
|
dss_desc.scratch_offset = val;
|
||
|
|
||
|
if (!of_property_read_u32(dev->of_node, "scratch-bit", &val))
|
||
|
dss_desc.scratch_bit = val;
|
||
|
}
|
||
|
|
||
|
static void dbg_snapshot_fixmap(void)
|
||
|
{
|
||
|
size_t last_buf;
|
||
|
size_t vaddr, paddr, size, offset;
|
||
|
unsigned long i;
|
||
|
|
||
|
for (i = 0; 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_ITEM_HEADER_ID) {
|
||
|
/* initialize kernel event to 0 except only header */
|
||
|
memset((size_t *)(vaddr + DSS_KEEP_HEADER_SZ), 0,
|
||
|
size - DSS_KEEP_HEADER_SZ);
|
||
|
|
||
|
dss_bl = dbg_snapshot_get_header_vaddr() + DSS_OFFSET_ITEM_INFO;
|
||
|
memset(dss_bl, 0, sizeof(struct dbg_snapshot_bl));
|
||
|
dss_bl->magic = DSS_BL_MAGIC;
|
||
|
dss_bl->item_count = 0;
|
||
|
} else if (i == DSS_ITEM_KERNEL_ID || i == DSS_ITEM_PLATFORM_ID) {
|
||
|
/* load last_buf address value(phy) by virt address */
|
||
|
if (i == DSS_ITEM_KERNEL_ID)
|
||
|
offset = DSS_OFFSET_LAST_LOGBUF;
|
||
|
else if (i == DSS_ITEM_PLATFORM_ID)
|
||
|
offset = DSS_OFFSET_LAST_PLATFORM_LOGBUF;
|
||
|
|
||
|
last_buf = (size_t)dbg_snapshot_get_val_offset(offset);
|
||
|
/* check physical address offset of logbuf */
|
||
|
if (last_buf >= paddr && (last_buf <= paddr + size)) {
|
||
|
/* assumed valid address, conversion to virt */
|
||
|
dss_items[i].curr_ptr = (unsigned char *)(vaddr +
|
||
|
(last_buf - paddr));
|
||
|
} else {
|
||
|
/* invalid address, set to first line */
|
||
|
dss_items[i].curr_ptr = (unsigned char *)vaddr;
|
||
|
/* initialize logbuf to 0 */
|
||
|
memset((void *)vaddr, 0, size);
|
||
|
}
|
||
|
} else {
|
||
|
/* initialized log to 0 if persist == false */
|
||
|
if (!dss_items[i].persist)
|
||
|
memset((void *)vaddr, 0, size);
|
||
|
}
|
||
|
|
||
|
dbg_snapshot_add_bl_item_info(dss_items[i].name, paddr, size);
|
||
|
}
|
||
|
|
||
|
/* output the information of debug-snapshot */
|
||
|
dbg_snapshot_output();
|
||
|
}
|
||
|
|
||
|
static void dbg_snapshot_set_version(void)
|
||
|
{
|
||
|
dbg_snapshot_set_val_offset(DSS_SIGN_MAGIC | dss_base->version, DSS_OFFSET_VERSION);
|
||
|
}
|
||
|
|
||
|
int dbg_snapshot_get_version(void)
|
||
|
{
|
||
|
unsigned int version;
|
||
|
|
||
|
version = dbg_snapshot_get_val_offset(DSS_OFFSET_VERSION);
|
||
|
if (((version >> 16) << 16) != DSS_SIGN_MAGIC)
|
||
|
return -1;
|
||
|
|
||
|
return (version << 16) >> 16;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_get_version);
|
||
|
|
||
|
bool dbg_snapshot_is_minized_kevents(void)
|
||
|
{
|
||
|
return dbg_snapshot_get_val_offset(DSS_OFFSET_KEVENTS_MINI_MAGIC) ==
|
||
|
DSS_KEVENTS_MINI_MAGIC;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(dbg_snapshot_is_minized_kevents);
|
||
|
|
||
|
static void dbg_snapshot_kevents_setup(void)
|
||
|
{
|
||
|
struct dbg_snapshot_item *kevents = &dss_items[DSS_ITEM_KEVENTS_ID];
|
||
|
size_t size;
|
||
|
|
||
|
dbg_snapshot_set_val_offset(0, DSS_OFFSET_KEVENTS_MINI_MAGIC);
|
||
|
if (!kevents->entry.enabled)
|
||
|
return;
|
||
|
|
||
|
if (kevents->entry.size >= sizeof(*dss_kevents.default_log)) {
|
||
|
dss_kevents.type = eDSS_LOG_TYPE_DEFAULT;
|
||
|
dss_kevents.default_log = (struct dbg_snapshot_log *)kevents->entry.vaddr;
|
||
|
size = sizeof(*dss_kevents.default_log);
|
||
|
dss_log = dss_kevents.default_log;
|
||
|
|
||
|
/* set fake translation to virtual address to debug trace */
|
||
|
dss_info.info_event = dss_log;
|
||
|
} else if (kevents->entry.size >= sizeof(*dss_kevents.minimized_log)) {
|
||
|
dss_kevents.type = eDSS_LOG_TYPE_MINIMIZED;
|
||
|
dss_kevents.minimized_log =
|
||
|
(struct dbg_snapshot_log_minimized *)kevents->entry.vaddr;
|
||
|
size = sizeof(*dss_kevents.minimized_log);
|
||
|
dbg_snapshot_set_val_offset(DSS_KEVENTS_MINI_MAGIC, DSS_OFFSET_KEVENTS_MINI_MAGIC);
|
||
|
} else {
|
||
|
pr_info("%s: size is too small(%lu bytes)\n", __func__, kevents->entry.size);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pr_info("dbg_snapshot_log struct size: %dKB\n", size / SZ_1K);
|
||
|
}
|
||
|
|
||
|
static void dbg_snapshot_boot_cnt(void)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = &dss_items[DSS_ITEM_PLATFORM_ID];
|
||
|
unsigned int reg;
|
||
|
|
||
|
reg = dbg_snapshot_get_val_offset(DSS_OFFSET_KERNEL_BOOT_CNT_MAGIC);
|
||
|
if (reg != DSS_BOOT_CNT_MAGIC) {
|
||
|
dbg_snapshot_set_val_offset(DSS_BOOT_CNT_MAGIC,
|
||
|
DSS_OFFSET_KERNEL_BOOT_CNT_MAGIC);
|
||
|
reg = 0;
|
||
|
} else {
|
||
|
reg = dbg_snapshot_get_val_offset(DSS_OFFSET_KERNEL_BOOT_CNT);
|
||
|
}
|
||
|
dbg_snapshot_set_val_offset(++reg, DSS_OFFSET_KERNEL_BOOT_CNT);
|
||
|
|
||
|
dev_info(dss_desc.dev, "Kernel Booting SEQ #%u\n", reg);
|
||
|
if (dbg_snapshot_get_enable() && item->entry.enabled) {
|
||
|
char _buf[SZ_128];
|
||
|
|
||
|
snprintf(_buf, SZ_128 - 1, "\nBooting SEQ #%u\n", reg);
|
||
|
dbg_snapshot_hook_logger((const char *)_buf, strlen(_buf),
|
||
|
DSS_ITEM_PLATFORM_ID);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static struct reserved_mem *_dbg_snapshot_rmem_available(struct device *dev,
|
||
|
struct device_node *rmem_np)
|
||
|
{
|
||
|
struct reserved_mem *rmem;
|
||
|
|
||
|
if (!of_device_is_available(rmem_np)) {
|
||
|
dev_err(dev, "rmem(%s) is disabled\n", rmem_np->name);
|
||
|
return NULL;
|
||
|
}
|
||
|
rmem = of_reserved_mem_lookup(rmem_np);
|
||
|
if (!rmem) {
|
||
|
dev_err(dev, "no such reserved mem of node name %s\n",
|
||
|
rmem_np->name);
|
||
|
return NULL;
|
||
|
}
|
||
|
if (!rmem->base || !rmem->size) {
|
||
|
dev_err(dev, "%s item wrong base(0x%x) or size(0x%x)\n",
|
||
|
rmem->name, rmem->base, rmem->size);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return rmem;
|
||
|
}
|
||
|
|
||
|
static void *_dbg_snapshot_rmem_set_vmap(struct reserved_mem *rmem)
|
||
|
{
|
||
|
pgprot_t prot = __pgprot(PROT_NORMAL_NC);
|
||
|
int i, page_size;
|
||
|
struct page *page, **pages;
|
||
|
void *vaddr;
|
||
|
|
||
|
page_size = rmem->size / PAGE_SIZE;
|
||
|
pages = kzalloc(sizeof(struct page *) * page_size, GFP_KERNEL);
|
||
|
page = phys_to_page(rmem->base);
|
||
|
|
||
|
for (i = 0; i < page_size; i++)
|
||
|
pages[i] = page++;
|
||
|
|
||
|
vaddr = vmap(pages, page_size, VM_NO_GUARD | VM_MAP, prot);
|
||
|
kfree(pages);
|
||
|
|
||
|
return vaddr;
|
||
|
}
|
||
|
|
||
|
static void dbg_snapshot_set_total_rmem_size(struct dbg_snapshot_base *base)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (!base)
|
||
|
return;
|
||
|
|
||
|
base->size = 0;
|
||
|
for (i = 0; i < (int)ARRAY_SIZE(dss_items); i++)
|
||
|
base->size += dss_items[i].entry.size;
|
||
|
}
|
||
|
|
||
|
static int dbg_snapshot_rmem_setup(struct device *dev)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item;
|
||
|
int i, mem_count = 0;
|
||
|
void *vaddr;
|
||
|
|
||
|
mem_count = of_count_phandle_with_args(dev->of_node, "memory-region", NULL);
|
||
|
if (mem_count <= 0) {
|
||
|
dev_err(dev, "no such memory-region\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < mem_count; i++) {
|
||
|
struct device_node *rmem_np;
|
||
|
struct reserved_mem *rmem;
|
||
|
|
||
|
rmem_np = of_parse_phandle(dev->of_node, "memory-region", i);
|
||
|
if (!rmem_np) {
|
||
|
dev_err(dev, "no such memory-region of index %d\n", i);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
rmem = _dbg_snapshot_rmem_available(dev, rmem_np);
|
||
|
if (!rmem)
|
||
|
continue;
|
||
|
|
||
|
vaddr = _dbg_snapshot_rmem_set_vmap(rmem);
|
||
|
if (!vaddr) {
|
||
|
dev_err(dev, "%s: paddr:%x page_size:%x failed to vmap\n",
|
||
|
rmem->name, rmem->base, page_size);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
item = dbg_snapshot_get_item_by_device_node(rmem_np);
|
||
|
if (!item) {
|
||
|
dev_err(dev, "no such item %s\n", rmem->name);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
item->entry.paddr = rmem->base;
|
||
|
item->entry.size = rmem->size;
|
||
|
item->entry.vaddr = (size_t)vaddr;
|
||
|
item->head_ptr = item->curr_ptr = (unsigned char *)vaddr;
|
||
|
dbg_snapshot_set_item_enable(item->name, true);
|
||
|
|
||
|
if (item == dbg_snapshot_get_item_by_index(DSS_ITEM_HEADER_ID)) {
|
||
|
dss_base = (struct dbg_snapshot_base *)vaddr;
|
||
|
dss_base->magic = DSS_MAGIC;
|
||
|
dss_base->version = DSS_VERSION;
|
||
|
dss_base->vaddr = (size_t)vaddr;
|
||
|
dss_base->paddr = rmem->base;
|
||
|
ess_base = dss_base;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dbg_snapshot_set_total_rmem_size(dss_base);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t dbg_snapshot_last_kmsg_read(struct file *file, char __user *buf,
|
||
|
size_t count, loff_t *ppos)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
unsigned int size = last_kmsg_size;
|
||
|
|
||
|
if (!last_kmsg || !size)
|
||
|
goto out;
|
||
|
|
||
|
if (*ppos > size)
|
||
|
goto out;
|
||
|
|
||
|
if (*ppos + count > size)
|
||
|
count = size - *ppos;
|
||
|
|
||
|
ret = copy_to_user(buf, last_kmsg + (*ppos), count);
|
||
|
if (ret)
|
||
|
return -EFAULT;
|
||
|
|
||
|
*ppos += count;
|
||
|
ret = count;
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t dbg_snapshot_first_kmsg_read(struct file *file, char __user *buf,
|
||
|
size_t count, loff_t *ppos)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
long first_vaddr = dbg_snapshot_get_item_vaddr(DSS_ITEM_FIRST);
|
||
|
long log_size = dbg_snapshot_get_item_size(DSS_ITEM_FIRST);
|
||
|
|
||
|
if (!first_vaddr || !log_size)
|
||
|
goto out;
|
||
|
|
||
|
if (*ppos > log_size)
|
||
|
goto out;
|
||
|
|
||
|
if (*ppos + count > log_size)
|
||
|
count = log_size - *ppos;
|
||
|
|
||
|
ret = copy_to_user(buf, (char *)first_vaddr + (*ppos), count);
|
||
|
if (ret)
|
||
|
return -EFAULT;
|
||
|
|
||
|
*ppos += count;
|
||
|
ret = count;
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int dbg_snapshot_dss_kmsg_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = dbg_snapshot_get_item_by_index(DSS_ITEM_KERNEL_ID);
|
||
|
size_t offset;
|
||
|
size_t after_cpy_offset;
|
||
|
|
||
|
if (!item)
|
||
|
return -EFAULT;
|
||
|
|
||
|
dss_kmsg_size = item->entry.size;
|
||
|
dss_kmsg = vzalloc(dss_kmsg_size);
|
||
|
if (!dss_kmsg)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
mutex_lock(&dss_kmsg_mutex);
|
||
|
|
||
|
offset = dbg_snapshot_get_val_offset(DSS_OFFSET_LAST_LOGBUF) - item->entry.paddr;
|
||
|
memcpy(dss_kmsg, (void *)item->entry.vaddr + offset, dss_kmsg_size - offset);
|
||
|
memcpy(dss_kmsg + dss_kmsg_size - offset, (void *)item->entry.vaddr, offset);
|
||
|
after_cpy_offset = dbg_snapshot_get_val_offset(DSS_OFFSET_LAST_LOGBUF) - item->entry.paddr;
|
||
|
|
||
|
if (after_cpy_offset >= offset)
|
||
|
dss_kmsg_valid_data_offset = after_cpy_offset - offset;
|
||
|
else
|
||
|
dss_kmsg_valid_data_offset = dss_kmsg_size + after_cpy_offset - offset;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t dbg_snapshot_dss_kmsg_read(struct file *file, char __user *buf,
|
||
|
size_t count, loff_t *ppos)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
long log_size = dss_kmsg_size - dss_kmsg_valid_data_offset;
|
||
|
|
||
|
if (!log_size)
|
||
|
goto out;
|
||
|
|
||
|
if (*ppos > log_size)
|
||
|
goto out;
|
||
|
|
||
|
if (*ppos + count > log_size)
|
||
|
count = log_size - *ppos;
|
||
|
|
||
|
ret = copy_to_user(buf, dss_kmsg + dss_kmsg_valid_data_offset + (*ppos), count);
|
||
|
if (ret)
|
||
|
return -EFAULT;
|
||
|
|
||
|
*ppos += count;
|
||
|
ret = count;
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int dbg_snapshot_dss_kmsg_release(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
vfree(dss_kmsg);
|
||
|
dss_kmsg = NULL;
|
||
|
dss_kmsg_size = 0;
|
||
|
dss_kmsg_valid_data_offset = 0;
|
||
|
mutex_unlock(&dss_kmsg_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct proc_ops proc_last_kmsg_op = {
|
||
|
.proc_read = dbg_snapshot_last_kmsg_read,
|
||
|
};
|
||
|
|
||
|
static const struct proc_ops proc_first_kmsg_op = {
|
||
|
.proc_read = dbg_snapshot_first_kmsg_read,
|
||
|
};
|
||
|
|
||
|
static const struct proc_ops proc_dss_kmsg_op = {
|
||
|
.proc_open = dbg_snapshot_dss_kmsg_open,
|
||
|
.proc_read = dbg_snapshot_dss_kmsg_read,
|
||
|
.proc_release = dbg_snapshot_dss_kmsg_release,
|
||
|
};
|
||
|
|
||
|
static void dbg_snapshot_init_proc(void)
|
||
|
{
|
||
|
struct dbg_snapshot_item *item = dbg_snapshot_get_item(DSS_ITEM_KERNEL);
|
||
|
size_t start, max, cur;
|
||
|
|
||
|
if (!item)
|
||
|
return;
|
||
|
|
||
|
if (!item->entry.enabled || !dss_desc.last_kmsg)
|
||
|
return;
|
||
|
|
||
|
start = (size_t)item->head_ptr;
|
||
|
max = (size_t)(item->head_ptr + item->entry.size);
|
||
|
cur = (size_t)item->curr_ptr;
|
||
|
|
||
|
last_kmsg_size = item->entry.size;
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_FIRST))
|
||
|
proc_create("first_kmsg", 0, NULL, &proc_first_kmsg_op);
|
||
|
|
||
|
last_kmsg = (char *)vzalloc(last_kmsg_size);
|
||
|
if (!last_kmsg)
|
||
|
return;
|
||
|
|
||
|
memcpy(last_kmsg, item->curr_ptr, max - cur);
|
||
|
memcpy(last_kmsg + max - cur, item->head_ptr, cur - start);
|
||
|
|
||
|
proc_create("last_kmsg", 0, NULL, &proc_last_kmsg_op);
|
||
|
proc_create("dss_kmsg", 0, NULL, &proc_dss_kmsg_op);
|
||
|
}
|
||
|
|
||
|
static ssize_t dss_panic_to_wdt_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return scnprintf(buf, PAGE_SIZE, "%sabled\n",
|
||
|
dss_desc.panic_to_wdt ? "En" : "Dis");
|
||
|
}
|
||
|
|
||
|
static ssize_t dss_panic_to_wdt_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
unsigned long mode;
|
||
|
|
||
|
if (!kstrtoul(buf, 10, &mode))
|
||
|
dss_desc.panic_to_wdt = !!mode;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static ssize_t dss_dpm_none_dump_mode_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return scnprintf(buf, PAGE_SIZE, "currnet DPM dump_mode: %x, "
|
||
|
"DPM none dump_mode: %x\n",
|
||
|
dss_dpm.dump_mode, dss_dpm.dump_mode_none);
|
||
|
}
|
||
|
|
||
|
static ssize_t dss_dpm_none_dump_mode_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
unsigned int mode;
|
||
|
|
||
|
if (kstrtoint(buf, 10, &mode))
|
||
|
return count;
|
||
|
|
||
|
dbg_snapshot_set_dpm_none_dump_mode(mode);
|
||
|
if (mode && dss_dpm.version) {
|
||
|
dss_dpm.dump_mode_none = 1;
|
||
|
dbg_snapshot_scratch_clear();
|
||
|
} else {
|
||
|
dss_dpm.dump_mode_none = 0;
|
||
|
dbg_snapshot_scratch_reg(DSS_SIGN_SCRATCH);
|
||
|
}
|
||
|
|
||
|
dev_info(dss_desc.dev, "DPM: success to switch %sNONE_DUMP mode\n",
|
||
|
dss_dpm.dump_mode_none ? "" : "NOT ");
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static ssize_t dss_logging_item_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
int i;
|
||
|
ssize_t n = 0;
|
||
|
|
||
|
if (!dbg_snapshot_get_enable())
|
||
|
goto out;
|
||
|
|
||
|
n += scnprintf(buf + n, PAGE_SIZE, "item_count: %d\n",
|
||
|
dss_bl->item_count);
|
||
|
for (i = 0; i < dss_bl->item_count; i++) {
|
||
|
n += scnprintf(buf + n, PAGE_SIZE - n,
|
||
|
"%s paddr: 0x%X / size: 0x%X\n",
|
||
|
dss_bl->item[i].name,
|
||
|
dss_bl->item[i].paddr,
|
||
|
dss_bl->item[i].size);
|
||
|
}
|
||
|
out:
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
DEVICE_ATTR_RW(dss_panic_to_wdt);
|
||
|
DEVICE_ATTR_RW(dss_dpm_none_dump_mode);
|
||
|
DEVICE_ATTR_RO(dss_logging_item);
|
||
|
|
||
|
static struct attribute *dss_sysfs_attrs[] = {
|
||
|
&dev_attr_dss_dpm_none_dump_mode.attr,
|
||
|
&dev_attr_dss_logging_item.attr,
|
||
|
&dev_attr_dss_panic_to_wdt.attr,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static struct attribute_group dss_sysfs_group = {
|
||
|
.attrs = dss_sysfs_attrs,
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group *dss_sysfs_groups[] = {
|
||
|
&dss_sysfs_group,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static void dbg_snapshot_console_write(struct console *con, const char *s,
|
||
|
unsigned c)
|
||
|
{
|
||
|
dbg_snapshot_hook_logger(s, c, DSS_ITEM_KERNEL_ID);
|
||
|
dbg_snapshot_hook_logger(s, c, DSS_ITEM_FIRST_ID);
|
||
|
}
|
||
|
|
||
|
static struct console dss_console = {
|
||
|
.name = "dss",
|
||
|
.write = dbg_snapshot_console_write,
|
||
|
.flags = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME,
|
||
|
.index = -1,
|
||
|
};
|
||
|
|
||
|
#if IS_ENABLED(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_ITEM_KERNEL_ID];
|
||
|
|
||
|
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 proc_ops sec_log_proc_ops = {
|
||
|
.proc_read = sec_log_read_all,
|
||
|
};
|
||
|
|
||
|
static void sec_log_init(void)
|
||
|
{
|
||
|
struct proc_dir_entry *entry;
|
||
|
struct dbg_snapshot_item *item = &dss_items[DSS_ITEM_KERNEL_ID];
|
||
|
|
||
|
if (!item->head_ptr)
|
||
|
return;
|
||
|
|
||
|
entry = proc_create("sec_log", 0440, NULL, &sec_log_proc_ops);
|
||
|
if (!entry) {
|
||
|
pr_err("%s: failed to create proc entry\n", __func__);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
proc_set_size(entry, item->entry.size);
|
||
|
}
|
||
|
#else
|
||
|
static inline void sec_log_init(void) {}
|
||
|
#endif /* CONFIG_SEC_PM_DEBUG */
|
||
|
|
||
|
static int dbg_snapshot_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
if (dbg_snapshot_dt_scan_dpm()) {
|
||
|
pr_err("%s: no such dpm node\n", __func__);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
dbg_snapshot_init_desc(&pdev->dev);
|
||
|
if (dbg_snapshot_rmem_setup(&pdev->dev)) {
|
||
|
dev_err(&pdev->dev, "%s failed\n", __func__);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
dbg_snapshot_fixmap();
|
||
|
dbg_snapshot_kevents_setup();
|
||
|
|
||
|
dbg_snapshot_init_dpm();
|
||
|
dbg_snapshot_init_utils(&pdev->dev);
|
||
|
dbg_snapshot_init_log();
|
||
|
dbg_snapshot_init_proc();
|
||
|
|
||
|
dbg_snapshot_set_enable(true);
|
||
|
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_KERNEL)) {
|
||
|
register_console(&dss_console);
|
||
|
console_suspend_enabled = false;
|
||
|
}
|
||
|
if (dbg_snapshot_get_item_enable(DSS_ITEM_PLATFORM))
|
||
|
dbg_snapshot_init_pmsg();
|
||
|
|
||
|
dbg_snapshot_boot_cnt();
|
||
|
dbg_snapshot_set_linux_banner();
|
||
|
|
||
|
if (sysfs_create_groups(&pdev->dev.kobj, dss_sysfs_groups))
|
||
|
dev_err(dss_desc.dev, "fail to register debug-snapshot sysfs\n");
|
||
|
|
||
|
dbg_snapshot_set_version();
|
||
|
|
||
|
sec_log_init();
|
||
|
|
||
|
dev_info(&pdev->dev, "%s successful.\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id dss_of_match[] = {
|
||
|
{ .compatible = "samsung,debug-snapshot" },
|
||
|
{},
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, dss_of_match);
|
||
|
|
||
|
static struct platform_driver dbg_snapshot_driver = {
|
||
|
.probe = dbg_snapshot_probe,
|
||
|
.driver = {
|
||
|
.name = "debug-snapshot",
|
||
|
.of_match_table = of_match_ptr(dss_of_match),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static __init int dbg_snapshot_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&dbg_snapshot_driver);
|
||
|
}
|
||
|
core_initcall(dbg_snapshot_init);
|
||
|
|
||
|
static void __exit dbg_snapshot_exit(void)
|
||
|
{
|
||
|
if (last_kmsg)
|
||
|
vfree(last_kmsg);
|
||
|
platform_driver_unregister(&dbg_snapshot_driver);
|
||
|
}
|
||
|
module_exit(dbg_snapshot_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("Debug-Snapshot");
|
||
|
MODULE_LICENSE("GPL v2");
|