// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Samsung TN debugging code * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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");