From 1ce2d41f8466868336bf10b5115552d0bc03ccb0 Mon Sep 17 00:00:00 2001 From: Huang Yiwei Date: Wed, 6 Apr 2022 18:59:57 +0800 Subject: [PATCH] soc: qcom: Add snapshot of minidump driver This is a snapshot of the minidump driver as of msm-5.15 commit <908a517b050d> ("Merge "sched/walt: remove duplicate definitions of rt_task_arrival_time""). Change-Id: I47eba0f9aedb1732724b94f567404287378c6189 Signed-off-by: Huang Yiwei --- drivers/soc/qcom/Kconfig | 71 ++ drivers/soc/qcom/Makefile | 4 + drivers/soc/qcom/elf.h | 45 + drivers/soc/qcom/minidump_log.c | 1396 +++++++++++++++++++++++++++ drivers/soc/qcom/minidump_memory.c | 1345 ++++++++++++++++++++++++++ drivers/soc/qcom/minidump_memory.h | 51 + drivers/soc/qcom/minidump_private.h | 82 ++ drivers/soc/qcom/msm_minidump.c | 661 +++++++++++++ drivers/soc/qcom/qcom_va_minidump.c | 788 +++++++++++++++ include/soc/qcom/minidump.h | 93 ++ 10 files changed, 4536 insertions(+) create mode 100644 drivers/soc/qcom/elf.h create mode 100644 drivers/soc/qcom/minidump_log.c create mode 100644 drivers/soc/qcom/minidump_memory.c create mode 100644 drivers/soc/qcom/minidump_memory.h create mode 100644 drivers/soc/qcom/minidump_private.h create mode 100644 drivers/soc/qcom/msm_minidump.c create mode 100644 drivers/soc/qcom/qcom_va_minidump.c create mode 100644 include/soc/qcom/minidump.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 1c3ac3aabe1c..f64ed3aaa2e2 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -293,4 +293,75 @@ config QCOM_CPU_VENDOR_HOOKS having scope to upstream. If unsure, say N. +config QCOM_MINIDUMP + tristate "QCOM Minidump Support" + depends on QCOM_SMEM + help + This enables minidump feature. It allows various clients to + register to dump their state at system bad state (panic/WDT,etc.,). + Minidump would dump all registered entries, only when DLOAD mode + is enabled. + +config QCOM_VA_MINIDUMP + tristate "QCOM VA Minidump Support" + depends on QCOM_MINIDUMP + help + This enables minidump feature for registering dynamic + data structures. It supports VA based registration + with minidump, which is made into an ELF. The region + for ELF is registered with legacy minidump. + +config QCOM_DYN_MINIDUMP_STACK + bool "QTI Dynamic Minidump Stack Registration Support" + depends on QCOM_MINIDUMP + help + This enables minidump dynamic current stack registration feature. + It allows current task stack to be available in minidump, for cases + where CPU is unable to register it from IPI_CPU_STOP. The stack data + can be used to unwind stack frames. + +config QCOM_MINIDUMP_FTRACE + bool "QCOM Minidump Support" + depends on QCOM_MINIDUMP + help + This enables ftrace buffer registration in minidump table. + On oops, ftrace content will be copied to that buffer. + This way ftrace buffer content becomes a part of minidump dump + collection. + +config QCOM_MINIDUMP_PANIC_DUMP + bool "QCOM Minidump Panic dumps Support" + depends on QCOM_MINIDUMP + help + This enables collection of debug information like runqueue + statistics etc. on panic in minidump. It dumps current, CFS, + and RT runqueue tasks running on each cpu. This help in + knowing the tasks running, pending, hogging on cpu during + panic. + +config QCOM_MINIDUMP_PANIC_CPU_CONTEXT + bool "QCOM Minidump Panic dumps Support" + depends on ARM64 && QCOM_MINIDUMP_PANIC_DUMP + help + This enables cpu context collection in minidump table, + on panic. + +config QCOM_MINIDUMP_PSTORE + bool "QCOM Minidump Pstore dumps Support" + depends on ARM64 && QCOM_MINIDUMP + help + This enables pstore registration in minidump table. + Here, pstore framework dump logs like pmsg, dmesg, + console ftrace etc. in the given carve-out memory + (non-volatile memory) and during warm reboot these + dump will be copied outside the system on panic. + +config MINIDUMP_MAX_ENTRIES + int "Minidump Maximum num of entries" + default 200 + depends on QCOM_MINIDUMP + help + This defines maximum number of entries to be allocated for application + subsytem in Minidump table. + endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 5dbac3170cfa..9cc0e2c24409 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -33,4 +33,8 @@ obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o obj-$(CONFIG_QCOM_CPU_VENDOR_HOOKS) += qcom_cpu_vendor_hooks.o +obj-$(CONFIG_QCOM_MINIDUMP) += minidump.o +minidump-y += msm_minidump.o minidump_log.o +minidump-$(CONFIG_QCOM_MINIDUMP_PANIC_DUMP) += minidump_memory.o +obj-$(CONFIG_QCOM_VA_MINIDUMP) += qcom_va_minidump.o obj-$(CONFIG_QCOM_LOGBUF_VENDOR_HOOKS) += qcom_logbuf_vh.o diff --git a/drivers/soc/qcom/elf.h b/drivers/soc/qcom/elf.h new file mode 100644 index 000000000000..e4db0aa0be36 --- /dev/null +++ b/drivers/soc/qcom/elf.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QCOM_ELF_COMMON_H +#define __QCOM_ELF_COMMON_H + +#include + +/* Generic helpers for ELF use */ +/* Return first section header */ +static inline struct elf_shdr *elf_sheader(struct elfhdr *hdr) +{ + return (struct elf_shdr *)((size_t)hdr + (size_t)hdr->e_shoff); +} + +/* Return idx section header */ +static inline struct elf_shdr *elf_section(struct elfhdr *hdr, int idx) +{ + return &elf_sheader(hdr)[idx]; +} + +/* Return first program header */ +static inline struct elf_phdr *elf_pheader(struct elfhdr *hdr) +{ + return (struct elf_phdr *)((size_t)hdr + (size_t)hdr->e_phoff); +} + +/* Return idx program header */ +static inline struct elf_phdr *elf_program(struct elfhdr *hdr, int idx) +{ + return &elf_pheader(hdr)[idx]; +} + +/* Return section's string table header */ +static inline char *elf_str_table(struct elfhdr *hdr) +{ + if (hdr->e_shstrndx == SHN_UNDEF) + return NULL; + return (char *)hdr + elf_section(hdr, hdr->e_shstrndx)->sh_offset; +} + +#endif diff --git a/drivers/soc/qcom/minidump_log.c b/drivers/soc/qcom/minidump_log.c new file mode 100644 index 000000000000..299f2c60d776 --- /dev/null +++ b/drivers/soc/qcom/minidump_log.c @@ -0,0 +1,1396 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_QCOM_MINIDUMP_PSTORE +#include +#include +#include +#include +#endif + +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP +#include +#include +#include + +#include + +#include "../../../kernel/sched/sched.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_CPU_CONTEXT +#include +#endif +#include "minidump_memory.h" +#endif + +#ifdef CONFIG_QCOM_DYN_MINIDUMP_STACK + +#include + +#ifdef CONFIG_VMAP_STACK +#define STACK_NUM_PAGES (THREAD_SIZE / PAGE_SIZE) +#else +#define STACK_NUM_PAGES 1 +#endif /* !CONFIG_VMAP_STACK */ + +struct md_stack_cpu_data { + int stack_mdidx[STACK_NUM_PAGES]; + struct md_region stack_mdr[STACK_NUM_PAGES]; +} ____cacheline_aligned_in_smp; + +static int md_current_stack_init __read_mostly; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct md_stack_cpu_data, md_stack_data); + +struct md_suspend_context_data { + int task_mdno; + int stack_mdidx[STACK_NUM_PAGES]; + struct md_region stack_mdr[STACK_NUM_PAGES]; + struct md_region task_mdr; + bool init; +}; + +static struct md_suspend_context_data md_suspend_context; +#endif + +static bool is_vmap_stack __read_mostly; + +#ifdef CONFIG_QCOM_MINIDUMP_FTRACE +#include +#include + +#define MD_FTRACE_BUF_SIZE SZ_2M + +static char *md_ftrace_buf_addr; +static size_t md_ftrace_buf_current; +static bool minidump_ftrace_in_oops; +static bool minidump_ftrace_dump = true; +#endif + +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP +/* Rnqueue information */ +#define MD_RUNQUEUE_PAGES 8 + +static bool md_in_oops_handler; +static atomic_t md_handle_done; +static struct seq_buf *md_runq_seq_buf; +static md_align_offset; + +/* CPU context information */ +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_CPU_CONTEXT +#define MD_CPU_CNTXT_PAGES 32 + +static int die_cpu = -1; +static struct seq_buf *md_cntxt_seq_buf; +static DEFINE_PER_CPU(struct pt_regs, regs_before_stop); +#endif + +/* Meminfo */ +static struct seq_buf *md_meminfo_seq_buf; + +/* Slabinfo */ +#ifdef CONFIG_SLUB_DEBUG +static struct seq_buf *md_slabinfo_seq_buf; +#endif + +#ifdef CONFIG_PAGE_OWNER +size_t md_pageowner_dump_size = SZ_2M; +char *md_pageowner_dump_addr; +#endif + +#ifdef CONFIG_SLUB_DEBUG +size_t md_slabowner_dump_size = SZ_2M; +char *md_slabowner_dump_addr; +#endif + +size_t md_dma_buf_info_size = SZ_256K; +char *md_dma_buf_info_addr; + +size_t md_dma_buf_procs_size = SZ_256K; +char *md_dma_buf_procs_addr; + +/* Modules information */ +#ifdef CONFIG_MODULES +#define MD_MODULE_PAGES 8 +static struct seq_buf *md_mod_info_seq_buf; +static DEFINE_SPINLOCK(md_modules_lock); + +static int n_modump; +static char *key_modules[10]; +module_param_array(key_modules, charp, &n_modump, 0644); +#endif /* CONFIG_MODULES */ +#endif + +static int register_stack_entry(struct md_region *ksp_entry, u64 sp, u64 size) +{ + struct page *sp_page; + int entry; + + ksp_entry->virt_addr = sp; + ksp_entry->size = size; + if (is_vmap_stack) { + sp_page = vmalloc_to_page((const void *) sp); + ksp_entry->phys_addr = page_to_phys(sp_page); + } else { + ksp_entry->phys_addr = virt_to_phys((uintptr_t *)sp); + } + + entry = msm_minidump_add_region(ksp_entry); + if (entry < 0) + printk_deferred("Failed to add stack of entry %s in Minidump\n", + ksp_entry->name); + return entry; +} + +static void register_kernel_sections(void) +{ + struct md_region ksec_entry; + char *data_name = "KDATABSS"; + char *rodata_name = "KROAIDATA"; + size_t static_size; + void __percpu *base; + unsigned int cpu; + void *_sdata, *__bss_stop; + void *start_ro, *end_ro; + + _sdata = android_debug_symbol(ADS_SDATA); + __bss_stop = android_debug_symbol(ADS_BSS_END); + base = android_debug_symbol(ADS_PER_CPU_START); + static_size = (size_t)(android_debug_symbol(ADS_PER_CPU_END) - base); + + strscpy(ksec_entry.name, data_name, sizeof(ksec_entry.name)); + ksec_entry.virt_addr = (u64)_sdata; + ksec_entry.phys_addr = virt_to_phys(_sdata); + ksec_entry.size = roundup((__bss_stop - _sdata), 4); + if (msm_minidump_add_region(&ksec_entry) < 0) + pr_err("Failed to add data section in Minidump\n"); + + start_ro = android_debug_symbol(ADS_START_RO_AFTER_INIT); + end_ro = android_debug_symbol(ADS_END_RO_AFTER_INIT); + strscpy(ksec_entry.name, rodata_name, sizeof(ksec_entry.name)); + ksec_entry.virt_addr = (uintptr_t)start_ro; + ksec_entry.phys_addr = virt_to_phys(start_ro); + ksec_entry.size = roundup((end_ro - start_ro), 4); + if (msm_minidump_add_region(&ksec_entry) < 0) + pr_err("Failed to add rodata section in Minidump\n"); + + /* Add percpu static sections */ + for_each_possible_cpu(cpu) { + void *start = per_cpu_ptr(base, cpu); + + memset(&ksec_entry, 0, sizeof(ksec_entry)); + scnprintf(ksec_entry.name, sizeof(ksec_entry.name), + "KSPERCPU%d", cpu); + ksec_entry.virt_addr = (uintptr_t)start; + ksec_entry.phys_addr = per_cpu_ptr_to_phys(start); + ksec_entry.size = static_size; + if (msm_minidump_add_region(&ksec_entry) < 0) + pr_err("Failed to add percpu sections in Minidump\n"); + } +} + +static inline bool in_stack_range( + u64 sp, u64 base_addr, unsigned int stack_size) +{ + u64 min_addr = base_addr; + u64 max_addr = base_addr + stack_size; + + return (min_addr <= sp && sp < max_addr); +} + +static unsigned int calculate_copy_pages(u64 sp, struct vm_struct *stack_area) +{ + u64 tsk_stack_base = (u64) stack_area->addr; + u64 offset; + unsigned int stack_pages, copy_pages; + + if (in_stack_range(sp, tsk_stack_base, get_vm_area_size(stack_area))) { + offset = sp - tsk_stack_base; + stack_pages = get_vm_area_size(stack_area) / PAGE_SIZE; + copy_pages = stack_pages - (offset / PAGE_SIZE); + } else { + copy_pages = 0; + } + return copy_pages; +} + +void dump_stack_minidump(u64 sp) +{ + struct md_region ksp_entry, ktsk_entry; + u32 cpu = smp_processor_id(); + struct vm_struct *stack_vm_area; + unsigned int i, copy_pages; + + if (IS_ENABLED(CONFIG_QCOM_DYN_MINIDUMP_STACK)) + return; + + if (is_idle_task(current)) + return; + + is_vmap_stack = IS_ENABLED(CONFIG_VMAP_STACK); + + if (sp < KIMAGE_VADDR || sp > -256UL) + sp = current_stack_pointer; + + /* + * Since stacks are now allocated with vmalloc, the translation to + * physical address is not a simple linear transformation like it is + * for kernel logical addresses, since vmalloc creates a virtual + * mapping. Thus, virt_to_phys() should not be used in this context; + * instead the page table must be walked to acquire the physical + * address of one page of the stack. + */ + stack_vm_area = task_stack_vm_area(current); + if (is_vmap_stack) { + sp &= ~(PAGE_SIZE - 1); + copy_pages = calculate_copy_pages(sp, stack_vm_area); + for (i = 0; i < copy_pages; i++) { + scnprintf(ksp_entry.name, sizeof(ksp_entry.name), + "KSTACK%d_%d", cpu, i); + (void)register_stack_entry(&ksp_entry, sp, PAGE_SIZE); + sp += PAGE_SIZE; + } + } else { + sp &= ~(THREAD_SIZE - 1); + scnprintf(ksp_entry.name, sizeof(ksp_entry.name), "KSTACK%d", + cpu); + (void)register_stack_entry(&ksp_entry, sp, THREAD_SIZE); + } + + scnprintf(ktsk_entry.name, sizeof(ktsk_entry.name), "KTASK%d", cpu); + ktsk_entry.virt_addr = (u64)current; + ktsk_entry.phys_addr = virt_to_phys((uintptr_t *)current); + ktsk_entry.size = sizeof(struct task_struct); + if (msm_minidump_add_region(&ktsk_entry) < 0) + pr_err("Failed to add current task %d in Minidump\n", cpu); +} + +#ifdef CONFIG_QCOM_DYN_MINIDUMP_STACK +static void update_stack_entry(struct md_region *ksp_entry, u64 sp, + int mdno) +{ + struct page *sp_page; + + ksp_entry->virt_addr = sp; + if (likely(is_vmap_stack)) { + sp_page = vmalloc_to_page((const void *) sp); + ksp_entry->phys_addr = page_to_phys(sp_page); + } else { + ksp_entry->phys_addr = virt_to_phys((uintptr_t *)sp); + } + if (msm_minidump_update_region(mdno, ksp_entry) < 0) { + printk_deferred("Failed to update stack entry %s in minidump\n", + ksp_entry->name); + } +} + +static void register_vmapped_stack(struct md_region *mdr, int *mdno, + u64 sp, char *name_str, bool update) +{ + int i; + + sp &= ~(PAGE_SIZE - 1); + for (i = 0; i < STACK_NUM_PAGES; i++) { + if (unlikely(!update)) { + scnprintf(mdr->name, sizeof(mdr->name), "%s_%d", + name_str, i); + *mdno = register_stack_entry(mdr, sp, PAGE_SIZE); + } else { + update_stack_entry(mdr, sp, *mdno); + } + sp += PAGE_SIZE; + mdr++; + mdno++; + } +} + +static void register_normal_stack(struct md_region *mdr, int *mdno, + u64 sp, char *name_str, bool update) +{ + sp &= ~(THREAD_SIZE - 1); + if (unlikely(!update)) { + scnprintf(mdr->name, sizeof(mdr->name), name_str); + *mdno = register_stack_entry(mdr, sp, THREAD_SIZE); + } else { + update_stack_entry(mdr, sp, *mdno); + } +} + +static void update_md_stack(struct md_region *stack_mdr, + int *stack_mdno, u64 sp) +{ + unsigned int i; + int *mdno; + + if (likely(is_vmap_stack)) { + for (i = 0; i < STACK_NUM_PAGES; i++) { + mdno = stack_mdno + i; + if (unlikely(*mdno < 0)) + return; + } + register_vmapped_stack(stack_mdr, stack_mdno, sp, NULL, true); + } else { + if (unlikely(*stack_mdno < 0)) + return; + register_normal_stack(stack_mdr, stack_mdno, sp, NULL, true); + } +} + +static void update_md_cpu_stack(struct task_struct *tsk, u32 cpu, u64 sp) +{ + struct md_stack_cpu_data *md_stack_cpu_d = &per_cpu(md_stack_data, cpu); + + if (is_idle_task(tsk) || !md_current_stack_init) + return; + + update_md_stack(md_stack_cpu_d->stack_mdr, + md_stack_cpu_d->stack_mdidx, sp); +} + +void md_current_stack_notifer(void *ignore, bool preempt, + struct task_struct *prev, struct task_struct *next) +{ + u32 cpu = task_cpu(next); + u64 sp = (u64)next->stack; + + update_md_cpu_stack(next, cpu, sp); +} + +void md_current_stack_ipi_handler(void *data) +{ + u32 cpu = smp_processor_id(); + struct vm_struct *stack_vm_area; + u64 sp = current_stack_pointer; + + if (is_idle_task(current)) + return; + if (likely(is_vmap_stack)) { + stack_vm_area = task_stack_vm_area(current); + sp = (u64)stack_vm_area->addr; + } + update_md_cpu_stack(current, cpu, sp); +} + +static void update_md_current_task(struct md_region *mdr, int mdno) +{ + mdr->virt_addr = (u64)current; + mdr->phys_addr = virt_to_phys((uintptr_t *)current); + if (msm_minidump_update_region(mdno, mdr) < 0) + pr_err("Failed to update %s current task in minidump\n", + mdr->name); +} + +static void update_md_suspend_current_stack(void) +{ + u64 sp = current_stack_pointer; + struct vm_struct *stack_vm_area; + + if (likely(is_vmap_stack)) { + stack_vm_area = task_stack_vm_area(current); + sp = (u64)stack_vm_area->addr; + } + update_md_stack(md_suspend_context.stack_mdr, + md_suspend_context.stack_mdidx, sp); +} + +static void update_md_suspend_current_task(void) +{ + if (unlikely(md_suspend_context.task_mdno < 0)) + return; + update_md_current_task(&md_suspend_context.task_mdr, + md_suspend_context.task_mdno); +} + +static void update_md_suspend_currents(void) +{ + if (!md_suspend_context.init) + return; + update_md_suspend_current_stack(); + update_md_suspend_current_task(); +} + +static void register_current_stack(void) +{ + int cpu; + u64 sp = current_stack_pointer; + struct md_stack_cpu_data *md_stack_cpu_d; + struct vm_struct *stack_vm_area; + char name_str[MAX_NAME_LENGTH]; + + /* + * Since stacks are now allocated with vmalloc, the translation to + * physical address is not a simple linear transformation like it is + * for kernel logical addresses, since vmalloc creates a virtual + * mapping. Thus, virt_to_phys() should not be used in this context; + * instead the page table must be walked to acquire the physical + * address of all pages of the stack. + */ + if (likely(is_vmap_stack)) { + stack_vm_area = task_stack_vm_area(current); + sp = (u64)stack_vm_area->addr; + } + for_each_possible_cpu(cpu) { + /* + * Let's register dummies for now, + * once system up and running, let the cpu update its currents. + */ + md_stack_cpu_d = &per_cpu(md_stack_data, cpu); + scnprintf(name_str, sizeof(name_str), "KSTACK%d", cpu); + if (is_vmap_stack) + register_vmapped_stack(md_stack_cpu_d->stack_mdr, + md_stack_cpu_d->stack_mdidx, sp, + name_str, false); + else + register_normal_stack(md_stack_cpu_d->stack_mdr, + md_stack_cpu_d->stack_mdidx, sp, + name_str, false); + } + + register_trace_sched_switch(md_current_stack_notifer, NULL); + md_current_stack_init = 1; + smp_call_function(md_current_stack_ipi_handler, NULL, 1); +} + +static void register_suspend_stack(void) +{ + char name_str[MAX_NAME_LENGTH]; + u64 sp = current_stack_pointer; + struct vm_struct *stack_vm_area = task_stack_vm_area(current); + + scnprintf(name_str, sizeof(name_str), "KSUSPSTK"); + if (is_vmap_stack) { + sp = (u64)stack_vm_area->addr; + register_vmapped_stack(md_suspend_context.stack_mdr, + md_suspend_context.stack_mdidx, + sp, name_str, false); + } else { + register_normal_stack(md_suspend_context.stack_mdr, + md_suspend_context.stack_mdidx, + sp, name_str, false); + } +} + +static void register_current_task(struct md_region *mdr, int *mdno, + char *name_str) +{ + scnprintf(mdr->name, sizeof(mdr->name), name_str); + mdr->virt_addr = (u64)current; + mdr->phys_addr = virt_to_phys((uintptr_t *)current); + mdr->size = sizeof(struct task_struct); + *mdno = msm_minidump_add_region(mdr); + if (*mdno < 0) + pr_err("Failed to add current task %s in Minidump\n", + mdr->name); +} + +static void register_suspend_current_task(void) +{ + char name_str[MAX_NAME_LENGTH]; + + scnprintf(name_str, sizeof(name_str), "KSUSPTASK"); + register_current_task(&md_suspend_context.task_mdr, + &md_suspend_context.task_mdno, name_str); +} + +static int minidump_pm_notifier(struct notifier_block *nb, + unsigned long event, void *unused) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + update_md_suspend_currents(); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block minidump_pm_nb = { + .notifier_call = minidump_pm_notifier, +}; + +static void register_suspend_context(void) +{ + register_suspend_stack(); + register_suspend_current_task(); + register_pm_notifier(&minidump_pm_nb); + md_suspend_context.init = true; +} +#endif + +#ifdef CONFIG_ARM64 +static void register_irq_stack(void) +{ + int cpu; + unsigned int i; + int irq_stack_pages_count; + u64 irq_stack_base; + struct md_region irq_sp_entry; + u64 sp; + u64 *irq_stack_ptr = android_debug_per_cpu_symbol(ADS_IRQ_STACK_PTR); + + for_each_possible_cpu(cpu) { + irq_stack_base = *(u64 *)(per_cpu_ptr((void *)irq_stack_ptr, cpu)); + if (is_vmap_stack) { + irq_stack_pages_count = IRQ_STACK_SIZE / PAGE_SIZE; + sp = irq_stack_base & ~(PAGE_SIZE - 1); + for (i = 0; i < irq_stack_pages_count; i++) { + scnprintf(irq_sp_entry.name, + sizeof(irq_sp_entry.name), + "KISTACK%d_%d", cpu, i); + register_stack_entry(&irq_sp_entry, sp, + PAGE_SIZE); + sp += PAGE_SIZE; + } + } else { + sp = irq_stack_base; + scnprintf(irq_sp_entry.name, sizeof(irq_sp_entry.name), + "KISTACK%d", cpu); + register_stack_entry(&irq_sp_entry, sp, IRQ_STACK_SIZE); + } + } +} +#else +static inline void register_irq_stack(void) {} +#endif + +#ifdef CONFIG_QCOM_MINIDUMP_FTRACE +static void minidump_add_trace_event(char *buf, size_t size) +{ + char *addr; + + if (!READ_ONCE(md_ftrace_buf_addr) || + (size > (size_t)MD_FTRACE_BUF_SIZE)) + return; + + if ((md_ftrace_buf_current + size) > (size_t)MD_FTRACE_BUF_SIZE) + md_ftrace_buf_current = 0; + addr = md_ftrace_buf_addr + md_ftrace_buf_current; + memcpy(addr, buf, size); + md_ftrace_buf_current += size; +} + +static void md_trace_oops_enter(void *unused, bool *enter_check) +{ + if (!minidump_ftrace_in_oops) { + minidump_ftrace_in_oops = true; + *enter_check = false; + } else { + *enter_check = true; + } +} + +static void md_trace_oops_exit(void *unused, bool *exit_check) +{ + minidump_ftrace_in_oops = false; +} + +static void md_update_trace_fmt(void *unused, bool *format_check) +{ + *format_check = false; +} + +static void md_buf_size_check(void *unused, unsigned long buffer_size, + bool *size_check) +{ + if (!minidump_ftrace_dump) { + *size_check = true; + return; + } + + if (buffer_size > (SZ_256K + PAGE_SIZE)) { + pr_err("Skip md ftrace buffer dump for: %#lx\n", buffer_size); + minidump_ftrace_dump = false; + *size_check = true; + } +} + +static void md_dump_trace_buf(void *unused, struct trace_seq *trace_buf, + bool *printk_check) +{ + if (minidump_ftrace_in_oops && minidump_ftrace_dump) { + minidump_add_trace_event(trace_buf->buffer, + trace_buf->seq.len); + *printk_check = false; + } +} + +static void md_register_trace_buf(void) +{ + struct md_region md_entry; + void *buffer_start; + + buffer_start = kzalloc(MD_FTRACE_BUF_SIZE, GFP_KERNEL); + + if (!buffer_start) + return; + + strscpy(md_entry.name, "KFTRACE", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)buffer_start; + md_entry.phys_addr = virt_to_phys(buffer_start); + md_entry.size = MD_FTRACE_BUF_SIZE; + if (msm_minidump_add_region(&md_entry) < 0) + pr_err("Failed to add ftrace buffer entry in Minidump\n"); + + register_trace_android_vh_ftrace_oops_enter(md_trace_oops_enter, + NULL); + register_trace_android_vh_ftrace_oops_exit(md_trace_oops_exit, + NULL); + register_trace_android_vh_ftrace_size_check(md_buf_size_check, + NULL); + register_trace_android_vh_ftrace_format_check(md_update_trace_fmt, + NULL); + register_trace_android_vh_ftrace_dump_buffer(md_dump_trace_buf, + NULL); + + /* Complete registration before adding enteries */ + smp_mb(); + WRITE_ONCE(md_ftrace_buf_addr, buffer_start); +} +#endif + +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP + +static void md_dump_align(void) +{ + int tab_offset = md_align_offset; + + while (tab_offset--) + seq_buf_printf(md_runq_seq_buf, " | "); + seq_buf_printf(md_runq_seq_buf, " |--"); +} + +static void md_dump_task_info(struct task_struct *task, char *status, + struct task_struct *curr) +{ + struct sched_entity *se; + + md_dump_align(); + if (!task) { + seq_buf_printf(md_runq_seq_buf, "%s : None(0)\n", status); + return; + } + + se = &task->se; + if (task == curr) { + seq_buf_printf(md_runq_seq_buf, + "[status: curr] pid: %d comm: %s preempt: %#x\n", + task_pid_nr(task), task->comm, + task->thread_info.preempt_count); + return; + } + + seq_buf_printf(md_runq_seq_buf, + "[status: %s] pid: %d tsk: %#lx comm: %s stack: %#lx", + status, task_pid_nr(task), + (unsigned long)task, + task->comm, + (unsigned long)task->stack); + seq_buf_printf(md_runq_seq_buf, + " prio: %d aff: %*pb", + task->prio, cpumask_pr_args(&task->cpus_mask)); +#ifdef CONFIG_SCHED_WALT + seq_buf_printf(md_runq_seq_buf, " enq: %lu wake: %lu sleep: %lu", + task->wts.last_enqueued_ts, task->wts.last_wake_ts, + task->wts.last_sleep_ts); +#endif + seq_buf_printf(md_runq_seq_buf, + " vrun: %lu arr: %lu sum_ex: %lu\n", + (unsigned long)se->vruntime, + (unsigned long)se->exec_start, + (unsigned long)se->sum_exec_runtime); +} + +static void md_dump_cfs_rq(struct cfs_rq *cfs, struct task_struct *curr); + +static void md_dump_cgroup_state(char *status, struct sched_entity *se_p, + struct task_struct *curr) +{ + struct task_struct *task; + struct cfs_rq *my_q = NULL; + unsigned int nr_running; + + if (!se_p) { + md_dump_task_info(NULL, status, NULL); + return; + } +#ifdef CONFIG_FAIR_GROUP_SCHED + my_q = se_p->my_q; +#endif + if (!my_q) { + task = container_of(se_p, struct task_struct, se); + md_dump_task_info(task, status, curr); + return; + } + nr_running = my_q->nr_running; + md_dump_align(); + seq_buf_printf(md_runq_seq_buf, "%s: %d process is grouping\n", + status, nr_running); + md_align_offset++; + md_dump_cfs_rq(my_q, curr); + md_align_offset--; +} + +static void md_dump_cfs_node_func(struct rb_node *node, + struct task_struct *curr) +{ + struct sched_entity *se_p = container_of(node, struct sched_entity, + run_node); + + md_dump_cgroup_state("pend", se_p, curr); +} + +static void md_rb_walk_cfs(struct rb_root_cached *rb_root_cached_p, + struct task_struct *curr) +{ + int max_walk = 200; /* Bail out, in case of loop */ + struct rb_node *leftmost = rb_root_cached_p->rb_leftmost; + struct rb_root *root = &rb_root_cached_p->rb_root; + struct rb_node *rb_node = rb_first(root); + + if (!leftmost) + return; + while (rb_node && max_walk--) { + md_dump_cfs_node_func(rb_node, curr); + rb_node = rb_next(rb_node); + } +} + +static void md_dump_cfs_rq(struct cfs_rq *cfs, struct task_struct *curr) +{ + struct rb_root_cached *rb_root_cached_p = &cfs->tasks_timeline; + + md_dump_cgroup_state("curr", cfs->curr, curr); + md_dump_cgroup_state("next", cfs->next, curr); + md_dump_cgroup_state("last", cfs->last, curr); + md_dump_cgroup_state("skip", cfs->skip, curr); + md_rb_walk_cfs(rb_root_cached_p, curr); +} + +static void md_dump_rt_rq(struct rt_rq *rt_rq, struct task_struct *curr) +{ + struct rt_prio_array *array = &rt_rq->active; + struct sched_rt_entity *rt_se; + int idx; + + /* Lifted most of the below code from dump_throttled_rt_tasks() */ + if (bitmap_empty(array->bitmap, MAX_RT_PRIO)) + return; + + idx = sched_find_first_bit(array->bitmap); + while (idx < MAX_RT_PRIO) { + list_for_each_entry(rt_se, array->queue + idx, run_list) { + struct task_struct *p; + +#ifdef CONFIG_RT_GROUP_SCHED + if (rt_se->my_q) + continue; +#endif + + p = container_of(rt_se, struct task_struct, rt); + md_dump_task_info(p, "pend", curr); + } + idx = find_next_bit(array->bitmap, MAX_RT_PRIO, idx + 1); + } +} + +static void md_dump_runqueues(void) +{ + int cpu; + struct rq *rq; + struct rt_rq *rt; + struct cfs_rq *cfs; + + if (!md_runq_seq_buf) + return; + + for_each_possible_cpu(cpu) { + rq = cpu_rq(cpu); + rt = &rq->rt; + cfs = &rq->cfs; + seq_buf_printf(md_runq_seq_buf, + "CPU%d %d process is running\n", + cpu, rq->nr_running); + md_dump_task_info(cpu_curr(cpu), "curr", NULL); + seq_buf_printf(md_runq_seq_buf, + "CFS %d process is pending\n", + cfs->nr_running); + md_dump_cfs_rq(cfs, cpu_curr(cpu)); + seq_buf_printf(md_runq_seq_buf, + "RT %d process is pending\n", + rt->rt_nr_running); + md_dump_rt_rq(rt, cpu_curr(cpu)); + seq_buf_printf(md_runq_seq_buf, "\n"); + } +} + +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_CPU_CONTEXT +/* + * dump a block of kernel memory from around the given address. + * Bulk of the code is lifted from arch/arm64/kernel/proccess.c. + */ +static void md_dump_data(unsigned long addr, int nbytes, const char *name) +{ + int i, j; + int nlines; + u32 *p; + + /* + * don't attempt to dump non-kernel addresses or + * values that are probably just small negative numbers + */ + if (addr < PAGE_OFFSET || addr > -256UL) + return; + + seq_buf_printf(md_cntxt_seq_buf, "\n%s: %#lx:\n", name, addr); + + /* + * round address down to a 32 bit boundary + * and always dump a multiple of 32 bytes + */ + p = (u32 *)(addr & ~(sizeof(u32) - 1)); + nbytes += (addr & (sizeof(u32) - 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 + */ + seq_buf_printf(md_cntxt_seq_buf, "%04lx ", + (unsigned long)p & 0xffff); + for (j = 0; j < 8; j++) { + u32 data = 0; + + if (get_kernel_nofault(data, p)) + seq_buf_printf(md_cntxt_seq_buf, " ********"); + else + seq_buf_printf(md_cntxt_seq_buf, " %08x", data); + ++p; + } + seq_buf_printf(md_cntxt_seq_buf, "\n"); + } +} + +static void md_reg_context_data(struct pt_regs *regs) +{ + unsigned int i; + int nbytes = 128; + + if (user_mode(regs) || !regs->pc) + return; + + md_dump_data(regs->pc - nbytes, nbytes * 2, "PC"); + md_dump_data(regs->regs[30] - nbytes, nbytes * 2, "LR"); + md_dump_data(regs->sp - nbytes, nbytes * 2, "SP"); + for (i = 0; i < 30; i++) { + char name[4]; + + snprintf(name, sizeof(name), "X%u", i); + md_dump_data(regs->regs[i] - nbytes, nbytes * 2, name); + } +} + +static inline void md_dump_panic_regs(void) +{ + struct pt_regs regs; + u64 tmp1, tmp2; + + /* Lifted from crash_setup_regs() */ + __asm__ __volatile__ ( + "stp x0, x1, [%2, #16 * 0]\n" + "stp x2, x3, [%2, #16 * 1]\n" + "stp x4, x5, [%2, #16 * 2]\n" + "stp x6, x7, [%2, #16 * 3]\n" + "stp x8, x9, [%2, #16 * 4]\n" + "stp x10, x11, [%2, #16 * 5]\n" + "stp x12, x13, [%2, #16 * 6]\n" + "stp x14, x15, [%2, #16 * 7]\n" + "stp x16, x17, [%2, #16 * 8]\n" + "stp x18, x19, [%2, #16 * 9]\n" + "stp x20, x21, [%2, #16 * 10]\n" + "stp x22, x23, [%2, #16 * 11]\n" + "stp x24, x25, [%2, #16 * 12]\n" + "stp x26, x27, [%2, #16 * 13]\n" + "stp x28, x29, [%2, #16 * 14]\n" + "mov %0, sp\n" + "stp x30, %0, [%2, #16 * 15]\n" + + "/* faked current PSTATE */\n" + "mrs %0, CurrentEL\n" + "mrs %1, SPSEL\n" + "orr %0, %0, %1\n" + "mrs %1, DAIF\n" + "orr %0, %0, %1\n" + "mrs %1, NZCV\n" + "orr %0, %0, %1\n" + /* pc */ + "adr %1, 1f\n" + "1:\n" + "stp %1, %0, [%2, #16 * 16]\n" + : "=&r" (tmp1), "=&r" (tmp2) + : "r" (®s) + : "memory" + ); + + seq_buf_printf(md_cntxt_seq_buf, "PANIC CPU : %d\n", + raw_smp_processor_id()); + md_reg_context_data(®s); +} + +static void md_dump_other_cpus_context(void) +{ + int cpu; + struct pt_regs regs; + + for_each_possible_cpu(cpu) { + regs = per_cpu(regs_before_stop, cpu); + seq_buf_printf(md_cntxt_seq_buf, "\nSTOPPED CPU : %d\n", cpu); + md_reg_context_data(®s); + } +} + +static int md_die_context_notify(struct notifier_block *self, + unsigned long val, void *data) +{ + struct die_args *args = (struct die_args *)data; + + if (md_in_oops_handler) + return NOTIFY_DONE; + md_in_oops_handler = true; + if (!md_cntxt_seq_buf) { + md_in_oops_handler = false; + return NOTIFY_DONE; + } + die_cpu = raw_smp_processor_id(); + seq_buf_printf(md_cntxt_seq_buf, "\nDIE CPU : %d\n", die_cpu); + md_reg_context_data(args->regs); + md_in_oops_handler = false; + return NOTIFY_DONE; +} + +static struct notifier_block md_die_context_nb = { + .notifier_call = md_die_context_notify, + .priority = INT_MAX - 2, /* < msm watchdog die notifier */ +}; + +static void md_ipi_stop(void *unused, struct pt_regs *regs) +{ + unsigned int cpu = smp_processor_id(); + + per_cpu(regs_before_stop, cpu) = *regs; +} +#endif + +void md_dump_process(void) +{ + if (md_in_oops_handler) + return; + if (!atomic_add_unless(&md_handle_done, 1, 1)) + return; + md_in_oops_handler = true; +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_CPU_CONTEXT + if (!md_cntxt_seq_buf) + goto dump_rq; + if (raw_smp_processor_id() != die_cpu) + md_dump_panic_regs(); + md_dump_other_cpus_context(); +dump_rq: +#endif + md_dump_runqueues(); + if (md_meminfo_seq_buf) + md_dump_meminfo(md_meminfo_seq_buf); + +#ifdef CONFIG_SLUB_DEBUG + if (md_slabinfo_seq_buf) + md_dump_slabinfo(md_slabinfo_seq_buf); +#endif + +#ifdef CONFIG_PAGE_OWNER + if (md_pageowner_dump_addr) + md_dump_pageowner(md_pageowner_dump_addr, md_pageowner_dump_size); +#endif + +#ifdef CONFIG_SLUB_DEBUG + if (md_slabowner_dump_addr) + md_dump_slabowner(md_slabowner_dump_addr, md_slabowner_dump_size); +#endif + if (md_dma_buf_info_addr) + md_dma_buf_info(md_dma_buf_info_addr, md_dma_buf_info_size); + if (md_dma_buf_procs_addr) + md_dma_buf_procs(md_dma_buf_procs_addr, md_dma_buf_procs_size); + md_in_oops_handler = false; +} +EXPORT_SYMBOL(md_dump_process); + +static int md_panic_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + md_dump_process(); + return NOTIFY_DONE; +} + +static struct notifier_block md_panic_blk = { + .notifier_call = md_panic_handler, + .priority = INT_MAX - 2, /* < msm watchdog panic notifier */ +}; + +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 = msm_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) +{ + char *buf; + struct seq_buf *seq_buf_p; + int ret; + + buf = kzalloc(num_pages * PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -EINVAL; + + seq_buf_p = kzalloc(sizeof(*seq_buf_p), GFP_KERNEL); + if (!seq_buf_p) { + ret = -EINVAL; + goto err_seq_buf; + } + + ret = md_register_minidump_entry(name, (uintptr_t)buf, + virt_to_phys(buf), + num_pages * PAGE_SIZE); + if (ret < 0) + goto err_entry_reg; + + seq_buf_init(seq_buf_p, buf, num_pages * PAGE_SIZE); + + /* Complete registration before populating data */ + smp_mb(); + WRITE_ONCE(*global_buf, seq_buf_p); + return 0; + +err_entry_reg: + kfree(seq_buf_p); +err_seq_buf: + kfree(buf); + return ret; +} + +static void md_register_panic_data(void) +{ + struct dentry *minidump_dir = NULL; + + md_register_panic_entries(MD_RUNQUEUE_PAGES, "KRUNQUEUE", + &md_runq_seq_buf); +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_CPU_CONTEXT + md_register_panic_entries(MD_CPU_CNTXT_PAGES, "KCNTXT", + &md_cntxt_seq_buf); + register_trace_android_vh_ipi_stop(md_ipi_stop, NULL); +#endif + md_register_panic_entries(MD_MEMINFO_PAGES, "MEMINFO", + &md_meminfo_seq_buf); +#ifdef CONFIG_SLUB_DEBUG + md_register_panic_entries(MD_SLABINFO_PAGES, "SLABINFO", + &md_slabinfo_seq_buf); +#endif + if (!minidump_dir) + minidump_dir = debugfs_create_dir("minidump", NULL); +#ifdef CONFIG_PAGE_OWNER + if (is_page_owner_enabled()) { + md_register_memory_dump(md_pageowner_dump_size, "PAGEOWNER"); + md_debugfs_pageowner(minidump_dir); + } +#endif +#ifdef CONFIG_SLUB_DEBUG + if (is_slub_debug_enabled()) { + md_register_memory_dump(md_slabowner_dump_size, "SLABOWNER"); + md_debugfs_slabowner(minidump_dir); + } +#endif + md_register_memory_dump(md_dma_buf_info_size, "DMABUF_INFO"); + md_debugfs_dmabufinfo(minidump_dir); + md_register_memory_dump(md_dma_buf_procs_size, "DMABUF_PROCS"); + md_debugfs_dmabufprocs(minidump_dir); +} +#endif + +static int register_vmap_mem(const char *name, void *virual_addr, size_t dump_len) +{ + int to_dump; + u64 phys_addr; + char entry_name[12]; + void *dump_addr = virual_addr; + int i = 0; + + while (dump_len) { + to_dump = min(dump_len, PAGE_SIZE - offset_in_page(dump_addr)); + phys_addr = page_to_phys(vmalloc_to_page((const void *)dump_addr)); + snprintf(entry_name, sizeof(entry_name), "%d_%s", i, name); + md_register_minidump_entry(entry_name, (u64)dump_addr, phys_addr, to_dump); + dump_addr += to_dump; + dump_len -= to_dump; + i++; + } + + return 0; +} + +struct module_sect_attr { + struct bin_attribute battr; + unsigned long address; +}; + +struct module_sect_attrs { + struct attribute_group grp; + unsigned int nsections; + struct module_sect_attr attrs[]; +}; + +static int md_module_process(struct module *mod) +{ + int i; + bool is_key_module = false; + unsigned long sec_addr, base_addr; + unsigned long dump_start, dump_end; + + for (i = 0; i < n_modump; i++) { + if (strcmp(key_modules[i], mod->name) == 0) + is_key_module = true; + } + + if (md_mod_info_seq_buf) { + base_addr = (unsigned long)mod->core_layout.base; + seq_buf_printf(md_mod_info_seq_buf, "name: %s, base: %lx", + mod->name, base_addr); + if (is_key_module) { + dump_start = base_addr + + mod->core_layout.ro_after_init_size; + dump_end = base_addr + mod->core_layout.size; + if (((dump_end - dump_start) / PAGE_SIZE) < + msm_minidump_get_available_region()) { + for (i = 0; i < mod->sect_attrs->nsections ; i++) { + sec_addr = mod->sect_attrs->attrs[i].address; + if (sec_addr >= dump_start && sec_addr < dump_end) { + seq_buf_printf(md_mod_info_seq_buf, ", %s: %lx", + mod->sect_attrs->attrs[i].battr.attr.name, + sec_addr); + } + } + register_vmap_mem(mod->name, (void *)dump_start, + (dump_end - dump_start)); + } else + pr_err("Failed to dump module %s\n", mod->name); + } + seq_buf_printf(md_mod_info_seq_buf, "\n"); + } + + return 0; +} + +static int md_get_present_module(const char *mod_name, + void *mod_addr, void *data) +{ + struct module *mod = container_of(mod_name, + struct module, name[0]); + + if (mod != THIS_MODULE) + md_module_process(mod); + return 0; +} + +static int md_module_notify(struct notifier_block *self, + unsigned long val, void *data) +{ + struct module *mod = data; + + spin_lock(&md_modules_lock); + if (mod->state == MODULE_STATE_LIVE) + md_module_process(mod); + spin_unlock(&md_modules_lock); + return 0; +} + +static struct notifier_block md_module_nb = { + .notifier_call = md_module_notify, +}; + +static void md_register_module_data(void) +{ + int ret; + + ret = md_register_panic_entries(MD_MODULE_PAGES, "KMODULES", + &md_mod_info_seq_buf); + if (ret) { + pr_err("Failed to register minidump module buffer\n"); + return; + } + + seq_buf_printf(md_mod_info_seq_buf, "=== MODULE INFO ===\n"); + ret = register_module_notifier(&md_module_nb); + if (ret) { + pr_err("Failed to register minidump module notifier\n"); + return; + } + + android_debug_for_each_module(md_get_present_module, NULL); +} + +#ifdef CONFIG_QCOM_MINIDUMP_PSTORE +static void register_pstore_info(void) +{ + int ret; + struct device_node *node; + struct resource resource; + struct reserved_mem *rmem = NULL; + unsigned int size; + phys_addr_t paddr; + unsigned long total_size; + struct md_region md_entry; + + node = of_find_compatible_node(NULL, NULL, "ramoops"); + if (IS_ERR_OR_NULL(node)) { + pr_err("Failed to get pstore node\n"); + return; + } + + ret = of_address_to_resource(node, 0, &resource); + if (ret) { + rmem = of_reserved_mem_lookup(node); + if (rmem) { + paddr = rmem->base; + total_size = rmem->size; + } else { + pr_err("Failed to get pstore mem\n"); + return; + } + } else { + paddr = resource.start; + total_size = resource_size(&resource); + } + + ret = of_property_read_u32(node, "record-size", &size); + if (!ret && size > 0) { + strscpy(md_entry.name, "KDMESG", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)phys_to_virt(paddr); + md_entry.phys_addr = paddr; + md_entry.size = size; + + if (msm_minidump_add_region(&md_entry) < 0) + pr_err("Failed to add dmesg in Minidump\n"); + + paddr += size; + } + + ret = of_property_read_u32(node, "console-size", &size); + if (!ret && size > 0) { + strscpy(md_entry.name, "KCONSOLE", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)phys_to_virt(paddr); + md_entry.phys_addr = paddr; + md_entry.size = size; + + if (msm_minidump_add_region(&md_entry) < 0) + pr_err("Failed to add console in Minidump\n"); + + paddr += size; + } + + ret = of_property_read_u32(node, "ftrace-size", &size); + if (!ret && size > 0) { + strscpy(md_entry.name, "KFTRACE", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)phys_to_virt(paddr); + md_entry.phys_addr = paddr; + md_entry.size = size; + + if (msm_minidump_add_region(&md_entry) < 0) + pr_err("Failed to add ftrace in Minidump\n"); + + paddr += size; + } + + ret = of_property_read_u32(node, "pmsg-size", &size); + if (!ret && size > 0) { + strscpy(md_entry.name, "KPMSG", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)phys_to_virt(paddr); + md_entry.phys_addr = paddr; + md_entry.size = size; + + if (msm_minidump_add_region(&md_entry) < 0) + pr_err("Failed to add pmsg in Minidump\n"); + + paddr += size; + } +} +#endif + +int msm_minidump_log_init(void) +{ + register_kernel_sections(); + is_vmap_stack = IS_ENABLED(CONFIG_VMAP_STACK); + register_irq_stack(); +#ifdef CONFIG_QCOM_DYN_MINIDUMP_STACK + register_current_stack(); + register_suspend_context(); +#endif +#ifdef CONFIG_QCOM_MINIDUMP_PSTORE + register_pstore_info(); +#endif +#ifdef CONFIG_QCOM_MINIDUMP_FTRACE + md_register_trace_buf(); +#endif + md_register_module_data(); +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_DUMP + md_register_panic_data(); + atomic_notifier_chain_register(&panic_notifier_list, &md_panic_blk); +#ifdef CONFIG_QCOM_MINIDUMP_PANIC_CPU_CONTEXT + register_die_notifier(&md_die_context_nb); +#endif +#endif + return 0; +} diff --git a/drivers/soc/qcom/minidump_memory.c b/drivers/soc/qcom/minidump_memory.c new file mode 100644 index 000000000000..394a7a5b77d3 --- /dev/null +++ b/drivers/soc/qcom/minidump_memory.c @@ -0,0 +1,1345 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "minidump_memory.h" +#include "../../../mm/slab.h" +#include "../mm/internal.h" + +#define DMA_BUF_HASH_SIZE (1 << 20) +#define DMA_BUF_HASH_SEED 0x9747b28c +static bool dma_buf_hash[DMA_BUF_HASH_SIZE]; + +struct priv_buf { + char *buf; + size_t size; + size_t offset; +}; + +struct dma_buf_priv { + struct priv_buf *priv_buf; + struct task_struct *task; + int count; + size_t size; +}; + +static void show_val_kb(struct seq_buf *m, const char *s, unsigned long num) +{ + seq_buf_printf(m, "%s : %lld KB\n", s, num << (PAGE_SHIFT - 10)); +} + +void md_dump_meminfo(struct seq_buf *m) +{ + struct sysinfo i; + long cached; + long available; + unsigned long pages[NR_LRU_LISTS]; + unsigned long sreclaimable, sunreclaim; + int lru; + unsigned long *addr; + + si_meminfo(&i); + si_swapinfo(&i); + + cached = global_node_page_state(NR_FILE_PAGES) - + total_swapcache_pages() - i.bufferram; + if (cached < 0) + cached = 0; + + for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++) + pages[lru] = global_node_page_state(NR_LRU_BASE + lru); + + available = si_mem_available(); + sreclaimable = global_node_page_state_pages(NR_SLAB_RECLAIMABLE_B); + sunreclaim = global_node_page_state_pages(NR_SLAB_UNRECLAIMABLE_B); + + show_val_kb(m, "MemTotal: ", i.totalram); + show_val_kb(m, "MemFree: ", i.freeram); + show_val_kb(m, "MemAvailable: ", available); + show_val_kb(m, "Buffers: ", i.bufferram); + show_val_kb(m, "Cached: ", cached); + show_val_kb(m, "SwapCached: ", total_swapcache_pages()); + show_val_kb(m, "Active: ", pages[LRU_ACTIVE_ANON] + + pages[LRU_ACTIVE_FILE]); + show_val_kb(m, "Inactive: ", pages[LRU_INACTIVE_ANON] + + pages[LRU_INACTIVE_FILE]); + show_val_kb(m, "Active(anon): ", pages[LRU_ACTIVE_ANON]); + show_val_kb(m, "Inactive(anon): ", pages[LRU_INACTIVE_ANON]); + show_val_kb(m, "Active(file): ", pages[LRU_ACTIVE_FILE]); + show_val_kb(m, "Inactive(file): ", pages[LRU_INACTIVE_FILE]); + show_val_kb(m, "Unevictable: ", pages[LRU_UNEVICTABLE]); + show_val_kb(m, "Mlocked: ", global_zone_page_state(NR_MLOCK)); + +#ifdef CONFIG_HIGHMEM + show_val_kb(m, "HighTotal: ", i.totalhigh); + show_val_kb(m, "HighFree: ", i.freehigh); + show_val_kb(m, "LowTotal: ", i.totalram - i.totalhigh); + show_val_kb(m, "LowFree: ", i.freeram - i.freehigh); +#endif + + show_val_kb(m, "SwapTotal: ", i.totalswap); + show_val_kb(m, "SwapFree: ", i.freeswap); + show_val_kb(m, "Dirty: ", + global_node_page_state(NR_FILE_DIRTY)); + show_val_kb(m, "Writeback: ", + global_node_page_state(NR_WRITEBACK)); + show_val_kb(m, "AnonPages: ", + global_node_page_state(NR_ANON_MAPPED)); + show_val_kb(m, "Mapped: ", + global_node_page_state(NR_FILE_MAPPED)); + show_val_kb(m, "Shmem: ", i.sharedram); + show_val_kb(m, "KReclaimable: ", sreclaimable + + global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE)); + show_val_kb(m, "Slab: ", sreclaimable + sunreclaim); + show_val_kb(m, "SReclaimable: ", sreclaimable); + show_val_kb(m, "SUnreclaim: ", sunreclaim); + seq_buf_printf(m, "KernelStack: %8lu kB\n", + global_node_page_state(NR_KERNEL_STACK_KB)); +#ifdef CONFIG_SHADOW_CALL_STACK + seq_buf_printf(m, "ShadowCallStack:%8lu kB\n", + global_node_page_state(NR_KERNEL_SCS_KB)); +#endif + show_val_kb(m, "PageTables: ", + global_node_page_state(NR_PAGETABLE)); + show_val_kb(m, "Bounce: ", + global_zone_page_state(NR_BOUNCE)); + show_val_kb(m, "WritebackTmp: ", + global_node_page_state(NR_WRITEBACK_TEMP)); + seq_buf_printf(m, "VmallocTotal: %8lu kB\n", + (unsigned long)VMALLOC_TOTAL >> 10); + show_val_kb(m, "VmallocUsed: ", + *(unsigned long *)android_debug_symbol(ADS_VMALLOC_NR_PAGES)); + show_val_kb(m, "Percpu: ", + *(unsigned long *)android_debug_symbol(ADS_PCPU_NR_PAGES)); + +#ifdef CONFIG_TRANSPARENT_HUGEPAGE + show_val_kb(m, "AnonHugePages: ", + global_node_page_state(NR_ANON_THPS) * HPAGE_PMD_NR); + show_val_kb(m, "ShmemHugePages: ", + global_node_page_state(NR_SHMEM_THPS) * HPAGE_PMD_NR); + show_val_kb(m, "ShmemPmdMapped: ", + global_node_page_state(NR_SHMEM_PMDMAPPED) * HPAGE_PMD_NR); + show_val_kb(m, "FileHugePages: ", + global_node_page_state(NR_FILE_THPS) * HPAGE_PMD_NR); + show_val_kb(m, "FilePmdMapped: ", + global_node_page_state(NR_FILE_PMDMAPPED) * HPAGE_PMD_NR); +#endif + +#ifdef CONFIG_CMA + addr = (unsigned long *)android_debug_symbol(ADS_TOTAL_CMA); + show_val_kb(m, "CmaTotal: ", *addr); + show_val_kb(m, "CmaFree: ", + global_zone_page_state(NR_FREE_CMA_PAGES)); +#endif +} + +#ifdef CONFIG_SLUB_DEBUG +static void slabinfo_stats(struct seq_buf *m, struct kmem_cache *cachep) +{ +#ifdef CONFIG_DEBUG_SLAB + { /* node stats */ + unsigned long high = cachep->high_mark; + unsigned long allocs = cachep->num_allocations; + unsigned long grown = cachep->grown; + unsigned long reaped = cachep->reaped; + unsigned long errors = cachep->errors; + unsigned long max_freeable = cachep->max_freeable; + unsigned long node_allocs = cachep->node_allocs; + unsigned long node_frees = cachep->node_frees; + unsigned long overflows = cachep->node_overflow; + + seq_buf_printf(m, + " : globalstat %7lu %6lu %5lu %4lu %4lu %4lu %4lu %4lu %4lu", + allocs, high, grown, + reaped, errors, max_freeable, + node_allocs, node_frees, overflows); + } + /* cpu stats */ + { + unsigned long allochit = atomic_read(&cachep->allochit); + unsigned long allocmiss = atomic_read(&cachep->allocmiss); + unsigned long freehit = atomic_read(&cachep->freehit); + unsigned long freemiss = atomic_read(&cachep->freemiss); + + seq_buf_printf(m, + " : cpustat %6lu %6lu %6lu %6lu", + allochit, allocmiss, freehit, freemiss); + } +#endif +} + +void md_dump_slabinfo(struct seq_buf *m) +{ + struct kmem_cache *s; + struct slabinfo sinfo; + struct list_head *slab_caches; + struct mutex *slab_mutex; + + slab_caches = (struct list_head *)android_debug_symbol(ADS_SLAB_CACHES); + slab_mutex = (struct mutex *) android_debug_symbol(ADS_SLAB_MUTEX); + + /* print_slabinfo_header */ + seq_buf_printf(m, + "# name "); + seq_buf_printf(m, + " : tunables "); + seq_buf_printf(m, + " : slabdata "); + #ifdef CONFIG_DEBUG_SLAB + seq_buf_printf(m, + " : globalstat "); + seq_buf_printf(m, + " : cpustat "); + #endif + seq_buf_printf(m, "\n"); + + /* Loop through all slabs */ + mutex_lock(slab_mutex); + list_for_each_entry(s, slab_caches, list) { + memset(&sinfo, 0, sizeof(sinfo)); + get_slabinfo(s, &sinfo); + + seq_buf_printf(m, "%-17s %6lu %6lu %6u %4u %4d", + s->name, sinfo.active_objs, sinfo.num_objs, s->size, + sinfo.objects_per_slab, (1 << sinfo.cache_order)); + + seq_buf_printf(m, " : tunables %4u %4u %4u", + sinfo.limit, sinfo.batchcount, sinfo.shared); + seq_buf_printf(m, " : slabdata %6lu %6lu %6lu", + sinfo.active_slabs, sinfo.num_slabs, sinfo.shared_avail); + slabinfo_stats(m, s); + seq_buf_printf(m, "\n"); + } + mutex_unlock(slab_mutex); +} +#endif + +bool md_register_memory_dump(int size, char *name) +{ + struct md_region md_entry; + void *buffer_start; + struct page *page; + int ret; + + page = cma_alloc(dma_contiguous_default_area, size >> PAGE_SHIFT, + 0, GFP_KERNEL); + + if (!page) { + pr_err("Failed to allocate %s minidump, increase cma size\n", + name); + return false; + } + + buffer_start = page_to_virt(page); + strscpy(md_entry.name, name, sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t) buffer_start; + md_entry.phys_addr = virt_to_phys(buffer_start); + md_entry.size = size; + ret = msm_minidump_add_region(&md_entry); + if (ret < 0) { + cma_release(dma_contiguous_default_area, page, size >> PAGE_SHIFT); + pr_err("Failed to add %s entry in Minidump\n", name); + return false; + } + memset(buffer_start, 0, size); + + /* Complete registration before adding enteries */ + smp_mb(); + +#ifdef CONFIG_PAGE_OWNER + if (!strcmp(name, "PAGEOWNER")) + WRITE_ONCE(md_pageowner_dump_addr, buffer_start); +#endif +#ifdef CONFIG_SLUB_DEBUG + if (!strcmp(name, "SLABOWNER")) + WRITE_ONCE(md_slabowner_dump_addr, buffer_start); +#endif + if (!strcmp(name, "DMABUF_INFO")) + WRITE_ONCE(md_dma_buf_info_addr, buffer_start); + if (!strcmp(name, "DMABUF_PROCS")) + WRITE_ONCE(md_dma_buf_procs_addr, buffer_start); + return true; +} + +bool md_unregister_memory_dump(char *name) +{ + struct page *page; + struct md_region *mdr; + struct md_region md_entry; + + mdr = md_get_region(name); + if (!mdr) { + pr_err("minidump entry for %s not found\n", name); + return false; + } + strscpy(md_entry.name, mdr->name, sizeof(md_entry.name)); + md_entry.virt_addr = mdr->virt_addr; + md_entry.phys_addr = mdr->phys_addr; + md_entry.size = mdr->size; + page = virt_to_page(mdr->virt_addr); + + if (msm_minidump_remove_region(&md_entry) < 0) + return false; + + cma_release(dma_contiguous_default_area, page, + (md_entry.size) >> PAGE_SHIFT); + return true; +} + +static void update_dump_size(char *name, size_t size, char **addr, size_t *dump_size) +{ + if ((*dump_size) == 0) { + if (md_register_memory_dump(size * SZ_1M, + name)) { + *dump_size = size * SZ_1M; + pr_info_ratelimited("%s Minidump set to %zd MB size\n", + name, size); + } + return; + } + if (md_unregister_memory_dump(name)) { + *addr = NULL; + if (size == 0) { + *dump_size = 0; + pr_info_ratelimited("%s Minidump : disabled\n", name); + return; + } + if (md_register_memory_dump(size * SZ_1M, + name)) { + *dump_size = size * SZ_1M; + pr_info_ratelimited("%s Minidump : set to %zd MB\n", + name, size); + } else if (md_register_memory_dump(*dump_size, + name)) { + pr_info_ratelimited("%s Minidump : Fallback to %zd MB\n", + name, (*dump_size) / SZ_1M); + } else { + pr_err_ratelimited("%s Minidump : disabled, Can't fallback to %zd MB,\n", + name, (*dump_size) / SZ_1M); + *dump_size = 0; + } + } else { + pr_err_ratelimited("Failed to unregister %s Minidump\n", name); + } +} + +#ifdef CONFIG_PAGE_OWNER +static unsigned long page_owner_filter = 0xF; +static unsigned long page_owner_handles_size = SZ_16K; +static int nr_handles; +static LIST_HEAD(accounted_call_site_list); +static DEFINE_MUTEX(accounted_call_site_lock); +struct accounted_call_site { + struct list_head list; + char name[50]; +}; + +bool is_page_owner_enabled(void) +{ + return *(bool *)android_debug_symbol(ADS_PAGE_OWNER_ENABLED); +} + +static bool found_stack(depot_stack_handle_t handle, + char *dump_addr, char *cur) +{ + int *handles, i; + + handles = (int *) (dump_addr + + md_pageowner_dump_size - page_owner_handles_size); + + for (i = 0; i < nr_handles; i++) + if (handle == handles[i]) + return true; + + if ((handles + nr_handles) + < (int *)(dump_addr + + md_pageowner_dump_size)) { + handles[nr_handles] = handle; + nr_handles += 1; + } else { + pr_err_ratelimited("Can't stores handles increase page_owner_handles_size\n"); + } + return false; +} + +static bool check_unaccounted(char *buf, ssize_t count, + struct page *page, depot_stack_handle_t handle) +{ + int i, ret = 0; + unsigned long *entries; + unsigned int nr_entries; + struct accounted_call_site *call_site; + + if ((page->flags & + ((1UL << PG_lru) | (1UL << PG_slab) | (1UL << PG_swapbacked)))) + return false; + + nr_entries = stack_depot_fetch(handle, &entries); + for (i = 0; i < nr_entries; i++) { + ret = scnprintf(buf, count, "%pS\n", + (void *)entries[i]); + if (ret == count - 1) + return false; + + mutex_lock(&accounted_call_site_lock); + list_for_each_entry(call_site, + &accounted_call_site_list, list) { + if (strnstr(buf, call_site->name, + strlen(buf))) { + mutex_unlock(&accounted_call_site_lock); + return false; + } + } + mutex_unlock(&accounted_call_site_lock); + } + return true; +} + +static ssize_t dump_page_owner_md(char *buf, size_t count, + unsigned long pfn, struct page *page, + depot_stack_handle_t handle) +{ + int i, bit, ret = 0; + unsigned long *entries; + unsigned int nr_entries; + + if (page_owner_filter == 0xF) + goto dump; + + for (bit = 1; page_owner_filter >= bit; bit *= 2) { + if (page_owner_filter & bit) { + switch (bit) { + case 0x1: + if (check_unaccounted(buf, count, page, handle)) + goto dump; + break; + case 0x2: + if (page->flags & (1UL << PG_slab)) + goto dump; + break; + case 0x4: + if (page->flags & (1UL << PG_swapbacked)) + goto dump; + break; + case 0x8: + if ((page->flags & (1UL << PG_lru)) && + ~(page->flags & (1UL << PG_swapbacked))) + goto dump; + break; + default: + break; + } + } + if (bit >= 0x8) + return ret; + } + + if (bit > page_owner_filter) + return ret; +dump: + nr_entries = stack_depot_fetch(handle, &entries); + if ((buf > (md_pageowner_dump_addr + + md_pageowner_dump_size - page_owner_handles_size)) + || !found_stack(handle, md_pageowner_dump_addr, buf)) { + ret = scnprintf(buf, count, "%lu %u %u\n", + pfn, handle, nr_entries); + if (ret == count - 1) + goto err; + + for (i = 0; i < nr_entries; i++) { + ret += scnprintf(buf + ret, count - ret, + "%p\n", (void *)entries[i]); + if (ret == count - 1) + goto err; + } + } else { + ret = scnprintf(buf, count, "%lu %u %u\n", pfn, handle, 0); + } +err: + return ret; +} + +void md_dump_pageowner(char *addr, size_t dump_size) +{ + unsigned long pfn; + struct page *page; + struct page_ext *page_ext; + depot_stack_handle_t handle; + ssize_t size; + unsigned long min_low_pfn, max_pfn; + + min_low_pfn = *(unsigned long *)android_debug_symbol(ADS_MIN_LOW_PFN); + max_pfn = *(unsigned long *)android_debug_symbol(ADS_MAX_PFN); + page = NULL; + pfn = min_low_pfn; + + /* Find a valid PFN or the start of a MAX_ORDER_NR_PAGES area */ + while (!pfn_valid(pfn) && (pfn & (MAX_ORDER_NR_PAGES - 1)) != 0) + pfn++; + + /* Find an allocated page */ + for (; pfn < max_pfn; pfn++) { + /* + * If the new page is in a new MAX_ORDER_NR_PAGES area, + * validate the area as existing, skip it if not + */ + if ((pfn & (MAX_ORDER_NR_PAGES - 1)) == 0 && !pfn_valid(pfn)) { + pfn += MAX_ORDER_NR_PAGES - 1; + continue; + } + + page = pfn_to_page(pfn); + if (PageBuddy(page)) { + unsigned long freepage_order = buddy_order_unsafe(page); + + if (freepage_order < MAX_ORDER) + pfn += (1UL << freepage_order) - 1; + continue; + } + + page_ext = lookup_page_ext(page); + if (unlikely(!page_ext)) + continue; + + /* + * Some pages could be missed by concurrent allocation or free, + * because we don't hold the zone lock. + */ + if (!test_bit(PAGE_EXT_OWNER, &page_ext->flags)) + continue; + + /* + * Although we do have the info about past allocation of free + * pages, it's not relevant for current memory usage. + */ + if (!test_bit(PAGE_EXT_OWNER_ALLOCATED, &page_ext->flags)) + continue; + + handle = get_page_owner_handle(page_ext, pfn); + if (!handle) + continue; + + size = dump_page_owner_md(addr, dump_size, pfn, page, handle); + if (size == dump_size - 1) { + pr_err("pageowner minidump region exhausted\n"); + return; + } + dump_size -= size; + addr += size; + } +} + +static DEFINE_MUTEX(page_owner_dump_size_lock); + +static ssize_t page_owner_dump_size_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long long size; + + if (kstrtoull_from_user(ubuf, count, 0, &size)) { + pr_err_ratelimited("Invalid format for size\n"); + return -EINVAL; + } + mutex_lock(&page_owner_dump_size_lock); + update_dump_size("PAGEOWNER", size, + &md_pageowner_dump_addr, &md_pageowner_dump_size); + mutex_unlock(&page_owner_dump_size_lock); + return count; +} + +static ssize_t page_owner_dump_size_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[100]; + + snprintf(buf, sizeof(buf), "%llu MB\n", + md_pageowner_dump_size / SZ_1M); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_page_owner_dump_size_ops = { + .open = simple_open, + .write = page_owner_dump_size_write, + .read = page_owner_dump_size_read, +}; + +static ssize_t page_owner_filter_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long filter; + + if (kstrtoul_from_user(ubuf, count, 0, &filter)) { + pr_err_ratelimited("Invalid format for filter\n"); + return -EINVAL; + } + + if (filter & (~0xF)) { + pr_err_ratelimited("Invalid filter : use following filters or any combinations of these\n" + "0x1 - unaccounted\n" + "0x2 - slab\n" + "0x4 - Anon\n" + "0x8 - File\n"); + return -EINVAL; + } + page_owner_filter = filter; + return count; +} + +static ssize_t page_owner_filter_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "0x%lx\n", page_owner_filter); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_page_owner_filter_ops = { + .open = simple_open, + .write = page_owner_filter_write, + .read = page_owner_filter_read, +}; + +static ssize_t page_owner_handle_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long size; + + if (kstrtoul_from_user(ubuf, count, 0, &size)) { + pr_err_ratelimited("Invalid format for handle size\n"); + return -EINVAL; + } + + if (size) { + if (size > (md_pageowner_dump_size / SZ_16K)) { + pr_err_ratelimited("size : %lu KB exceeds max size : %lu KB\n", + size, (md_pageowner_dump_size / SZ_16K)); + goto err; + } + page_owner_handles_size = size * SZ_1K; + } +err: + return count; +} + +static ssize_t page_owner_handle_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "%lu KB\n", + (page_owner_handles_size / SZ_1K)); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_page_owner_handle_ops = { + .open = simple_open, + .write = page_owner_handle_write, + .read = page_owner_handle_read, +}; + +static ssize_t page_owner_call_site_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + struct accounted_call_site *call_site; + char buf[50]; + + if (count >= 50) { + pr_err_ratelimited("Input string size too large\n"); + return -EINVAL; + } + + memset(buf, 0, 50); + + if (copy_from_user(buf, ubuf, count)) { + pr_err_ratelimited("Couldn't copy from user\n"); + return -EFAULT; + } + + if (!isalpha(buf[0]) && buf[0] != '_') { + pr_err_ratelimited("Invalid call site name\n"); + return -EINVAL; + } + + call_site = kzalloc(sizeof(*call_site), GFP_KERNEL); + if (!call_site) + return -ENOMEM; + + strscpy(call_site->name, buf, strlen(call_site->name)); + mutex_lock(&accounted_call_site_lock); + list_add_tail(&call_site->list, &accounted_call_site_list); + mutex_unlock(&accounted_call_site_lock); + + return count; +} + +static ssize_t page_owner_call_site_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char *kbuf; + struct accounted_call_site *call_site; + int i = 1, ret = 0; + size_t size = PAGE_SIZE; + + kbuf = kmalloc(size, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + ret = scnprintf(kbuf, count, "%s\n", "Accounted call sites:"); + mutex_lock(&accounted_call_site_lock); + list_for_each_entry(call_site, &accounted_call_site_list, list) { + ret += scnprintf(kbuf + ret, size - ret, + "%d. %s\n", i, call_site->name); + i += 1; + if (ret == size) { + ret = -ENOMEM; + mutex_unlock(&accounted_call_site_lock); + goto err; + } + } + mutex_unlock(&accounted_call_site_lock); + ret = simple_read_from_buffer(ubuf, count, offset, kbuf, strlen(kbuf)); +err: + kfree(kbuf); + return ret; +} + +static const struct file_operations proc_page_owner_call_site_ops = { + .open = simple_open, + .write = page_owner_call_site_write, + .read = page_owner_call_site_read, +}; + +void md_debugfs_pageowner(struct dentry *minidump_dir) +{ + debugfs_create_file("page_owner_dump_size_mb", 0400, minidump_dir, NULL, + &proc_page_owner_dump_size_ops); + debugfs_create_file("page_owner_filter", 0400, minidump_dir, NULL, + &proc_page_owner_filter_ops); + debugfs_create_file("page_owner_handles_size_kb", 0400, minidump_dir, NULL, + &proc_page_owner_handle_ops); + debugfs_create_file("page_owner_call_sites", 0400, minidump_dir, NULL, + &proc_page_owner_call_site_ops); +} +#endif + +#ifdef CONFIG_SLUB_DEBUG +#define STACK_HASH_SEED 0x9747b28c + +static unsigned long slab_owner_filter; +static unsigned long slab_owner_handles_size = SZ_16K; +static int num_handles; + +bool is_slub_debug_enabled(void) +{ + slab_flags_t slub_debug; + + slub_debug = *(slab_flags_t *)android_debug_symbol(ADS_SLUB_DEBUG); + if (slub_debug) + return true; + return false; +} + +static bool find_stack(u32 handle, + char *md_slabowner_dump_addr, char *cur) +{ + int *handles, i; + + handles = (int *) (md_slabowner_dump_addr + + md_slabowner_dump_size - slab_owner_handles_size); + + for (i = 0; i < num_handles; i++) + if (handle == handles[i]) + return true; + + if ((handles + num_handles) + < (int *)(md_slabowner_dump_addr + + md_slabowner_dump_size)) { + handles[num_handles] = handle; + num_handles += 1; + } else { + pr_err_ratelimited("Can't stores handles increase slab_owner_handle_size\n"); + } + return false; +} + +/* Calculate hash for a stack */ +static u32 hash_stack(const unsigned long *entries, unsigned int size) +{ + return jhash2((u32 *)entries, + size * sizeof(unsigned long) / sizeof(u32), + STACK_HASH_SEED); +} + +static int dump_tracking(const struct kmem_cache *s, + const void *object, + const struct track *t, void *private) +{ + int ret = 0; + u32 handle, nr_entries; + struct priv_buf *priv_buf; + char *buf; + size_t size; + + if (!t->addr) + return 0; + + priv_buf = (struct priv_buf *)private; + buf = priv_buf->buf + priv_buf->offset; + size = priv_buf->size - priv_buf->offset; +#ifdef CONFIG_STACKTRACE + { + int i; + + for (i = 0; i < TRACK_ADDRS_COUNT; i++) + if (t->addrs[i]) + continue; + else + break; + nr_entries = i; + handle = hash_stack(t->addrs, nr_entries); + + if ((buf > (md_slabowner_dump_addr + + md_slabowner_dump_size - slab_owner_handles_size)) + || !find_stack(handle, md_slabowner_dump_addr, buf)) { + + ret = scnprintf(buf, size, "%p %u %u\n", + object, handle, nr_entries); + if (ret == size - 1) + goto err; + + for (i = 0; i < nr_entries; i++) { + ret += scnprintf(buf + ret, size - ret, + "%p\n", (void *)t->addrs[i]); + if (ret == size - 1) + goto err; + } + } else { + ret = scnprintf(buf, size, "%p %u %u\n", + object, handle, 0); + } + } +#else + ret = scnprintf(buf, size, "%p %p\n", object, (void *)t->addr); + +#endif +err: + priv_buf->offset += ret; + return ret; +} + +void md_dump_slabowner(char *m, size_t dump_size) +{ + struct kmem_cache *s; + int node; + struct priv_buf buf; + struct kmem_cache_node *n; + ssize_t ret; + int i; + + buf.buf = m; + buf.size = dump_size; + buf.offset = 0; + + for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) { + if (!test_bit(i, &slab_owner_filter)) + continue; + s = kmalloc_caches[KMALLOC_NORMAL][i]; + if (!s) + continue; + ret = scnprintf(buf.buf, buf.size, "%s\n", s->name); + if (ret == buf.size - 1) + return; + buf.buf += ret; + for_each_kmem_cache_node(s, node, n) { + unsigned long flags; + struct page *page; + + if (!atomic_long_read(&n->nr_slabs)) + continue; + + spin_lock_irqsave(&n->list_lock, flags); + list_for_each_entry(page, &n->partial, lru) { + ret = get_each_object_track(s, page, TRACK_ALLOC, + dump_tracking, &buf); + if (buf.offset == buf.size - 1) { + spin_unlock_irqrestore(&n->list_lock, flags); + pr_err("slabowner minidump region exhausted\n"); + return; + } + } + list_for_each_entry(page, &n->full, lru) { + ret = get_each_object_track(s, page, TRACK_ALLOC, + dump_tracking, &buf); + if (buf.offset == buf.size - 1) { + spin_unlock_irqrestore(&n->list_lock, flags); + pr_err("slabowner minidump region exhausted\n"); + return; + } + } + spin_unlock_irqrestore(&n->list_lock, flags); + } + ret = scnprintf(buf.buf, buf.size, "\n"); + if (ret == buf.size - 1) + return; + buf.buf += ret; + } +} + +static ssize_t slab_owner_dump_size_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long long size; + + if (kstrtoull_from_user(ubuf, count, 0, &size)) { + pr_err_ratelimited("Invalid format for size\n"); + return -EINVAL; + } + update_dump_size("SLABOWNER", size, + &md_slabowner_dump_addr, &md_slabowner_dump_size); + return count; +} + +static ssize_t slab_owner_dump_size_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[100]; + + snprintf(buf, sizeof(buf), "%llu MB\n", md_slabowner_dump_size/SZ_1M); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_slab_owner_dump_size_ops = { + .open = simple_open, + .write = slab_owner_dump_size_write, + .read = slab_owner_dump_size_read, +}; + +static ssize_t slab_owner_filter_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long filter; + int bit, i; + struct kmem_cache *s; + + if (kstrtoul_from_user(ubuf, count, 0, &filter)) { + pr_err_ratelimited("Invalid format for filter\n"); + return -EINVAL; + } + + for (i = 0, bit = 1; filter >= bit; bit *= 2, i++) { + if (filter & bit) { + s = kmalloc_caches[KMALLOC_NORMAL][i]; + if (!s) { + pr_err("Invalid filter : %lx kmalloc-%d doesn't exist\n", + filter, bit); + return -EINVAL; + } + } + } + slab_owner_filter = filter; + return count; +} + +static ssize_t slab_owner_filter_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "0x%lx\n", slab_owner_filter); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_slab_owner_filter_ops = { + .open = simple_open, + .write = slab_owner_filter_write, + .read = slab_owner_filter_read, +}; + +static ssize_t slab_owner_handle_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long size; + + if (kstrtoul_from_user(ubuf, count, 0, &size)) { + pr_err_ratelimited("Invalid format for handle size\n"); + return -EINVAL; + } + + if (size) { + if (size > (md_slabowner_dump_size / SZ_16K)) { + pr_err_ratelimited("size : %lu KB exceeds max size : %lu KB\n", + size, (md_slabowner_dump_size / SZ_16K)); + goto err; + } + slab_owner_handles_size = size * SZ_1K; + } +err: + return count; +} + +static ssize_t slab_owner_handle_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "%lu KB\n", + (slab_owner_handles_size / SZ_1K)); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_slab_owner_handle_ops = { + .open = simple_open, + .write = slab_owner_handle_write, + .read = slab_owner_handle_read, +}; + +void md_debugfs_slabowner(struct dentry *minidump_dir) +{ + int i; + + debugfs_create_file("slab_owner_dump_size_mb", 0400, minidump_dir, NULL, + &proc_slab_owner_dump_size_ops); + debugfs_create_file("slab_owner_filter", 0400, minidump_dir, NULL, + &proc_slab_owner_filter_ops); + debugfs_create_file("slab_owner_handles_size_kb", 0400, + minidump_dir, NULL, &proc_slab_owner_handle_ops); + for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) { + if (kmalloc_caches[KMALLOC_NORMAL][i]) + set_bit(i, &slab_owner_filter); + } +} +#endif /* CONFIG_SLUB_DEBUG */ + +static int dump_bufinfo(const struct dma_buf *buf_obj, void *private) +{ + int ret; + struct dma_buf_attachment *attach_obj; + struct dma_resv *robj; + struct dma_resv_list *fobj; + struct dma_fence *fence; + unsigned int seq; + int attach_count, shared_count, i = 0; + struct dma_buf_priv *buf = (struct dma_buf_priv *)private; + struct priv_buf *priv_buf = buf->priv_buf; + + + ret = dma_resv_lock(buf_obj->resv, NULL); + if (ret) + goto err; + + ret = scnprintf(priv_buf->buf + priv_buf->offset, + priv_buf->size - priv_buf->offset, + "%08zu\t%08x\t%08x\t%08ld\t%s\t%08lu\t%s\n", + buf_obj->size, + buf_obj->file->f_flags, buf_obj->file->f_mode, + file_count(buf_obj->file), + buf_obj->exp_name, + file_inode(buf_obj->file)->i_ino, + buf_obj->name ?: ""); + priv_buf->offset += ret; + if (priv_buf->offset == priv_buf->size - 1) + goto err; + + robj = buf_obj->resv; + while (true) { + seq = read_seqcount_begin(&robj->seq); + rcu_read_lock(); + fobj = rcu_dereference(robj->fence); + shared_count = fobj ? fobj->shared_count : 0; + fence = rcu_dereference(robj->fence_excl); + if (!read_seqcount_retry(&robj->seq, seq)) + break; + rcu_read_unlock(); + } + + if (fence) { + ret = scnprintf(priv_buf->buf + priv_buf->offset, + priv_buf->size - priv_buf->offset, + "\tExclusive fence: %s %s %ssignalled\n", + fence->ops->get_driver_name(fence), + fence->ops->get_timeline_name(fence), + dma_fence_is_signaled(fence) ? "" : "un"); + priv_buf->offset += ret; + if (priv_buf->offset == priv_buf->size - 1) + goto err; + } + for (i = 0; i < shared_count; i++) { + fence = rcu_dereference(fobj->shared[i]); + if (!dma_fence_get_rcu(fence)) + continue; + ret = scnprintf(priv_buf->buf + priv_buf->offset, + priv_buf->size - priv_buf->offset, + "\tShared fence: %s %s %ssignalled\n", + fence->ops->get_driver_name(fence), + fence->ops->get_timeline_name(fence), + dma_fence_is_signaled(fence) ? "" : "un"); + priv_buf->offset += ret; + if (priv_buf->offset == priv_buf->size - 1) + goto err; + dma_fence_put(fence); + } + rcu_read_unlock(); + + ret = scnprintf(priv_buf->buf + priv_buf->offset, + priv_buf->size - priv_buf->offset, + "\tAttached Devices:\n"); + priv_buf->offset += ret; + if (priv_buf->offset == priv_buf->size - 1) + goto err; + attach_count = 0; + + list_for_each_entry(attach_obj, &buf_obj->attachments, node) { + ret = scnprintf(priv_buf->buf + priv_buf->offset, + priv_buf->size - priv_buf->offset, + "\t%s\n", dev_name(attach_obj->dev)); + priv_buf->offset += ret; + if (priv_buf->offset == priv_buf->size - 1) + goto err; + attach_count++; + } + dma_resv_unlock(buf_obj->resv); + + ret = scnprintf(priv_buf->buf + priv_buf->offset, + priv_buf->size - priv_buf->offset, + "Total %d devices attached\n\n", + attach_count); + priv_buf->offset += ret; + if (priv_buf->offset == priv_buf->size - 1) + goto err; + + buf->count += 1; + buf->size += buf_obj->size; + + return 0; +err: + pr_err("DMABUF_INFO minidump region exhausted\n"); + return -ENOSPC; +} + +void md_dma_buf_info(char *m, size_t dump_size) +{ + int ret; + struct dma_buf_priv dma_buf_priv; + struct priv_buf buf; + + buf.buf = m; + buf.size = dump_size; + buf.offset = 0; + dma_buf_priv.priv_buf = &buf; + dma_buf_priv.count = 0; + dma_buf_priv.size = 0; + + ret = scnprintf(buf.buf, buf.size, "\nDma-buf Objects:\n"); + ret += scnprintf(buf.buf + ret, buf.size - ret, + "%-8s\t%-8s\t%-8s\t%-8s\texp_name\t%-8s\n", + "size", "flags", "mode", "count", "ino"); + buf.offset = ret; + + get_each_dmabuf(dump_bufinfo, &dma_buf_priv); + + scnprintf(buf.buf + buf.offset, buf.size - buf.offset, + "\nTotal %d objects, %zu bytes\n", + dma_buf_priv.count, dma_buf_priv.size); +} + +static ssize_t dma_buf_info_size_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long long size; + + if (kstrtoull_from_user(ubuf, count, 0, &size)) { + pr_err_ratelimited("Invalid format for size\n"); + return -EINVAL; + } + update_dump_size("DMABUF_INFO", size, + &md_dma_buf_info_addr, &md_dma_buf_info_size); + return count; +} + +static ssize_t dma_buf_info_size_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[100]; + + snprintf(buf, sizeof(buf), "%llu MB\n", md_dma_buf_info_size/SZ_1M); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_dma_buf_info_size_ops = { + .open = simple_open, + .write = dma_buf_info_size_write, + .read = dma_buf_info_size_read, +}; + +void md_debugfs_dmabufinfo(struct dentry *minidump_dir) +{ + debugfs_create_file("dma_buf_info_size_mb", 0400, minidump_dir, NULL, + &proc_dma_buf_info_size_ops); +} + +static int get_dma_info(const void *data, struct file *file, unsigned int n) +{ + struct priv_buf *buf; + struct dma_buf_priv *dma_buf_priv; + struct dma_buf *dmabuf; + struct task_struct *task; + int ret; + u32 index; + + if (!is_dma_buf_file(file)) + return 0; + + dma_buf_priv = (struct dma_buf_priv *)data; + buf = dma_buf_priv->priv_buf; + task = dma_buf_priv->task; + if (dma_buf_priv->count == 0) { + ret = scnprintf(buf->buf + buf->offset, buf->size - buf->offset, + "\n%s (PID %d)\nDMA Buffers:\n", + task->comm, task->tgid); + buf->offset += ret; + if (buf->offset == buf->size - 1) + return -EINVAL; + } + dmabuf = (struct dma_buf *)file->private_data; + index = jhash(dmabuf, sizeof(struct dma_buf), DMA_BUF_HASH_SEED); + index = index & (DMA_BUF_HASH_SIZE - 1); + if (dma_buf_hash[index]) + return 0; + dma_buf_hash[index] = true; + dma_buf_priv->count += 1; + ret = scnprintf(buf->buf + buf->offset, buf->size - buf->offset, + "%-8s\t%-8s\t%-8s\t%-8s\texp_name\t%-8s\n", + "size", "flags", "mode", "count", "ino"); + buf->offset += ret; + if (buf->offset == buf->size - 1) + return -EINVAL; + ret = scnprintf(buf->buf + buf->offset, buf->size - buf->offset, + "%08zu\t%08x\t%08x\t%08ld\t%s\t%08lu\t%s\n", + dmabuf->size, + dmabuf->file->f_flags, dmabuf->file->f_mode, + file_count(dmabuf->file), + dmabuf->exp_name, + file_inode(dmabuf->file)->i_ino, + dmabuf->name ?: ""); + buf->offset += ret; + if (buf->offset == buf->size - 1) + return -EINVAL; + dma_buf_priv->size += dmabuf->size; + return 0; +} + +void md_dma_buf_procs(char *m, size_t dump_size) +{ + struct task_struct *task, *thread; + struct files_struct *files; + int ret = 0; + struct priv_buf buf; + struct dma_buf_priv dma_buf_priv; + + buf.buf = m; + buf.size = dump_size; + buf.offset = 0; + dma_buf_priv.priv_buf = &buf; + dma_buf_priv.count = 0; + dma_buf_priv.size = 0; + + rcu_read_lock(); + for_each_process(task) { + struct files_struct *group_leader_files = NULL; + + dma_buf_priv.task = task; + for_each_thread(task, thread) { + task_lock(thread); + if (unlikely(!group_leader_files)) + group_leader_files = task->group_leader->files; + files = thread->files; + if (files && (group_leader_files != files || + thread == task->group_leader)) + ret = iterate_fd(files, 0, get_dma_info, &dma_buf_priv); + task_unlock(thread); + if (ret) + goto err; + } + if (dma_buf_priv.count) { + ret = scnprintf(buf.buf + buf.offset, buf.size - buf.offset, + "\nTotal %d objects, %zu bytes\n", + dma_buf_priv.count, dma_buf_priv.size); + buf.offset += ret; + if (buf.offset == buf.size - 1) + goto err; + dma_buf_priv.count = 0; + dma_buf_priv.size = 0; + memset(dma_buf_hash, 0, sizeof(dma_buf_hash)); + } + } + rcu_read_unlock(); + return; +err: + rcu_read_unlock(); + pr_err("DMABUF_PROCS Minidump region exhausted\n"); +} + +static ssize_t dma_buf_procs_size_write(struct file *file, + const char __user *ubuf, + size_t count, loff_t *offset) +{ + unsigned long long size; + + if (kstrtoull_from_user(ubuf, count, 0, &size)) { + pr_err_ratelimited("Invalid format for size\n"); + return -EINVAL; + } + update_dump_size("DMABUF_PROCS", size, + &md_dma_buf_procs_addr, &md_dma_buf_procs_size); + return count; +} + +static ssize_t dma_buf_procs_size_read(struct file *file, char __user *ubuf, + size_t count, loff_t *offset) +{ + char buf[100]; + + snprintf(buf, sizeof(buf), "%llu MB\n", md_dma_buf_procs_size/SZ_1M); + return simple_read_from_buffer(ubuf, count, offset, buf, strlen(buf)); +} + +static const struct file_operations proc_dma_buf_procs_size_ops = { + .open = simple_open, + .write = dma_buf_procs_size_write, + .read = dma_buf_procs_size_read, +}; + +void md_debugfs_dmabufprocs(struct dentry *minidump_dir) +{ + debugfs_create_file("dma_buf_procs_size_mb", 0400, minidump_dir, NULL, + &proc_dma_buf_procs_size_ops); +} diff --git a/drivers/soc/qcom/minidump_memory.h b/drivers/soc/qcom/minidump_memory.h new file mode 100644 index 000000000000..a85793e910ed --- /dev/null +++ b/drivers/soc/qcom/minidump_memory.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define MD_MEMINFO_PAGES 1 +#define MD_SLABINFO_PAGES 8 +#ifdef CONFIG_PAGE_OWNER +extern size_t md_pageowner_dump_size; +extern char *md_pageowner_dump_addr; +#endif +#ifdef CONFIG_SLUB_DEBUG +extern size_t md_slabowner_dump_size; +extern char *md_slabowner_dump_addr; +#endif +extern size_t md_dma_buf_info_size; +extern char *md_dma_buf_info_addr; +extern size_t md_dma_buf_procs_size; +extern char *md_dma_buf_procs_addr; + +void md_dump_meminfo(struct seq_buf *m); +#ifdef CONFIG_SLUB_DEBUG +void md_dump_slabinfo(struct seq_buf *m); +#else +static inline void md_dump_slabinfo(struct seq_buf *m) {} +#endif +bool md_register_memory_dump(int size, char *name); +bool md_unregister_memory_dump(char *name); +#ifdef CONFIG_PAGE_OWNER +bool is_page_owner_enabled(void); +void md_dump_pageowner(char *addr, size_t dump_size); +void md_debugfs_pageowner(struct dentry *minidump_dir); +#else +static inline bool is_page_owner_enabled(void) { return false; } +static inline void md_dump_pageowner(char *addr, size_t dump_size) {} +static inline void md_debugfs_pageowner(struct dentry *minidump_dir) {} +#endif +#ifdef CONFIG_SLUB_DEBUG +bool is_slub_debug_enabled(void); +void md_dump_slabowner(char *addr, size_t dump_size); +void md_debugfs_slabowner(struct dentry *minidump_dir); +#else +static inline bool is_slub_debug_enabled(void) { return false; } +static inline void md_dump_slabowner(char *addr, size_t dump_size) {} +static inline void md_debugfs_slabowner(struct dentry *minidump_dir) {} +#endif +void md_dma_buf_info(char *m, size_t dump_size); +void md_debugfs_dmabufinfo(struct dentry *minidump_dir); +void md_dma_buf_procs(char *m, size_t dump_size); +void md_debugfs_dmabufprocs(struct dentry *minidump_dir); diff --git a/drivers/soc/qcom/minidump_private.h b/drivers/soc/qcom/minidump_private.h new file mode 100644 index 000000000000..f8b9ff8c1f61 --- /dev/null +++ b/drivers/soc/qcom/minidump_private.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2019, 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __MINIDUMP_PRIVATE_H +#define __MINIDUMP_PRIVATE_H + +#define MD_REVISION 1 +#define SBL_MINIDUMP_SMEM_ID 602 +#define MAX_NUM_OF_SS 10 +#define MD_SS_HLOS_ID 0 +#define SMEM_ENTRY_SIZE 40 + +/* Bootloader has 16 byte support, 4 bytes reserved for itself */ +#define MAX_REGION_NAME_LENGTH 16 + +#define MD_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0) +#define MD_REGION_INVALID ('I' << 24 | 'N' << 16 | 'V' << 8 | 'A' << 0) +#define MD_REGION_INIT ('I' << 24 | 'N' << 16 | 'I' << 8 | 'T' << 0) +#define MD_REGION_NOINIT 0 + +#define MD_SS_ENCR_REQ (0 << 24 | 'Y' << 16 | 'E' << 8 | 'S' << 0) +#define MD_SS_ENCR_NOTREQ (0 << 24 | 0 << 16 | 'N' << 8 | 'R' << 0) +#define MD_SS_ENCR_NONE ('N' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0) +#define MD_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0) +#define MD_SS_ENCR_START ('S' << 24 | 'T' << 16 | 'R' << 8 | 'T' << 0) +#define MD_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0) +#define MD_SS_DISABLED ('D' << 24 | 'S' << 16 | 'B' << 8 | 'L' << 0) + +/** + * md_ss_region - Minidump region + * @name : Name of the region to be dumped + * @seq_num: : Use to differentiate regions with same name. + * @md_valid : This entry to be dumped (if set to 1) + * @region_base_address : Physical address of region to be dumped + * @region_size : Size of the region + */ +struct md_ss_region { + char name[MAX_REGION_NAME_LENGTH]; + u32 seq_num; + u32 md_valid; + u64 region_base_address; + u64 region_size; +}; + +/** + * md_ss_toc: Sub system SMEM Table of content + * @md_ss_toc_init : SS toc init status + * @md_ss_enable_status : if set to 1, Bootloader would dump this SS regions + * @encryption_status: Encryption status for this subsystem + * @encryption_required : Decides to encrypt the SS regions or not + * @ss_region_count : Number of regions added in this SS toc + * @md_ss_smem_regions_baseptr : regions base pointer of the Subsystem + */ +struct md_ss_toc { + u32 md_ss_toc_init; + u32 md_ss_enable_status; + u32 encryption_status; + u32 encryption_required; + u32 ss_region_count; + u64 md_ss_smem_regions_baseptr; +}; + +/** + * md_global_toc: Global Table of Content + * @md_toc_init : Global Minidump init status + * @md_revision : Minidump revision + * @md_enable_status : Minidump enable status + * @md_ss_toc : Array of subsystems toc + */ +struct md_global_toc { + u32 md_toc_init; + u32 md_revision; + u32 md_enable_status; + struct md_ss_toc md_ss_toc[MAX_NUM_OF_SS]; +}; + +int msm_minidump_log_init(void); + +#endif diff --git a/drivers/soc/qcom/msm_minidump.c b/drivers/soc/qcom/msm_minidump.c new file mode 100644 index 000000000000..5eb208ddc9b4 --- /dev/null +++ b/drivers/soc/qcom/msm_minidump.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "Minidump: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "minidump_private.h" +#include "elf.h" + +#define MAX_NUM_ENTRIES (CONFIG_MINIDUMP_MAX_ENTRIES + 1) +#define MAX_STRTBL_SIZE (MAX_NUM_ENTRIES * MAX_REGION_NAME_LENGTH) + +/** + * md_table : Local Minidump toc holder + * @num_regions : Number of regions requested + * @md_ss_toc : HLOS toc pointer + * @md_gbl_toc : Global toc pointer + * @md_regions : HLOS regions base pointer + * @entry : array of HLOS regions requested + */ +struct md_table { + u32 revision; + u32 num_regions; + struct md_ss_toc *md_ss_toc; + struct md_global_toc *md_gbl_toc; + struct md_ss_region *md_regions; + struct md_region entry[MAX_NUM_ENTRIES]; +}; + +/** + * md_elfhdr: Minidump table elf header + * @ehdr: elf main header + * @shdr: Section header + * @phdr: Program header + * @elf_offset: section offset in elf + * @strtable_idx: string table current index position + */ +struct md_elfhdr { + struct elfhdr *ehdr; + struct elf_shdr *shdr; + struct elf_phdr *phdr; + u64 elf_offset; + u64 strtable_idx; +}; + +/* Protect elfheader and smem table from deferred calls contention */ +static DEFINE_SPINLOCK(mdt_lock); +static DEFINE_RWLOCK(mdt_remove_lock); +static struct md_table minidump_table; +static struct md_elfhdr minidump_elfheader; +static int first_removed_entry = INT_MAX; +static bool md_init_done; + +/* Number of pending entries to be added in ToC regions */ +static unsigned int pendings; + +static inline char *elf_lookup_string(struct elfhdr *hdr, int offset) +{ + char *strtab = elf_str_table(hdr); + + if ((strtab == NULL) || (minidump_elfheader.strtable_idx < offset)) + return NULL; + return strtab + offset; +} + +static inline unsigned int set_section_name(const char *name) +{ + char *strtab = elf_str_table(minidump_elfheader.ehdr); + int idx = minidump_elfheader.strtable_idx; + int ret = 0; + + if ((strtab == NULL) || (name == NULL)) + return 0; + + ret = idx; + idx += strscpy((strtab + idx), name, MAX_REGION_NAME_LENGTH); + minidump_elfheader.strtable_idx = idx + 1; + + return ret; +} + +struct md_region *md_get_region(char *name) +{ + struct md_region *mdr; + int i, regno = minidump_table.num_regions; + + for (i = 0; i < regno; i++) { + mdr = &minidump_table.entry[i]; + if (!strcmp(mdr->name, name)) + return mdr; + } + return NULL; +} + +static inline int md_region_num(const char *name, int *seqno) +{ + struct md_ss_region *mde = minidump_table.md_regions; + int i, regno = minidump_table.md_ss_toc->ss_region_count; + int ret = -EINVAL; + + for (i = 0; i < regno; i++, mde++) { + if (!strcmp(mde->name, name)) { + ret = i; + if (mde->seq_num > *seqno) + *seqno = mde->seq_num; + } + } + return ret; +} + +static inline int md_entry_num(const struct md_region *entry) +{ + struct md_region *mdr; + int i, regno = minidump_table.num_regions; + + for (i = 0; i < regno; i++) { + mdr = &minidump_table.entry[i]; + if (!strcmp(mdr->name, entry->name)) + return i; + } + return -ENOENT; +} + +/* Update Mini dump table in SMEM */ +static void md_update_ss_toc(const struct md_region *entry) +{ + struct md_ss_region *mdr; + struct elfhdr *hdr = minidump_elfheader.ehdr; + struct elf_shdr *shdr = elf_section(hdr, hdr->e_shnum++); + struct elf_phdr *phdr = elf_program(hdr, hdr->e_phnum++); + int seq = 0, reg_cnt = minidump_table.md_ss_toc->ss_region_count; + + mdr = &minidump_table.md_regions[reg_cnt]; + + strscpy(mdr->name, entry->name, sizeof(mdr->name)); + mdr->region_base_address = entry->phys_addr; + mdr->region_size = entry->size; + if (md_region_num(entry->name, &seq) >= 0) + mdr->seq_num = seq + 1; + + /* Update elf header */ + shdr->sh_type = SHT_PROGBITS; + shdr->sh_name = set_section_name(mdr->name); + shdr->sh_addr = (elf_addr_t)entry->virt_addr; + shdr->sh_size = mdr->region_size; + shdr->sh_flags = SHF_WRITE; + shdr->sh_offset = minidump_elfheader.elf_offset; + shdr->sh_entsize = 0; + + phdr->p_type = PT_LOAD; + phdr->p_offset = minidump_elfheader.elf_offset; + phdr->p_vaddr = entry->virt_addr; + phdr->p_paddr = entry->phys_addr; + phdr->p_filesz = phdr->p_memsz = mdr->region_size; + phdr->p_flags = PF_R | PF_W; + minidump_elfheader.elf_offset += shdr->sh_size; + mdr->md_valid = MD_REGION_VALID; + minidump_table.md_ss_toc->ss_region_count++; +} + +bool msm_minidump_enabled(void) +{ + bool ret = false; + unsigned long flags; + + spin_lock_irqsave(&mdt_lock, flags); + if (minidump_table.md_ss_toc && + (minidump_table.md_ss_toc->md_ss_enable_status == + MD_SS_ENABLED)) + ret = true; + spin_unlock_irqrestore(&mdt_lock, flags); + return ret; +} +EXPORT_SYMBOL(msm_minidump_enabled); + +int msm_minidump_get_available_region(void) +{ + int res; + unsigned long flags; + + spin_lock_irqsave(&mdt_lock, flags); + res = MAX_NUM_ENTRIES - minidump_table.num_regions; + spin_unlock_irqrestore(&mdt_lock, flags); + + return res; +} +EXPORT_SYMBOL(msm_minidump_get_available_region); + +static inline int validate_region(const struct md_region *entry) +{ + if (!entry) + return -EINVAL; + + if ((strlen(entry->name) > MAX_NAME_LENGTH) || !entry->virt_addr || + (!IS_ALIGNED(entry->size, 4))) { + printk_deferred("Invalid entry details\n"); + return -EINVAL; + } + + return 0; +} + +int msm_minidump_update_region(int regno, const struct md_region *entry) +{ + int ret = 0; + struct md_region *mdr; + struct md_ss_region *mdssr; + struct elfhdr *hdr = minidump_elfheader.ehdr; + struct elf_shdr *shdr; + struct elf_phdr *phdr; + unsigned long flags; + + /* Ensure that init completes before we update regions */ + if (!smp_load_acquire(&md_init_done)) + return -EINVAL; + + if (validate_region(entry) || (regno >= MAX_NUM_ENTRIES)) + return -EINVAL; + + read_lock_irqsave(&mdt_remove_lock, flags); + + if (regno >= first_removed_entry) { + printk_deferred("Region:[%s] was moved\n", entry->name); + ret = -EINVAL; + goto err_unlock; + } + + ret = md_entry_num(entry); + if (ret < 0) { + printk_deferred("Region:[%s] does not exist to update.\n", entry->name); + goto err_unlock; + } + + mdr = &minidump_table.entry[regno]; + mdr->virt_addr = entry->virt_addr; + mdr->phys_addr = entry->phys_addr; + + mdssr = &minidump_table.md_regions[regno + 1]; + mdssr->region_base_address = entry->phys_addr; + + shdr = elf_section(hdr, regno + 4); + phdr = elf_program(hdr, regno + 1); + + shdr->sh_addr = (elf_addr_t)entry->virt_addr; + phdr->p_vaddr = entry->virt_addr; + phdr->p_paddr = entry->phys_addr; + +err_unlock: + read_unlock_irqrestore(&mdt_remove_lock, flags); + + return ret; +} +EXPORT_SYMBOL(msm_minidump_update_region); + +int msm_minidump_add_region(const struct md_region *entry) +{ + u32 entries; + u32 toc_init; + struct md_region *mdr; + unsigned long flags; + + if (validate_region(entry)) + return -EINVAL; + + spin_lock_irqsave(&mdt_lock, flags); + if (md_entry_num(entry) >= 0) { + spin_unlock_irqrestore(&mdt_lock, flags); + printk_deferred("Entry name already exist.\n"); + return -EEXIST; + } + + entries = minidump_table.num_regions; + if (entries >= MAX_NUM_ENTRIES) { + printk_deferred("Maximum entries reached.\n"); + spin_unlock_irqrestore(&mdt_lock, flags); + return -ENOMEM; + } + + toc_init = 0; + if (minidump_table.md_ss_toc && + (minidump_table.md_ss_toc->md_ss_enable_status == + MD_SS_ENABLED)) { + toc_init = 1; + if (minidump_table.md_ss_toc->ss_region_count >= MAX_NUM_ENTRIES) { + spin_unlock_irqrestore(&mdt_lock, flags); + printk_deferred("Maximum regions in minidump table reached.\n"); + return -ENOMEM; + } + } + + mdr = &minidump_table.entry[entries]; + strscpy(mdr->name, entry->name, sizeof(mdr->name)); + mdr->virt_addr = entry->virt_addr; + mdr->phys_addr = entry->phys_addr; + mdr->size = entry->size; + mdr->id = entry->id; + + minidump_table.num_regions = entries + 1; + + if (toc_init) + md_update_ss_toc(entry); + else + pendings++; + + spin_unlock_irqrestore(&mdt_lock, flags); + + return entries; +} +EXPORT_SYMBOL(msm_minidump_add_region); + +int msm_minidump_clear_headers(const struct md_region *entry) +{ + struct elfhdr *hdr = minidump_elfheader.ehdr; + struct elf_shdr *shdr = NULL, *tshdr = NULL; + struct elf_phdr *phdr = NULL, *tphdr = NULL; + int pidx, shidx, strln, i; + char *shname; + u64 esize; + + esize = entry->size; + for (i = 0; i < hdr->e_phnum; i++) { + phdr = elf_program(hdr, i); + if ((phdr->p_paddr == entry->phys_addr) && + (phdr->p_memsz == entry->size)) + break; + } + if (i == hdr->e_phnum) { + printk_deferred("Cannot find entry in elf\n"); + return -EINVAL; + } + pidx = i; + + for (i = 0; i < hdr->e_shnum; i++) { + shdr = elf_section(hdr, i); + shname = elf_lookup_string(hdr, shdr->sh_name); + if (shname && !strcmp(shname, entry->name)) + if ((shdr->sh_addr == entry->virt_addr) && + (shdr->sh_size == entry->size)) + break; + + } + if (i == hdr->e_shnum) { + printk_deferred("Cannot find entry in elf\n"); + return -EINVAL; + } + shidx = i; + + if (shdr->sh_offset != phdr->p_offset) { + printk_deferred("Invalid entry details in elf, Minidump broken..\n"); + return -EINVAL; + } + + /* Clear name in string table */ + strln = strlen(shname) + 1; + memmove(shname, shname + strln, + (minidump_elfheader.strtable_idx - shdr->sh_name)); + minidump_elfheader.strtable_idx -= strln; + + /* Clear program header */ + tphdr = elf_program(hdr, pidx); + for (i = pidx; i < hdr->e_phnum - 1; i++) { + tphdr = elf_program(hdr, i + 1); + phdr = elf_program(hdr, i); + memcpy(phdr, tphdr, sizeof(struct elf_phdr)); + phdr->p_offset = phdr->p_offset - esize; + } + memset(tphdr, 0, sizeof(struct elf_phdr)); + hdr->e_phnum--; + + /* Clear section header */ + tshdr = elf_section(hdr, shidx); + for (i = shidx; i < hdr->e_shnum - 1; i++) { + tshdr = elf_section(hdr, i + 1); + shdr = elf_section(hdr, i); + memcpy(shdr, tshdr, sizeof(struct elf_shdr)); + shdr->sh_offset -= esize; + shdr->sh_name -= strln; + } + memset(tshdr, 0, sizeof(struct elf_shdr)); + hdr->e_shnum--; + + minidump_elfheader.elf_offset -= esize; + return 0; +} + +int msm_minidump_remove_region(const struct md_region *entry) +{ + int rcount, ecount, seq = 0, rgno, entryno, ret; + unsigned long flags; + + if (!entry || !minidump_table.md_ss_toc || + (minidump_table.md_ss_toc->md_ss_enable_status != + MD_SS_ENABLED)) + return -EINVAL; + + spin_lock_irqsave(&mdt_lock, flags); + write_lock(&mdt_remove_lock); + ret = md_entry_num(entry); + if (ret < 0) { + write_unlock(&mdt_remove_lock); + spin_unlock_irqrestore(&mdt_lock, flags); + printk_deferred("Not able to find the entry %s in table\n", entry->name); + return ret; + } + entryno = ret; + rgno = md_region_num(entry->name, &seq); + if (rgno < 0) { + write_unlock(&mdt_remove_lock); + spin_unlock_irqrestore(&mdt_lock, flags); + printk_deferred("Not able to find the region %s (%d,%d) in table\n", + entry->name, entryno, rgno); + return -EINVAL; + } + ecount = minidump_table.num_regions; + rcount = minidump_table.md_ss_toc->ss_region_count; + if (first_removed_entry > entryno) + first_removed_entry = entryno; + minidump_table.md_ss_toc->md_ss_toc_init = 0; + + /* Remove entry from: entry list, ss region list and elf header */ + memmove(&minidump_table.entry[entryno], + &minidump_table.entry[entryno + 1], + ((ecount - entryno - 1) * sizeof(struct md_region))); + memset(&minidump_table.entry[ecount - 1], 0, sizeof(struct md_region)); + + + memmove(&minidump_table.md_regions[rgno], + &minidump_table.md_regions[rgno + 1], + ((rcount - rgno - 1) * sizeof(struct md_ss_region))); + memset(&minidump_table.md_regions[rcount - 1], 0, + sizeof(struct md_ss_region)); + + ret = msm_minidump_clear_headers(entry); + if (ret) + goto out; + + minidump_table.md_ss_toc->ss_region_count--; + minidump_table.md_ss_toc->md_ss_toc_init = 1; + minidump_table.num_regions--; +out: + write_unlock(&mdt_remove_lock); + spin_unlock_irqrestore(&mdt_lock, flags); + + if (ret) + printk_deferred("Minidump is broken..disable Minidump collection\n"); + return ret; +} +EXPORT_SYMBOL(msm_minidump_remove_region); + +static int msm_minidump_add_header(void) +{ + struct md_ss_region *mdreg = &minidump_table.md_regions[0]; + struct elfhdr *ehdr; + struct elf_shdr *shdr; + struct elf_phdr *phdr; + unsigned int strtbl_off, elfh_size, phdr_off; + char *banner, *linux_banner; + + linux_banner = android_debug_symbol(ADS_LINUX_BANNER); + /* Header buffer contains: + * elf header, MAX_NUM_ENTRIES+4 of section and program elf headers, + * string table section and linux banner. + */ + elfh_size = sizeof(*ehdr) + MAX_STRTBL_SIZE + + (strlen(linux_banner) + 1) + + ((sizeof(*shdr) + sizeof(*phdr)) + * (MAX_NUM_ENTRIES + 4)); + + elfh_size = ALIGN(elfh_size, 4); + + minidump_elfheader.ehdr = kzalloc(elfh_size, GFP_KERNEL); + if (!minidump_elfheader.ehdr) + return -ENOMEM; + + strscpy(mdreg->name, "KELF_HEADER", sizeof(mdreg->name)); + mdreg->region_base_address = virt_to_phys(minidump_elfheader.ehdr); + mdreg->region_size = elfh_size; + + ehdr = minidump_elfheader.ehdr; + /* Assign section/program headers offset */ + minidump_elfheader.shdr = shdr = (struct elf_shdr *)(ehdr + 1); + minidump_elfheader.phdr = phdr = + (struct elf_phdr *)(shdr + MAX_NUM_ENTRIES); + phdr_off = sizeof(*ehdr) + (sizeof(*shdr) * MAX_NUM_ENTRIES); + + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); + ehdr->e_ident[EI_CLASS] = ELF_CLASS; + ehdr->e_ident[EI_DATA] = ELF_DATA; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_ident[EI_OSABI] = ELF_OSABI; + ehdr->e_type = ET_CORE; + ehdr->e_machine = ELF_ARCH; + ehdr->e_version = EV_CURRENT; + ehdr->e_ehsize = sizeof(*ehdr); + ehdr->e_phoff = phdr_off; + ehdr->e_phentsize = sizeof(*phdr); + ehdr->e_shoff = sizeof(*ehdr); + ehdr->e_shentsize = sizeof(*shdr); + ehdr->e_shstrndx = 1; + + minidump_elfheader.elf_offset = elfh_size; + + /* + * First section header should be NULL, + * 2nd section is string table. + */ + minidump_elfheader.strtable_idx = 1; + strtbl_off = sizeof(*ehdr) + + ((sizeof(*phdr) + sizeof(*shdr)) * MAX_NUM_ENTRIES); + shdr++; + shdr->sh_type = SHT_STRTAB; + shdr->sh_offset = (elf_addr_t)strtbl_off; + shdr->sh_size = MAX_STRTBL_SIZE; + shdr->sh_entsize = 0; + shdr->sh_flags = 0; + shdr->sh_name = set_section_name("STR_TBL"); + shdr++; + + /* 3rd section is for minidump_table VA, used by parsers */ + shdr->sh_type = SHT_PROGBITS; + shdr->sh_entsize = 0; + shdr->sh_flags = 0; + shdr->sh_addr = (elf_addr_t)&minidump_table; + shdr->sh_name = set_section_name("minidump_table"); + shdr++; + + /* 4th section is linux banner */ + banner = (char *)ehdr + strtbl_off + MAX_STRTBL_SIZE; + strscpy(banner, linux_banner, MAX_STRTBL_SIZE); + + shdr->sh_type = SHT_PROGBITS; + shdr->sh_offset = (elf_addr_t)(strtbl_off + MAX_STRTBL_SIZE); + shdr->sh_size = strlen(linux_banner) + 1; + shdr->sh_addr = (elf_addr_t)linux_banner; + shdr->sh_entsize = 0; + shdr->sh_flags = SHF_WRITE; + shdr->sh_name = set_section_name("linux_banner"); + + phdr->p_type = PT_LOAD; + phdr->p_offset = (elf_addr_t)(strtbl_off + MAX_STRTBL_SIZE); + phdr->p_vaddr = (elf_addr_t)linux_banner; + phdr->p_paddr = virt_to_phys(linux_banner); + phdr->p_filesz = phdr->p_memsz = strlen(linux_banner) + 1; + phdr->p_flags = PF_R | PF_W; + + /* Update headers count*/ + ehdr->e_phnum = 1; + ehdr->e_shnum = 4; + + mdreg->md_valid = MD_REGION_VALID; + return 0; +} + +static int msm_minidump_driver_remove(struct platform_device *pdev) +{ + /* TO-DO. + *Free the required resources and set the global + * variables as minidump is not initialized. + */ + return 0; +} + +static int msm_minidump_driver_probe(struct platform_device *pdev) +{ + unsigned int i; + size_t size; + struct md_region *mdr; + struct md_global_toc *md_global_toc; + struct md_ss_toc *md_ss_toc; + unsigned long flags; + + /* Get Minidump table */ + md_global_toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, + &size); + if (IS_ERR_OR_NULL(md_global_toc)) { + pr_err("SMEM is not initialized.\n"); + return PTR_ERR(md_global_toc); + } + + /*Check global minidump support initialization */ + if (!md_global_toc->md_toc_init) { + pr_err("System Minidump TOC not initialized\n"); + return -ENODEV; + } + + minidump_table.md_gbl_toc = md_global_toc; + minidump_table.revision = md_global_toc->md_revision; + md_ss_toc = &md_global_toc->md_ss_toc[MD_SS_HLOS_ID]; + + md_ss_toc->encryption_status = MD_SS_ENCR_DONE; + md_ss_toc->encryption_required = MD_SS_ENCR_NOTREQ; + + minidump_table.md_ss_toc = md_ss_toc; + minidump_table.md_regions = devm_kzalloc(&pdev->dev, (MAX_NUM_ENTRIES * + sizeof(struct md_ss_region)), GFP_KERNEL); + if (!minidump_table.md_regions) + return -ENOMEM; + + md_ss_toc->md_ss_smem_regions_baseptr = + virt_to_phys(minidump_table.md_regions); + + /* First entry would be ELF header */ + md_ss_toc->ss_region_count = 1; + msm_minidump_add_header(); + + /* Add pending entries to HLOS TOC */ + spin_lock_irqsave(&mdt_lock, flags); + md_ss_toc->md_ss_toc_init = 1; + md_ss_toc->md_ss_enable_status = MD_SS_ENABLED; + for (i = 0; i < pendings; i++) { + mdr = &minidump_table.entry[i]; + md_update_ss_toc(mdr); + } + + pendings = 0; + spin_unlock_irqrestore(&mdt_lock, flags); + + /* All updates above should be visible, before init completes */ + smp_store_release(&md_init_done, true); + msm_minidump_log_init(); + pr_info("Enabled with max number of regions %d\n", + CONFIG_MINIDUMP_MAX_ENTRIES); + + return 0; +} + +static const struct of_device_id msm_minidump_of_match[] = { + { .compatible = "qcom,minidump" }, + { } +}; +MODULE_DEVICE_TABLE(of, msm_minidump_of_match); + +static struct platform_driver msm_minidump_driver = { + .driver = { + .name = "qcom-minidump", + .of_match_table = msm_minidump_of_match, + }, + .probe = msm_minidump_driver_probe, + .remove = msm_minidump_driver_remove, +}; +module_platform_driver(msm_minidump_driver); + +MODULE_DESCRIPTION("MSM Mini Dump Driver"); +MODULE_IMPORT_NS(MINIDUMP); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/qcom_va_minidump.c b/drivers/soc/qcom/qcom_va_minidump.c new file mode 100644 index 000000000000..bf240097a18a --- /dev/null +++ b/drivers/soc/qcom/qcom_va_minidump.c @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "va-minidump: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "elf.h" + +struct va_md_tree_node { + struct va_md_entry entry; + int lindex; + int rindex; +}; + +struct va_md_elf_info { + unsigned long ehdr; + unsigned long shdr_cnt; + unsigned long phdr_cnt; + unsigned long pload_size; + unsigned long str_tbl_size; +}; + +#define VA_MD_VADDR_MARKER -1 +#define VA_MD_CB_MARKER -2 +#define MAX_ELF_SECTION 0xFFFFU + +struct va_minidump_data { + phys_addr_t mem_phys_addr; + unsigned int total_mem_size; + unsigned long elf_mem; + unsigned int num_sections; + unsigned long str_tbl_idx; + struct va_md_elf_info elf; + struct md_region md_entry; + bool in_oops_handler; + bool va_md_minidump_reg; + bool va_md_init; + struct list_head va_md_list; + struct kset *va_md_kset; +}; + +/* + * Incase, client uses stack memory to allocate + * notifier block, we have to make a copy. + */ +struct notifier_block_list { + struct notifier_block nb; + struct list_head nb_list; +}; + +struct va_md_s_data { + struct kobject s_kobj; + struct atomic_notifier_head va_md_s_notif_list; + struct list_head va_md_s_list; + struct list_head va_md_s_nb_list; + bool enable; +}; + +struct va_minidump_data va_md_data; +static DEFINE_MUTEX(va_md_lock); + +#define to_va_md_attr(_attr) container_of(_attr, struct va_md_attribute, attr) +#define to_va_md_s_data(obj) container_of(obj, struct va_md_s_data, s_kobj) + +struct va_md_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct attribute *attr, + char *buf); + ssize_t (*store)(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count); +}; + +static ssize_t attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct va_md_attribute *va_md_attr = to_va_md_attr(attr); + ssize_t ret = -EIO; + + if (va_md_attr->show) + ret = va_md_attr->show(kobj, attr, buf); + + return ret; +} + +static ssize_t attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct va_md_attribute *va_md_attr = to_va_md_attr(attr); + ssize_t ret = -EIO; + + if (va_md_attr->store) + ret = va_md_attr->store(kobj, attr, buf, count); + + return ret; +} + +static const struct sysfs_ops va_md_sysfs_ops = { + .show = attr_show, + .store = attr_store, +}; + +static struct kobj_type va_md_kobj_type = { + .sysfs_ops = &va_md_sysfs_ops, +}; + +static ssize_t enable_show(struct kobject *kobj, struct attribute *this, char *buf) +{ + struct va_md_s_data *vamd_sdata = to_va_md_s_data(kobj); + + return scnprintf(buf, PAGE_SIZE, "enable: %u\n", vamd_sdata->enable); +} + +static ssize_t enable_store(struct kobject *kobj, struct attribute *this, + const char *buf, size_t count) +{ + struct va_md_s_data *vamd_sdata = to_va_md_s_data(kobj); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) { + pr_err("Invalid value passed\n"); + return ret; + } + + vamd_sdata->enable = val; + return count; +} + +static struct va_md_attribute va_md_s_attr = __ATTR_RW(enable); +static struct attribute *va_md_s_attrs[] = { + &va_md_s_attr.attr, + NULL +}; + +static struct attribute_group va_md_s_attr_group = { + .attrs = va_md_s_attrs, +}; + +bool qcom_va_md_enabled(void) +{ + /* + * Ensure that minidump is enabled and va-minidump is initialized + * before we start registration. + */ + return msm_minidump_enabled() && smp_load_acquire(&va_md_data.va_md_init); +} +EXPORT_SYMBOL(qcom_va_md_enabled); + +int qcom_va_md_register(char *name, struct notifier_block *nb) +{ + int ret = 0; + struct va_md_s_data *va_md_s_data; + struct notifier_block_list *nbl, *temp_nbl; + struct kobject *kobj; + + if (!qcom_va_md_enabled()) { + pr_err("qcom va minidump driver is not initialized\n"); + return -ENODEV; + } + + nbl = kzalloc(sizeof(struct notifier_block_list), GFP_KERNEL); + if (!nbl) + return -ENOMEM; + + nbl->nb = *nb; + mutex_lock(&va_md_lock); + kobj = kset_find_obj(va_md_data.va_md_kset, name); + if (kobj) { + pr_warn("subsystem: %s is already registered\n", name); + kobject_put(kobj); + va_md_s_data = to_va_md_s_data(kobj); + goto register_notifier; + } + + va_md_s_data = kzalloc(sizeof(*va_md_s_data), GFP_KERNEL); + if (!va_md_s_data) { + ret = -ENOMEM; + kfree(nbl); + goto out; + } + + va_md_s_data->s_kobj.kset = va_md_data.va_md_kset; + ret = kobject_init_and_add(&va_md_s_data->s_kobj, &va_md_kobj_type, + &va_md_data.va_md_kset->kobj, name); + if (ret) { + pr_err("%s: Error in kobject creation\n", __func__); + kobject_put(&va_md_s_data->s_kobj); + kfree(nbl); + goto out; + } + + kobject_uevent(&va_md_s_data->s_kobj, KOBJ_ADD); + ret = sysfs_create_group(&va_md_s_data->s_kobj, &va_md_s_attr_group); + if (ret) { + pr_err("%s: Error in creation sysfs_create_group\n", __func__); + kobject_put(&va_md_s_data->s_kobj); + kfree(nbl); + goto out; + } + + ATOMIC_INIT_NOTIFIER_HEAD(&va_md_s_data->va_md_s_notif_list); + INIT_LIST_HEAD(&va_md_s_data->va_md_s_nb_list); + va_md_s_data->enable = false; + list_add_tail(&va_md_s_data->va_md_s_list, &va_md_data.va_md_list); + +register_notifier: + list_for_each_entry(temp_nbl, &va_md_s_data->va_md_s_nb_list, nb_list) { + if (temp_nbl->nb.notifier_call == nbl->nb.notifier_call) { + pr_warn("subsystem:%s callback is already registered\n", name); + kfree(nbl); + ret = -EEXIST; + goto out; + } + } + + atomic_notifier_chain_register(&va_md_s_data->va_md_s_notif_list, &nbl->nb); + list_add_tail(&nbl->nb_list, &va_md_s_data->va_md_s_nb_list); +out: + mutex_unlock(&va_md_lock); + return ret; +} +EXPORT_SYMBOL(qcom_va_md_register); + +static void va_md_add_entry(struct va_md_entry *entry) +{ + struct va_md_tree_node *dst = ((struct va_md_tree_node *)va_md_data.elf_mem) + + va_md_data.num_sections; + unsigned int len = strlen(entry->owner); + + dst->entry = *entry; + WARN_ONCE(len > MAX_OWNER_STRING - 1, + "Client entry name %s (len = %u) is greater than expected %u\n", + entry->owner, len, MAX_OWNER_STRING - 1); + dst->entry.owner[MAX_OWNER_STRING - 1] = '\0'; + + if (entry->vaddr) { + dst->lindex = VA_MD_VADDR_MARKER; + dst->rindex = VA_MD_VADDR_MARKER; + } else { + dst->lindex = VA_MD_CB_MARKER; + dst->rindex = VA_MD_CB_MARKER; + } + + va_md_data.num_sections++; +} + +static bool va_md_check_overlap(struct va_md_entry *entry, unsigned int index) +{ + unsigned long ent_start, ent_end; + unsigned long node_start, node_end; + struct va_md_tree_node *node = (struct va_md_tree_node *)va_md_data.elf_mem; + + node_start = node[index].entry.vaddr; + node_end = node[index].entry.vaddr + node[index].entry.size - 1; + ent_start = entry->vaddr; + ent_end = entry->vaddr + entry->size - 1; + + if (((node_start <= ent_start) && (ent_start <= node_end)) || + ((node_start <= ent_end) && (ent_end <= node_end)) || + ((ent_start <= node_start) && (node_end <= ent_end))) + return true; + + return false; +} + +static bool va_md_move_left(struct va_md_entry *entry, unsigned int index) +{ + unsigned long ent_start, ent_end; + unsigned long node_start, node_end; + struct va_md_tree_node *node = (struct va_md_tree_node *)va_md_data.elf_mem; + + node_start = node[index].entry.vaddr; + node_end = node[index].entry.vaddr + node[index].entry.size - 1; + ent_start = entry->vaddr; + ent_end = entry->vaddr + entry->size - 1; + + if ((ent_start < node_start) && (ent_end < node_start)) + return true; + + return false; +} + +static bool va_md_move_right(struct va_md_entry *entry, unsigned int index) +{ + unsigned long ent_start, ent_end; + unsigned long node_start, node_end; + struct va_md_tree_node *node = (struct va_md_tree_node *)va_md_data.elf_mem; + + node_start = node[index].entry.vaddr; + node_end = node[index].entry.vaddr + node[index].entry.size - 1; + ent_start = entry->vaddr; + ent_end = entry->vaddr + entry->size - 1; + + if ((ent_start > node_end) && (ent_end > node_end)) + return true; + + return false; +} + +static int va_md_tree_insert(struct va_md_entry *entry) +{ + unsigned int baseindex = 0; + int ret = 0; + static int num_nodes; + struct va_md_tree_node *tree = (struct va_md_tree_node *)va_md_data.elf_mem; + + if (!entry->vaddr || !va_md_data.num_sections) { + va_md_add_entry(entry); + goto out; + } + + while (baseindex < va_md_data.num_sections) { + if ((tree[baseindex].lindex == VA_MD_CB_MARKER) && + (tree[baseindex].rindex == VA_MD_CB_MARKER)) { + baseindex++; + continue; + } + + if (va_md_check_overlap(entry, baseindex)) { + entry->owner[MAX_OWNER_STRING - 1] = '\0'; + pr_err("Overlapping region owner:%s\n", entry->owner); + ret = -EINVAL; + goto out; + } + + if (va_md_move_left(entry, baseindex)) { + if (tree[baseindex].lindex == VA_MD_VADDR_MARKER) { + tree[baseindex].lindex = va_md_data.num_sections; + va_md_add_entry(entry); + num_nodes++; + goto exit_loop; + } else { + baseindex = tree[baseindex].lindex; + continue; + } + + } else if (va_md_move_right(entry, baseindex)) { + if (tree[baseindex].rindex == VA_MD_VADDR_MARKER) { + tree[baseindex].rindex = va_md_data.num_sections; + va_md_add_entry(entry); + num_nodes++; + goto exit_loop; + } else { + baseindex = tree[baseindex].rindex; + continue; + } + } else { + pr_err("Warning: Corrupted Binary Search Tree\n"); + } + } + +exit_loop: + if (!num_nodes) { + va_md_add_entry(entry); + num_nodes++; + } + +out: + return ret; +} + +static bool va_md_overflow_check(void) +{ + unsigned long end_addr; + unsigned long start_addr = va_md_data.elf_mem; + + start_addr += sizeof(struct va_md_tree_node) * va_md_data.num_sections; + end_addr = start_addr + sizeof(struct va_md_tree_node) - 1; + + if (end_addr > va_md_data.elf_mem + va_md_data.total_mem_size - 1) + return true; + else + return false; +} + +int qcom_va_md_add_region(struct va_md_entry *entry) +{ + if (!va_md_data.in_oops_handler) + return -EINVAL; + + if ((!entry->vaddr == !entry->cb) || (entry->size <= 0)) { + entry->owner[MAX_OWNER_STRING - 1] = '\0'; + pr_err("Invalid entry from owner:%s\n", entry->owner); + return -EINVAL; + } + + if (va_md_data.num_sections > MAX_ELF_SECTION) { + pr_err("MAX_ELF_SECTION reached\n"); + return -ENOSPC; + } + + if (va_md_overflow_check()) { + pr_err("Total CMA consumed for Qcom VA minidump\n"); + return -ENOMEM; + } + + return va_md_tree_insert(entry); +} +EXPORT_SYMBOL(qcom_va_md_add_region); + +static void qcom_va_md_minidump_registration(void) +{ + strscpy(va_md_data.md_entry.name, "KVA_DUMP", sizeof(va_md_data.md_entry.name)); + + va_md_data.md_entry.virt_addr = va_md_data.elf.ehdr; + va_md_data.md_entry.phys_addr = va_md_data.mem_phys_addr + + (sizeof(struct va_md_tree_node) * va_md_data.num_sections); + va_md_data.md_entry.size = sizeof(struct elfhdr) + + (sizeof(struct elf_shdr) * va_md_data.elf.shdr_cnt) + + (sizeof(struct elf_phdr) * va_md_data.elf.phdr_cnt) + + va_md_data.elf.pload_size + va_md_data.elf.str_tbl_size; + va_md_data.md_entry.size = ALIGN(va_md_data.md_entry.size, 4); + + if (msm_minidump_add_region(&va_md_data.md_entry) < 0) { + pr_err("Failed to register VA driver CMA region with minidump\n"); + va_md_data.va_md_minidump_reg = false; + return; + } + + va_md_data.va_md_minidump_reg = true; +} + +static inline unsigned long set_sec_name(struct elfhdr *ehdr, const char *name) +{ + char *strtab = elf_str_table(ehdr); + unsigned long idx = va_md_data.str_tbl_idx; + unsigned long ret = 0; + + if ((strtab == NULL) || (name == NULL)) + return 0; + + ret = idx; + idx += strscpy((strtab + idx), name, MAX_OWNER_STRING); + va_md_data.str_tbl_idx = idx + 1; + return ret; +} + +static void qcom_va_add_elf_hdr(void) +{ + struct elfhdr *ehdr = (struct elfhdr *)va_md_data.elf.ehdr; + unsigned long phdr_off; + + phdr_off = sizeof(*ehdr) + (sizeof(struct elf_shdr) * va_md_data.elf.shdr_cnt); + + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); + ehdr->e_ident[EI_CLASS] = ELF_CLASS; + ehdr->e_ident[EI_DATA] = ELF_DATA; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_ident[EI_OSABI] = ELF_OSABI; + ehdr->e_type = ET_CORE; + ehdr->e_machine = ELF_ARCH; + ehdr->e_version = EV_CURRENT; + ehdr->e_ehsize = sizeof(*ehdr); + ehdr->e_shoff = sizeof(*ehdr); + ehdr->e_shentsize = sizeof(struct elf_shdr); + ehdr->e_shstrndx = 1; + ehdr->e_phentsize = sizeof(struct elf_phdr); + ehdr->e_phoff = phdr_off; +} + +static void qcom_va_add_hdrs(void) +{ + struct elf_shdr *shdr; + struct elf_phdr *phdr; + unsigned long strtbl_off, offset, i; + struct elfhdr *ehdr = (struct elfhdr *)va_md_data.elf.ehdr; + struct va_md_tree_node *arr = (struct va_md_tree_node *)va_md_data.elf_mem; + + strtbl_off = ehdr->e_phoff + (sizeof(*phdr) * va_md_data.elf.phdr_cnt); + + /* First section header is NULL */ + shdr = elf_section(ehdr, ehdr->e_shnum); + ehdr->e_shnum++; + + /* String table section */ + va_md_data.str_tbl_idx = 1; + shdr = elf_section(ehdr, ehdr->e_shnum); + ehdr->e_shnum++; + + shdr->sh_type = SHT_STRTAB; + shdr->sh_offset = strtbl_off; + shdr->sh_name = set_sec_name(ehdr, "STR_TBL"); + shdr->sh_size = va_md_data.elf.str_tbl_size; + + offset = strtbl_off + va_md_data.elf.str_tbl_size; + for (i = 0; i < (va_md_data.elf.shdr_cnt - 2); i++) { + /* section header */ + shdr = elf_section(ehdr, ehdr->e_shnum); + shdr->sh_type = SHT_PROGBITS; + shdr->sh_name = set_sec_name(ehdr, arr[i].entry.owner); + shdr->sh_size = arr[i].entry.size; + shdr->sh_flags = SHF_WRITE; + shdr->sh_offset = offset; + + /* program header */ + phdr = elf_program(ehdr, ehdr->e_phnum); + phdr->p_type = PT_LOAD; + phdr->p_offset = offset; + phdr->p_filesz = phdr->p_memsz = arr[i].entry.size; + phdr->p_flags = PF_R | PF_W; + + if (arr[i].entry.vaddr) { + shdr->sh_addr = phdr->p_vaddr = arr[i].entry.vaddr; + memcpy((void *)(va_md_data.elf.ehdr + offset), + (void *)shdr->sh_addr, shdr->sh_size); + } else { + shdr->sh_addr = phdr->p_vaddr = va_md_data.elf.ehdr + offset; + arr[i].entry.cb((void *)(va_md_data.elf.ehdr + offset), + shdr->sh_size); + } + + offset += shdr->sh_size; + ehdr->e_shnum++; + ehdr->e_phnum++; + } +} + +static int qcom_va_md_calc_size(unsigned int shdr_cnt) +{ + unsigned int len, size = 0; + static unsigned long tot_size; + struct va_md_tree_node *arr = (struct va_md_tree_node *)va_md_data.elf_mem; + + if (!shdr_cnt) { + tot_size = sizeof(struct va_md_tree_node) * va_md_data.num_sections; + size = (sizeof(struct elfhdr) + (2 * sizeof(struct elf_shdr)) + + strlen("STR_TBL") + 2); + } + + len = strlen(arr[shdr_cnt].entry.owner); + size += (sizeof(struct elf_shdr) + sizeof(struct elf_phdr) + + arr[shdr_cnt].entry.size + len + 1); + tot_size += size; + if (tot_size > va_md_data.total_mem_size) { + pr_err("Total CMA consumed, no space left\n"); + return -ENOSPC; + } + + if (!shdr_cnt) { + va_md_data.elf.ehdr = va_md_data.elf_mem + (sizeof(struct va_md_tree_node) + * va_md_data.num_sections); + va_md_data.elf.shdr_cnt = 2; + va_md_data.elf.phdr_cnt = 0; + va_md_data.elf.pload_size = 0; + va_md_data.elf.str_tbl_size = strlen("STR_TBL") + 2; + } + + va_md_data.elf.shdr_cnt++; + va_md_data.elf.phdr_cnt++; + va_md_data.elf.pload_size += arr[shdr_cnt].entry.size; + va_md_data.elf.str_tbl_size += (len + 1); + + return 0; +} + +static int qcom_va_md_calc_elf_size(void) +{ + unsigned int i; + int ret = 0; + + if (va_md_overflow_check()) { + pr_err("Total CMA consumed, no space to create ELF\n"); + return -ENOSPC; + } + + pr_debug("Num sections:%u\n", va_md_data.num_sections); + for (i = 0; i < va_md_data.num_sections; i++) { + ret = qcom_va_md_calc_size(i); + if (ret < 0) + break; + } + + return ret; +} + +static int qcom_va_md_panic_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + unsigned long size; + struct va_md_s_data *va_md_s_data; + + if (va_md_data.in_oops_handler) + return NOTIFY_DONE; + + va_md_data.in_oops_handler = true; + + list_for_each_entry(va_md_s_data, &va_md_data.va_md_list, va_md_s_list) { + if (va_md_s_data->enable) + atomic_notifier_call_chain(&va_md_s_data->va_md_s_notif_list, + 0, NULL); + } + + if (!va_md_data.num_sections) + goto out; + + if (qcom_va_md_calc_elf_size() < 0) + goto out; + + size = sizeof(struct elfhdr) + + (sizeof(struct elf_shdr) * va_md_data.elf.shdr_cnt) + + (sizeof(struct elf_phdr) * va_md_data.elf.phdr_cnt) + + va_md_data.elf.pload_size + va_md_data.elf.str_tbl_size; + size = ALIGN(size, 4); + memset((void *)va_md_data.elf.ehdr, 0, size); + + qcom_va_md_minidump_registration(); +out: + return NOTIFY_DONE; +} + +static int qcom_va_md_elf_panic_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + if (!va_md_data.num_sections || !va_md_data.va_md_minidump_reg) + goto out; + + qcom_va_add_elf_hdr(); + qcom_va_add_hdrs(); + +out: + va_md_data.in_oops_handler = false; + return NOTIFY_DONE; +} + +static struct notifier_block qcom_va_md_panic_blk = { + .notifier_call = qcom_va_md_panic_handler, + .priority = INT_MAX - 3, +}; + +static struct notifier_block qcom_va_md_elf_panic_blk = { + .notifier_call = qcom_va_md_elf_panic_handler, + .priority = INT_MAX - 4, +}; + +static int qcom_va_md_reserve_mem(struct device *dev) +{ + struct device_node *node; + unsigned int size[2]; + int ret = 0; + + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (node) { + ret = of_reserved_mem_device_init_by_idx(dev, dev->of_node, 0); + of_node_put(dev->of_node); + if (ret) { + pr_err("Failed to initialize CMA mem, ret %d\n", + ret); + goto out; + } + } + + ret = of_property_read_u32_array(node, "size", size, 2); + if (ret) { + pr_err("Failed to get size of CMA, ret %d\n", ret); + goto out; + } + + va_md_data.total_mem_size = size[1]; + +out: + return ret; +} + +static int qcom_va_md_driver_remove(struct platform_device *pdev) +{ + struct va_md_s_data *va_md_s_data, *tmp; + struct notifier_block_list *nbl, *tmpnbl; + + mutex_lock(&va_md_lock); + list_for_each_entry_safe(va_md_s_data, tmp, &va_md_data.va_md_list, va_md_s_list) { + list_for_each_entry_safe(nbl, tmpnbl, &va_md_s_data->va_md_s_nb_list, nb_list) { + atomic_notifier_chain_unregister(&va_md_s_data->va_md_s_notif_list, + &nbl->nb); + list_del(&nbl->nb_list); + kfree(nbl); + } + + list_del(&va_md_s_data->va_md_s_nb_list); + sysfs_remove_group(&va_md_s_data->s_kobj, &va_md_s_attr_group); + kobject_put(&va_md_s_data->s_kobj); + list_del(&va_md_s_data->va_md_s_list); + kfree(va_md_s_data); + } + + mutex_unlock(&va_md_lock); + kset_unregister(va_md_data.va_md_kset); + atomic_notifier_chain_unregister(&panic_notifier_list, &qcom_va_md_elf_panic_blk); + atomic_notifier_chain_unregister(&panic_notifier_list, &qcom_va_md_panic_blk); + vunmap((void *)va_md_data.elf_mem); + return 0; +} + +static int qcom_va_md_driver_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + void *vaddr; + int count; + struct page **pages, *page; + dma_addr_t dma_handle; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + ret = qcom_va_md_reserve_mem(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "CMA for VA based minidump is not present\n"); + goto out; + } + + vaddr = dma_alloc_coherent(&pdev->dev, va_md_data.total_mem_size, &dma_handle, + GFP_KERNEL); + if (!vaddr) { + ret = -ENOMEM; + goto out; + } + + dma_free_coherent(&pdev->dev, va_md_data.total_mem_size, vaddr, dma_handle); + page = phys_to_page(dma_to_phys(&pdev->dev, dma_handle)); + count = PAGE_ALIGN(va_md_data.total_mem_size) >> PAGE_SHIFT; + pages = kmalloc_array(count, sizeof(struct page *), GFP_KERNEL); + if (!pages) + return -ENOMEM; + + for (i = 0; i < count; i++) + pages[i] = nth_page(page, i); + + vaddr = vmap(pages, count, VM_DMA_COHERENT, pgprot_dmacoherent(PAGE_KERNEL)); + kfree(pages); + + va_md_data.mem_phys_addr = dma_to_phys(&pdev->dev, dma_handle); + va_md_data.elf_mem = (unsigned long)vaddr; + + atomic_notifier_chain_register(&panic_notifier_list, &qcom_va_md_panic_blk); + atomic_notifier_chain_register(&panic_notifier_list, &qcom_va_md_elf_panic_blk); + + INIT_LIST_HEAD(&va_md_data.va_md_list); + va_md_data.va_md_kset = kset_create_and_add("va-minidump", NULL, kernel_kobj); + if (!va_md_data.va_md_kset) { + dev_err(&pdev->dev, "Failed to create kset for va-minidump\n"); + vunmap((void *)va_md_data.elf_mem); + ret = -ENOMEM; + goto out; + } + + /* All updates above should be visible, before init completes */ + smp_store_release(&va_md_data.va_md_init, true); +out: + return ret; +} + +static const struct of_device_id qcom_va_md_of_match[] = { + {.compatible = "qcom,va-minidump"}, + {} +}; + +MODULE_DEVICE_TABLE(of, qcom_va_md_of_match); + +static struct platform_driver qcom_va_md_driver = { + .driver = { + .name = "qcom-va-minidump", + .of_match_table = qcom_va_md_of_match, + }, + .probe = qcom_va_md_driver_probe, + .remove = qcom_va_md_driver_remove, +}; + +module_platform_driver(qcom_va_md_driver); + +MODULE_DESCRIPTION("Qcom VA Minidump Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/minidump.h b/include/soc/qcom/minidump.h new file mode 100644 index 000000000000..e0fe4f7e4cb8 --- /dev/null +++ b/include/soc/qcom/minidump.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __MINIDUMP_H +#define __MINIDUMP_H + +#include + +#define MAX_NAME_LENGTH 12 +/* md_region - Minidump table entry + * @name: Entry name, Minidump will dump binary with this name. + * @id: Entry ID, used only for SDI dumps. + * @virt_addr: Address of the entry. + * @phys_addr: Physical address of the entry to dump. + * @size: Number of byte to dump from @address location + * it should be 4 byte aligned. + */ +struct md_region { + char name[MAX_NAME_LENGTH]; + u32 id; + u64 virt_addr; + u64 phys_addr; + u64 size; +}; + +/* + * Register an entry in Minidump table + * Returns: + * region number: entry position in minidump table. + * Negative error number on failures. + */ +#if IS_ENABLED(CONFIG_QCOM_MINIDUMP) +extern int msm_minidump_add_region(const struct md_region *entry); +extern int msm_minidump_remove_region(const struct md_region *entry); +/* + * Update registered region address in Minidump table. + * It does not hold any locks, so strictly serialize the region updates. + * Returns: + * Zero: on successfully update + * Negetive error number on failures. + */ +extern int msm_minidump_update_region(int regno, const struct md_region *entry); +extern bool msm_minidump_enabled(void); +extern struct md_region *md_get_region(char *name); +extern void dump_stack_minidump(u64 sp); +extern int msm_minidump_get_available_region(void); +extern void md_dump_process(void); +#else +static inline int msm_minidump_add_region(const struct md_region *entry) +{ + /* Return quietly, if minidump is not supported */ + return 0; +} +static inline int msm_minidump_remove_region(const struct md_region *entry) +{ + return 0; +} +static inline bool msm_minidump_enabled(void) { return false; } +static inline struct md_region *md_get_region(char *name) { return NULL; } +static inline void dump_stack_minidump(u64 sp) {} +static inline void add_trace_event(char *buf, size_t size) {} +static inline void md_dump_process(void) {} +#endif + + +#define MAX_OWNER_STRING 32 +struct va_md_entry { + unsigned long vaddr; + unsigned char owner[MAX_OWNER_STRING]; + unsigned int size; + void (*cb)(void *dst, unsigned long size); +}; + +#if IS_ENABLED(CONFIG_QCOM_VA_MINIDUMP) +extern bool qcom_va_md_enabled(void); +extern int qcom_va_md_register(char *name, struct notifier_block *nb); +extern int qcom_va_md_add_region(struct va_md_entry *entry); +#else +static inline bool qcom_va_md_enabled(void) { return false; } +static inline int qcom_va_md_register(char *name, struct notifier_block *nb) +{ + return -ENODEV; +} + +static inline int qcom_va_md_add_region(struct va_md_entry *entry) +{ + return -ENODEV; +} +#endif + +#endif