diff --git a/drivers/watchdog_cpu_ctx/Android.mk b/drivers/watchdog_cpu_ctx/Android.mk new file mode 100644 index 000000000000..9adfd2643398 --- /dev/null +++ b/drivers/watchdog_cpu_ctx/Android.mk @@ -0,0 +1,8 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := watchdog_cpu_ctx.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk diff --git a/drivers/watchdog_cpu_ctx/Kbuild b/drivers/watchdog_cpu_ctx/Kbuild new file mode 100644 index 000000000000..48830cc18184 --- /dev/null +++ b/drivers/watchdog_cpu_ctx/Kbuild @@ -0,0 +1,5 @@ +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += -I$(TOP)/motorola/kernel/modules/include + +obj-m += watchdog_cpu_ctx.o \ No newline at end of file diff --git a/drivers/watchdog_cpu_ctx/Makefile b/drivers/watchdog_cpu_ctx/Makefile new file mode 100644 index 000000000000..34b09dd815a7 --- /dev/null +++ b/drivers/watchdog_cpu_ctx/Makefile @@ -0,0 +1,10 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean \ No newline at end of file diff --git a/drivers/watchdog_cpu_ctx/watchdog_cpu_ctx.c b/drivers/watchdog_cpu_ctx/watchdog_cpu_ctx.c new file mode 100644 index 000000000000..6614a5ed85cd --- /dev/null +++ b/drivers/watchdog_cpu_ctx/watchdog_cpu_ctx.c @@ -0,0 +1,667 @@ +/* + * Copyright (C) 2018 Motorola Mobility LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "watchdog_cpu_ctx.h" + +#define MSMDBG(fmt, args...) mmi_annotate(fmt, ##args) + +#define MSMWDT_ERR(fmt, args...) do { \ + pr_err("WdogCtx: "fmt, ##args); \ + MSMWDTD("WdogCtx: "fmt, ##args); \ +} while (0) + +#define MSMWDTD_IFWDOG(fmt, args...) do { \ + if (bi_powerup_reason() == PU_REASON_WDOG_AP_RESET) \ + MSMDBG(fmt, ##args); \ +} while (0) + +#define MSMWDTD(fmt, args...) MSMWDTD_IFWDOG(fmt, ##args) + +/* Check memory_dump.h to verify this is not going over the max or + * conflicting with another entry. Also must match BL + */ +#define MSM_DUMP_DATA_WDOG_CPU_CTX 0x200 + +struct platform_data { + phys_addr_t mem_address; + size_t mem_size; +}; + +const struct msm_wdog_cpuctx_header mwc_header[] = { + { + .type = CPUCTX_LNX_INFO, + .magic = CPUCTX_LNX_INFO_MAGIC, + .offset = offsetof(struct msm_wdog_cpuctx, lnx), + .length = sizeof(struct msm_wdog_lnx_info), + }, + { + .type = CPUCTX_STAT, + .magic = CPUCTX_STAT_MAGIC, + .offset = offsetof(struct msm_wdog_cpuctx, stat), + .length = sizeof(struct msm_wdog_cpuctx_stat), + }, + { + .type = CPUCTX_DUMP_DATA, + .magic = CPUCTX_DUMP_DATA_MAGIC, + .offset = offsetof(struct msm_wdog_cpuctx, cpu_data), + .length = sizeof(struct msm_dump_data), + }, + { + .type = CPUCTX_TASK_STRUCT, + .magic = CPUCTX_TASK_STRUCT_MAGIC, + .offset = offsetof(struct msm_wdog_cpuctx, task), + .length = sizeof(struct task_struct), + }, + { + .type = CPUCTX_THREAD, + .magic = CPUCTX_THREAD_MAGIC, + .offset = offsetof(struct msm_wdog_cpuctx, stack), + .length = THREAD_SIZE, + }, +}; + +static int aa64_pa_max(void) +{ +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,9,0) + int pa_range = read_cpuid(SYS_ID_AA64MMFR0_EL1) & 0xf; +#else + int pa_range = read_cpuid(ID_AA64MMFR0_EL1) & 0xf; +#endif + switch (pa_range) { + case 0x0: + return 32; + case 0x1: + return 36; + case 0x2: + return 40; + case 0x3: + return 42; + case 0x4: + return 44; + case 0x5: + return 48; + default: + pr_err("PAMax: Reserved PARange: %#x\n", pa_range); + break; + } + return 32; +} + +static unsigned long get_kaslr_offset(void) +{ + void __iomem *kaslr_addr; + unsigned kaslr_magic_num; + unsigned kaslr_offset_low = 0; + unsigned kaslr_offset_high = 0; + + kaslr_addr = ioremap(KASLR_PHYS_ADDR, KASLR_REGION_LEN); + if(!kaslr_addr) + goto out; + + kaslr_magic_num = ioread32(kaslr_addr); + if(kaslr_magic_num != KASLR_MAGIC_NUM) + goto unmap; + + kaslr_offset_low = ioread32(kaslr_addr + 4); + kaslr_offset_high = ioread32(kaslr_addr + 8); +unmap: + iounmap(kaslr_addr); +out: + return ((unsigned long)kaslr_offset_high << 32 | + (unsigned long)kaslr_offset_low); +} + +static void msm_wdog_ctx_lnx(struct msm_wdog_lnx_info *lnx) +{ + /* Since this is a module we cannot link to these symbols + * directly, so use kallsyms. kallsyms is needed to find the + * names of the stacktrace symbols anyways. + */ + unsigned long _text_addr = kallsyms_lookup_name("_text"); + /* This is a way to get the address of swapper_pg_dir which + * is not available via kallsyms_lookup_name directly + */ + unsigned long init_mm_addr = kallsyms_lookup_name("init_mm"); + struct mm_struct *init_mm = NULL; + if(init_mm_addr) { + init_mm = (struct mm_struct *)init_mm_addr; + } + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,9,0) + lnx->ti_tsk_offset = offsetof(struct thread_info, task); +#endif + lnx->tsk_size = sizeof(struct task_struct); + lnx->aa64 = 1; + lnx->lpae = 0; + lnx->text_paddr = _text_addr ? virt_to_phys((void *)_text_addr) : 0; + lnx->pgd_paddr = init_mm ? virt_to_phys(init_mm->pgd) : 0; + lnx->page_shift = PAGE_SHIFT; + lnx->thread_size = THREAD_SIZE; + lnx->va_bits = VA_BITS; + lnx->pa_bits = aa64_pa_max(); + lnx->kaslr_offset = get_kaslr_offset(); +} + +static void msm_wdog_ctx_header_init(struct msm_wdog_cpuctx *ctxi) +{ + struct msm_wdog_cpuctx_header *header = &ctxi->header[0]; + + memset_io(header, 0, sizeof(*header)); + memcpy(header, mwc_header, sizeof(mwc_header)); +} + +static void msm_wdog_ctx_reset(struct msm_wdog_cpuctx *ctx, size_t ctx_size) +{ + struct msm_wdog_cpuctx *ctxi; + int cpu; + + memset_io(ctx, 0, ctx_size); + for_each_cpu(cpu, cpu_present_mask) { + ctxi = &ctx[cpu]; + ctxi->info.sig = MSM_WDOG_CTX_SIG; + ctxi->info.rev = MSM_WDOG_CTX_REV; + ctxi->info.size = WDOG_CPUCTX_SIZE_PERCPU; +#if IS_ENABLED(CONFIG_KALLSYMS) + ctxi->info.syms_avail = 1; +#else + ctxi->info.syms_avail = 0; +#endif + msm_wdog_ctx_header_init(ctxi); + msm_wdog_ctx_lnx(&ctxi->lnx); + + if(!ctxi->lnx.text_paddr || + !ctxi->lnx.pgd_paddr || + !ctxi->lnx.kaslr_offset) + ctxi->info.syms_avail = 0; + } +} + +static inline int virt_is_valid(unsigned long addr) +{ + return (addr >= VA_START); +} + +static int msm_wdog_cpu_regs_version_unknown(uint32_t version) +{ + if (version != CPU_FORMAT_VERSION4) + return 1; + return 0; +} + +static int msm_wdog_ctx_header_check(struct msm_wdog_cpuctx *ctxi) +{ + struct msm_wdog_cpuctx_header *header = &ctxi->header[0]; + return memcmp(header, mwc_header, sizeof(mwc_header)); +} + +static void msm_wdog_show_sc_status(uint32_t sc_status) +{ + if (sc_status & SC_STATUS_DBI) + MSMWDTD("SDI: Secure watchdog bite. "); + if (sc_status & SC_STATUS_CTX_BY_TZ) + MSMWDTD("TZ: Non-secure watchdog bite. "); + MSMWDTD("%sS ", (sc_status & SC_STATUS_NS) ? "N" : " "); + if (sc_status & SC_STATUS_WDT) + MSMWDTD("WDT "); + if (sc_status & SC_STATUS_SGI) + MSMWDTD("SGI "); + if (sc_status & SC_STATUS_WARM_BOOT) + MSMWDTD("WARM_BOOT "); + MSMWDTD("\n"); +} + +static void msm_wdt_show_thread_saved_pc(struct task_struct *p) +{ + MSMWDTD(" %016lx ", (unsigned long)p->thread.cpu_context.pc); +} + +static int msm_wdt_unwind_frame_aa64(struct stackframe *frame, + unsigned long stack) +{ + unsigned long high, low; + unsigned long fp = frame->fp; + + low = frame->sp; + high = ALIGN(low, THREAD_SIZE) - 0x20; + + if (fp < low || fp > high || fp & 0xf) + return -EINVAL; + + frame->sp = fp + 0x10; + frame->fp = (*(unsigned long *)(fp) & (THREAD_SIZE - 1)) + stack; + frame->pc = *(unsigned long *)(fp + 8); + + return 0; +} + +static void msm_wdt_show_raw_mem(unsigned long addr, int nbytes, + unsigned long old_addr, const char *label) +{ + int i, j; + int nlines; + unsigned long *p; + + MSMWDTD("%s: %#lx: ", label, old_addr); + if (!virt_is_valid(old_addr)) { + MSMWDTD("is not valid kernel address.\n"); + return; + } + MSMWDTD("\n"); + + /* + * round address down to unsigned long aligned boundary + * and always dump a multiple of 32 bytes + */ + p = (unsigned long *)(addr & ~(sizeof(unsigned long) - 1)); + nbytes += (addr & (sizeof(unsigned long) - 1)); + nlines = (nbytes + 31) / 32; + + for (i = 0; i < nlines; i++) { + /* + * just display low 16 bits of address to keep + * each line of the dump < 80 characters + */ + MSMWDTD("%04lx ", (unsigned long)old_addr & 0xffff); + for (j = 0; j < (32 / sizeof(unsigned long)); j++) { + if (sizeof(unsigned long) == 4) + MSMWDTD(" %08lx", *p); + else + MSMWDTD(" %016lx", *p); + ++p; + old_addr += sizeof(*p); + } + MSMWDTD("\n"); + } +} + +static void msm_wdt_show_regs(struct sysdbgCPUCtxtType *sysdbg_ctx) +{ + struct sysdbg_cpu_ctxt_regs *regs = &sysdbg_ctx->cpu_regs; + uint64_t *gp_reg; + int i; + + MSMWDTD("PC is at %016llx\n", regs->pc); + MSMWDTD("sp : %016llx ", regs->sp_el1); + MSMWDTD("x30: %016llx\n", regs->x30); + gp_reg = (uint64_t *)regs; + for (i = 29; i >= 0; i--) { + MSMWDTD("x%-2d: %016llx ", i, gp_reg[i]); + if (i % 2 == 0) + MSMWDTD("\n"); + } + MSMWDTD("currentEL : %016llx\n", regs->currentEL); + MSMWDTD("EL3: elr : %016llx spsr : %016llx sp : %016llx\n", + regs->elr_el3, regs->spsr_el3, regs->sp_el3); + MSMWDTD("EL2: elr : %016llx spsr : %016llx sp : %016llx\n", + regs->elr_el2, regs->spsr_el2, regs->sp_el2); + MSMWDTD("EL1: elr : %016llx spsr : %016llx sp : %016llx\n", + regs->elr_el1, regs->spsr_el1, regs->sp_el1); + MSMWDTD("EL0: sp : %016llx\n", regs->sp_el0); +} + +static void msm_wdt_show_task(struct task_struct *p, struct thread_info *ti) +{ + unsigned state; + const char *stat_nam = TASK_STATE_TO_CHAR_STR; + + state = p->state ? __ffs(p->state) + 1 : 0; + MSMWDTD("%-15.15s %c", p->comm, + state < strlen(stat_nam) - 1 ? stat_nam[state] : '?'); + if (state == TASK_RUNNING) + MSMWDTD(" running "); + else + msm_wdt_show_thread_saved_pc(p); + MSMWDTD("pid %6d tgid %6d 0x%08lx\n", task_pid_nr(p), task_tgid_nr(p), + (unsigned long)ti->flags); +} + +static void msm_wdt_unwind(struct sysdbgCPUCtxtType *sysdbg_ctx, + unsigned long addr, unsigned long kaslr_offset, + unsigned long stack) +{ + struct stackframe frame; + int offset; + char sym_buf[KSYM_NAME_LEN]; + struct sysdbg_cpu_ctxt_regs *regs = &sysdbg_ctx->cpu_regs; + unsigned long cur_kaslr_offset = get_kaslr_offset(); + + if (!virt_is_valid(addr)) { + MSMWDTD("%016lx is not valid kernel address.\n", addr); + return; + } + + if ((regs->sp_el1 & ~(THREAD_SIZE - 1)) == addr) { + frame.fp = (regs->x29 & (THREAD_SIZE - 1)) + stack; + frame.sp = (regs->sp_el1 & (THREAD_SIZE - 1)) + stack; + frame.pc = regs->pc; + } + else { + MSMWDTD("Unexpected stack address\n"); + return; + } + + offset = (frame.sp - stack - 128) & ~(128 - 1); + msm_wdt_show_raw_mem(stack, 96, addr, "thread_info"); + msm_wdt_show_raw_mem(stack + offset, THREAD_SIZE - offset, + addr + offset, "stack"); + + sprint_symbol(sym_buf, + regs->pc - kaslr_offset + cur_kaslr_offset); + MSMWDTD("PC: %s <%016llx>\n", sym_buf, regs->pc); + sprint_symbol(sym_buf, + regs->x30 - kaslr_offset + cur_kaslr_offset); + MSMWDTD("LR: %s <%016llx>\n", sym_buf, regs->x30); + + while (1) { + int urc; + unsigned long where = frame.pc; + + /* Symbols are located at a random offset...subtract + * the old one and add the current one + */ + sprint_symbol(sym_buf, + frame.pc - kaslr_offset + cur_kaslr_offset); + MSMWDTD("[<%016lx>] %s\n", where, sym_buf); + + urc = msm_wdt_unwind_frame_aa64(&frame, stack); + if (urc < 0) + break; + } +} + +static void msm_wdog_ctx_print(struct msm_wdog_cpuctx *ctx, + phys_addr_t paddr, size_t ctx_size) +{ + struct msm_wdog_cpuctx *ctxi; + struct msm_dump_data *cpu_data; + struct msm_wdog_cpuctx_info *info; + struct msm_wdog_cpuctx_stat *stat; + cpumask_t cpus, cpus_nodump, cpus_regs, cpus_dd; + unsigned long stack_tmp = 0; + int cpu; + + cpumask_clear(&cpus); + for_each_cpu(cpu, cpu_present_mask) { + ctxi = &ctx[cpu]; + cpu_data = &ctxi->cpu_data; + if (msm_wdog_ctx_header_check(ctxi)) { + MSMWDTD_IFWDOG("CPU%d: ctx header invalid\n", cpu); + continue; + } + + info = &ctxi->info; + if ((info->sig != MSM_WDOG_CTX_SIG) || + (info->rev2 != MSM_WDOG_CTX_REV) || + (info->rev != MSM_WDOG_CTX_REV) || + (info->size != WDOG_CPUCTX_SIZE_PERCPU) || + (info->ret != ERR_NONE)) { + MSMWDTD_IFWDOG("CPU%d: sig %x rev %x/%x sz %x ret %x\n", + cpu, info->sig, (unsigned)info->rev, + info->rev2, info->size, info->ret); + continue; + } + cpumask_set_cpu(cpu, &cpus); + } + + if (cpumask_empty(&cpus)) + return; + + cpumask_clear(&cpus_nodump); + cpumask_clear(&cpus_regs); + cpumask_clear(&cpus_dd); + + for_each_cpu(cpu, &cpus) { + uint32_t *status; + ctxi = &ctx[cpu]; + cpu_data = &ctxi->cpu_data; + stat = &ctxi->stat; + + if (!cpu_data->magic && !cpu_data->version && + !ctxi->sysdbg.data.status[0]) { + MSMWDTD_IFWDOG("CPU%d: No Dump!\n", cpu); + cpumask_set_cpu(cpu, &cpus_nodump); + continue; + } + if (cpu_data->magic != DUMP_MAGIC_NUMBER) { + MSMWDTD_IFWDOG("CPU%d: dump magic mismatch %x/%x\n", + cpu, cpu_data->magic, DUMP_MAGIC_NUMBER); + continue; + } + if (msm_wdog_cpu_regs_version_unknown(cpu_data->version)) { + MSMWDTD_IFWDOG("CPU%d: unknown version %d\n", + cpu, cpu_data->version); + continue; + } + cpumask_set_cpu(cpu, &cpus_regs); + status = &ctxi->sysdbg.data.status[0]; + MSMWDTD("CPU%d: %x %x ", cpu, status[0], status[1]); + msm_wdog_show_sc_status(status[1]); + if (stat->ret == ERR_NONE || stat->ret == ERR_TASK_INVAL) + cpumask_set_cpu(cpu, &cpus_dd); + } + + if (cpumask_equal(&cpus_nodump, cpu_present_mask)) { + MSMWDTD_IFWDOG("Might be Secure Watchdog Bite!\n"); + return; + } + if (cpumask_empty(&cpus_regs)) + return; + MSMWDTD("\n"); + for_each_cpu(cpu, &cpus_regs) { + struct msm_wdog_copy *job; + + ctxi = &ctx[cpu]; + stat = &ctxi->stat; + MSMWDTD("CPU%d: ret %x", cpu, stat->ret); + if (stat->stack_va) { + MSMWDTD(" stack %lx ", (unsigned long)stat->stack_va); + job = &stat->jobs[LNX_STACK]; + MSMWDTD("%lx -> %lx (%lx) ", (unsigned long)job->from, + (unsigned long)job->to, + (unsigned long)job->size); + job = &stat->jobs[LNX_TASK]; + MSMWDTD("%lx -> %lx (%lx)", (unsigned long)job->from, + (unsigned long)job->to, + (unsigned long)job->size); + } + MSMWDTD("\n"); + } + for_each_cpu(cpu, &cpus_regs) { + ctxi = &ctx[cpu]; + MSMWDTD("\nCPU%d\n", cpu); + msm_wdt_show_regs(&ctxi->sysdbg.data); + } + for_each_cpu(cpu, &cpus_dd) { + unsigned long data; + + ctxi = &ctx[cpu]; + stat = &ctxi->stat; + if (!IS_ALIGNED((unsigned long)ctxi->stack, THREAD_SIZE) + && !stack_tmp) { + stack_tmp = __get_free_pages(GFP_KERNEL, + THREAD_SIZE_ORDER); + if (!stack_tmp) + MSMWDT_ERR("Alloc temp stack failed.\n"); + } + if (stack_tmp) { + memcpy_fromio((void *)stack_tmp, ctxi->stack, + THREAD_SIZE); + data = stack_tmp; + } else { + data = (unsigned long)ctxi->stack; + } + + MSMWDTD("\nCPU%d\n", cpu); + if(stat->ret != ERR_TASK_INVAL) { +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,9,0) + msm_wdt_show_task(&ctxi->task, + (struct thread_info *)ctxi->stack); +#else + msm_wdt_show_task(&ctxi->task, + &ctxi->task.thread_info); +#endif + } + msm_wdt_unwind(&ctxi->sysdbg.data, ctxi->stat.stack_va, + ctx->lnx.kaslr_offset, data); + } + MSMWDTD("\n"); + if (stack_tmp) + free_pages(stack_tmp, THREAD_SIZE_ORDER); +} + +static void watchdog_cpu_ctx_table_register(struct device *dev, + struct platform_data *pdata) +{ + int err = 0; + struct msm_dump_entry dump_entry; + struct msm_dump_data *ctx_dump_data; + + ctx_dump_data = kzalloc(sizeof(struct msm_dump_data), + GFP_KERNEL); + + if (!ctx_dump_data) { + dev_err(dev, "Cannot alloc dump data structure.\n"); + goto err; + } + + ctx_dump_data->addr = pdata->mem_address; + ctx_dump_data->len = pdata->mem_size; + dump_entry.id = MSM_DUMP_DATA_WDOG_CPU_CTX; + dump_entry.addr = virt_to_phys(ctx_dump_data); + err = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); + if (err) { + dev_err(dev, "Registering dump data failed.\n"); + kfree(ctx_dump_data); + } +err: + return; +} + +static int watchdog_cpu_ctx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct platform_data *pdata; + struct resource res; + struct device_node *node; + struct msm_wdog_cpuctx *ctx_vaddr; + int err = 0; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + err = -ENOMEM; + goto err; + } + + /* Get reserved memory region from Device-tree */ + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) { + dev_err(dev, "No %s specified\n", "memory-region"); + goto err; + } + + err = of_address_to_resource(node, 0, &res); + of_node_put(node); + if (err) { + dev_err(dev, "No memory address assigned to the region\n"); + goto err; + } + + pdata->mem_size = resource_size(&res); + pdata->mem_address = res.start; + + dev_info(dev, "size %lx", pdata->mem_size); + dev_info(dev, "addr %lx", (unsigned long)pdata->mem_address); + dev_info(dev, "size per cpu is %lx", WDOG_CPUCTX_SIZE_PERCPU); + + if (pdata->mem_size < WDOG_CPUCTX_SIZE) { + dev_err(dev, "Mem size too small %zx/%lu\n", + pdata->mem_size, WDOG_CPUCTX_SIZE); + err = -ENOMEM; + goto err; + } + + watchdog_cpu_ctx_table_register(dev, pdata); + + ctx_vaddr = dma_remap(dev, NULL, pdata->mem_address, + pdata->mem_size, 0); + if (!ctx_vaddr) { + dev_err(dev, "Cannot remap buffer %pa size %zx\n", + &pdata->mem_address, pdata->mem_size); + err = -ENOMEM; + goto err; + } + + msm_wdog_ctx_print(ctx_vaddr, pdata->mem_address, pdata->mem_size); + msm_wdog_ctx_reset(ctx_vaddr, pdata->mem_size); + +err: + return err; +} + +static int watchdog_cpu_ctx_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id watchdog_cpu_ctx_match[] = { + { .compatible = "mmi,watchdog_cpu_ctx" }, + {} +}; + +static struct platform_driver watchdog_cpu_ctx_driver = { + .probe = watchdog_cpu_ctx_probe, + .remove = watchdog_cpu_ctx_remove, + .driver = { + .name = "watchdog_cpu_ctx", + .of_match_table = watchdog_cpu_ctx_match, + }, +}; + +static int watchdog_cpu_ctx_init(void) +{ + return platform_driver_register(&watchdog_cpu_ctx_driver); +} + +static void watchdog_cpu_ctx_exit(void) +{ + platform_driver_unregister(&watchdog_cpu_ctx_driver); +} + +module_init(watchdog_cpu_ctx_init); +module_exit(watchdog_cpu_ctx_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Watchdog cpu ctx"); diff --git a/drivers/watchdog_cpu_ctx/watchdog_cpu_ctx.h b/drivers/watchdog_cpu_ctx/watchdog_cpu_ctx.h new file mode 100644 index 000000000000..5a59245650cd --- /dev/null +++ b/drivers/watchdog_cpu_ctx/watchdog_cpu_ctx.h @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2018 Motorola Mobility LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __WATCHDOG_CPU_CTX_H +#define __WATCHDOG_CPU_CTX_H + +#include +#include + +#define MAX_CPU_CTX_SIZE 2048 +#define CPU_FORMAT_VERSION4 0x14 + +#define DUMP_MAGIC_NUMBER 0x42445953 +#define CPUCTX_LNX_INFO_MAGIC 0x4c4e5831 +#define CPUCTX_STAT_MAGIC 0x4c4e5832 +#define CPUCTX_DUMP_DATA_MAGIC 0x4c4e5833 +#define CPUCTX_TASK_STRUCT_MAGIC 0x4c4e5834 +#define CPUCTX_THREAD_MAGIC 0x4c4e5835 + +#define MSM_WDOG_CTX_SIG 0x77647473 +#define MSM_WDOG_CTX_REV 0x00020000 + +#define RET_STAGE_SHIFT (28) +#define RET_SECTION_SHIFT (24) +#define RET_ERR_SHIFT (16) +#define RET_MISC_SHIFT (12) + +#define RET_TZ (0xfu << RET_STAGE_SHIFT) +#define ERR_NONE (RET_TZ | (0xffu << RET_ERR_SHIFT)) +#define ERR_NO_SYMS (RET_TZ | (0xe9u << RET_ERR_SHIFT)) +#define ERR_TASK_INVAL (RET_TZ | (0xeau << RET_ERR_SHIFT)) + +#define SC_STATUS_NS 0x01 +#define SC_STATUS_WDT 0x02 +#define SC_STATUS_SGI 0x04 +#define SC_STATUS_WARM_BOOT 0x08 +#define SC_STATUS_DBI 0x10 +#define SC_STATUS_CTX_BY_TZ 0x20 + +#ifndef THREAD_SIZE_ORDER +#define THREAD_SIZE_ORDER (get_order(THREAD_SIZE)) +#endif + +enum lnx_jobs { + LNX_STACK, + LNX_TASK, + LNX_CTX_AREAS, +}; + +enum cpuctx_type { + CPUCTX_LNX_INFO, + CPUCTX_STAT, + CPUCTX_DUMP_DATA, + CPUCTX_TASK_STRUCT, + CPUCTX_THREAD, + CPUCTX_TYPES, + CPUCTX_MAX = 20, +}; + +struct msm_wdog_copy { + uint64_t from; + uint64_t to; + uint64_t size; +} __packed __aligned(4); + +struct sysdbg_cpu_ctxt_regs { + uint64_t x0; + uint64_t x1; + uint64_t x2; + uint64_t x3; + uint64_t x4; + uint64_t x5; + uint64_t x6; + uint64_t x7; + uint64_t x8; + uint64_t x9; + uint64_t x10; + uint64_t x11; + uint64_t x12; + uint64_t x13; + uint64_t x14; + uint64_t x15; + uint64_t x16; + uint64_t x17; + uint64_t x18; + uint64_t x19; + uint64_t x20; + uint64_t x21; + uint64_t x22; + uint64_t x23; + uint64_t x24; + uint64_t x25; + uint64_t x26; + uint64_t x27; + uint64_t x28; + uint64_t x29; + uint64_t x30; + uint64_t pc; + uint64_t currentEL; + uint64_t sp_el3; + uint64_t elr_el3; + uint64_t spsr_el3; + uint64_t sp_el2; + uint64_t elr_el2; + uint64_t spsr_el2; + uint64_t sp_el1; + uint64_t elr_el1; + uint64_t spsr_el1; + uint64_t sp_el0; + uint64_t __reserved1; + uint64_t __reserved2; + uint64_t __reserved3; + uint64_t __reserved4; +}; + +struct sysdbgCPUCtxtType { + uint32_t status[4]; + struct sysdbg_cpu_ctxt_regs cpu_regs; + struct sysdbg_cpu_ctxt_regs __reserved3; /* Secure - Not used */ +}; + +union sysdbg_cpuctx { + struct sysdbgCPUCtxtType data; + uint8_t space[MAX_CPU_CTX_SIZE]; +}; + +struct msm_wdog_cpuctx_info { + uint32_t sig; + uint32_t rev; + uint32_t ret; + uint32_t size; + uint32_t rev2; + uint32_t syms_avail; + uint32_t reserve2; + uint32_t reserve3; +} __packed __aligned(4); + +struct msm_wdog_cpuctx_header { + uint32_t type; /* cpuctx_type */ + uint32_t magic; + uint64_t offset; + uint64_t length; +} __packed __aligned(4); + +struct msm_wdog_lnx_info { + uint32_t tsk_size; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,9,0) + uint32_t ti_tsk_offset; +#endif + uint32_t aa64; + uint32_t lpae; + uint64_t text_paddr; + uint64_t pgd_paddr; + uint32_t thread_size; + uint32_t va_bits; + uint32_t pa_bits; + uint32_t page_shift; + uint32_t aa32_pxn; + uint32_t pad1; + uint64_t kaslr_offset; +} __packed __aligned(4); + +struct msm_wdog_cpuctx_stat { + uint32_t ret; + uint32_t reserved; + uint64_t stack_va; + struct msm_wdog_copy jobs[LNX_CTX_AREAS]; +} __packed __aligned(4); + +struct msm_wdog_cpuctx { + union sysdbg_cpuctx sysdbg; + struct msm_wdog_cpuctx_info info; + struct msm_wdog_cpuctx_header header[CPUCTX_MAX]; + struct msm_wdog_lnx_info lnx; + struct msm_wdog_cpuctx_stat stat; + struct msm_dump_data cpu_data; + struct task_struct task; + u8 stack[THREAD_SIZE] __aligned(1024); +} __packed __aligned(4); + +#define WDOG_CPUCTX_SIZE_PERCPU (sizeof(struct msm_wdog_cpuctx)) +#define WDOG_CPUCTX_SIZE (num_present_cpus() * WDOG_CPUCTX_SIZE_PERCPU) + +#define PHYS_PFN_OFFSET (PHYS_OFFSET >> PAGE_SHIFT) + +#define KASLR_PHYS_ADDR 0x146bf6d0 +#define KASLR_MAGIC_NUM 0xdead4ead +#define KASLR_REGION_LEN 32 + +#endif /* __WATCHDOG_CPU_CTX_H */ \ No newline at end of file