// SPDX-License-Identifier: GPL-2.0-only /* * sec_debug_extra_info.c * * Copyright (c) 2019 Samsung Electronics Co., Ltd * http://www.samsung.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sec_debug_internal.h" #include "sec_debug_extra_info_keys.c" #define EXTRA_VERSION "VE12" #define MAX_EXTRA_INFO_HDR_LEN 6 #define MAX_CALL_ENTRY 128 #define ETR_A_PROC_SIZE SZ_2K static bool exin_ready; static struct sec_debug_shared_buffer *sh_buf; static void *slot_end_addr; static long __read_mostly rr_pwrsrc; module_param(rr_pwrsrc, long, 0440); /*****************************************************************/ /* UNIT FUNCTIONS */ /*****************************************************************/ static int is_exist_key(char (*keys)[MAX_ITEM_KEY_LEN], const char *key, int size) { int mcnt = 0; int i; for (i = 0; i < size; i++) if (!strcmp(key, keys[i])) mcnt++; return mcnt; } static bool is_exist_abcfmt(const char *key) { int match_cnt = 0; match_cnt += is_exist_key(akeys, key, ARRAY_SIZE(akeys)); match_cnt += is_exist_key(bkeys, key, ARRAY_SIZE(bkeys)); match_cnt += is_exist_key(ckeys, key, ARRAY_SIZE(ckeys)); match_cnt += is_exist_key(fkeys, key, ARRAY_SIZE(fkeys)); match_cnt += is_exist_key(mkeys, key, ARRAY_SIZE(mkeys)); match_cnt += is_exist_key(tkeys, key, ARRAY_SIZE(tkeys)); if (match_cnt == 0 || (match_cnt > 1 && strcmp(key, "ID") && strcmp(key, "RR"))) { pr_crit("%s: %s is exist in the abcfmt keys %d times\n", __func__, key, match_cnt); #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) panic("%s: key match error", __func__); #endif return false; } return true; } static bool sec_debug_extra_info_check_key(void) { int i; for (i = 0; i < ARRAY_SIZE(key32); i++) if (!is_exist_abcfmt(key32[i])) return false; for (i = 0; i < ARRAY_SIZE(key64); i++) if (!is_exist_abcfmt(key64[i])) return false; for (i = 0; i < ARRAY_SIZE(key256); i++) if (!is_exist_abcfmt(key256[i])) return false; for (i = 0; i < ARRAY_SIZE(key1024); i++) if (!is_exist_abcfmt(key1024[i])) return false; return true; } 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 *)secdbg_base_get_ncva(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, unsigned 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); } EXPORT_SYMBOL(get_bk_item_val); 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); } } EXPORT_SYMBOL(get_bk_item_val_as_string); static int is_ocp; static int is_key_in_blocklist(const char *key) { char blkey[][MAX_ITEM_KEY_LEN] = { "KTIME", "BAT", "ODR", "DDRID", "PSITE", "ASB", "ASV", "IDS", }; 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; if (!strncmp(key, "OCP", keylen)) { if (is_ocp) return 1; is_ocp = 1; } return 0; } static int is_key_in_once_list(const char *s, const char *key) { char blkey[][MAX_ITEM_KEY_LEN] = { "SPCNT", "HLFREQ", }; int nr_blkey, val_len, i; int ret = 0; val_len = get_val_len(s); nr_blkey = ARRAY_SIZE(blkey); for (i = 0; i < nr_blkey; i++) { if (!strncmp(key, blkey[i], strlen(key))) ret++; } if (!ret) return 0; for (i = 0; i < nr_blkey; i++) { if (strnstr(s, blkey[i], val_len)) 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_blocklist(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); if (is_key_in_once_list(v, key)) goto unlock_keyorder; /* keep previous value */ len_prev = get_val_len(v); if ((!len_prev) || (len_prev >= MAX_ITEM_VAL_LEN)) 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, "%s", 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 - MAX_ITEM_KEY_LEN, fmt, args); va_end(args); set_key_order(key); } } static void __init 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, "%s", 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; } 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); } #ifdef DEBUG static void __init 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); } } } #else static void __init dump_all_keys(void) {} #endif static void __init 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 = secdbg_base_get_ncva(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, "%s", keys[i]); base += size; addr = secdbg_base_get_ncva(base); } sh_buf->sec_debug_sbidx[type].cnt = i; } static void __init sec_debug_extra_info_key_init(void) { int nr_keys; nr_keys = ARRAY_SIZE(key32); init_shared_buffer(SLOT_32, nr_keys, (void *)key32); nr_keys = ARRAY_SIZE(key64); init_shared_buffer(SLOT_64, nr_keys, (void *)key64); nr_keys = ARRAY_SIZE(key256); init_shared_buffer(SLOT_256, nr_keys, (void *)key256); nr_keys = ARRAY_SIZE(key1024); init_shared_buffer(SLOT_1024, nr_keys, (void *)key1024); } static void __init 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 = secdbg_base_get_ncva(slot_base + total_size); pr_info("%s: dst: %llx src: %llx (%x)\n", __func__, (u64)backup_base, (u64)secdbg_base_get_ncva(slot_base), total_size); memcpy(backup_base, secdbg_base_get_ncva(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; pr_debug("%s: SLOT %2d: paddr: 0x%x\n", __func__, i, sh_buf->sec_debug_sbidx[i].paddr); pr_debug("%s: SLOT %2d: size: %d\n", __func__, i, sh_buf->sec_debug_sbidx[i].size); pr_debug("%s: SLOT %2d: nr: %d\n", __func__, i, sh_buf->sec_debug_sbidx[i].nr); pr_debug("%s: SLOT %2d: cnt: %d\n", __func__, i, sh_buf->sec_debug_sbidx[i].cnt); } } static void __init sec_debug_extra_info_dump_sb_index(void) { int i; for (i = 0; i < NR_SLOT; i++) { pr_debug("%s: SLOT%02d: paddr: %x\n", __func__, i, sh_buf->sec_debug_sbidx[i].paddr); pr_debug("%s: SLOT%02d: cnt: %d\n", __func__, i, sh_buf->sec_debug_sbidx[i].cnt); pr_debug("%s: SLOT%02d: blmark: %lx\n", __func__, i, sh_buf->sec_debug_sbidx[i].blmark); pr_debug("\n"); } } static void __init 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 __init 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_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; int last_offset = 0, offset = 0; memset(ptr, 0, ETR_A_PROC_SIZE); 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 + offset + strlen(p) + get_val_len(v) + MAX_EXTRA_INFO_HDR_LEN; max_len = (unsigned long)start_addr + ETR_A_PROC_SIZE; if (len > max_len) { *(ptr + last_offset - 1) = '\0'; pr_crit("%s: length overfolw: fail to write - %s:%s\n", __func__, (char *)p, v); break; } offset += scnprintf(ptr + offset, ETR_A_PROC_SIZE - offset, "\"%s\":\"%s\"", (char *)p, v); if ((i + 1) != nr_keys) offset += scnprintf(ptr + offset, ETR_A_PROC_SIZE - offset, ","); last_offset = offset; } pr_info("%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 secdbg_exin_get_extra_info_A(char *ptr) { int nr_keys; nr_keys = ARRAY_SIZE(akeys); sec_debug_store_extra_info(akeys, nr_keys, ptr); } EXPORT_SYMBOL(secdbg_exin_get_extra_info_A); void secdbg_exin_get_extra_info_B(char *ptr) { int nr_keys; nr_keys = ARRAY_SIZE(bkeys); sec_debug_store_extra_info(bkeys, nr_keys, ptr); } EXPORT_SYMBOL(secdbg_exin_get_extra_info_B); void secdbg_exin_get_extra_info_C(char *ptr) { int nr_keys; nr_keys = ARRAY_SIZE(ckeys); sec_debug_store_extra_info(ckeys, nr_keys, ptr); } EXPORT_SYMBOL(secdbg_exin_get_extra_info_C); void secdbg_exin_get_extra_info_M(char *ptr) { int nr_keys; nr_keys = ARRAY_SIZE(mkeys); sec_debug_store_extra_info(mkeys, nr_keys, ptr); } EXPORT_SYMBOL(secdbg_exin_get_extra_info_M); void secdbg_exin_get_extra_info_F(char *ptr) { int nr_keys; nr_keys = ARRAY_SIZE(fkeys); sec_debug_store_extra_info(fkeys, nr_keys, ptr); } EXPORT_SYMBOL(secdbg_exin_get_extra_info_F); void secdbg_exin_get_extra_info_T(char *ptr) { int nr_keys; nr_keys = ARRAY_SIZE(tkeys); sec_debug_store_extra_info(tkeys, nr_keys, ptr); } EXPORT_SYMBOL(secdbg_exin_get_extra_info_T); static void __init sec_debug_extra_info_buffer_init(void) { unsigned long tmp_addr; struct sec_debug_sb_index tmp_idx; bool flag_valid = false; unsigned long item_size; flag_valid = sec_debug_extra_info_check_magic(); sec_debug_extra_info_dump_sb_index(); tmp_idx.cnt = 0; tmp_idx.blmark = 0; /* SLOT_32, 32B, 128 items */ tmp_addr = secdbg_base_get_buf_base(SDN_MAP_EXTRA_INFO); tmp_idx.paddr = (unsigned int)tmp_addr; tmp_idx.size = 32; tmp_idx.nr = 128; sec_debug_init_extra_info_sbidx(SLOT_32, tmp_idx, flag_valid); /* SLOT_64, 64B, 128 items */ tmp_addr += tmp_idx.size * tmp_idx.nr; tmp_idx.paddr = (unsigned int)tmp_addr; tmp_idx.size = 64; tmp_idx.nr = 128; sec_debug_init_extra_info_sbidx(SLOT_64, tmp_idx, flag_valid); /* SLOT_256, 256B, 64 items */ tmp_addr += tmp_idx.size * tmp_idx.nr; tmp_idx.paddr = (unsigned int)tmp_addr; tmp_idx.size = 256; tmp_idx.nr = 64; sec_debug_init_extra_info_sbidx(SLOT_256, tmp_idx, flag_valid); /* SLOT_1024, 1024B, 32 items */ tmp_addr += tmp_idx.size * tmp_idx.nr; tmp_idx.paddr = (unsigned int)tmp_addr; tmp_idx.size = 1024; tmp_idx.nr = 32; sec_debug_init_extra_info_sbidx(SLOT_1024, tmp_idx, flag_valid); /*items size = 1024B item start addr + 1024B item size - exin start addr*/ item_size = tmp_addr + (tmp_idx.size * tmp_idx.nr) - secdbg_base_get_buf_base(SDN_MAP_EXTRA_INFO); if (secdbg_base_get_buf_size(SDN_MAP_EXTRA_INFO) / 2 < item_size) { pr_crit("%s: item size overflow: rsvd: %lu, item size: %lu\n", __func__, secdbg_base_get_buf_size(SDN_MAP_EXTRA_INFO) / 2, item_size); #if !defined(CONFIG_SAMSUNG_PRODUCT_SHIP) panic("%s: size error", __func__); #endif } /* backup shared buffer contents */ sec_debug_extra_info_copy_shared_buffer(flag_valid); sec_debug_extra_info_key_init(); dump_all_keys(); slot_end_addr = (void *)secdbg_base_get_ncva(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))); } static void __init sec_debug_set_extra_info_id(void) { struct timespec64 ts; ktime_get_real_ts64(&ts); set_bk_item_val("ID", SLOT_BK_32, "%09lu%s", ts.tv_nsec, EXTRA_VERSION); } static void sec_debug_set_reset_reason_using_module_param(void) { char rr_c[RR_C] = {'S', 'W', 'D', 'K', 'M', 'P', 'R', 'B', 'N', 'T', 'C'}; int reset_reason = (rr_pwrsrc & 0xff00000000) >> 32; int offsrc = (rr_pwrsrc & 0x00ffff0000) >> 16; int onsrc = rr_pwrsrc & 0x000000ffff; if (!get_bk_item("RR") && reset_reason > 0) set_bk_item_val("RR", SLOT_BK_32, "%cP", rr_c[reset_reason-1]); if (!get_bk_item("PWR")) set_bk_item_val("PWR", SLOT_BK_64, " %02X %02X", (onsrc & 0xff00) >> 8, (onsrc & 0x00ff)); if (!get_bk_item("PWROFF")) set_bk_item_val("PWROFF", SLOT_BK_64, " %02X %02X", (offsrc & 0xff00) >> 8, (offsrc & 0x00ff)); } static void secdbg_exin_set_ktime(void) { u64 ts_nsec; ts_nsec = local_clock(); do_div(ts_nsec, 1000000000); set_item_val("KTIME", "%lu", (unsigned long)ts_nsec); } void secdbg_exin_set_hwid(int asb_ver, int psite, const char *dramstr) { set_item_val("ASB", "%d", asb_ver); set_item_val("PSITE", "%d", psite); if (dramstr) set_item_val("DDRID", "%s", dramstr); } EXPORT_SYMBOL(secdbg_exin_set_hwid); void secdbg_exin_set_asv(int bg, int mg, int lg, int g3dg, int mifg) { set_item_val("ASV", "%d-%d-%d-%d-%d", bg, mg, lg, g3dg, mifg); } EXPORT_SYMBOL(secdbg_exin_set_asv); void secdbg_exin_set_ids(int bids, int mids, int lids, int gids) { set_item_val("IDS", "%d-%d-%d-%d", bids, mids, lids, gids); } EXPORT_SYMBOL(secdbg_exin_set_ids); void secdbg_exin_set_panic(const char *str) { if (strstr(str, "\nPC is at")) strcpy(strstr(str, "\nPC is at"), ""); set_item_val("PANIC", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_panic); void secdbg_exin_set_sysmmu(const char *str) { set_item_val("SMU", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_sysmmu); void secdbg_exin_set_busmon(const char *str) { set_item_val("BUS", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_busmon); void secdbg_exin_set_smpl(unsigned long count) { clear_item_val("SPCNT"); set_item_val("SPCNT", "%lu", count); } EXPORT_SYMBOL(secdbg_exin_set_smpl); void secdbg_exin_set_decon(const char *str) { set_item_val("DCN", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_decon); void secdbg_exin_set_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); } EXPORT_SYMBOL(secdbg_exin_set_batt); void secdbg_exin_set_finish(void) { secdbg_exin_set_ktime(); } EXPORT_SYMBOL(secdbg_exin_set_finish); void secdbg_exin_set_mfc_error(const char *str) { clear_item_val("STACK"); set_item_val("STACK", "MFC ERROR"); set_item_val("MFC", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_mfc_error); void secdbg_exin_set_aud(const char *str) { clear_item_val("AUD"); set_item_val("AUD", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_aud); void secdbg_exin_set_epd(const char *str) { set_item_val("EPD", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_epd); void secdbg_exin_set_ufs(const char *str) { clear_item_val("UFS"); set_item_val("UFS", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_ufs); /* OCP total limitation */ #define MAX_OCP_CNT (0xFF) /* S2MPS23 */ #define S2MPS23_BUCK_CNT (9) /* S2MPS24 */ /* no irq in sub-pmic */ static int __add_pmic_irq_info(char *p, int max_buf_len, int *cnt, int nr) { int i, tmp = 0, offset = 0; for (i = 0; i < nr; i++) { tmp = cnt[i]; if (tmp > MAX_OCP_CNT) tmp = MAX_OCP_CNT; offset += scnprintf(p + offset, max_buf_len - offset, "%02x,", tmp); } /* to remove , in the end */ if (nr != 0) p--; offset += scnprintf(p + offset, max_buf_len - offset, "/"); return offset; } #define MOCP_SOCP_SIZE SZ_256 void secdbg_exin_set_main_ocp(void *main_ocp_cnt, void *main_oi_cnt, int buck_cnt) { char str_ocp[MOCP_SOCP_SIZE] = {0, }; int offset = 0; offset += __add_pmic_irq_info(str_ocp, MOCP_SOCP_SIZE, main_ocp_cnt, buck_cnt); offset += __add_pmic_irq_info(str_ocp, MOCP_SOCP_SIZE - offset, main_oi_cnt, buck_cnt); if (offset >= MOCP_SOCP_SIZE) pr_crit("%s: length overflow\n", __func__); clear_item_val("MOCP"); set_item_val("MOCP", "%s", str_ocp); } EXPORT_SYMBOL(secdbg_exin_set_main_ocp); void secdbg_exin_set_sub_ocp(void) { #if 0 /* TODO: no irq in sub pmic (s2mps26) */ char *p, str_ocp[SZ_64] = {0, }; p = str_ocp; p = __add_pmic_irq_info(p, s2mps24_buck_ocp_cnt, S2MPS24_BUCK_CNT); p = __add_pmic_irq_info(p, s2mps24_buck_oi_cnt, S2MPS24_BUCK_OI_MAX); clear_item_val("SOCP"); set_item_val("SOCP", "%s", str_ocp); #endif } EXPORT_SYMBOL(secdbg_exin_set_sub_ocp); void secdbg_exin_set_hardlockup_type(const char *fmt, ...) { va_list args; char tmp[MAX_ITEM_VAL_LEN] = {0, }; va_start(args, fmt); vsnprintf(tmp, MAX_ITEM_VAL_LEN, fmt, args); va_end(args); set_item_val("HLTYPE", "%s", tmp); } EXPORT_SYMBOL(secdbg_exin_set_hardlockup_type); void secdbg_exin_set_hardlockup_data(const char *str) { set_item_val("HLDATA", "%s", str); } EXPORT_SYMBOL(secdbg_exin_set_hardlockup_data); void secdbg_exin_set_hardlockup_freq(const char *domain, struct freq_log *freq) { void *p; char *v; char tmp[MAX_ITEM_VAL_LEN] = {0, }; char freq_string[MAX_ITEM_VAL_LEN] = {0, }; int offset = 0; p = get_item("HLFREQ"); if (!p) { pr_crit("%s: fail to find\n", __func__); return; } if (!get_max_len(p)) { pr_crit("%s: fail to get max len\n", __func__); return; } v = get_item_val(p); offset = snprintf(freq_string, MAX_ITEM_VAL_LEN, "%s:%d>%d%c ", domain, freq->old_freq / 1000, freq->target_freq / 1000, (freq->en == 1) ? '+' : '-'); snprintf(tmp, MAX_ITEM_VAL_LEN, "%s %s", v, freq_string); clear_item_val("HLFREQ"); set_item_val("HLFREQ", "%s", tmp); } EXPORT_SYMBOL(secdbg_exin_set_hardlockup_freq); void secdbg_exin_set_hardlockup_ehld(unsigned int hl_info, unsigned int cpu) { int i; int offset = 0; char tmp[MAX_ITEM_VAL_LEN] = {0, }; char tmp_per_cpu[MAX_ITEM_VAL_LEN] = {0, }; for (i = 0; i < MAX_ETYPES; i++) { if ((hl_info & (1 << i)) != 0) offset += scnprintf(tmp_per_cpu + offset, MAX_ITEM_VAL_LEN - offset, "1"); else offset += scnprintf(tmp_per_cpu + offset, MAX_ITEM_VAL_LEN - offset, "0"); } snprintf(tmp, MAX_ITEM_VAL_LEN, "%s_%s", get_item_val("HLEHLD"), tmp_per_cpu); clear_item_val("HLEHLD"); set_item_val("HLEHLD", "%s", tmp); } EXPORT_SYMBOL(secdbg_exin_set_hardlockup_ehld); static int set_debug_reset_extra_info_proc_show(struct seq_file *m, void *v) { char buf[ETR_A_PROC_SIZE]; secdbg_exin_get_extra_info_A(buf); seq_printf(m, "%s", 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 proc_ops sec_debug_reset_extra_info_proc_fops = { .proc_open = sec_debug_reset_extra_info_proc_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_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; } EXPORT_SYMBOL(simulate_extra_info_force_error); static void secdbg_exin_set_fault(enum secdbg_exin_fault_type type, unsigned long addr, struct pt_regs *regs) { phys_addr_t paddr = 0; u64 lr; if (!regs) return; if (compat_user_mode(regs)) lr = regs->compat_lr; else lr = regs->regs[30]; set_item_val("FAULT", "0x%lx", addr); set_item_val("PC", "%pS", regs->pc); set_item_val("LR", "%pS", user_mode(regs) ? lr : ptrauth_strip_insn_pac(lr)); 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)); } } static void secdbg_exin_set_regs(struct pt_regs *regs) { char fbuf[MAX_ITEM_VAL_LEN]; int offset = 0, i; char *v; v = get_item_val("REGS"); if (!v) { pr_crit("%s: no REGS in items\n", __func__); return; } if (get_val_len(v)) { pr_crit("%s: already %s in REGS\n", __func__, v); return; } memset(fbuf, 0, MAX_ITEM_VAL_LEN); pr_crit("%s: set regs\n", __func__); offset += scnprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "pc:%llx/", regs->pc); offset += scnprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "sp:%llx/", regs->sp); offset += scnprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "pstate:%llx/", regs->pstate); for (i = 0; i < 31; i++) offset += scnprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "x%d:%llx/", i, regs->regs[i]); set_item_val("REGS", fbuf); } static void secdbg_exin_set_stack(long *entries, int nr_entries) { char fbuf[MAX_ITEM_VAL_LEN]; char *v; unsigned int i; int offset = 0; 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); for (i = 0; i < nr_entries; i++) offset += scnprintf(fbuf + offset, MAX_ITEM_VAL_LEN - offset, "%ps:", (void *)entries[i]); set_item_val("STACK", fbuf); } static void secdbg_exin_set_backtrace(struct pt_regs *regs) { unsigned long entry[MAX_CALL_ENTRY]; unsigned int nr_entries = 0; if (!regs) { nr_entries = stack_trace_save(entry, ARRAY_SIZE(entry), 0); } else { nr_entries = stack_trace_save_regs(regs, entry, ARRAY_SIZE(entry), 1); } if (!nr_entries) { if (!regs) pr_err("no trace for current\n"); else pr_err("no trace for [pc :%llx]\n", regs->pc); return; } if (regs) secdbg_exin_set_regs(regs); secdbg_exin_set_stack(entry, nr_entries); } static void secdbg_exin_set_backtrace_task(struct task_struct *tsk) { unsigned long entry[MAX_CALL_ENTRY]; unsigned int nr_entries = 0; if (!tsk) { nr_entries = stack_trace_save(entry, ARRAY_SIZE(entry), 0); } else { /* skipnr : skipping __switch_to */ nr_entries = stack_trace_save_tsk(tsk, entry, ARRAY_SIZE(entry), 1); } if (!nr_entries) { if (!tsk) pr_err("no trace for current\n"); else pr_err("no trace for [%s :%d]\n", tsk->comm, tsk->pid); return; } secdbg_exin_set_stack(entry, nr_entries); } static const char *esr_class_str[] = { [0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC", [ESR_ELx_EC_UNKNOWN] = "Unknown/Uncategorized", [ESR_ELx_EC_WFx] = "WFI/WFE", [ESR_ELx_EC_CP15_32] = "CP15 MCR/MRC", [ESR_ELx_EC_CP15_64] = "CP15 MCRR/MRRC", [ESR_ELx_EC_CP14_MR] = "CP14 MCR/MRC", [ESR_ELx_EC_CP14_LS] = "CP14 LDC/STC", [ESR_ELx_EC_FP_ASIMD] = "ASIMD", [ESR_ELx_EC_CP10_ID] = "CP10 MRC/VMRS", [ESR_ELx_EC_PAC] = "PAC", [ESR_ELx_EC_CP14_64] = "CP14 MCRR/MRRC", [ESR_ELx_EC_BTI] = "BTI", [ESR_ELx_EC_ILL] = "PSTATE.IL", [ESR_ELx_EC_SVC32] = "SVC (AArch32)", [ESR_ELx_EC_HVC32] = "HVC (AArch32)", [ESR_ELx_EC_SMC32] = "SMC (AArch32)", [ESR_ELx_EC_SVC64] = "SVC (AArch64)", [ESR_ELx_EC_HVC64] = "HVC (AArch64)", [ESR_ELx_EC_SMC64] = "SMC (AArch64)", [ESR_ELx_EC_SYS64] = "MSR/MRS (AArch64)", [ESR_ELx_EC_SVE] = "SVE", [ESR_ELx_EC_ERET] = "ERET/ERETAA/ERETAB", [ESR_ELx_EC_FPAC] = "FPAC", [ESR_ELx_EC_IMP_DEF] = "EL3 IMP DEF", [ESR_ELx_EC_IABT_LOW] = "IABT (lower EL)", [ESR_ELx_EC_IABT_CUR] = "IABT (current EL)", [ESR_ELx_EC_PC_ALIGN] = "PC Alignment", [ESR_ELx_EC_DABT_LOW] = "DABT (lower EL)", [ESR_ELx_EC_DABT_CUR] = "DABT (current EL)", [ESR_ELx_EC_SP_ALIGN] = "SP Alignment", [ESR_ELx_EC_FP_EXC32] = "FP (AArch32)", [ESR_ELx_EC_FP_EXC64] = "FP (AArch64)", [ESR_ELx_EC_SERROR] = "SError", [ESR_ELx_EC_BREAKPT_LOW] = "Breakpoint (lower EL)", [ESR_ELx_EC_BREAKPT_CUR] = "Breakpoint (current EL)", [ESR_ELx_EC_SOFTSTP_LOW] = "Software Step (lower EL)", [ESR_ELx_EC_SOFTSTP_CUR] = "Software Step (current EL)", [ESR_ELx_EC_WATCHPT_LOW] = "Watchpoint (lower EL)", [ESR_ELx_EC_WATCHPT_CUR] = "Watchpoint (current EL)", [ESR_ELx_EC_BKPT32] = "BKPT (AArch32)", [ESR_ELx_EC_VECTOR32] = "Vector catch (AArch32)", [ESR_ELx_EC_BRK64] = "BRK (AArch64)", }; static void secdbg_exin_set_esr(unsigned int esr) { set_item_val("ESR", "%s (0x%08x)", esr_class_str[ESR_ELx_EC(esr)], esr); } #define MAX_UNFZ_VAL_LEN (240) void secdbg_exin_set_unfz(const char *comm, int pid) { void *p; char *v; char tmp[MAX_UNFZ_VAL_LEN] = {0, }; int max = MAX_UNFZ_VAL_LEN; int len_prev, len_remain, len_this = 0; 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) || (len_prev >= MAX_UNFZ_VAL_LEN)) 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; /* put last key at the first of ODR */ /* +1 to add NULL (by snprintf) */ if (pid < 0) len_this = scnprintf((char *)(v + len_this), len_remain - len_this, "%s/", comm); else len_this = scnprintf((char *)(v + len_this), len_remain - len_this, "%s:%d/", comm, pid); /* -1 to remove NULL between KEYS */ /* +1 to add NULL (by snprintf) */ scnprintf((char *)(v + len_this), len_remain - len_this, "%s", tmp); } EXPORT_SYMBOL(secdbg_exin_set_unfz); char *secdbg_exin_get_unfz(void) { void *p; int max = MAX_UNFZ_VAL_LEN; p = get_item("UNFZ"); if (!p) { pr_crit("%s: fail to find\n", __func__); return NULL; } max = get_max_len(p); if (!max) { pr_crit("%s: fail to get max len\n", __func__); return NULL; } return get_item_val(p); } EXPORT_SYMBOL(secdbg_exin_get_unfz); static int secdbg_exin_panic_handler(struct notifier_block *nb, unsigned long l, void *buf) { secdbg_exin_set_panic(buf); secdbg_exin_set_backtrace(NULL); secdbg_exin_set_finish(); return NOTIFY_DONE; } static struct notifier_block nb_panic_block = { .notifier_call = secdbg_exin_panic_handler, }; static void secdbg_exin_set_bug(const char *file, unsigned int line) { set_item_val("BUG", "%s:%u", file, line); } static void android_vh_watchdog_timer_softlockup(void *data, int duration, struct pt_regs *regs, bool is_panic) { if (is_panic) { if (regs) { secdbg_exin_set_fault(WATCHDOG_FAULT, (unsigned long)regs->pc, regs); secdbg_exin_set_backtrace(regs); } panic("softlockup: hung tasks"); } } static void android_rvh_do_undefinstr(void *data, struct pt_regs *regs, bool user) { if (!user_mode(regs)) secdbg_exin_set_fault(UNDEF_FAULT, (unsigned long)regs->pc, regs); } static void android_rvh_bad_mode(void *data, struct pt_regs *regs, unsigned int esr, int reason) { if (!user_mode(regs)) { if (reason == 0xFA017) { secdbg_exin_set_fault(PTRAUTH_FAULT, (unsigned long)regs->pc, regs); secdbg_exin_set_esr(esr); } else { secdbg_exin_set_fault(BAD_MODE_FAULT, (unsigned long)regs->pc, regs); secdbg_exin_set_esr(esr); } } } static void android_rvh_arm64_serror_panic(void *data, struct pt_regs *regs, unsigned int esr) { if (regs && !user_mode(regs)) { secdbg_exin_set_fault(SERROR_FAULT, (unsigned long)regs->pc, regs); secdbg_exin_set_esr(esr); } } static void android_rvh_die_kernel_fault(void *data, struct pt_regs *regs, unsigned int esr, unsigned long addr, const char *msg) { secdbg_exin_set_fault(KERNEL_FAULT, addr, regs); secdbg_exin_set_esr(esr); } static void android_rvh_do_sea(void *data, struct pt_regs *regs, unsigned int esr, unsigned long addr, const char *msg) { if (!user_mode(regs)) { secdbg_exin_set_fault(SEABORT_FAULT, addr, regs); secdbg_exin_set_esr(esr); } } static void android_rvh_do_mem_abort(void *data, struct pt_regs *regs, unsigned int esr, unsigned long addr, const char *msg) { secdbg_exin_set_fault(MEM_ABORT_FAULT, addr, regs); secdbg_exin_set_esr(esr); } static void android_rvh_do_sp_pc_abort(void *data, struct pt_regs *regs, unsigned int esr, unsigned long addr, bool user) { if (!user_mode(regs)) { secdbg_exin_set_fault(SP_PC_ABORT_FAULT, addr, regs); secdbg_exin_set_esr(esr); } } static bool is_bug_reported; static void android_rvh_report_bug(void *data, const char *file, unsigned int line, unsigned long bugaddr) { is_bug_reported = true; if (file) secdbg_exin_set_bug(file, line); } static void android_vh_try_to_freeze_todo_unfrozen(void *data, struct task_struct *p) { secdbg_exin_set_backtrace_task(p); secdbg_exin_set_unfz(p->comm, p->pid); } static void android_vh_try_to_freeze_todo(void *data, unsigned int todo, unsigned int elapsed_msecs, bool wq_busy) { const char *sys_state[SYSTEM_SUSPEND + 1] = { "BOOTING", "SCHEDULING", "RUNNING", "HALT", "POWER_OFF", "RESTART", "SUSPEND", }; secdbg_exin_set_unfz(sys_state[system_state], -1); if (IS_ENABLED(CONFIG_SEC_DEBUG_FAIL_TO_FREEZE_PANIC)) panic("fail to freeze tasks: %s", secdbg_exin_get_unfz()); } static int secdbg_exin_die_handler(struct notifier_block *nb, unsigned long l, void *buf) { struct die_args *args = (struct die_args *)buf; struct pt_regs *regs = args->regs; u64 lr; if (args->err) secdbg_exin_set_esr(args->err); if (!regs) return NOTIFY_DONE; if (!user_mode(regs)) secdbg_exin_set_backtrace(regs); if (is_bug_reported) secdbg_exin_set_fault(BUG_FAULT, (unsigned long)regs->pc, regs); if (compat_user_mode(regs)) lr = regs->compat_lr; else lr = regs->regs[30]; set_item_val("PC", "%pS", regs->pc); set_item_val("LR", "%pS", user_mode(regs) ? lr : ptrauth_strip_insn_pac(lr)); return NOTIFY_DONE; } static struct notifier_block nb_die_block = { .notifier_call = secdbg_exin_die_handler, .priority = INT_MAX, }; static void register_vendor_hooks(void) { register_trace_android_vh_watchdog_timer_softlockup(android_vh_watchdog_timer_softlockup, NULL); register_trace_android_rvh_do_undefinstr(android_rvh_do_undefinstr, NULL); register_trace_android_rvh_bad_mode(android_rvh_bad_mode, NULL); register_trace_android_rvh_arm64_serror_panic(android_rvh_arm64_serror_panic, NULL); register_trace_android_rvh_die_kernel_fault(android_rvh_die_kernel_fault, NULL); register_trace_android_rvh_do_sea(android_rvh_do_sea, NULL); register_trace_android_rvh_do_mem_abort(android_rvh_do_mem_abort, NULL); register_trace_android_rvh_do_sp_pc_abort(android_rvh_do_sp_pc_abort, NULL); register_trace_android_rvh_report_bug(android_rvh_report_bug, NULL); register_trace_android_vh_try_to_freeze_todo_unfrozen(android_vh_try_to_freeze_todo_unfrozen, NULL); register_trace_android_vh_try_to_freeze_todo(android_vh_try_to_freeze_todo, NULL); register_die_notifier(&nb_die_block); } static int __init secdbg_extra_info_init(void) { struct proc_dir_entry *entry; pr_info("%s: start\n", __func__); if (!sec_debug_extra_info_check_key()) pr_crit("%s: keys and abcfmt is not matched\n", __func__); sh_buf = secdbg_base_get_debug_base(SDN_MAP_EXTRA_INFO); if (!sh_buf) { pr_err("%s: No extra info buffer\n", __func__); return -EFAULT; } 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, ETR_A_PROC_SIZE); sec_debug_set_extra_info_id(); sec_debug_set_reset_reason_using_module_param(); atomic_notifier_chain_register(&panic_notifier_list, &nb_panic_block); register_vendor_hooks(); pr_info("%s: done\n", __func__); return 0; } module_init(secdbg_extra_info_init); MODULE_DESCRIPTION("Samsung Debug Extra info driver"); MODULE_LICENSE("GPL v2");