diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 352f95323e89..733eef7fdbd8 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -478,6 +478,15 @@ config QCOM_MINIDUMP Minidump would dump all registered entries, only when DLOAD mode is enabled. +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 MINIDUMP_MAX_ENTRIES int "Minidump Maximum num of entries" default 200 diff --git a/drivers/soc/qcom/minidump_log.c b/drivers/soc/qcom/minidump_log.c index 72118b7f1154..843480b96557 100644 --- a/drivers/soc/qcom/minidump_log.c +++ b/drivers/soc/qcom/minidump_log.c @@ -3,6 +3,7 @@ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. */ +#include #include #include #include @@ -10,12 +11,38 @@ #include #include #include +#include +#include #include #include #include +#include #include #include +#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); +#endif + +static bool is_vmap_stack __read_mostly; + + static void __init register_log_buf(void) { char **log_bufp; @@ -37,23 +64,25 @@ static void __init register_log_buf(void) pr_err("Failed to add logbuf in Minidump\n"); } -static void register_stack_entry(struct md_region *ksp_entry, u64 sp, u64 size, +static int register_stack_entry(struct md_region *ksp_entry, u64 sp, u64 size, u32 cpu) { struct page *sp_page; - struct vm_struct *stack_vm_area = task_stack_vm_area(current); + int entry; ksp_entry->virt_addr = sp; ksp_entry->size = size; - if (stack_vm_area) { + 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); } - if (msm_minidump_add_region(ksp_entry) < 0) + entry = msm_minidump_add_region(ksp_entry); + if (entry < 0) pr_err("Failed to add stack of cpu %d in Minidump\n", cpu); + return entry; } static void __init register_kernel_sections(void) @@ -86,8 +115,8 @@ static void __init register_kernel_sections(void) } } -static inline bool in_stack_range(u64 sp, u64 base_addr, unsigned int - stack_size) +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; @@ -118,9 +147,14 @@ void dump_stack_minidump(u64 sp) 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; @@ -133,20 +167,21 @@ void dump_stack_minidump(u64 sp) * address of one page of the stack. */ stack_vm_area = task_stack_vm_area(current); - if (stack_vm_area) { + 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); - register_stack_entry(&ksp_entry, sp, PAGE_SIZE, cpu); + (void)register_stack_entry(&ksp_entry, sp, + PAGE_SIZE, cpu); sp += PAGE_SIZE; } } else { sp &= ~(THREAD_SIZE - 1); scnprintf(ksp_entry.name, sizeof(ksp_entry.name), "KSTACK%d", cpu); - register_stack_entry(&ksp_entry, sp, THREAD_SIZE, cpu); + (void)register_stack_entry(&ksp_entry, sp, THREAD_SIZE, cpu); } scnprintf(ktsk_entry.name, sizeof(ktsk_entry.name), "KTASK%d", cpu); @@ -157,6 +192,150 @@ void dump_stack_minidump(u64 sp) 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, u32 cpu) +{ + 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) { + pr_err_ratelimited( + "Failed to update cpu[%d] current stack in minidump\n", + cpu); + } +} + +static void register_vmapped_stack(struct md_stack_cpu_data *md_stack_cpu_d, + u64 sp, u32 cpu, bool update) +{ + struct md_region *mdr; + int *mdno; + int i; + + sp &= ~(PAGE_SIZE - 1); + for (i = 0; i < STACK_NUM_PAGES; i++) { + mdr = md_stack_cpu_d->stack_mdr + i; + mdno = md_stack_cpu_d->stack_mdidx + i; + if (unlikely(!update)) { + scnprintf(mdr->name, sizeof(mdr->name), + "KSTACK%d_%d", cpu, i); + *mdno = register_stack_entry(mdr, sp, PAGE_SIZE, cpu); + } else { + update_stack_entry(mdr, sp, *mdno, cpu); + } + sp += PAGE_SIZE; + } +} + +static void register_normal_stack(struct md_stack_cpu_data *md_stack_cpu_d, + u64 sp, u32 cpu, bool update) +{ + struct md_region *mdr; + + mdr = md_stack_cpu_d->stack_mdr; + sp &= ~(THREAD_SIZE - 1); + if (unlikely(!update)) { + scnprintf(mdr->name, sizeof(mdr->name), "KSTACK%d", cpu); + *md_stack_cpu_d->stack_mdidx = register_stack_entry( + mdr, sp, THREAD_SIZE, cpu); + } else { + update_stack_entry(mdr, sp, + *md_stack_cpu_d->stack_mdidx, cpu); + } +} + +static void update_md_stack(u32 cpu, u64 sp) +{ + struct md_stack_cpu_data *md_stack_cpu_d = + &per_cpu(md_stack_data, cpu); + int *mdno; + unsigned int i; + + if (is_idle_task(current) || !md_current_stack_init) + return; + + if (likely(is_vmap_stack)) { + for (i = 0; i < STACK_NUM_PAGES; i++) { + mdno = md_stack_cpu_d->stack_mdidx + i; + if (unlikely(*mdno < 0)) + return; + } + register_vmapped_stack(md_stack_cpu_d, sp, cpu, true); + } else { + if (unlikely(*md_stack_cpu_d->stack_mdidx < 0)) + return; + register_normal_stack(md_stack_cpu_d, sp, cpu, true); + } +} + +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_stack(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_stack(cpu, sp); +} + +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; + + /* + * 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); + if (is_vmap_stack) + register_vmapped_stack(md_stack_cpu_d, sp, cpu, false); + else + register_normal_stack(md_stack_cpu_d, sp, cpu, 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); +} +#endif + #ifdef CONFIG_ARM64 static void register_irq_stack(void) { @@ -169,7 +348,7 @@ static void register_irq_stack(void) for_each_possible_cpu(cpu) { irq_stack_base = (u64)per_cpu(irq_stack_ptr, cpu); - if (IS_ENABLED(CONFIG_VMAP_STACK)) { + 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++) { @@ -196,7 +375,11 @@ static inline void register_irq_stack(void) {} static int __init 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(); +#endif register_log_buf(); return 0; }