803 lines
20 KiB
C
803 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/vmalloc.h> /* needed by vmalloc */
|
|
#include <linux/sysfs.h>
|
|
#include <linux/device.h> /* needed by device_* */
|
|
#include <linux/workqueue.h>
|
|
#include <linux/io.h>
|
|
#include <linux/mutex.h>
|
|
#if IS_ENABLED(CONFIG_MTK_AEE_FEATURE)
|
|
#include <mt-plat/aee.h>
|
|
#endif
|
|
#include <linux/sched_clock.h>
|
|
#include <linux/ratelimit.h>
|
|
#include <linux/delay.h>
|
|
#include "scp_ipi.h"
|
|
#include "scp_helper.h"
|
|
#include "scp_excep.h"
|
|
#include "scp_feature_define.h"
|
|
#include "scp_l1c.h"
|
|
|
|
struct scp_aed_cfg {
|
|
int *log;
|
|
int log_size;
|
|
int *phy;
|
|
int phy_size;
|
|
char *detail;
|
|
struct MemoryDump *pMemoryDump;
|
|
int memory_dump_size;
|
|
};
|
|
|
|
struct scp_status_reg {
|
|
unsigned int pc;
|
|
unsigned int lr;
|
|
unsigned int psp;
|
|
unsigned int sp;
|
|
unsigned int m2h;
|
|
unsigned int h2m;
|
|
};
|
|
static unsigned char *scp_A_detail_buffer;
|
|
static unsigned char *scp_A_dump_buffer;
|
|
static unsigned char *scp_A_dump_buffer_last;
|
|
static unsigned int scp_A_dump_length;
|
|
static unsigned int scp_A_task_context_addr;
|
|
static struct scp_work_struct scp_aed_work;
|
|
static struct scp_status_reg scp_A_aee_status;
|
|
static struct mutex scp_excep_mutex;
|
|
static struct mutex scp_A_excep_dump_mutex;
|
|
int scp_ee_enable;
|
|
|
|
|
|
/* An ELF note in memory */
|
|
struct memelfnote {
|
|
const char *name;
|
|
int type;
|
|
unsigned int datasz;
|
|
void *data;
|
|
};
|
|
|
|
static int notesize(struct memelfnote *en)
|
|
{
|
|
int sz;
|
|
|
|
sz = sizeof(struct elf32_note);
|
|
sz += roundup((strlen(en->name) + 1), 4);
|
|
sz += roundup(en->datasz, 4);
|
|
|
|
return sz;
|
|
}
|
|
|
|
static uint8_t *storenote(struct memelfnote *men, uint8_t *bufp)
|
|
{
|
|
struct elf32_note en;
|
|
|
|
en.n_namesz = strlen(men->name) + 1;
|
|
en.n_descsz = men->datasz;
|
|
en.n_type = men->type;
|
|
|
|
memcpy(bufp, &en, sizeof(en));
|
|
bufp += sizeof(en);
|
|
|
|
memcpy(bufp, men->name, en.n_namesz);
|
|
bufp += en.n_namesz;
|
|
|
|
bufp = (uint8_t *) roundup((unsigned long)bufp, 4);
|
|
memcpy(bufp, men->data, men->datasz);
|
|
bufp += men->datasz;
|
|
|
|
bufp = (uint8_t *) roundup((unsigned long)bufp, 4);
|
|
return bufp;
|
|
}
|
|
|
|
static uint8_t *core_write_cpu_note(int cpu, struct elf32_phdr *nhdr,
|
|
uint8_t *bufp, enum scp_core_id id)
|
|
{
|
|
struct memelfnote notes;
|
|
struct elf32_prstatus prstatus;
|
|
char cpustr[16];
|
|
|
|
memset(&prstatus, 0, sizeof(struct elf32_prstatus));
|
|
snprintf(cpustr, sizeof(cpustr), "CPU%d", cpu);
|
|
/* set up the process status */
|
|
notes.name = cpustr;
|
|
notes.type = NT_PRSTATUS;
|
|
notes.datasz = sizeof(struct elf32_prstatus);
|
|
notes.data = &prstatus;
|
|
|
|
prstatus.pr_pid = cpu + 1;
|
|
if (scp_A_task_context_addr && (id == SCP_A_ID)) {
|
|
memcpy_from_scp((void *)&(prstatus.pr_reg),
|
|
(void *)(SCP_TCM + scp_A_task_context_addr),
|
|
sizeof(prstatus.pr_reg));
|
|
}
|
|
|
|
if (prstatus.pr_reg[15] == 0x0 && (id == SCP_A_ID))
|
|
prstatus.pr_reg[15] = readl(SCP_A_DEBUG_PC_REG);
|
|
if (prstatus.pr_reg[14] == 0x0 && (id == SCP_A_ID))
|
|
prstatus.pr_reg[14] = readl(SCP_A_DEBUG_LR_REG);
|
|
if (prstatus.pr_reg[13] == 0x0 && (id == SCP_A_ID))
|
|
prstatus.pr_reg[13] = readl(SCP_A_DEBUG_PSP_REG);
|
|
|
|
|
|
nhdr->p_filesz += notesize(¬es);
|
|
return storenote(¬es, bufp);
|
|
}
|
|
|
|
void exception_header_init(void *oldbufp, enum scp_core_id id)
|
|
{
|
|
struct elf32_phdr *nhdr, *phdr;
|
|
struct elf32_hdr *elf;
|
|
off_t offset = 0;
|
|
|
|
uint8_t *bufp = oldbufp;
|
|
uint32_t cpu;
|
|
uint32_t dram_size = 0;
|
|
|
|
/* NT_PRPSINFO */
|
|
struct elf32_prpsinfo prpsinfo;
|
|
struct memelfnote notes;
|
|
|
|
elf = (struct elf32_hdr *) bufp;
|
|
bufp += sizeof(struct elf32_hdr);
|
|
offset += sizeof(struct elf32_hdr);
|
|
elf_setup_eident(elf->e_ident, ELFCLASS32);
|
|
|
|
/*setup elf header*/
|
|
elf->e_type = ET_CORE;
|
|
elf->e_machine = EM_ARM;
|
|
elf->e_version = EV_CURRENT;
|
|
elf->e_entry = 0;
|
|
elf->e_phoff = sizeof(struct elf32_hdr);
|
|
elf->e_shoff = 0;
|
|
elf->e_flags = ELF_CORE_EFLAGS;
|
|
elf->e_ehsize = sizeof(struct elf32_hdr);
|
|
elf->e_phentsize = sizeof(struct elf32_phdr);
|
|
elf->e_phnum = 2;
|
|
elf->e_shentsize = 0;
|
|
elf->e_shnum = 0;
|
|
elf->e_shstrndx = 0;
|
|
|
|
nhdr = (struct elf32_phdr *) bufp;
|
|
bufp += sizeof(struct elf32_phdr);
|
|
offset += sizeof(struct elf32_phdr);
|
|
memset(nhdr, 0, sizeof(struct elf32_phdr));
|
|
nhdr->p_type = PT_NOTE;
|
|
|
|
phdr = (struct elf32_phdr *) bufp;
|
|
bufp += sizeof(struct elf32_phdr);
|
|
offset += sizeof(struct elf32_phdr);
|
|
phdr->p_flags = PF_R|PF_W|PF_X;
|
|
phdr->p_offset = CRASH_MEMORY_HEADER_SIZE;
|
|
phdr->p_vaddr = CRASH_MEMORY_OFFSET;
|
|
phdr->p_paddr = CRASH_MEMORY_OFFSET;
|
|
|
|
#if SCP_RECOVERY_SUPPORT
|
|
if ((int)scp_region_info_copy.ap_dram_size > 0)
|
|
dram_size = scp_region_info_copy.ap_dram_size;
|
|
#endif
|
|
|
|
phdr->p_filesz = CRASH_MEMORY_LENGTH + roundup(dram_size, 4);
|
|
phdr->p_memsz = CRASH_MEMORY_LENGTH + roundup(dram_size, 4);
|
|
|
|
|
|
phdr->p_align = 0;
|
|
phdr->p_type = PT_LOAD;
|
|
|
|
nhdr->p_offset = offset;
|
|
|
|
/* set up the process info */
|
|
notes.name = CORE_STR;
|
|
notes.type = NT_PRPSINFO;
|
|
notes.datasz = sizeof(struct elf32_prpsinfo);
|
|
notes.data = &prpsinfo;
|
|
|
|
memset(&prpsinfo, 0, sizeof(struct elf32_prpsinfo));
|
|
prpsinfo.pr_state = 0;
|
|
prpsinfo.pr_sname = 'R';
|
|
prpsinfo.pr_zomb = 0;
|
|
prpsinfo.pr_gid = prpsinfo.pr_uid = 0x0;
|
|
strlcpy(prpsinfo.pr_fname, "freertos8",
|
|
sizeof(prpsinfo.pr_fname));
|
|
strlcpy(prpsinfo.pr_psargs, "freertos8", ELF_PRARGSZ);
|
|
|
|
nhdr->p_filesz += notesize(¬es);
|
|
bufp = storenote(¬es, bufp);
|
|
|
|
/* Store pre-cpu backtrace */
|
|
for (cpu = 0; cpu < 1; cpu++)
|
|
bufp = core_write_cpu_note(cpu, nhdr, bufp, id);
|
|
}
|
|
/*dump scp reg*/
|
|
void scp_reg_copy(void *bufp)
|
|
{
|
|
struct scp_reg_dump_list *scp_reg;
|
|
uint32_t tmp;
|
|
|
|
scp_reg = (struct scp_reg_dump_list *) bufp;
|
|
|
|
/*setup scp reg*/
|
|
scp_reg->scp_reg_magic = 0xDEADBEEF;
|
|
scp_reg->ap_resource = readl(SCP_AP_RESOURCE);
|
|
scp_reg->bus_resource = readl(SCP_BUS_RESOURCE);
|
|
scp_reg->slp_protect = readl(SCP_SLP_PROTECT_CFG);
|
|
scp_reg->cpu_sleep_status = readl(SCP_CPU_SLEEP_STATUS);
|
|
scp_reg->clk_sw_sel = readl(SCP_CLK_SW_SEL);
|
|
scp_reg->clk_enable = readl(SCP_CLK_ENABLE);
|
|
scp_reg->clk_high_core = readl(SCP_CLK_HIGH_CORE_CG);
|
|
scp_reg->debug_wdt_sp = readl(SCP_WDT_SP);
|
|
scp_reg->debug_wdt_lr = readl(SCP_WDT_LR);
|
|
scp_reg->debug_wdt_psp = readl(SCP_WDT_PSP);
|
|
scp_reg->debug_wdt_pc = readl(SCP_WDT_PC);
|
|
scp_reg->debug_addr_s2r = readl(SCP_DEBUG_ADDR_S2R);
|
|
scp_reg->debug_addr_dma = readl(SCP_DEBUG_ADDR_DMA);
|
|
scp_reg->debug_addr_spi0 = readl(SCP_DEBUG_ADDR_SPI0);
|
|
scp_reg->debug_addr_spi1 = readl(SCP_DEBUG_ADDR_SPI1);
|
|
scp_reg->debug_addr_spi2 = readl(SCP_DEBUG_ADDR_SPI2);
|
|
scp_reg->debug_bus_status = readl(SCP_DEBUG_BUS_STATUS);
|
|
/* scp_reg->debug_infra_mon = readl(SCP_SYS_INFRA_MON); */
|
|
tmp = readl(SCP_BUS_CTRL)&(~dbg_irq_info_sel_mask);
|
|
writel(tmp | (0 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
scp_reg->infra_addr_latch = readl(SCP_DEBUG_IRQ_INFO);
|
|
writel(tmp | (1 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
scp_reg->ddebug_latch = readl(SCP_DEBUG_IRQ_INFO);
|
|
writel(tmp | (2 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
scp_reg->pdebug_latch = readl(SCP_DEBUG_IRQ_INFO);
|
|
writel(tmp | (3 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
scp_reg->pc_value = readl(SCP_DEBUG_IRQ_INFO);
|
|
scp_reg->scp_reg_magic_end = 0xDEADBEEF;
|
|
}
|
|
|
|
/*dump scp l1c header*/
|
|
void scp_sub_header_init(void *bufp)
|
|
{
|
|
struct scp_dump_header_list *scp_sub_head;
|
|
|
|
scp_sub_head = (struct scp_dump_header_list *) bufp;
|
|
/*setup scp reg*/
|
|
scp_sub_head->scp_head_magic = 0xDEADBEEF;
|
|
#if SCP_RECOVERY_SUPPORT
|
|
memcpy(&scp_sub_head->scp_region_info,
|
|
&scp_region_info_copy, sizeof(scp_region_info_copy));
|
|
#endif
|
|
scp_sub_head->scp_head_magic_end = 0xDEADBEEF;
|
|
}
|
|
|
|
/*
|
|
* return last lr for debugging
|
|
*/
|
|
uint32_t scp_dump_lr(void)
|
|
{
|
|
if (is_scp_ready(SCP_A_ID))
|
|
return readl(SCP_A_DEBUG_LR_REG);
|
|
else
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
/*
|
|
* return last pc for debugging
|
|
*/
|
|
uint32_t scp_dump_pc(void)
|
|
{
|
|
if (is_scp_ready(SCP_A_ID))
|
|
return readl(SCP_A_DEBUG_PC_REG);
|
|
else
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
/*
|
|
* dump scp register for debugging
|
|
*/
|
|
void scp_A_dump_regs(void)
|
|
{
|
|
uint32_t tmp;
|
|
|
|
if (is_scp_ready(SCP_A_ID)) {
|
|
pr_debug("[SCP]ready PC:0x%x,LR:0x%x,PSP:0x%x,SP:0x%x\n"
|
|
, readl(SCP_A_DEBUG_PC_REG), readl(SCP_A_DEBUG_LR_REG)
|
|
, readl(SCP_A_DEBUG_PSP_REG), readl(SCP_A_DEBUG_SP_REG));
|
|
} else {
|
|
pr_debug("[SCP]not ready PC:0x%x,LR:0x%x,PSP:0x%x,SP:0x%x\n"
|
|
, readl(SCP_A_DEBUG_PC_REG), readl(SCP_A_DEBUG_LR_REG)
|
|
, readl(SCP_A_DEBUG_PSP_REG), readl(SCP_A_DEBUG_SP_REG));
|
|
}
|
|
|
|
pr_debug("[SCP]GIPC 0x%x\n", readl(SCP_GIPC_IN_REG));
|
|
pr_debug("[SCP]BUS_CTRL 0x%x\n", readl(SCP_BUS_CTRL));
|
|
pr_debug("[SCP]SLEEP_STATUS 0x%x\n", readl(SCP_CPU_SLEEP_STATUS));
|
|
pr_debug("[SCP]INFRA_STATUS 0x%x\n", readl(INFRA_CTRL_STATUS));
|
|
pr_debug("[SCP]IRQ_STATUS 0x%x\n", readl(SCP_INTC_IRQ_STATUS));
|
|
pr_debug("[SCP]IRQ_ENABLE 0x%x\n", readl(SCP_INTC_IRQ_ENABLE));
|
|
pr_debug("[SCP]IRQ_SLEEP 0x%x\n", readl(SCP_INTC_IRQ_SLEEP));
|
|
pr_debug("[SCP]IRQ_STATUS_MSB 0x%x\n", readl(SCP_INTC_IRQ_STATUS_MSB));
|
|
pr_debug("[SCP]IRQ_ENABLE_MSB 0x%x\n", readl(SCP_INTC_IRQ_ENABLE_MSB));
|
|
pr_debug("[SCP]IRQ_SLEEP_MSB 0x%x\n", readl(SCP_INTC_IRQ_SLEEP_MSB));
|
|
pr_debug("[SCP]CLK_CTRL_SEL 0x%x\n", readl(SCP_CLK_SW_SEL));
|
|
pr_debug("[SCP]CLK_ENABLE 0x%x\n", readl(SCP_CLK_ENABLE));
|
|
pr_debug("[SCP]SLEEP_DEBUG 0x%x\n", readl(SCP_A_SLEEP_DEBUG_REG));
|
|
|
|
tmp = readl(SCP_BUS_CTRL)&(~dbg_irq_info_sel_mask);
|
|
writel(tmp | (0 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
pr_debug("[SCP]BUS:INFRA LATCH, 0x%x\n", readl(SCP_DEBUG_IRQ_INFO));
|
|
writel(tmp | (1 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
pr_debug("[SCP]BUS:DCACHE LATCH, 0x%x\n", readl(SCP_DEBUG_IRQ_INFO));
|
|
writel(tmp | (2 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
pr_debug("[SCP]BUS:ICACHE LATCH, 0x%x\n", readl(SCP_DEBUG_IRQ_INFO));
|
|
writel(tmp | (3 << dbg_irq_info_sel_shift), SCP_BUS_CTRL);
|
|
pr_debug("[SCP]BUS:PC LATCH, 0x%x\n", readl(SCP_DEBUG_IRQ_INFO));
|
|
}
|
|
|
|
/*
|
|
* save scp register when scp crash
|
|
* these data will be used to generate EE
|
|
*/
|
|
void scp_aee_last_reg(void)
|
|
{
|
|
pr_debug("[SCP] %s begins\n", __func__);
|
|
|
|
scp_A_aee_status.pc = readl(SCP_A_DEBUG_PC_REG);
|
|
scp_A_aee_status.lr = readl(SCP_A_DEBUG_LR_REG);
|
|
scp_A_aee_status.psp = readl(SCP_A_DEBUG_PSP_REG);
|
|
scp_A_aee_status.sp = readl(SCP_A_DEBUG_SP_REG);
|
|
scp_A_aee_status.m2h = readl(SCP_A_TO_HOST_REG);
|
|
scp_A_aee_status.h2m = readl(SCP_GIPC_IN_REG);
|
|
|
|
pr_debug("[SCP] %s ends\n", __func__);
|
|
}
|
|
|
|
/*
|
|
* this function need SCP to keeping awaken
|
|
* scp_crash_dump: dump scp tcm info.
|
|
* @param MemoryDump: scp dump struct
|
|
* @param scp_core_id: core id
|
|
* @return: scp dump size
|
|
*/
|
|
static unsigned int scp_crash_dump(struct MemoryDump *pMemoryDump,
|
|
enum scp_core_id id)
|
|
{
|
|
unsigned int lock;
|
|
unsigned int *reg;
|
|
unsigned int scp_dump_size;
|
|
unsigned int scp_awake_fail_flag;
|
|
#if SCP_RECOVERY_SUPPORT
|
|
uint32_t dram_start = 0;
|
|
#endif
|
|
uint32_t dram_size = 0;
|
|
|
|
/*flag use to indicate scp awake success or not*/
|
|
scp_awake_fail_flag = 0;
|
|
/*check SRAM lock ,awake scp*/
|
|
if (scp_awake_lock(id) == -1) {
|
|
pr_err("[SCP] %s: awake scp fail, scp id=%u\n", __func__, id);
|
|
scp_awake_fail_flag = 1;
|
|
}
|
|
|
|
reg = (unsigned int *) (scpreg.cfg + SCP_LOCK_OFS);
|
|
lock = *reg;
|
|
*reg &= ~SCP_TCM_LOCK_BIT;
|
|
dsb(SY);
|
|
if ((*reg & SCP_TCM_LOCK_BIT)) {
|
|
pr_debug("[SCP]unlock failed, skip dump\n");
|
|
return 0;
|
|
}
|
|
|
|
#if SCP_RECOVERY_SUPPORT
|
|
/* L1C support? */
|
|
if ((int)(scp_region_info_copy.ap_dram_size) <= 0) {
|
|
scp_dump_size = sizeof(struct MemoryDump) + CRASH_MEMORY_LENGTH;
|
|
} else {
|
|
|
|
dram_start = scp_region_info_copy.ap_dram_start;
|
|
dram_size = scp_region_info_copy.ap_dram_size;
|
|
scp_dump_size = sizeof(struct MemoryDump) + CRASH_MEMORY_LENGTH +
|
|
roundup(dram_size, 4);
|
|
}
|
|
#else
|
|
scp_dump_size = 0;
|
|
#endif
|
|
exception_header_init(pMemoryDump, id);
|
|
/* init sub header*/
|
|
scp_sub_header_init(&(pMemoryDump->scp_dump_header));
|
|
|
|
memcpy_from_scp((void *)&(pMemoryDump->memory),
|
|
(void *)(SCP_TCM + CRASH_MEMORY_OFFSET),
|
|
(SCP_A_TCM_SIZE - CRASH_MEMORY_OFFSET));
|
|
|
|
/* L1C support? */
|
|
if (dram_size) {
|
|
/* l1c enable,flua l1c */
|
|
scp_l1c_flua(SCP_DL1C);
|
|
scp_l1c_flua(SCP_IL1C);
|
|
udelay(10);
|
|
#if SCP_RECOVERY_SUPPORT
|
|
pr_debug("scp:scp_l1c_start_virt %p\n",
|
|
scp_l1c_start_virt);
|
|
/* copy dram data*/
|
|
memcpy((void *)&(pMemoryDump->scp_reg_dump),
|
|
scp_l1c_start_virt, dram_size);
|
|
/* dump scp reg */
|
|
#endif
|
|
scp_reg_copy((void *)(&pMemoryDump->scp_reg_dump) +
|
|
roundup(dram_size, 4));
|
|
} else {
|
|
/* dump scp reg */
|
|
scp_reg_copy(&(pMemoryDump->scp_reg_dump));
|
|
}
|
|
|
|
*reg = lock;
|
|
dsb(SY);
|
|
/*check SRAM unlock*/
|
|
if (scp_awake_fail_flag != 1) {
|
|
if (scp_awake_unlock(id) == -1)
|
|
pr_debug("[SCP]%s awake unlock fail, scp id=%u\n",
|
|
__func__, id);
|
|
}
|
|
|
|
return scp_dump_size;
|
|
}
|
|
/*
|
|
* generate aee argument without dump scp register
|
|
* @param aed_str: exception description
|
|
* @param aed: struct to store argument for aee api
|
|
*/
|
|
static void scp_prepare_aed(char *aed_str, struct scp_aed_cfg *aed)
|
|
{
|
|
char *detail, *log;
|
|
u8 *phy;
|
|
u32 log_size, phy_size;
|
|
int ret = 0;
|
|
|
|
pr_debug("[SCP] %s begins\n", __func__);
|
|
|
|
aed->detail = NULL;
|
|
detail = scp_A_detail_buffer;
|
|
if (!detail)
|
|
return;
|
|
|
|
memset(detail, 0, SCP_AED_STR_LEN);
|
|
ret = snprintf(detail, SCP_AED_STR_LEN, "%s\n", aed_str);
|
|
if (ret < 0)
|
|
pr_err("[SCP] detail msg is truncated\n");
|
|
|
|
detail[SCP_AED_STR_LEN - 1] = '\0';
|
|
|
|
log_size = 0;
|
|
log = NULL;
|
|
|
|
phy_size = 0;
|
|
phy = NULL;
|
|
|
|
aed->log = (int *)log;
|
|
aed->log_size = log_size;
|
|
aed->phy = (int *)phy;
|
|
aed->phy_size = phy_size;
|
|
aed->detail = detail;
|
|
|
|
pr_debug("[SCP] %s ends\n", __func__);
|
|
}
|
|
|
|
/*
|
|
* generate aee argument with scp register dump
|
|
* @param aed_str: exception description
|
|
* @param aed: struct to store argument for aee api
|
|
* @param id: identify scp core id
|
|
*/
|
|
static void scp_prepare_aed_dump(char *aed_str,
|
|
struct scp_aed_cfg *aed,
|
|
enum scp_core_id id)
|
|
{
|
|
u8 *scp_detail;
|
|
u8 *scp_dump_ptr;
|
|
int ret = 0;
|
|
|
|
u32 memory_dump_size;
|
|
struct MemoryDump *pMemoryDump = NULL;
|
|
|
|
char *scp_A_log = NULL;
|
|
|
|
pr_debug("[SCP] %s begins:%s\n", __func__, aed_str);
|
|
scp_aee_last_reg();
|
|
|
|
scp_A_log = scp_get_last_log(SCP_A_ID);
|
|
|
|
|
|
/* prepare scp aee detail information */
|
|
scp_detail = scp_A_detail_buffer;
|
|
|
|
if (!scp_detail) {
|
|
pr_err("[SCP AEE]detail buf is null\n");
|
|
} else {
|
|
/* prepare scp aee detail information*/
|
|
memset(scp_detail, 0, SCP_AED_STR_LEN);
|
|
|
|
ret = snprintf(scp_detail, SCP_AED_STR_LEN,
|
|
"%s\nscp_A pc=0x%08x, lr=0x%08x, psp=0x%08x, sp=0x%08x\n"
|
|
"last log:\n%s",
|
|
aed_str, scp_A_aee_status.pc,
|
|
scp_A_aee_status.lr,
|
|
scp_A_aee_status.psp,
|
|
scp_A_aee_status.sp,
|
|
scp_A_log);
|
|
|
|
if (ret < 0)
|
|
pr_err("[SCP AEE] detail buf msg is truncated\n");
|
|
|
|
scp_detail[SCP_AED_STR_LEN - 1] = '\0';
|
|
}
|
|
|
|
/*prepare scp A db file*/
|
|
memory_dump_size = 0;
|
|
scp_dump_ptr = scp_A_dump_buffer_last;
|
|
if (!scp_dump_ptr) {
|
|
pr_err("[SCP AEE]MemoryDump buf is null, size=0x%x\n",
|
|
memory_dump_size);
|
|
memory_dump_size = 0;
|
|
} else {
|
|
pr_debug("[SCP AEE]scp A dump ptr:%p\n", scp_dump_ptr);
|
|
pMemoryDump = (struct MemoryDump *) scp_dump_ptr;
|
|
memset(pMemoryDump, 0x0, sizeof(*pMemoryDump) + CRASH_MEMORY_LENGTH);
|
|
memory_dump_size = scp_crash_dump(pMemoryDump, SCP_A_ID);
|
|
}
|
|
/* scp_dump_buffer_set */
|
|
mutex_lock(&scp_A_excep_dump_mutex);
|
|
scp_A_dump_buffer_last = scp_A_dump_buffer;
|
|
scp_A_dump_buffer = scp_dump_ptr;
|
|
scp_A_dump_length = memory_dump_size;
|
|
mutex_unlock(&scp_A_excep_dump_mutex);
|
|
|
|
|
|
aed->log = NULL;
|
|
aed->log_size = 0;
|
|
aed->phy = NULL;
|
|
aed->phy_size = 0;
|
|
aed->detail = scp_detail;
|
|
aed->pMemoryDump = NULL;
|
|
aed->memory_dump_size = 0;
|
|
|
|
pr_debug("[SCP] %s ends\n", __func__);
|
|
}
|
|
|
|
/*
|
|
* generate an exception according to exception type
|
|
* @param type: exception type
|
|
*/
|
|
void scp_aed(enum scp_excep_id type, enum scp_core_id id)
|
|
{
|
|
struct scp_aed_cfg aed;
|
|
char *scp_aed_title;
|
|
|
|
mutex_lock(&scp_excep_mutex);
|
|
|
|
/* get scp title and exception type*/
|
|
switch (type) {
|
|
case EXCEP_LOAD_FIRMWARE:
|
|
scp_prepare_aed("scp firmware load exception", &aed);
|
|
if (id == SCP_A_ID)
|
|
scp_aed_title = "SCP_A load firmware exception";
|
|
else
|
|
scp_aed_title = "SCP load firmware exception";
|
|
break;
|
|
case EXCEP_RESET:
|
|
if (id == SCP_A_ID)
|
|
scp_aed_title = "SCP_A reset exception";
|
|
else
|
|
scp_aed_title = "SCP reset exception";
|
|
break;
|
|
case EXCEP_BOOTUP:
|
|
if (id == SCP_A_ID)
|
|
scp_aed_title = "SCP_A boot exception";
|
|
else
|
|
scp_aed_title = "SCP boot exception";
|
|
scp_get_log(id);
|
|
break;
|
|
case EXCEP_RUNTIME:
|
|
if (id == SCP_A_ID)
|
|
scp_aed_title = "SCP_A runtime exception";
|
|
else
|
|
scp_aed_title = "SCP runtime exception";
|
|
scp_get_log(id);
|
|
break;
|
|
default:
|
|
scp_prepare_aed("scp unknown exception", &aed);
|
|
if (id == SCP_A_ID)
|
|
scp_aed_title = "SCP_A unknown exception";
|
|
else
|
|
scp_aed_title = "SCP unknown exception";
|
|
break;
|
|
}
|
|
/*print scp message*/
|
|
pr_debug("scp_aed_title=%s", scp_aed_title);
|
|
|
|
if (type != EXCEP_LOAD_FIRMWARE)
|
|
scp_prepare_aed_dump(scp_aed_title, &aed, id);
|
|
/*print detail info. in kernel*/
|
|
pr_debug("%s", aed.detail);
|
|
|
|
#if IS_ENABLED(CONFIG_MTK_AEE_FEATURE)
|
|
/* scp aed api, only detail information available*/
|
|
aed_common_exception_api("scp", NULL, 0, NULL, 0,
|
|
aed.detail, DB_OPT_DEFAULT);
|
|
#endif
|
|
pr_debug("[SCP] scp exception dump is done\n");
|
|
|
|
mutex_unlock(&scp_excep_mutex);
|
|
}
|
|
|
|
/*
|
|
* generate an exception and reset scp right now
|
|
* NOTE: this function may be blocked and
|
|
* should not be called in interrupt context
|
|
* @param type: exception type
|
|
*/
|
|
void scp_aed_reset_inplace(enum scp_excep_id type, enum scp_core_id id)
|
|
{
|
|
pr_debug("[SCP] %s begins\n", __func__);
|
|
if (scp_ee_enable)
|
|
scp_aed(type, id);
|
|
else
|
|
pr_debug("[SCP]ee disable value=%d\n", scp_ee_enable);
|
|
|
|
#if (SCP_RECOVERY_SUPPORT == 0)
|
|
/* workaround for QA, not reset SCP in WDT */
|
|
if (type == EXCEP_RUNTIME)
|
|
return;
|
|
|
|
#endif
|
|
|
|
#if SCP_RECOVERY_SUPPORT
|
|
if (atomic_read(&scp_reset_status) == RESET_STATUS_START) {
|
|
/*complete scp ee, if scp reset by wdt or awake fail*/
|
|
pr_debug("[SCP]aed finished, complete it\n");
|
|
complete(&scp_sys_reset_cp);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* callback function for work struct
|
|
* generate an exception and reset scp
|
|
* NOTE: this function may be blocked
|
|
* and should not be called in interrupt context
|
|
* @param ws: work struct
|
|
*/
|
|
static void scp_aed_reset_ws(struct work_struct *ws)
|
|
{
|
|
struct scp_work_struct *sws = container_of(ws,
|
|
struct scp_work_struct, work);
|
|
enum scp_excep_id type = (enum scp_excep_id) sws->flags;
|
|
enum scp_core_id id = (enum scp_core_id) sws->id;
|
|
|
|
pr_debug("[SCP] %s begins: scp_excep_id=%u scp_core_id=%u\n",
|
|
__func__, type, id);
|
|
scp_aed_reset_inplace(type, id);
|
|
}
|
|
|
|
|
|
/*
|
|
* schedule a work to generate an exception and reset scp
|
|
* @param type: exception type
|
|
*/
|
|
void scp_aed_reset(enum scp_excep_id type, enum scp_core_id id)
|
|
{
|
|
scp_aed_work.flags = (unsigned int) type;
|
|
scp_aed_work.id = (unsigned int) id;
|
|
scp_schedule_work(&scp_aed_work);
|
|
}
|
|
|
|
static ssize_t scp_A_dump_show(struct file *filep,
|
|
struct kobject *kobj, struct bin_attribute *attr,
|
|
char *buf, loff_t offset, size_t size)
|
|
{
|
|
unsigned int length = 0;
|
|
|
|
mutex_lock(&scp_A_excep_dump_mutex);
|
|
|
|
if (offset >= 0 && offset < scp_A_dump_length) {
|
|
if ((offset + size) > scp_A_dump_length)
|
|
size = scp_A_dump_length - offset;
|
|
|
|
memcpy(buf, scp_A_dump_buffer + offset, size);
|
|
length = size;
|
|
}
|
|
|
|
mutex_unlock(&scp_A_excep_dump_mutex);
|
|
|
|
return length;
|
|
}
|
|
|
|
|
|
struct bin_attribute bin_attr_scp_dump = {
|
|
.attr = {
|
|
.name = "scp_dump",
|
|
.mode = 0444,
|
|
},
|
|
.size = 0,
|
|
.read = scp_A_dump_show,
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
* init a work struct
|
|
*/
|
|
int scp_excep_init(void)
|
|
{
|
|
int dram_size = 0;
|
|
|
|
mutex_init(&scp_excep_mutex);
|
|
mutex_init(&scp_A_excep_dump_mutex);
|
|
|
|
INIT_WORK(&scp_aed_work.work, scp_aed_reset_ws);
|
|
|
|
|
|
#if SCP_RECOVERY_SUPPORT
|
|
/* support L1C or not? */
|
|
if ((int)(scp_region_info->ap_dram_size) > 0)
|
|
dram_size = scp_region_info->ap_dram_size;
|
|
#else
|
|
dram_size = 0;
|
|
#endif
|
|
|
|
/* alloc dump memory */
|
|
scp_A_detail_buffer = vmalloc(SCP_AED_STR_LEN);
|
|
if (!scp_A_detail_buffer)
|
|
return -1;
|
|
|
|
scp_A_dump_buffer = vmalloc(sizeof(struct MemoryDump) +
|
|
CRASH_MEMORY_LENGTH + roundup(dram_size, 4));
|
|
if (!scp_A_dump_buffer)
|
|
goto _err;
|
|
|
|
scp_A_dump_buffer_last = vmalloc(sizeof(struct MemoryDump) +
|
|
CRASH_MEMORY_LENGTH + roundup(dram_size, 4));
|
|
if (!scp_A_dump_buffer_last)
|
|
goto _err1;
|
|
|
|
/* init global values */
|
|
scp_A_dump_length = 0;
|
|
/* 1: ee on, 0: ee disable */
|
|
scp_ee_enable = 1;
|
|
|
|
return 0;
|
|
|
|
_err1:
|
|
vfree(scp_A_dump_buffer);
|
|
_err:
|
|
vfree(scp_A_detail_buffer);
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* This function is called in the interrupt context. Note that scp_region_info
|
|
* was initialized in scp_region_info_init() which must be called before this
|
|
* function is called.
|
|
*****************************************************************************/
|
|
void scp_ram_dump_init(void)
|
|
{
|
|
#if SCP_RECOVERY_SUPPORT
|
|
scp_A_task_context_addr = scp_region_info->TaskContext_ptr;
|
|
pr_debug("[SCP] get scp_A_task_context_addr: 0x%x\n",
|
|
scp_A_task_context_addr);
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* cleanup scp exception
|
|
*/
|
|
void scp_excep_cleanup(void)
|
|
{
|
|
vfree(scp_A_detail_buffer);
|
|
vfree(scp_A_dump_buffer_last);
|
|
vfree(scp_A_dump_buffer);
|
|
|
|
scp_A_task_context_addr = 0;
|
|
|
|
pr_debug("[SCP] %s ends\n", __func__);
|
|
}
|
|
|