| /* |
| * Copyright (c) 2015 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com/ |
| * |
| * EXYNOS - EL3 monitor support |
| * Author: Jang Hyunsung <hs79.jang@samsung.com> |
| * |
| * 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 <linux/slab.h> |
| #include <linux/smc.h> |
| #include <linux/kallsyms.h> |
| #include <asm/cacheflush.h> |
| |
| #include <soc/samsung/exynos-el3_mon.h> |
| |
| static char *smc_lockup; |
| |
| #define EXYNOS_GET_IN_PD_DOWN 0 |
| #define EXYNOS_WAKEUP_PD_DOWN 1 |
| |
| #ifdef CONFIG_EXYNOS_KERNEL_PROTECTION |
| static int __init exynos_protect_kernel_text(void) |
| { |
| int ret = 0; |
| unsigned long ktext_start_va = 0; |
| unsigned long ktext_start_pa = 0; |
| unsigned long ktext_end_va = 0; |
| unsigned long ktext_end_pa = 0; |
| |
| /* Get virtual addresses of kernel text */ |
| ktext_start_va = kallsyms_lookup_name("_text"); |
| if (!ktext_start_va) { |
| pr_err("%s: [ERROR] Kernel text start address is invalid\n", |
| __func__); |
| return -1; |
| } |
| ktext_end_va = kallsyms_lookup_name("_etext"); |
| if (!ktext_end_va) { |
| pr_err("%s: [ERROR] Kernel text end address is invalid\n", |
| __func__); |
| return -1; |
| } |
| |
| /* Translate VA to PA */ |
| ktext_start_pa = virt_to_phys((void *)ktext_start_va); |
| ktext_end_pa = virt_to_phys((void *)ktext_end_va); |
| |
| pr_info("%s: Kernel text start VA(%pK), PA(%pK)\n", |
| __func__, (void *)ktext_start_va, (void *)ktext_start_pa); |
| pr_info("%s: Kernel text end VA(%pK), PA(%pK)\n", |
| __func__, (void *)ktext_end_va, (void *)ktext_end_pa); |
| |
| /* Request to protect kernel text area */ |
| ret = exynos_smc(SMC_CMD_PROTECT_KERNEL_TEXT, |
| ktext_start_pa, |
| ktext_end_pa, |
| 0); |
| if (ret) { |
| switch (ret) { |
| case EXYNOS_ERROR_NOT_VALID_ADDRESS: |
| pr_err("%s: [ERROR] Invalid address\n", __func__); |
| break; |
| case EXYNOS_ERROR_TZASC_WRONG_REGION: |
| pr_err("%s: [ERROR] Wrong TZASC region\n", __func__); |
| break; |
| case EXYNOS_ERROR_ALREADY_INITIALIZED: |
| pr_err("%s: [ERROR] Already initialized\n", __func__); |
| break; |
| default: |
| pr_err("%s: [ERROR] Unknown error [ret = %#x]\n", |
| __func__, ret); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| pr_info("%s: Success to set Kernel code as read-only\n", __func__); |
| |
| return 0; |
| } |
| core_initcall(exynos_protect_kernel_text); |
| #endif |
| |
| static int __init exynos_set_debug_mem(void) |
| { |
| int ret; |
| static char *smc_debug_mem; |
| char *phys; |
| |
| smc_debug_mem = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| |
| 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, PAGE_SIZE); |
| __dma_flush_range(smc_debug_mem, smc_debug_mem+PAGE_SIZE); |
| |
| phys = (char *)virt_to_phys(smc_debug_mem); |
| pr_err("%s: alloc kmem for smc_dbg virt: 0x%pK phys: 0x%pK size: %ld.\n", |
| __func__, smc_debug_mem, phys, PAGE_SIZE); |
| ret = exynos_smc(SMC_CMD_SET_DEBUG_MEM, (u64)phys, (u64)PAGE_SIZE, 0); |
| |
| /* correct return value is input size */ |
| if (ret != PAGE_SIZE) { |
| pr_err("%s: Can not set the address to el3 monitor. " |
| "ret = 0x%x. free the kmem\n", __func__, ret); |
| kfree(smc_debug_mem); |
| } |
| |
| return 0; |
| } |
| arch_initcall(exynos_set_debug_mem); |
| |
| static int __init exynos_get_reason_mem(void) |
| { |
| smc_lockup = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| |
| if (!smc_lockup) { |
| pr_err("%s: kmalloc for smc_lockup failed.\n", __func__); |
| smc_lockup = NULL; |
| } |
| |
| return 0; |
| } |
| arch_initcall(exynos_get_reason_mem); |
| |
| struct __exception_info { |
| unsigned long exception_type; |
| unsigned long sp_el1; |
| unsigned long sp_el3; |
| unsigned long elr_el3; |
| unsigned long esr_el3; |
| }; |
| |
| struct __lockup_info { |
| struct __exception_info exception_info[NR_CPUS]; |
| }; |
| |
| static const char *ename[] = { |
| "info38961", |
| "sync", |
| "irq", |
| "fiq", |
| "async", |
| "stack corruption", |
| "unknown" |
| }; |
| |
| static const char *el_mode[] = { |
| "el1 mode", |
| "el3 mode" |
| }; |
| |
| #define EXYNOS_EXCEPTION_FROM_SHIFT (63) |
| |
| #define EXYNOS_EXCEPTION_FROM_EL3 (1) |
| #define EXYNOS_EXCEPTION_FROM_EL1 (0) |
| |
| |
| static int exynos_parse_reason(struct __lockup_info *ptr) |
| { |
| int i, count, ekind, efrom; |
| struct __lockup_info *lockup_info = ptr; |
| unsigned long etype, elr_el3, sp_el1, sp_el3, esr_el3; |
| |
| for(i = 0, count = 0; i < NR_CPUS; i++) { |
| etype = lockup_info->exception_info[i].exception_type; |
| |
| if (!etype) { |
| /* this core has not got stuck in EL3 monitor */ |
| continue; |
| } |
| |
| /* add 1 to count for the core got stuck in EL3 monitor */ |
| count++; |
| |
| /* parsing the information */ |
| ekind = (etype & 0xf) > 6 ? 6 : (etype & 0xf) - 1; |
| efrom = (etype >> EXYNOS_EXCEPTION_FROM_SHIFT) & 0x1; |
| elr_el3 = lockup_info->exception_info[i].elr_el3; |
| sp_el1 = lockup_info->exception_info[i].sp_el1; |
| sp_el3 = lockup_info->exception_info[i].sp_el3; |
| esr_el3 = lockup_info->exception_info[i].esr_el3; |
| |
| /* it got stuck due to unexpected exception */ |
| pr_emerg("%s: %dth core gets stuck in EL3 monitor due to " \ |
| "%s exception from %s.\n", \ |
| __func__, i, ename[ekind], el_mode[efrom]); |
| pr_emerg("%s: elr 0x%lx sp_el1 0x%lx sp_el3 0x%lx " \ |
| "esr_el3 0x%lx\n", __func__, elr_el3, sp_el1, \ |
| sp_el3, esr_el3); |
| } |
| |
| /* count should be more than '1' */ |
| return !count; |
| } |
| |
| int exynos_check_hardlockup_reason(void) |
| { |
| int ret; |
| char *phys; |
| |
| if (!smc_lockup) { |
| pr_err("%s: fail to alloc memory for storing lockup info.\n", |
| __func__); |
| return 0; |
| } |
| |
| /* to map & flush memory */ |
| memset(smc_lockup, 0x00, PAGE_SIZE); |
| __dma_flush_range(smc_lockup, smc_lockup + PAGE_SIZE); |
| |
| phys = (char *)virt_to_phys(smc_lockup); |
| pr_err("%s: smc_lockup virt: 0x%p phys: 0x%p size: %ld.\n", |
| __func__, smc_lockup, phys, PAGE_SIZE); |
| |
| ret = exynos_smc(SMC_CMD_GET_LOCKUP_REASON, (u64)phys, (u64)PAGE_SIZE, 0); |
| |
| if (ret) { |
| pr_emerg("%s: SMC_CMD_GET_LOCKUP_REASON returns 0x%x. fail " \ |
| "to get the information.\n", __func__, ret); |
| goto check_exit; |
| } |
| |
| ret = exynos_parse_reason((struct __lockup_info *)smc_lockup); |
| |
| check_exit: |
| return ret; |
| } |
| |
| static void exynos_smart_exception_handler(unsigned int id, |
| unsigned long elr, unsigned long esr, |
| unsigned long sctlr, unsigned long ttbr, |
| unsigned long tcr, unsigned long x6) |
| { |
| 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("tcr_el1 : 0x%016lx, \tlr (EL1) : 0x%016lx\n\n", |
| tcr, 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("tcr_el3 : 0x%016lx, \tscr_el3 : 0x%016lx\n\n", |
| tcr, x6); |
| } |
| |
| pr_err("[WARNING] IT'S GOING TO CAUSE KERNEL PANIC FOR DEBUGGING.\n\n"); |
| |
| pr_err("=========================================" |
| "=========================================\n"); |
| /* make kernel panic */ |
| BUG(); |
| |
| /* SHOULD NOT be here */ |
| while(1); |
| } |
| |
| static int __init exynos_set_seh_address(void) |
| { |
| int ret; |
| unsigned long addr = (unsigned long)exynos_smart_exception_handler; |
| |
| pr_info("%s: send smc call with SMC_CMD_SET_SEH_ADDRESS.\n", __func__); |
| |
| 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%x.\n", __func__, ret); |
| else |
| pr_err("%s: set the seh address to el3 monitor well.\n", |
| __func__); |
| |
| return 0; |
| } |
| arch_initcall(exynos_set_seh_address); |
| |
| int exynos_tz_peri_save(unsigned int addr) |
| { |
| return exynos_smc(SMC_CMD_PREAPRE_PD_ONOFF, |
| EXYNOS_GET_IN_PD_DOWN, addr, 0); |
| } |
| |
| int exynos_tz_peri_restore(unsigned int addr) |
| { |
| return exynos_smc(SMC_CMD_PREAPRE_PD_ONOFF, |
| EXYNOS_WAKEUP_PD_DOWN, addr, 0); |
| } |