diff --git a/drivers/soc/rockchip/minidump/minidump_log.c b/drivers/soc/rockchip/minidump/minidump_log.c index 4b5ea9e432e2..8c911827de70 100644 --- a/drivers/soc/rockchip/minidump/minidump_log.c +++ b/drivers/soc/rockchip/minidump/minidump_log.c @@ -36,7 +36,6 @@ #ifdef CONFIG_ROCKCHIP_MINIDUMP_PANIC_DUMP #include #include -#include #include "../../../kernel/sched/sched.h" @@ -49,6 +48,7 @@ #include #include #include +#include #ifdef CONFIG_ROCKCHIP_MINIDUMP_PANIC_CPU_CONTEXT #include #endif @@ -149,6 +149,7 @@ static DEFINE_SPINLOCK(md_modules_lock); static struct md_region note_md_entry; static DEFINE_PER_CPU_SHARED_ALIGNED(struct elf_prstatus *, cpu_epr); +static struct elf_prstatus *epr_hang_task[8]; static int register_stack_entry(struct md_region *ksp_entry, u64 sp, u64 size) { @@ -594,14 +595,15 @@ static Elf_Word *append_elf_note(Elf_Word *buf, char *name, unsigned int type, static void register_note_section(void) { - int ret = 0, i = 0; + int ret = 0, i = 0, j = 0; size_t data_len; Elf_Word *buf; void *buffer_start; struct elf_prstatus *epr; + struct user_pt_regs *regs; struct md_region *mdr = ¬e_md_entry; - buffer_start = kzalloc(PAGE_SIZE, GFP_KERNEL); + buffer_start = kzalloc(PAGE_SIZE * 2, GFP_KERNEL); if (!buffer_start) return; @@ -611,11 +613,26 @@ static void register_note_section(void) buf = (Elf_Word *)mdr->virt_addr; data_len = sizeof(struct elf_prstatus); + for_each_possible_cpu(i) { buf = append_elf_note(buf, "CORE", NT_PRSTATUS, data_len); epr = (struct elf_prstatus *)buf; epr->pr_pid = i; per_cpu(cpu_epr, i) = epr; + regs = (struct user_pt_regs *)&epr->pr_reg; + regs->pc = (u64)register_note_section; /* just for fun */ + + buf += DIV_ROUND_UP(data_len, sizeof(Elf_Word)); + } + + j = i; + for (; i < 16; i++) { + buf = append_elf_note(buf, "TASK", NT_PRSTATUS, data_len); + epr = (struct elf_prstatus *)buf; + epr->pr_pid = i; + epr_hang_task[i - j] = epr; + regs = (struct user_pt_regs *)&epr->pr_reg; + regs->pc = (u64)register_note_section; /* just for fun */ buf += DIV_ROUND_UP(data_len, sizeof(Elf_Word)); } @@ -626,17 +643,117 @@ static void register_note_section(void) pr_err("Failed to add %s entry in Minidump\n", mdr->name); } +static int md_register_minidump_entry(char *name, u64 virt_addr, + u64 phys_addr, u64 size) +{ + struct md_region md_entry; + int ret; + + strscpy(md_entry.name, name, sizeof(md_entry.name)); + md_entry.virt_addr = virt_addr; + md_entry.phys_addr = phys_addr; + md_entry.size = size; + ret = rk_minidump_add_region(&md_entry); + if (ret < 0) + pr_err("Failed to add %s entry in Minidump\n", name); + return ret; +} + +static int md_is_kernel_address(u64 addr) +{ + u32 data; + + if (addr < PAGE_OFFSET || addr > -4096UL) + return 0; + + if (addr >= (u64)_text && addr < (u64)_end) + return 0; + + if (aarch64_insn_read((void *)addr, &data)) + return 0; + else + return 1; +} + +static int md_save_page(u64 addr, bool flush) +{ + u64 phys_addr, virt_addr; + struct page *page; + char buf[32]; + int ret; + + if (md_is_kernel_address(addr)) { + if (!md_is_in_the_region(addr)) { + virt_addr = addr & PAGE_MASK; + sprintf(buf, "%x", (u32)(virt_addr >> 12)); + + if (__is_lm_address(virt_addr)) { + phys_addr = virt_to_phys((void *)virt_addr); + } else if (virt_addr >= VMALLOC_START && virt_addr < VMALLOC_END) { + page = vmalloc_to_page((const void *) virt_addr); + phys_addr = page_to_phys(page); + } else { + return -1; + } + + ret = md_register_minidump_entry(buf, (uintptr_t)virt_addr, + phys_addr, PAGE_SIZE); + if (ret > 0 && flush) + rk_md_flush_dcache_area((void *)virt_addr, PAGE_SIZE); + } else { + if (flush) + rk_md_flush_dcache_area((void *)(addr & PAGE_MASK), PAGE_SIZE); + } + return 0; + } + return -1; +} + +static void md_save_pages(u64 addr, bool flush) +{ + u64 *p, *end; + + if (!md_save_page(addr, flush)) { + addr &= ~0x7; + p = (u64 *)addr; + end = (u64 *)((addr & ~(PAGE_SIZE - 1)) + PAGE_SIZE); + while (p < end) { + if (!md_is_kernel_address((u64)p)) + break; + md_save_page(*p++, flush); + } + } +} + void rk_minidump_update_cpu_regs(struct pt_regs *regs) { int cpu = raw_smp_processor_id(); + struct user_pt_regs *old_regs; + int i = 0; + struct elf_prstatus *epr = per_cpu(cpu_epr, cpu); if (!epr) return; + if (system_state == SYSTEM_RESTART) + return; + + old_regs = (struct user_pt_regs *)&epr->pr_reg; + /* if epr has been saved, don't save it again in panic notifier*/ + if (old_regs->sp != 0) + return; + memcpy((void *)&epr->pr_reg, (void *)regs, sizeof(elf_gregset_t)); rk_md_flush_dcache_area((void *)&epr->pr_reg, sizeof(elf_gregset_t)); rk_md_flush_dcache_area((void *)(regs->sp & ~(PAGE_SIZE - 1)), PAGE_SIZE); + + /* dump sp */ + md_save_pages(regs->sp, true); + + /*dump x0-x28, x29 is lr, x30 is fp*/ + for (i = 0; i < 29; i++) + md_save_pages(regs->regs[i], true); } EXPORT_SYMBOL(rk_minidump_update_cpu_regs); @@ -1028,7 +1145,10 @@ static inline void md_dump_panic_regs(void) seq_buf_printf(md_cntxt_seq_buf, "PANIC CPU : %d\n", raw_smp_processor_id()); - md_reg_context_data(®s); + if (in_interrupt()) + md_reg_context_data(get_irq_regs()); + else + md_reg_context_data(®s); } static int md_die_context_notify(struct notifier_block *self, @@ -1056,6 +1176,43 @@ static struct notifier_block md_die_context_nb = { }; #endif +static int rk_minidump_collect_hang_task(void) +{ + struct task_struct *g, *p; + struct elf_prstatus *epr; + struct user_pt_regs *regs; + int idx = 0, i = 0; + + for_each_process_thread(g, p) { + touch_nmi_watchdog(); + touch_all_softlockup_watchdogs(); + if (p->state == TASK_UNINTERRUPTIBLE && p->state != TASK_IDLE) { + epr = epr_hang_task[idx++]; + regs = (struct user_pt_regs *)&epr->pr_reg; + regs->regs[19] = (unsigned long)(p->thread.cpu_context.x19); + regs->regs[20] = (unsigned long)(p->thread.cpu_context.x20); + regs->regs[21] = (unsigned long)(p->thread.cpu_context.x21); + regs->regs[22] = (unsigned long)(p->thread.cpu_context.x22); + regs->regs[23] = (unsigned long)(p->thread.cpu_context.x23); + regs->regs[24] = (unsigned long)(p->thread.cpu_context.x24); + regs->regs[25] = (unsigned long)(p->thread.cpu_context.x25); + regs->regs[26] = (unsigned long)(p->thread.cpu_context.x26); + regs->regs[27] = (unsigned long)(p->thread.cpu_context.x27); + regs->regs[28] = (unsigned long)(p->thread.cpu_context.x28); + regs->regs[29] = (unsigned long)(p->thread.cpu_context.fp); + regs->sp = (unsigned long)(p->thread.cpu_context.sp); + regs->pc = (unsigned long)p->thread.cpu_context.pc; + md_save_pages(regs->sp, true); + for (i = 19; i < 29; i++) + md_save_pages(regs->regs[i], true); + rk_md_flush_dcache_area((void *)epr, sizeof(struct elf_prstatus)); + } + if (idx >= 8) + return 0; + } + return 0; +} + static int md_panic_handler(struct notifier_block *this, unsigned long event, void *ptr) { @@ -1093,6 +1250,8 @@ dump_rq: if (md_dma_buf_procs_addr) md_dma_buf_procs(md_dma_buf_procs_addr, md_dma_buf_procs_size); + rk_minidump_collect_hang_task(); + rk_minidump_flush_elfheader(); md_in_oops_handler = false; return NOTIFY_DONE; @@ -1103,22 +1262,6 @@ static struct notifier_block md_panic_blk = { .priority = INT_MAX - 2, }; -static int md_register_minidump_entry(char *name, u64 virt_addr, - u64 phys_addr, u64 size) -{ - struct md_region md_entry; - int ret; - - strscpy(md_entry.name, name, sizeof(md_entry.name)); - md_entry.virt_addr = virt_addr; - md_entry.phys_addr = phys_addr; - md_entry.size = size; - ret = rk_minidump_add_region(&md_entry); - if (ret < 0) - pr_err("Failed to add %s entry in Minidump\n", name); - return ret; -} - static int md_register_panic_entries(int num_pages, char *name, struct seq_buf **global_buf) { @@ -1249,6 +1392,43 @@ static void md_register_module_data(void) } #endif /* CONFIG_ROCKCHIP_MINIDUMP_PANIC_DUMP */ +#ifdef CONFIG_HARDLOCKUP_DETECTOR +int rk_minidump_hardlock_notify(struct notifier_block *nb, unsigned long event, + void *p) +{ + struct elf_prstatus *epr; + struct user_pt_regs *regs; + unsigned long hardlock_cpu = event; +#ifdef CONFIG_ROCKCHIP_DYN_MINIDUMP_STACK + int i = 0; + struct md_stack_cpu_data *md_stack_cpu_d; + struct md_region *mdr; +#endif + + if (hardlock_cpu >= num_possible_cpus()) + return NOTIFY_DONE; + +#ifdef CONFIG_ROCKCHIP_DYN_MINIDUMP_STACK + md_stack_cpu_d = &per_cpu(md_stack_data, hardlock_cpu); + for (i = 0; i < STACK_NUM_PAGES; i++) { + mdr = &md_stack_cpu_d->stack_mdr[i]; + if (md_is_kernel_address(mdr->virt_addr)) + rk_md_flush_dcache_area((void *)mdr->virt_addr, mdr->size); + } +#endif + epr = per_cpu(cpu_epr, hardlock_cpu); + if (!epr) + return NOTIFY_DONE; + regs = (struct user_pt_regs *)&epr->pr_reg; + regs->pc = (u64)p; +#ifdef CONFIG_ROCKCHIP_DYN_MINIDUMP_STACK + regs->sp = mdr->virt_addr + mdr->size; +#endif + rk_md_flush_dcache_area((void *)epr, sizeof(struct elf_prstatus)); + return NOTIFY_OK; +} +#endif + int rk_minidump_log_init(void) { is_vmap_stack = IS_ENABLED(CONFIG_VMAP_STACK); diff --git a/drivers/soc/rockchip/minidump/minidump_private.h b/drivers/soc/rockchip/minidump/minidump_private.h index 12cf81266e57..f2dadce35bf0 100644 --- a/drivers/soc/rockchip/minidump/minidump_private.h +++ b/drivers/soc/rockchip/minidump/minidump_private.h @@ -86,4 +86,5 @@ int rk_minidump_log_init(void); extern void rk_minidump_flush_elfheader(void); extern void dump_stack_minidump(u64 sp); extern struct md_region *md_get_region(char *name); +int md_is_in_the_region(u64 addr); #endif diff --git a/drivers/soc/rockchip/minidump/rk_minidump.c b/drivers/soc/rockchip/minidump/rk_minidump.c index 908bb5c4754e..7fa439746364 100644 --- a/drivers/soc/rockchip/minidump/rk_minidump.c +++ b/drivers/soc/rockchip/minidump/rk_minidump.c @@ -212,6 +212,26 @@ static inline int validate_region(const struct md_region *entry) return 0; } +int md_is_in_the_region(u64 addr) +{ + struct md_region *mdr; + u32 entries; + int i; + + entries = minidump_table.num_regions; + + for (i = 0; i < entries; i++) { + mdr = &minidump_table.entry[i]; + if (mdr->virt_addr <= addr && addr < (mdr->virt_addr + mdr->size)) + break; + } + + if (i < entries) + return 1; + else + return 0; +} + int rk_minidump_update_region(int regno, const struct md_region *entry) { int ret = 0; @@ -655,9 +675,10 @@ static int rk_minidump_driver_probe(struct platform_device *pdev) phdr = (Elf64_Phdr *)(md_elf_mem + (ulong)ehdr->e_phoff); phdr += ehdr->e_phnum - 1; md_elf_size = phdr->p_memsz + phdr->p_offset; - - pr_info("Create /proc/rk_md/minidump...\n"); + pr_info("Create /proc/rk_md/minidump, size:0x%llx...\n", md_elf_size); proc_rk_minidump = proc_create("minidump", 0400, base_dir, &rk_minidump_proc_ops); + } else { + pr_info("Create /proc/rk_md/minidump fail...\n"); } /* Check global minidump support initialization */ diff --git a/include/soc/rockchip/rk_minidump.h b/include/soc/rockchip/rk_minidump.h index e42afda50a88..d025a9a35bbc 100644 --- a/include/soc/rockchip/rk_minidump.h +++ b/include/soc/rockchip/rk_minidump.h @@ -45,6 +45,8 @@ int rk_minidump_remove_region(const struct md_region *entry); int rk_minidump_update_region(int regno, const struct md_region *entry); bool rk_minidump_enabled(void); void rk_minidump_update_cpu_regs(struct pt_regs *regs); +int rk_minidump_hardlock_notify(struct notifier_block *nb, unsigned long event, + void *p); #else static inline int rk_minidump_add_region(const struct md_region *entry) { @@ -61,7 +63,12 @@ static inline int rk_minidump_update_region(int regno, const struct md_region *e } static inline bool rk_minidump_enabled(void) { return false; } static inline void rk_minidump_update_cpu_regs(struct pt_regs *regs) { return; } +static inline int rk_minidump_hardlock_notify(struct notifier_block *nb, + unsigned long event, void *p) +{ + return 0; +} #endif -extern void rk_md_flush_dcache_area(void *addr, size_t len); +void rk_md_flush_dcache_area(void *addr, size_t len); #endif /* __RK_MINIDUMP_H */