391 lines
10 KiB
C
Executable file
391 lines
10 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* Samsung TN debugging code
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irqdesc.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/reboot.h>
|
|
|
|
#include <linux/coredump.h>
|
|
#include <linux/sec_debug.h>
|
|
#include "sec_debug_internal.h"
|
|
|
|
static struct sec_debug_gen3 *secdbg_base_va;
|
|
static unsigned int sec_debug_gen3_size;
|
|
static struct sec_debug_base_param *secdbg_bb_param;
|
|
|
|
static uint64_t get_sdn_lv1(int type)
|
|
{
|
|
if (type < secdbg_base_va->used_offset)
|
|
return (uint64_t)secdbg_base_built_get_ncva(secdbg_base_va->lv1_data[type].addr);
|
|
|
|
return 0;
|
|
};
|
|
|
|
/* from PA to NonCachable VA */
|
|
static unsigned long sdn_ncva_to_pa_offset;
|
|
|
|
void *secdbg_base_built_get_ncva(unsigned long pa)
|
|
{
|
|
return (void *)(pa + sdn_ncva_to_pa_offset);
|
|
}
|
|
EXPORT_SYMBOL(secdbg_base_built_get_ncva);
|
|
|
|
void secdbg_base_built_set_task_in_soft_lockup(uint64_t task)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->task_in_soft_lockup = task;
|
|
}
|
|
}
|
|
|
|
void secdbg_base_built_set_cpu_in_soft_lockup(uint64_t cpu)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->cpu_in_soft_lockup = cpu;
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_TASK_IN_STATE_INFO)
|
|
void secdbg_base_built_set_task_in_pm_suspend(struct task_struct *task)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->task_in_pm_suspend = (uint64_t)task;
|
|
}
|
|
}
|
|
|
|
static void secdbg_base_built_set_task_in_sys_reboot(struct task_struct *task)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->task_in_sys_reboot = (uint64_t)task;
|
|
}
|
|
}
|
|
|
|
static void secdbg_base_built_set_task_in_sys_shutdown(struct task_struct *task)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->task_in_sys_shutdown = (uint64_t)task;
|
|
}
|
|
}
|
|
|
|
void secdbg_base_built_set_task_in_dev_shutdown(struct task_struct *task)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->task_in_dev_shutdown = (uint64_t)task;
|
|
}
|
|
}
|
|
|
|
void secdbg_base_built_set_task_in_sync_irq(struct task_struct *task, unsigned int irq, struct irq_desc *desc)
|
|
{
|
|
const char *name = (desc && desc->action) ? desc->action->name : NULL;
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (!secdbg_base_va)
|
|
return;
|
|
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->sync_irq_task = (uint64_t)task;
|
|
kernd->sync_irq_num = irq;
|
|
kernd->sync_irq_name = (uint64_t)name;
|
|
kernd->sync_irq_desc = (uint64_t)desc;
|
|
|
|
if (desc) {
|
|
kernd->sync_irq_threads_active = desc->threads_active.counter;
|
|
|
|
if (desc->action && (desc->action->irq == irq) && desc->action->thread)
|
|
kernd->sync_irq_thread = (uint64_t)(desc->action->thread);
|
|
else
|
|
kernd->sync_irq_thread = 0;
|
|
}
|
|
}
|
|
#endif /* SEC_DEBUG_TASK_IN_STATE_INFO */
|
|
|
|
static int secdbg_base_built_reboot_handler(struct notifier_block *nb,
|
|
unsigned long state, void *cmd)
|
|
{
|
|
|
|
switch (state) {
|
|
case SYS_RESTART:
|
|
secdbg_base_built_set_task_in_sys_reboot(current);
|
|
break;
|
|
case SYS_POWER_OFF:
|
|
secdbg_base_built_set_task_in_sys_shutdown(current);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block secdbg_base_reboot_nb = {
|
|
.notifier_call = secdbg_base_built_reboot_handler,
|
|
.priority = INT_MAX,
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_UNFROZEN_TASK)
|
|
void secdbg_base_built_set_unfrozen_task(struct task_struct *task, uint64_t count)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->unfrozen_task = (uint64_t)task;
|
|
kernd->unfrozen_task_count = (uint64_t)count;
|
|
}
|
|
}
|
|
#endif /* SEC_DEBUG_UNFROZEN_TASK */
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_PM_DEVICE_INFO)
|
|
void secdbg_base_built_set_shutdown_device(const char *fname, const char *dname)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->sdi.shutdown_func = (uint64_t)fname;
|
|
kernd->sdi.shutdown_device = (uint64_t)dname;
|
|
}
|
|
}
|
|
|
|
void secdbg_base_built_set_suspend_device(const char *fname, const char *dname)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->sdi.suspend_func = (uint64_t)fname;
|
|
kernd->sdi.suspend_device = (uint64_t)dname;
|
|
}
|
|
}
|
|
#endif /* SEC_DEBUG_PM_DEVICE_INFO */
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_WATCHDOGD_FOOTPRINT)
|
|
void secdbg_base_built_wdd_set_emerg_addr(unsigned long addr)
|
|
{
|
|
struct sec_debug_kernel_data *kernd;
|
|
|
|
if (secdbg_base_va) {
|
|
kernd = (struct sec_debug_kernel_data *)get_sdn_lv1(SDN_LV1_KERNEL_DATA);
|
|
kernd->wddinfo.emerg_addr = addr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_HANDLE_BAD_STACK)
|
|
#ifdef CONFIG_S3C2410_BUILTIN_WATCHDOG
|
|
extern int s3c2410wdt_builtin_expire_watchdog_raw(void);
|
|
#else
|
|
static inline int s3c2410wdt_builtin_expire_watchdog_raw(void) { return 0; }
|
|
#endif
|
|
|
|
static DEFINE_PER_CPU(unsigned long, secdbg_bad_stack_chk);
|
|
|
|
void secdbg_base_built_check_handle_bad_stack(void)
|
|
{
|
|
if (this_cpu_read(secdbg_bad_stack_chk)) {
|
|
/* reentrance handle_bad_stack */
|
|
s3c2410wdt_builtin_expire_watchdog_raw();
|
|
cpu_park_loop();
|
|
}
|
|
|
|
this_cpu_write(secdbg_bad_stack_chk, 0xBAD);
|
|
}
|
|
#endif /* CONFIG_SEC_DEBUG_HANDLE_BAD_STACK */
|
|
|
|
void *secdbg_base_built_get_debug_base(int type)
|
|
{
|
|
if (secdbg_base_va) {
|
|
if (type == SDN_MAP_AUTO_COMMENT)
|
|
return (void *)get_sdn_lv1(SDN_LV1_AUTO_COMMENT);
|
|
}
|
|
|
|
pr_crit("%s: return NULL\n", __func__);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
unsigned long secdbg_base_built_get_buf_base(int type)
|
|
{
|
|
struct secdbg_logbuf_list *p;
|
|
|
|
if (secdbg_base_va) {
|
|
p = (struct secdbg_logbuf_list *)get_sdn_lv1(SDN_LV1_LOGBUF_MAP);
|
|
|
|
pr_crit("%s: return %p (%llx)\n", __func__,
|
|
secdbg_base_va, p->data[type].base);
|
|
return p->data[type].base;
|
|
}
|
|
|
|
pr_crit("%s: return 0\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned long secdbg_base_built_get_buf_size(int type)
|
|
{
|
|
struct secdbg_logbuf_list *p;
|
|
|
|
if (secdbg_base_va) {
|
|
p = (struct secdbg_logbuf_list *)get_sdn_lv1(SDN_LV1_LOGBUF_MAP);
|
|
|
|
pr_crit("%s: return %p (%llx)\n", __func__,
|
|
secdbg_base_va, p->data[type].size);
|
|
return p->data[type].size;
|
|
}
|
|
|
|
pr_crit("%s: return 0\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void secdbg_base_built_wait_for_init(struct sec_debug_base_param *param)
|
|
{
|
|
/*
|
|
* We must ensure sdn is cleared completely hear.
|
|
* Pairs with the smp_store_release() in
|
|
* secdbg_base_set_sdn_ready().
|
|
*/
|
|
smp_cond_load_acquire(¶m->init_sdn_done, VAL);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SEC_DEBUG_COREDUMP)
|
|
static int (*func_hook_coredump_extra_note_size)(void);
|
|
static int (*func_hook_coredump_extra_note_write)(struct coredump_params *);
|
|
|
|
void register_coredump_hook_notes_size(int (*func)(void))
|
|
{
|
|
func_hook_coredump_extra_note_size = func;
|
|
}
|
|
EXPORT_SYMBOL(register_coredump_hook_notes_size);
|
|
|
|
void register_coredump_hook_notes_write(int (*func)(struct coredump_params *))
|
|
{
|
|
func_hook_coredump_extra_note_write = func;
|
|
}
|
|
EXPORT_SYMBOL(register_coredump_hook_notes_write);
|
|
|
|
#ifdef ARCH_HAVE_EXTRA_ELF_NOTES
|
|
int elf_coredump_extra_notes_size(void)
|
|
{
|
|
if (func_hook_coredump_extra_note_size)
|
|
return func_hook_coredump_extra_note_size();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int elf_coredump_extra_notes_write(struct coredump_params *cprm)
|
|
{
|
|
if (func_hook_coredump_extra_note_write)
|
|
return func_hook_coredump_extra_note_write(cprm);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
static int secdbg_base_built_probe(struct platform_device *pdev)
|
|
{
|
|
struct reserved_mem *rmem;
|
|
struct device_node *rmem_np;
|
|
|
|
rmem_np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
|
|
if (!rmem_np) {
|
|
pr_crit("%s: no such memory-region\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
rmem = of_reserved_mem_lookup(rmem_np);
|
|
if (!rmem) {
|
|
pr_crit("%s: no such reserved mem of node name %s\n",
|
|
__func__, pdev->dev.of_node->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!rmem->base || !rmem->size || !rmem->priv) {
|
|
pr_info_once("%s: not ready ...\n", __func__);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
pr_info("%s: start\n", __func__);
|
|
|
|
secdbg_bb_param = (struct sec_debug_base_param *)rmem->priv;
|
|
secdbg_base_built_wait_for_init(secdbg_bb_param);
|
|
|
|
secdbg_base_va = (struct sec_debug_gen3 *)(secdbg_bb_param->sdn_vaddr);
|
|
sec_debug_gen3_size = (unsigned int)(rmem->size);
|
|
|
|
sdn_ncva_to_pa_offset = (unsigned long)secdbg_base_va - (unsigned long)rmem->base;
|
|
pr_info("%s: offset from va: %lx\n", __func__, sdn_ncva_to_pa_offset);
|
|
|
|
pr_info("%s: va: %llx ++ %x\n", __func__, (uint64_t)(rmem->priv), (unsigned int)(rmem->size));
|
|
|
|
secdbg_comm_auto_comment_init();
|
|
secdbg_base_built_set_memtab_info((struct sec_debug_memtab *)get_sdn_lv1(SDN_LV1_MEMTAB));
|
|
|
|
register_reboot_notifier(&secdbg_base_reboot_nb);
|
|
|
|
pr_info("%s: done\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id sec_debug_built_of_match[] = {
|
|
{ .compatible = "samsung,sec_debug_built" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sec_debug_built_of_match);
|
|
|
|
static struct platform_driver sec_debug_built_driver = {
|
|
.probe = secdbg_base_built_probe,
|
|
.driver = {
|
|
.name = "sec_debug_base_built",
|
|
.of_match_table = of_match_ptr(sec_debug_built_of_match),
|
|
},
|
|
};
|
|
|
|
static __init int secdbg_base_built_init(void)
|
|
{
|
|
pr_info("%s: init\n", __func__);
|
|
|
|
return platform_driver_register(&sec_debug_built_driver);
|
|
}
|
|
late_initcall_sync(secdbg_base_built_init);
|
|
|
|
static void __exit secdbg_base_built_exit(void)
|
|
{
|
|
platform_driver_unregister(&sec_debug_built_driver);
|
|
}
|
|
module_exit(secdbg_base_built_exit);
|
|
|
|
MODULE_DESCRIPTION("Samsung Debug base builtin driver");
|
|
MODULE_LICENSE("GPL v2");
|