1252 lines
26 KiB
C
Executable File
1252 lines
26 KiB
C
Executable File
/*
|
|
*sec_debug_extrainfo.c
|
|
*
|
|
* Copyright (c) 2016 Samsung Electronics Co., Ltd
|
|
* http://www.samsung.com
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sec_debug.h>
|
|
#include <linux/soc/samsung/exynos-soc.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/esr.h>
|
|
|
|
#include <soc/samsung/exynos-pmu.h>
|
|
#include <linux/sec_ext.h>
|
|
|
|
#define SDEI_DEBUG (0)
|
|
#define EXTRA_VERSION "RI25"
|
|
|
|
#include "sec_debug_extra_info_keys.c"
|
|
#include "sec_debug_internal.h"
|
|
|
|
static bool exin_ready;
|
|
|
|
static struct sec_debug_shared_buffer *sh_buf;
|
|
static void *slot_end_addr;
|
|
|
|
char *ftype_items[MAX_ITEM_VAL_LEN] = {
|
|
"UNDF", "BAD", "WATCH", "KERN", "MEM",
|
|
"SPPC", "PAGE", "AUF", "EUF", "AUOF",
|
|
"BUG", "PANIC",
|
|
};
|
|
|
|
/*****************************************************************/
|
|
/* UNIT FUNCTIONS */
|
|
/*****************************************************************/
|
|
static int get_val_len(const char *s)
|
|
{
|
|
if (s)
|
|
return strnlen(s, MAX_ITEM_VAL_LEN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_key_len(const char *s)
|
|
{
|
|
return strnlen(s, MAX_ITEM_KEY_LEN);
|
|
}
|
|
|
|
static void *get_slot_addr(int idx)
|
|
{
|
|
return (void *)phys_to_virt(sh_buf->sec_debug_sbidx[idx].paddr);
|
|
}
|
|
|
|
static int get_max_len(void *p)
|
|
{
|
|
int i;
|
|
|
|
if (p < get_slot_addr(SLOT_32) || (p >= slot_end_addr)) {
|
|
pr_crit("%s: addr(%p) is not in range\n", __func__, p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
for (i = SLOT_32 + 1; i < NR_SLOT; i++)
|
|
if (p < get_slot_addr(i))
|
|
return sh_buf->sec_debug_sbidx[i - 1].size;
|
|
|
|
return sh_buf->sec_debug_sbidx[SLOT_END].size;
|
|
}
|
|
|
|
static void *__get_item(int slot, int idx)
|
|
{
|
|
void *p, *base;
|
|
unsigned int size, nr;
|
|
|
|
size = sh_buf->sec_debug_sbidx[slot].size;
|
|
nr = sh_buf->sec_debug_sbidx[slot].nr;
|
|
|
|
if (nr <= idx) {
|
|
pr_crit("%s: SLOT %d has %d items (%d)\n",
|
|
__func__, slot, nr, idx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
base = get_slot_addr(slot);
|
|
p = (void *)(base + (idx * size));
|
|
|
|
return p;
|
|
}
|
|
|
|
static void *search_item_by_key(const char *key, int start, int end)
|
|
{
|
|
int s, i, keylen = get_key_len(key);
|
|
void *p;
|
|
char *keyname;
|
|
unsigned int max;
|
|
|
|
if (!sh_buf || !exin_ready) {
|
|
pr_info("%s: (%s) extra_info is not ready\n", __func__, key);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
for (s = start; s < end; s++) {
|
|
if (sh_buf->sec_debug_sbidx[s].cnt)
|
|
max = sh_buf->sec_debug_sbidx[s].cnt;
|
|
else
|
|
max = sh_buf->sec_debug_sbidx[s].nr;
|
|
|
|
for (i = 0; i < max; i++) {
|
|
p = __get_item(s, i);
|
|
if (!p)
|
|
break;
|
|
|
|
keyname = (char *)p;
|
|
if (keylen != get_key_len(keyname))
|
|
continue;
|
|
|
|
if (!strncmp(keyname, key, keylen))
|
|
return p;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *get_item(const char *key)
|
|
{
|
|
return search_item_by_key(key, SLOT_32, NR_MAIN_SLOT);
|
|
}
|
|
|
|
static void *get_bk_item(const char *key)
|
|
{
|
|
return search_item_by_key(key, SLOT_BK_32, NR_SLOT);
|
|
}
|
|
|
|
static char *get_item_val(const char *key)
|
|
{
|
|
void *p;
|
|
|
|
p = get_item(key);
|
|
if (!p) {
|
|
pr_crit("%s: no key %s\n", __func__, key);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return ((char *)p + MAX_ITEM_KEY_LEN);
|
|
}
|
|
|
|
char *get_bk_item_val(const char *key)
|
|
{
|
|
void *p;
|
|
|
|
p = get_bk_item(key);
|
|
if (!p)
|
|
return NULL;
|
|
|
|
return ((char *)p + MAX_ITEM_KEY_LEN);
|
|
}
|
|
|
|
void get_bk_item_val_as_string(const char *key, char *buf)
|
|
{
|
|
void *v;
|
|
int len;
|
|
|
|
v = get_bk_item_val(key);
|
|
if (v) {
|
|
len = get_val_len(v);
|
|
if (len)
|
|
memcpy(buf, v, len);
|
|
}
|
|
}
|
|
|
|
static int is_key_in_blacklist(const char *key)
|
|
{
|
|
char blkey[][MAX_ITEM_KEY_LEN] = {
|
|
"KTIME", "BAT", "FTYPE", "ODR", "DDRID",
|
|
"PSTIE", "ASB",
|
|
};
|
|
|
|
int nr_blkey, keylen, i;
|
|
|
|
keylen = get_key_len(key);
|
|
nr_blkey = ARRAY_SIZE(blkey);
|
|
|
|
for (i = 0; i < nr_blkey; i++)
|
|
if (!strncmp(key, blkey[i], keylen))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DEFINE_SPINLOCK(keyorder_lock);
|
|
|
|
static void set_key_order(const char *key)
|
|
{
|
|
void *p;
|
|
char *v;
|
|
char tmp[MAX_ITEM_VAL_LEN] = {0, };
|
|
int max = MAX_ITEM_VAL_LEN;
|
|
int len_prev, len_remain, len_this;
|
|
|
|
if (is_key_in_blacklist(key))
|
|
return;
|
|
|
|
spin_lock(&keyorder_lock);
|
|
|
|
p = get_item("ODR");
|
|
if (!p) {
|
|
pr_crit("%s: fail to find %s\n", __func__, key);
|
|
|
|
goto unlock_keyorder;
|
|
}
|
|
|
|
max = get_max_len(p);
|
|
if (!max) {
|
|
pr_crit("%s: fail to get max len %s\n", __func__, key);
|
|
|
|
goto unlock_keyorder;
|
|
}
|
|
|
|
v = get_item_val(p);
|
|
|
|
/* keep previous value */
|
|
len_prev = get_val_len(v);
|
|
if ((!len_prev) || (MAX_ITEM_VAL_LEN <= len_prev))
|
|
len_prev = MAX_ITEM_VAL_LEN - 1;
|
|
|
|
snprintf(tmp, len_prev + 1, "%s", v);
|
|
|
|
/* calculate the remained size */
|
|
len_remain = max;
|
|
|
|
/* get_item_val returned address without key */
|
|
len_remain -= MAX_ITEM_KEY_LEN;
|
|
|
|
/* need comma */
|
|
len_this = get_key_len(key) + 1;
|
|
len_remain -= len_this;
|
|
|
|
/* put last key at the first of ODR */
|
|
/* +1 to add NULL (by snprintf) */
|
|
snprintf(v, len_this + 1, "%s,", key);
|
|
|
|
/* -1 to remove NULL between KEYS */
|
|
/* +1 to add NULL (by snprintf) */
|
|
snprintf((char *)(v + len_this), len_remain + 1, tmp);
|
|
|
|
unlock_keyorder:
|
|
spin_unlock(&keyorder_lock);
|
|
}
|
|
|
|
static void set_item_val(const char *key, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
void *p;
|
|
char *v;
|
|
int max = MAX_ITEM_VAL_LEN;
|
|
|
|
p = get_item(key);
|
|
if (!p) {
|
|
pr_crit("%s: fail to find %s\n", __func__, key);
|
|
|
|
return;
|
|
}
|
|
|
|
max = get_max_len(p);
|
|
if (!max) {
|
|
pr_crit("%s: fail to get max len %s\n", __func__, key);
|
|
|
|
return;
|
|
}
|
|
|
|
v = get_item_val(p);
|
|
if (!get_val_len(v)) {
|
|
va_start(args, fmt);
|
|
vsnprintf(v, max, fmt, args);
|
|
va_end(args);
|
|
|
|
set_key_order(key);
|
|
}
|
|
}
|
|
|
|
static void set_bk_item_val(const char *key, int slot, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
void *p;
|
|
char *v;
|
|
int max = MAX_ITEM_VAL_LEN;
|
|
unsigned int cnt;
|
|
|
|
p = get_bk_item(key);
|
|
if (!p) {
|
|
pr_crit("%s: fail to find %s\n", __func__, key);
|
|
|
|
if (slot > SLOT_MAIN_END) {
|
|
cnt = sh_buf->sec_debug_sbidx[slot].cnt;
|
|
sh_buf->sec_debug_sbidx[slot].cnt++;
|
|
|
|
p = __get_item(slot, cnt);
|
|
if (!p) {
|
|
pr_crit("%s: slot%2d cnt: %d, fail\n", __func__, slot, cnt);
|
|
|
|
return;
|
|
}
|
|
|
|
snprintf((char *)p, get_key_len(key) + 1, key);
|
|
|
|
v = ((char *)p + MAX_ITEM_KEY_LEN);
|
|
|
|
pr_crit("%s: add slot%2d cnt: %d (%s)\n", __func__, slot, cnt, (char *)p);
|
|
|
|
goto set_val;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
max = get_max_len(p);
|
|
if (!max) {
|
|
pr_crit("%s: fail to get max len %s\n", __func__, key);
|
|
|
|
if (slot > SLOT_MAIN_END) {
|
|
max = MAX_ITEM_VAL_LEN;
|
|
} else {
|
|
pr_crit("%s: slot(%d) is not in bk slot\n", __func__, slot);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
v = get_bk_item_val(p);
|
|
if (!v) {
|
|
pr_crit("%s: fail to find value address for %s\n", __func__, key);
|
|
|
|
return;
|
|
} else {
|
|
if (get_val_len(v)) {
|
|
pr_crit("%s: some value is in %s\n", __func__, key);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
set_val:
|
|
va_start(args, fmt);
|
|
vsnprintf(v, max, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static void clear_item_val(const char *key)
|
|
{
|
|
void *p;
|
|
int max_len;
|
|
|
|
p = get_item(key);
|
|
if (!p) {
|
|
pr_crit("%s: fail to find %s\n", __func__, key);
|
|
|
|
return;
|
|
}
|
|
|
|
max_len = get_max_len(p);
|
|
if (!max_len) {
|
|
pr_crit("%s: fail to get max len %s\n", __func__, key);
|
|
|
|
return;
|
|
}
|
|
|
|
memset(get_item_val(p), 0, max_len - MAX_ITEM_KEY_LEN);
|
|
}
|
|
|
|
static void dump_slot(int type, void *ptr)
|
|
{
|
|
struct seq_file *m;
|
|
unsigned int cnt, i;
|
|
char *p, *v;
|
|
|
|
if (ptr)
|
|
m = (struct seq_file *)ptr;
|
|
else
|
|
m = NULL;
|
|
|
|
if (!exin_ready) {
|
|
pr_crit("%s: EXIN is not ready\n", __func__);
|
|
return;
|
|
}
|
|
|
|
/* temporally for backup slot */
|
|
cnt = sh_buf->sec_debug_sbidx[type].cnt;
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
p = __get_item(type, i);
|
|
if (!p)
|
|
break;
|
|
|
|
v = p + MAX_ITEM_KEY_LEN;
|
|
|
|
if (m)
|
|
seq_printf(m, "%s: %s - %s\n", __func__, p, v);
|
|
else
|
|
pr_crit("%s: %s - %s\n", __func__, p, v);
|
|
}
|
|
}
|
|
|
|
static void dump_slots(int start, int end, void *ptr)
|
|
{
|
|
int i;
|
|
|
|
for (i = start; i < end; i++)
|
|
dump_slot(i, ptr);
|
|
}
|
|
|
|
|
|
static void dump_all_keys(void)
|
|
{
|
|
void *p;
|
|
int s, i;
|
|
unsigned int cnt;
|
|
|
|
if (!exin_ready) {
|
|
pr_crit("%s: EXIN is not ready\n", __func__);
|
|
|
|
return;
|
|
}
|
|
|
|
for (s = 0; s < NR_SLOT; s++) {
|
|
cnt = sh_buf->sec_debug_sbidx[s].cnt;
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
p = __get_item(s, i);
|
|
if (!p) {
|
|
pr_crit("%s: %d/%d: no item %p\n", __func__, s, i, p);
|
|
break;
|
|
}
|
|
|
|
if (!get_key_len(p))
|
|
break;
|
|
|
|
pr_crit("%s: [%d][%02d] key %s\n",
|
|
__func__, s, i, (char *)p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************/
|
|
/* INIT FUNCTIONS */
|
|
/*****************************************************************/
|
|
static void init_shared_buffer(int type, int nr_keys, void *ptr)
|
|
{
|
|
char (* keys)[MAX_ITEM_KEY_LEN];
|
|
unsigned int base, size, nr;
|
|
void *addr;
|
|
int i;
|
|
|
|
keys = (char (*)[MAX_ITEM_KEY_LEN])ptr;
|
|
|
|
base = sh_buf->sec_debug_sbidx[type].paddr;
|
|
size = sh_buf->sec_debug_sbidx[type].size;
|
|
nr = sh_buf->sec_debug_sbidx[type].nr;
|
|
|
|
addr = phys_to_virt(base);
|
|
memset(addr, 0, size * nr);
|
|
|
|
pr_crit("%s: SLOT%d: nr keys: %d\n", __func__, type, nr_keys);
|
|
|
|
for (i = 0; i < nr_keys; i++) {
|
|
/* NULL is considered as +1 */
|
|
snprintf((char *)addr, get_key_len(keys[i]) + 1, keys[i]);
|
|
|
|
base += size;
|
|
addr = phys_to_virt(base);
|
|
}
|
|
|
|
sh_buf->sec_debug_sbidx[type].cnt = i;
|
|
}
|
|
|
|
static void sec_debug_extra_info_key_init(void)
|
|
{
|
|
int nr_keys;
|
|
|
|
nr_keys = sizeof(key32) / sizeof(key32[0]);
|
|
init_shared_buffer(SLOT_32, nr_keys, (void *)key32);
|
|
|
|
nr_keys = sizeof(key64) / sizeof(key64[0]);
|
|
init_shared_buffer(SLOT_64, nr_keys, (void *)key64);
|
|
|
|
nr_keys = sizeof(key256) / sizeof(key256[0]);
|
|
init_shared_buffer(SLOT_256, nr_keys, (void *)key256);
|
|
|
|
nr_keys = sizeof(key1024) / sizeof(key1024[0]);
|
|
init_shared_buffer(SLOT_1024, nr_keys, (void *)key1024);
|
|
}
|
|
|
|
static void sec_debug_extra_info_copy_shared_buffer(bool mflag)
|
|
{
|
|
int i;
|
|
unsigned int total_size = 0, slot_base;
|
|
char *backup_base;
|
|
|
|
for (i = 0; i < NR_MAIN_SLOT; i++)
|
|
total_size += sh_buf->sec_debug_sbidx[i].nr * sh_buf->sec_debug_sbidx[i].size;
|
|
|
|
slot_base = sh_buf->sec_debug_sbidx[SLOT_32].paddr;
|
|
|
|
backup_base = phys_to_virt(slot_base + total_size);
|
|
|
|
pr_crit("%s: dst: %p src: %p (%x)\n",
|
|
__func__, backup_base, phys_to_virt(slot_base), total_size);
|
|
|
|
memcpy(backup_base, phys_to_virt(slot_base), total_size);
|
|
|
|
/* backup shared buffer header info */
|
|
memcpy(&(sh_buf->sec_debug_sbidx[SLOT_BK_32]),
|
|
&(sh_buf->sec_debug_sbidx[SLOT_32]),
|
|
sizeof(struct sec_debug_sb_index) * (NR_SLOT - NR_MAIN_SLOT));
|
|
|
|
for (i = SLOT_BK_32; i < NR_SLOT; i++) {
|
|
sh_buf->sec_debug_sbidx[i].paddr += total_size;
|
|
if (SDEI_DEBUG) {
|
|
pr_crit("%s: SLOT %2d: paddr: 0x%x\n",
|
|
__func__, i, sh_buf->sec_debug_sbidx[i].paddr);
|
|
pr_crit("%s: SLOT %2d: size: %d\n",
|
|
__func__, i, sh_buf->sec_debug_sbidx[i].size);
|
|
pr_crit("%s: SLOT %2d: nr: %d\n",
|
|
__func__, i, sh_buf->sec_debug_sbidx[i].nr);
|
|
pr_crit("%s: SLOT %2d: cnt: %d\n",
|
|
__func__, i, sh_buf->sec_debug_sbidx[i].cnt);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sec_debug_extra_info_dump_sb_index(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NR_SLOT; i++) {
|
|
pr_info("%s: SLOT%02d: paddr: %x\n",
|
|
__func__, i, sh_buf->sec_debug_sbidx[i].paddr);
|
|
pr_info("%s: SLOT%02d: cnt: %d\n",
|
|
__func__, i, sh_buf->sec_debug_sbidx[i].cnt);
|
|
pr_info("%s: SLOT%02d: blmark: %lx\n",
|
|
__func__, i, sh_buf->sec_debug_sbidx[i].blmark);
|
|
pr_info("\n");
|
|
}
|
|
}
|
|
|
|
static void sec_debug_init_extra_info_sbidx(int type, struct sec_debug_sb_index idx, bool mflag)
|
|
{
|
|
sh_buf->sec_debug_sbidx[type].paddr = idx.paddr;
|
|
sh_buf->sec_debug_sbidx[type].size = idx.size;
|
|
sh_buf->sec_debug_sbidx[type].nr = idx.nr;
|
|
sh_buf->sec_debug_sbidx[type].cnt = idx.cnt;
|
|
sh_buf->sec_debug_sbidx[type].blmark = 0;
|
|
|
|
pr_crit("%s: slot: %d / paddr: 0x%x / size: %d / nr: %d\n",
|
|
__func__, type,
|
|
sh_buf->sec_debug_sbidx[type].paddr,
|
|
sh_buf->sec_debug_sbidx[type].size,
|
|
sh_buf->sec_debug_sbidx[type].nr);
|
|
}
|
|
|
|
static bool sec_debug_extra_info_check_magic(void)
|
|
{
|
|
if (sh_buf->magic[0] != SEC_DEBUG_SHARED_MAGIC0)
|
|
return false;
|
|
|
|
if (sh_buf->magic[1] != SEC_DEBUG_SHARED_MAGIC1)
|
|
return false;
|
|
|
|
if (sh_buf->magic[2] != SEC_DEBUG_SHARED_MAGIC2)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void sec_debug_extra_info_buffer_init(void)
|
|
{
|
|
unsigned long tmp_addr;
|
|
struct sec_debug_sb_index tmp_idx;
|
|
bool flag_valid = false;
|
|
|
|
flag_valid = sec_debug_extra_info_check_magic();
|
|
|
|
if (SDEI_DEBUG)
|
|
sec_debug_extra_info_dump_sb_index();
|
|
|
|
tmp_idx.cnt = 0;
|
|
tmp_idx.blmark = 0;
|
|
|
|
/* SLOT_32, 32B, 64 items */
|
|
tmp_addr = sec_debug_get_buf_base(SDN_MAP_EXTRA_INFO);
|
|
tmp_idx.paddr = (unsigned int)tmp_addr;
|
|
tmp_idx.size = 32;
|
|
tmp_idx.nr = 64;
|
|
sec_debug_init_extra_info_sbidx(SLOT_32, tmp_idx, flag_valid);
|
|
|
|
/* SLOT_64, 64B, 64 items */
|
|
tmp_addr += tmp_idx.size * tmp_idx.nr;
|
|
tmp_idx.paddr = (unsigned int)tmp_addr;
|
|
tmp_idx.size = 64;
|
|
tmp_idx.nr = 64;
|
|
sec_debug_init_extra_info_sbidx(SLOT_64, tmp_idx, flag_valid);
|
|
|
|
/* SLOT_256, 256B, 16 items */
|
|
tmp_addr += tmp_idx.size * tmp_idx.nr;
|
|
tmp_idx.paddr = (unsigned int)tmp_addr;
|
|
tmp_idx.size = 256;
|
|
tmp_idx.nr = 16;
|
|
sec_debug_init_extra_info_sbidx(SLOT_256, tmp_idx, flag_valid);
|
|
|
|
/* SLOT_1024, 1024B, 16 items */
|
|
tmp_addr += tmp_idx.size * tmp_idx.nr;
|
|
tmp_idx.paddr = (unsigned int)tmp_addr;
|
|
tmp_idx.size = 1024;
|
|
tmp_idx.nr = 16;
|
|
sec_debug_init_extra_info_sbidx(SLOT_1024, tmp_idx, flag_valid);
|
|
|
|
/* backup shared buffer contents */
|
|
sec_debug_extra_info_copy_shared_buffer(flag_valid);
|
|
|
|
sec_debug_extra_info_key_init();
|
|
|
|
if (SDEI_DEBUG)
|
|
dump_all_keys();
|
|
|
|
slot_end_addr = (void *)phys_to_virt(sh_buf->sec_debug_sbidx[SLOT_END].paddr +
|
|
((phys_addr_t)(sh_buf->sec_debug_sbidx[SLOT_END].size) *
|
|
(phys_addr_t)(sh_buf->sec_debug_sbidx[SLOT_END].nr)));
|
|
}
|
|
|
|
#define MAX_EXTRA_INFO_LEN (MAX_ITEM_KEY_LEN + MAX_ITEM_VAL_LEN)
|
|
|
|
static void sec_debug_store_extra_info(char (* keys)[MAX_ITEM_KEY_LEN], int nr_keys, char *ptr)
|
|
{
|
|
int i;
|
|
unsigned long len, max_len;
|
|
void *p;
|
|
char *v, *start_addr = ptr;
|
|
|
|
memset(ptr, 0, SZ_1K);
|
|
|
|
for (i = 0; i < nr_keys; i++) {
|
|
p = get_bk_item(keys[i]);
|
|
if (!p) {
|
|
pr_crit("%s: no key: %s\n", __func__, keys[i]);
|
|
|
|
continue;
|
|
}
|
|
|
|
v = p + MAX_ITEM_KEY_LEN;
|
|
|
|
/* get_key_len returns length of the key + 1 */
|
|
len = (unsigned long)ptr + strlen(p) + get_val_len(v)
|
|
+ MAX_EXTRA_INFO_HDR_LEN;
|
|
|
|
max_len = (unsigned long)start_addr + SZ_1K;
|
|
|
|
if (len > max_len)
|
|
break;
|
|
|
|
ptr += snprintf(ptr, MAX_EXTRA_INFO_LEN, "\"%s\":\"%s\"", (char *)p, v);
|
|
|
|
if ((i + 1) != nr_keys)
|
|
ptr += sprintf(ptr, ",");
|
|
}
|
|
|
|
printk("%s: %s\n", __func__, ptr);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* sec_debug_extra_info details
|
|
*
|
|
* etr_a : basic reset information
|
|
* etr_b : basic reset information
|
|
* etr_c : hard-lockup information (callstack)
|
|
* etr_m : mfc error information
|
|
*
|
|
******************************************************************************/
|
|
|
|
void sec_debug_store_extra_info_A(char *ptr)
|
|
{
|
|
int nr_keys;
|
|
|
|
nr_keys = sizeof(akeys) / sizeof(akeys[0]);
|
|
|
|
sec_debug_store_extra_info(akeys, nr_keys, ptr);
|
|
}
|
|
|
|
void sec_debug_store_extra_info_B(char *ptr)
|
|
{
|
|
int nr_keys;
|
|
|
|
nr_keys = sizeof(bkeys) / sizeof(bkeys[0]);
|
|
|
|
sec_debug_store_extra_info(bkeys, nr_keys, ptr);
|
|
}
|
|
|
|
void sec_debug_store_extra_info_C(char *ptr)
|
|
{
|
|
int nr_keys;
|
|
|
|
nr_keys = sizeof(ckeys) / sizeof(ckeys[0]);
|
|
|
|
sec_debug_store_extra_info(ckeys, nr_keys, ptr);
|
|
}
|
|
|
|
void sec_debug_store_extra_info_M(char *ptr)
|
|
{
|
|
int nr_keys;
|
|
|
|
nr_keys = sizeof(mkeys) / sizeof(mkeys[0]);
|
|
|
|
sec_debug_store_extra_info(mkeys, nr_keys, ptr);
|
|
}
|
|
|
|
void sec_debug_store_extra_info_F(char *ptr)
|
|
{
|
|
int nr_keys;
|
|
|
|
nr_keys = sizeof(fkeys) / sizeof(fkeys[0]);
|
|
|
|
sec_debug_store_extra_info(fkeys, nr_keys, ptr);
|
|
}
|
|
|
|
/* get dram info from bootloader by cmdline */
|
|
#define MAX_DRAMINFO 15
|
|
static char dram_info[MAX_DRAMINFO + 1];
|
|
|
|
static int __init sec_hw_param_get_dram_info(char *arg)
|
|
{
|
|
if (strlen(arg) > MAX_DRAMINFO)
|
|
return 0;
|
|
|
|
memcpy(dram_info, arg, (int)strlen(arg));
|
|
|
|
return 0;
|
|
}
|
|
early_param("androidboot.dram_info", sec_hw_param_get_dram_info);
|
|
|
|
static void sec_debug_set_extra_info_id(void)
|
|
{
|
|
struct timespec ts;
|
|
|
|
getnstimeofday(&ts);
|
|
|
|
set_bk_item_val("ID", SLOT_BK_32, "%09lu%s", ts.tv_nsec, EXTRA_VERSION);
|
|
|
|
set_item_val("ASB", "%d", id_get_asb_ver());
|
|
set_item_val("PSITE", "%d", id_get_product_line());
|
|
set_item_val("DDRID", "%s", dram_info);
|
|
}
|
|
|
|
static void sec_debug_set_extra_info_ktime(void)
|
|
{
|
|
u64 ts_nsec;
|
|
|
|
ts_nsec = local_clock();
|
|
do_div(ts_nsec, 1000000000);
|
|
|
|
set_item_val("KTIME", "%lu", (unsigned long)ts_nsec);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_fault(enum sec_debug_extra_fault_type type,
|
|
unsigned long addr, struct pt_regs *regs)
|
|
{
|
|
|
|
phys_addr_t paddr = 0;
|
|
|
|
if (regs) {
|
|
pr_crit("%s = %s / 0x%lx\n", __func__, ftype_items[type], addr);
|
|
|
|
set_item_val("FTYPE", "%s", ftype_items[type]);
|
|
set_item_val("FAULT", "0x%lx", addr);
|
|
set_item_val("PC", "%pS", regs->pc);
|
|
set_item_val("LR", "%pS",
|
|
compat_user_mode(regs) ?
|
|
regs->compat_lr : regs->regs[30]);
|
|
|
|
if (type == UNDEF_FAULT && addr >= kimage_voffset) {
|
|
paddr = virt_to_phys((void *)addr);
|
|
|
|
pr_crit("%s: 0x%x / 0x%x\n", __func__,
|
|
upper_32_bits(paddr), lower_32_bits(paddr));
|
|
// exynos_pmu_write(EXYNOS_PMU_INFORM8, lower_32_bits(paddr));
|
|
// exynos_pmu_write(EXYNOS_PMU_INFORM9, upper_32_bits(paddr));
|
|
}
|
|
}
|
|
}
|
|
|
|
void sec_debug_set_extra_info_bug(const char *file, unsigned int line)
|
|
{
|
|
set_item_val("BUG", "%s:%u", file, line);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_panic(char *str)
|
|
{
|
|
if (strstr(str, "\nPC is at"))
|
|
strcpy(strstr(str, "\nPC is at"), "");
|
|
|
|
set_item_val("PANIC", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_backtrace(struct pt_regs *regs)
|
|
{
|
|
char fbuf[MAX_ITEM_VAL_LEN];
|
|
char buf[64];
|
|
struct stackframe frame;
|
|
int offset = 0;
|
|
int sym_name_len;
|
|
char *v;
|
|
|
|
v = get_item_val("STACK");
|
|
if (!v) {
|
|
pr_crit("%s: no STACK in items\n", __func__);
|
|
|
|
return;
|
|
}
|
|
|
|
if (get_val_len(v)) {
|
|
pr_crit("%s: already %s in STACK\n", __func__, v);
|
|
|
|
return;
|
|
}
|
|
|
|
memset(fbuf, 0, MAX_ITEM_VAL_LEN);
|
|
|
|
pr_crit("sec_debug_store_backtrace\n");
|
|
|
|
if (regs) {
|
|
frame.fp = regs->regs[29];
|
|
//frame.sp = regs->sp;
|
|
frame.pc = regs->pc;
|
|
} else {
|
|
frame.fp = (unsigned long)__builtin_frame_address(0);
|
|
//frame.sp = current_stack_pointer;
|
|
frame.pc = (unsigned long)sec_debug_set_extra_info_backtrace;
|
|
}
|
|
|
|
while (1) {
|
|
unsigned long where = frame.pc;
|
|
int ret;
|
|
|
|
ret = unwind_frame(NULL, &frame);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
snprintf(buf, sizeof(buf), "%pf", (void *)where);
|
|
sym_name_len = strlen(buf);
|
|
|
|
if (offset + sym_name_len > MAX_ITEM_VAL_LEN)
|
|
break;
|
|
|
|
if (offset)
|
|
offset += sprintf(fbuf + offset, ":");
|
|
|
|
snprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "%s", buf);
|
|
offset += sym_name_len;
|
|
}
|
|
|
|
set_item_val("STACK", fbuf);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_backtrace_cpu(struct pt_regs *regs, int cpu)
|
|
{
|
|
char fbuf[MAX_ITEM_VAL_LEN];
|
|
char key[MAX_ITEM_KEY_LEN];
|
|
char buf[64];
|
|
struct stackframe frame;
|
|
int offset = 0;
|
|
int sym_name_len;
|
|
char *v;
|
|
|
|
snprintf(key, 5, "CPU%d", cpu);
|
|
|
|
v = get_item_val(key);
|
|
if (!v) {
|
|
pr_crit("%s: no %s in items\n", __func__, key);
|
|
|
|
return;
|
|
}
|
|
|
|
if (get_val_len(v)) {
|
|
pr_crit("%s: already %s in %s\n", __func__, v, key);
|
|
|
|
return;
|
|
}
|
|
|
|
memset(fbuf, 0, MAX_ITEM_VAL_LEN);
|
|
|
|
pr_crit("sec_debug_store_backtrace_cpu(%d)\n", cpu);
|
|
|
|
if (regs) {
|
|
frame.fp = regs->regs[29];
|
|
// frame.sp = regs->sp;
|
|
frame.pc = regs->pc;
|
|
} else {
|
|
frame.fp = (unsigned long)__builtin_frame_address(0);
|
|
// frame.sp = current_stack_pointer;
|
|
frame.pc = (unsigned long)sec_debug_set_extra_info_backtrace_cpu;
|
|
}
|
|
|
|
while (1) {
|
|
unsigned long where = frame.pc;
|
|
int ret;
|
|
|
|
ret = unwind_frame(NULL, &frame);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
snprintf(buf, sizeof(buf), "%pf", (void *)where);
|
|
sym_name_len = strlen(buf);
|
|
|
|
if (offset + sym_name_len > MAX_ITEM_VAL_LEN)
|
|
break;
|
|
|
|
if (offset)
|
|
offset += sprintf(fbuf + offset, ":");
|
|
|
|
snprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "%s", buf);
|
|
|
|
offset += sym_name_len;
|
|
}
|
|
|
|
set_item_val(key, fbuf);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_backtrace_task(struct task_struct *tsk)
|
|
{
|
|
char fbuf[MAX_ITEM_VAL_LEN];
|
|
char buf[64];
|
|
struct stackframe frame;
|
|
int offset = 0;
|
|
int sym_name_len;
|
|
char *v;
|
|
int cnt = 0;
|
|
|
|
if (!tsk) {
|
|
pr_crit("%s: no TASK, quit\n", __func__);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!try_get_task_stack(tsk)) {
|
|
pr_crit("%s: fail to get task stack, quit\n", __func__);
|
|
|
|
return;
|
|
}
|
|
|
|
v = get_item_val("STACK");
|
|
if (!v) {
|
|
pr_crit("%s: no STACK in items\n", __func__);
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (get_val_len(v)) {
|
|
pr_crit("%s: already %s in STACK\n", __func__, v);
|
|
|
|
goto out;
|
|
}
|
|
|
|
memset(fbuf, 0, MAX_ITEM_VAL_LEN);
|
|
|
|
pr_crit("sec_debug_store_backtrace_task\n");
|
|
|
|
frame.fp = thread_saved_fp(tsk);
|
|
frame.pc = thread_saved_pc(tsk);
|
|
|
|
while (1) {
|
|
unsigned long where = frame.pc;
|
|
int ret;
|
|
|
|
ret = unwind_frame(tsk, &frame);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
snprintf(buf, sizeof(buf), "%pf", (void *)where);
|
|
sym_name_len = strlen(buf);
|
|
|
|
if (offset + sym_name_len > MAX_ITEM_VAL_LEN)
|
|
break;
|
|
|
|
if (offset)
|
|
offset += sprintf(fbuf + offset, ":");
|
|
|
|
snprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "%s", buf);
|
|
offset += sym_name_len;
|
|
|
|
cnt++;
|
|
}
|
|
|
|
set_item_val("STACK", fbuf);
|
|
|
|
out:
|
|
put_task_stack(tsk);
|
|
}
|
|
|
|
|
|
void sec_debug_set_extra_info_sysmmu(char *str)
|
|
{
|
|
set_item_val("SMU", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_busmon(char *str)
|
|
{
|
|
set_item_val("BUS", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_dpm_timeout(char *devname)
|
|
{
|
|
set_item_val("DPM", "%s", devname);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_smpl(unsigned long count)
|
|
{
|
|
clear_item_val("SMP");
|
|
set_item_val("SMP", "%lu", count);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_ufs_error(char *str)
|
|
{
|
|
set_item_val("ETC", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_zswap(char *str)
|
|
{
|
|
set_item_val("ETC", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_aud(char *str)
|
|
{
|
|
set_item_val("AUD", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_epd(char *str)
|
|
{
|
|
set_item_val("EPD", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_mfc_error(char *str)
|
|
{
|
|
clear_item_val("STACK");
|
|
set_item_val("STACK", "MFC ERROR");
|
|
set_item_val("MFC", "%s", str);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_esr(unsigned int esr)
|
|
{
|
|
set_item_val("ESR", "%s (0x%08x)",
|
|
esr_get_class_string(esr), esr);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_merr(char *merr)
|
|
{
|
|
set_item_val("MER", "%s", merr);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_hint(u64 hint)
|
|
{
|
|
if (hint)
|
|
set_item_val("HINT", "%llx", hint);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_decon(unsigned int err)
|
|
{
|
|
set_item_val("DCN", "%08x", err);
|
|
}
|
|
|
|
void sec_debug_set_extra_info_batt(int cap, int volt, int temp, int curr)
|
|
{
|
|
clear_item_val("BAT");
|
|
set_item_val("BAT", "%03d/%04d/%04d/%06d", cap, volt, temp, curr);
|
|
}
|
|
|
|
void sec_debug_finish_extra_info(void)
|
|
{
|
|
sec_debug_set_extra_info_ktime();
|
|
}
|
|
|
|
#define MAX_UNFZ_VAL_LEN (240)
|
|
|
|
void sec_debug_set_extra_info_unfz(char *comm)
|
|
{
|
|
void *p;
|
|
char *v;
|
|
char tmp[MAX_UNFZ_VAL_LEN] = {0, };
|
|
int max = MAX_UNFZ_VAL_LEN;
|
|
int len_prev, len_remain, len_this;
|
|
|
|
p = get_item("UNFZ");
|
|
if (!p) {
|
|
pr_crit("%s: fail to find %s\n", __func__, comm);
|
|
|
|
return;
|
|
}
|
|
|
|
max = get_max_len(p);
|
|
if (!max) {
|
|
pr_crit("%s: fail to get max len %s\n", __func__, comm);
|
|
|
|
return;
|
|
}
|
|
|
|
v = get_item_val(p);
|
|
|
|
/* keep previous value */
|
|
len_prev = get_val_len(v);
|
|
if ((!len_prev) || (MAX_UNFZ_VAL_LEN <= len_prev))
|
|
len_prev = MAX_UNFZ_VAL_LEN - 1;
|
|
|
|
snprintf(tmp, len_prev + 1, "%s", v);
|
|
|
|
/* calculate the remained size */
|
|
len_remain = max;
|
|
|
|
/* get_item_val returned address without key */
|
|
len_remain -= MAX_ITEM_KEY_LEN;
|
|
|
|
/* need comma */
|
|
len_this = strlen(comm) + 1;
|
|
len_remain -= len_this;
|
|
|
|
/* put last key at the first of ODR */
|
|
/* +1 to add NULL (by snprintf) */
|
|
snprintf(v, len_this + 1, "%s,", comm);
|
|
|
|
/* -1 to remove NULL between KEYS */
|
|
/* +1 to add NULL (by snprintf) */
|
|
snprintf((char *)(v + len_this), len_remain + 1, tmp);
|
|
}
|
|
|
|
/*********** TEST V3 **************************************/
|
|
static void test_v3(void *seqm)
|
|
{
|
|
struct seq_file *m = (struct seq_file *)seqm;
|
|
|
|
seq_printf(m, " -- SEC DEBUG SHARED INFO V3 (SHARED BUFFER) -- \n");
|
|
dump_slots(SLOT_32, NR_MAIN_SLOT, m);
|
|
|
|
seq_printf(m, " -- SEC DEBUG SHARED INFO V3 (SHARED BUFFER BK) -- \n");
|
|
dump_slots(SLOT_BK_32, NR_SLOT, m);
|
|
}
|
|
/*********** TEST V3 **************************************/
|
|
static int set_debug_reset_rwc_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
char *rstcnt;
|
|
|
|
rstcnt = get_bk_item_val("RSTCNT");
|
|
if (!rstcnt)
|
|
seq_printf(m, "%d", secdbg_rere_get_rstcnt_from_cmdline());
|
|
else
|
|
seq_printf(m, "%s", rstcnt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sec_debug_reset_rwc_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, set_debug_reset_rwc_proc_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations sec_debug_reset_rwc_proc_fops = {
|
|
.open = sec_debug_reset_rwc_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int set_debug_reset_extra_info_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
char buf[SZ_1K];
|
|
|
|
if (0)
|
|
test_v3(m);
|
|
|
|
sec_debug_store_extra_info_A(buf);
|
|
seq_printf(m, buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sec_debug_reset_extra_info_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, set_debug_reset_extra_info_proc_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations sec_debug_reset_extra_info_proc_fops = {
|
|
.open = sec_debug_reset_extra_info_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
void simulate_extra_info_force_error(unsigned int magic)
|
|
{
|
|
if (!exin_ready) {
|
|
pr_crit("%s: EXIN is not ready\n", __func__);
|
|
return;
|
|
}
|
|
|
|
sh_buf->magic[0] = magic;
|
|
}
|
|
|
|
static int __init sec_debug_extra_info_init(void)
|
|
{
|
|
struct proc_dir_entry *entry;
|
|
|
|
sh_buf = sec_debug_get_debug_base(SDN_MAP_EXTRA_INFO);
|
|
if (sh_buf) {
|
|
sec_debug_extra_info_buffer_init();
|
|
|
|
sh_buf->magic[0] = SEC_DEBUG_SHARED_MAGIC0;
|
|
sh_buf->magic[1] = SEC_DEBUG_SHARED_MAGIC1;
|
|
sh_buf->magic[2] = SEC_DEBUG_SHARED_MAGIC2;
|
|
sh_buf->magic[3] = SEC_DEBUG_SHARED_MAGIC3;
|
|
}
|
|
|
|
exin_ready = true;
|
|
|
|
entry = proc_create("reset_reason_extra_info",
|
|
0644, NULL, &sec_debug_reset_extra_info_proc_fops);
|
|
if (!entry)
|
|
return -ENOMEM;
|
|
|
|
proc_set_size(entry, SZ_1K);
|
|
|
|
entry = proc_create("reset_rwc", S_IWUGO, NULL,
|
|
&sec_debug_reset_rwc_proc_fops);
|
|
|
|
if (!entry)
|
|
return -ENOMEM;
|
|
|
|
sec_debug_set_extra_info_id();
|
|
|
|
return 0;
|
|
}
|
|
late_initcall(sec_debug_extra_info_init);
|