blob: 73467d4e1058746e265c6b6dee0bf3d10fb8f825 [file] [log] [blame]
/*
* 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);
}