/* * Copyright (c) 2015 Samsung Electronics Co., Ltd. * http://www.samsung.com/ * * EXYNOS - Smart Exception Handler * Author: Jang Hyunsung * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include static char *smc_debug_mem; static struct page *page_ptr; static unsigned long cm_debug_func_addr = 0; void exynos_seh_set_cm_debug_function(unsigned long addr) { cm_debug_func_addr = addr; } EXPORT_SYMBOL(exynos_seh_set_cm_debug_function); static void exynos_smart_exception_handler(unsigned int id, unsigned long elr, unsigned long esr, unsigned long sctlr, unsigned long ttbr, unsigned long far, unsigned long x6, unsigned int offset) { int i; unsigned long *ptr; unsigned long tmp; pr_err("=========================================" "=========================================\n"); if (id) pr_err("%s: There has been an unexpected exception from " "a LDFW which has smc id 0x%x\n\n", __func__, id); else pr_err("%s: There has been an unexpected exception from " "the EL3 monitor.\n\n", __func__); if (id) { pr_err("elr_el1 : 0x%016lx, \tesr_el1 : 0x%016lx\n", elr, esr); pr_err("sctlr_el1 : 0x%016lx, \tttbr_el1 : 0x%016lx\n", sctlr, ttbr); pr_err("far_el1 : 0x%016lx, \tlr (EL1) : 0x%016lx\n\n", far, x6); } else { pr_err("elr_el3 : 0x%016lx, \tesr_el3 : 0x%016lx\n", elr, esr); pr_err("sctlr_el3 : 0x%016lx, \tttbr_el3 : 0x%016lx\n", sctlr, ttbr); pr_err("far_el3 : 0x%016lx, \tscr_el3 : 0x%016lx\n\n", far, x6); } if ((offset > 0x0 && offset < (PAGE_SIZE * 2)) && !(offset % 0x8) && (smc_debug_mem)) { /* Flush smc_debug_mem for cache coherency */ flush_dcache_page(page_ptr); tmp = (unsigned long)smc_debug_mem; tmp += (unsigned long)offset; ptr = (unsigned long *)tmp; for (i = 0; i < 15; i++) { pr_err("x%02d : 0x%016lx, \tx%02d : 0x%016lx\n", i * 2, ptr[i * 2], i * 2 + 1, ptr[i * 2 + 1]); } pr_err("x%02d : 0x%016lx\n", i * 2, ptr[i * 2]); } pr_err("\n[WARNING] IT'S GOING TO CAUSE KERNEL PANIC FOR DEBUGGING.\n\n"); pr_err("=========================================" "=========================================\n"); #if IS_ENABLED(CONFIG_EXYNOS_CRYPTOMANAGER) /* If exception is occured in CM LDFW, call debug function */ if ((id & 0x1000) & (cm_debug_func_addr != 0)) { ((void (*) (void))(cm_debug_func_addr))(); } #endif /* make kernel panic */ BUG(); /* SHOULD NOT be here */ while(1); } static int __init exynos_set_seh_address(void) { unsigned long addr = (unsigned long)exynos_smart_exception_handler; unsigned long ret; char *phys; unsigned int size = PAGE_SIZE * 2; pr_info("%s: send smc call with SMC_CMD_SET_SEH_ADDRESS.\n", __func__); /* Set debug mem */ page_ptr = alloc_pages(GFP_KERNEL, 1); if (!page_ptr) { pr_err("%s: alloc_page for smc_debug failed.\n", __func__); return 0; } smc_debug_mem = page_address(page_ptr); if (!smc_debug_mem) { pr_err("%s: kmalloc for smc_debug failed.\n", __func__); return 0; } /* to map & flush memory */ memset(smc_debug_mem, 0x00, size); flush_dcache_page(page_ptr); phys = (char *)virt_to_phys(smc_debug_mem); pr_err("%s: alloc kmem for smc_dbg virt: 0x%lx phys: 0x%lx size: %d.\n", __func__, smc_debug_mem, phys, size); ret = exynos_smc(SMC_CMD_SET_DEBUG_MEM, (u64)phys, (u64)size, 0); /* correct return value is input size */ if (ret != size) { pr_err("%s: Can not set the address to el3 monitor. " "ret = 0x%lx. free the kmem\n", __func__, ret); __free_pages(page_ptr, 1); return 0; } ret = exynos_smc(SMC_CMD_SET_SEH_ADDRESS, addr, 0, 0); /* return value not zero means failure */ if (ret) pr_err("%s: did not set the seh address to el3 monitor. " "ret = 0x%lx.\n", __func__, ret); else pr_err("%s: set the seh address to el3 monitor well.\n", __func__); return 0; } module_init(exynos_set_seh_address); MODULE_LICENSE("GPL");