diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 7256b2c3f391..1c3ac3aabe1c 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -236,6 +236,15 @@ config QCOM_STATS various SoC level low power modes statistics and export to debugfs interface. +config QCOM_LOGBUF_VENDOR_HOOKS + tristate "QTI Logbuf Vendor Hooks Support" + depends on ARCH_QCOM && ANDROID_VENDOR_HOOKS + help + This enables to keep copy of initial log_buf + of minimum 512KB from bootup. It can help in + debugging issues which are manifestation + of failure during initial bootup. + config QCOM_WCNSS_CTRL tristate "Qualcomm WCNSS control driver" depends on ARCH_QCOM || COMPILE_TEST @@ -272,4 +281,16 @@ config MSM_BOOT_TIME_MARKER An instrumentation for boot time measurement. To create an entry, call "place_marker" function. At userspace, write marker name to "/sys/kernel/boot_kpi/kpi_values" + +config QCOM_CPU_VENDOR_HOOKS + tristate "QTI Vendor Hooks Support" + depends on ARCH_QCOM && ANDROID_VENDOR_HOOKS + help + CPU vendor hooks driver registers with andriod vendor hooks + provided by core kernel to extend kernel functionality. + + Currently these features are not supported by upstream kernel and not + having scope to upstream. + If unsure, say N. + endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 91aed0ce3a02..5dbac3170cfa 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -32,3 +32,5 @@ obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) += kryo-l2-accessors.o 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_LOGBUF_VENDOR_HOOKS) += qcom_logbuf_vh.o diff --git a/drivers/soc/qcom/qcom_cpu_vendor_hooks.c b/drivers/soc/qcom/qcom_cpu_vendor_hooks.c new file mode 100644 index 000000000000..3d610b022d2a --- /dev/null +++ b/drivers/soc/qcom/qcom_cpu_vendor_hooks.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "VendorHooks: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static DEFINE_PER_CPU(struct pt_regs, regs_before_stop); +static DEFINE_RAW_SPINLOCK(stop_lock); + +static void printk_hotplug(void *unused, int *flag) +{ + *flag = 1; +} + +static void trace_ipi_stop(void *unused, struct pt_regs *regs) +{ + unsigned int cpu = smp_processor_id(); + unsigned long flags; + + per_cpu(regs_before_stop, cpu) = *regs; + raw_spin_lock_irqsave(&stop_lock, flags); + pr_crit("CPU%u: stopping\n", cpu); + show_regs(regs); + raw_spin_unlock_irqrestore(&stop_lock, flags); +} + +static void timer_recalc_index(void *unused, + unsigned int lvl, unsigned long *expires) +{ + *expires -= 1; +} + +#if IS_ENABLED(CONFIG_DEBUG_SPINLOCK) && \ + (IS_ENABLED(CONFIG_DEBUG_SPINLOCK_BITE_ON_BUG) || IS_ENABLED(CONFIG_DEBUG_SPINLOCK_PANIC_ON_BUG)) +static int entry_spin_bug(struct kretprobe_instance *ri, struct pt_regs *regs) +{ + raw_spinlock_t *lock = (raw_spinlock_t *)regs->regs[0]; + const char *msg = (const char *)regs->regs[1]; + struct task_struct *owner = READ_ONCE(lock->owner); + + if (!debug_locks_off()) + return 0; + + /* Dup of spin_bug in kernel/locking/spinlock_debug.c */ + if (owner == SPINLOCK_OWNER_INIT) + owner = NULL; + printk(KERN_EMERG "BUG: spinlock %s on CPU#%d, %s/%d\n", + msg, raw_smp_processor_id(), + current->comm, task_pid_nr(current)); + printk(KERN_EMERG " lock: %pS, .magic: %08x, .owner: %s/%d, " + ".owner_cpu: %d\n", + lock, READ_ONCE(lock->magic), + owner ? owner->comm : "", + owner ? task_pid_nr(owner) : -1, + READ_ONCE(lock->owner_cpu)); + +#if IS_ENABLED(CONFIG_DEBUG_SPINLOCK_BITE_ON_BUG) + qcom_wdt_trigger_bite(); +#elif IS_ENABLED(CONFIG_DEBUG_SPINLOCK_PANIC_ON_BUG) + BUG(); +#else +# error "Neither CONFIG_DEBUG_SPINLOCK_BITE_ON_BUG nor CONFIG_DEBUG_SPINLOCK_PANIC_ON_BUG is enabled yet trying to enable spin_bug hook" +#endif + return 0; +} + +struct kretprobe spin_bug_probe = { + .entry_handler = entry_spin_bug, + .maxactive = 1, + .kp.symbol_name = "spin_bug", +}; + +static void register_spinlock_bug_hook(struct platform_device *pdev) +{ + int ret; + + ret = register_kretprobe(&spin_bug_probe); + if (ret) + dev_err(&pdev->dev, "Failed to register spin_bug_probe: %x\n", ret); +} +#else +static inline void register_spinlock_bug_hook(struct platform_device *pdev) { } +#endif + +#ifdef CONFIG_RANDOMIZE_BASE +#define KASLR_OFFSET_MASK 0x00000000FFFFFFFF +static void __iomem *map_prop_mem(const char *propname) +{ + struct device_node *np = of_find_compatible_node(NULL, NULL, propname); + void __iomem *addr; + + if (!np) { + pr_err("Unable to find DT property: %s\n", propname); + return NULL; + } + + addr = of_iomap(np, 0); + if (!addr) + pr_err("Unable to map memory for DT property: %s\n", propname); + return addr; +} + +static void store_kaslr_offset(void) +{ + void __iomem *mem = map_prop_mem("qcom,msm-imem-kaslr_offset"); + + if (!mem) + return; + + __raw_writel(0xdead4ead, mem); + __raw_writel((kimage_vaddr - KIMAGE_VADDR) & KASLR_OFFSET_MASK, + mem + 4); + __raw_writel(((kimage_vaddr - KIMAGE_VADDR) >> 32) & KASLR_OFFSET_MASK, + mem + 8); + + iounmap(mem); +} +#else +static void store_kaslr_offset(void) {} +#endif /* CONFIG_RANDOMIZE_BASE */ + +static int cpu_vendor_hooks_driver_probe(struct platform_device *pdev) +{ + int ret; + + store_kaslr_offset(); + + ret = register_trace_android_vh_ipi_stop(trace_ipi_stop, NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register android_vh_ipi_stop hook\n"); + return ret; + } + + ret = register_trace_android_vh_printk_hotplug(printk_hotplug, NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to android_vh_printk_hotplug hook\n"); + unregister_trace_android_vh_ipi_stop(trace_ipi_stop, NULL); + return ret; + } + + ret = register_trace_android_vh_timer_calc_index(timer_recalc_index, NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to android_vh_timer_calc_index hook\n"); + unregister_trace_android_vh_ipi_stop(trace_ipi_stop, NULL); + unregister_trace_android_vh_printk_hotplug(printk_hotplug, NULL); + return ret; + } + + register_spinlock_bug_hook(pdev); + + return ret; +} + +static int cpu_vendor_hooks_driver_remove(struct platform_device *pdev) +{ + /* Reset all initialized global variables and unregister callbacks. */ + unregister_trace_android_vh_ipi_stop(trace_ipi_stop, NULL); + unregister_trace_android_vh_printk_hotplug(printk_hotplug, NULL); + unregister_trace_android_vh_timer_calc_index(timer_recalc_index, NULL); + return 0; +} + +static const struct of_device_id cpu_vendor_hooks_of_match[] = { + { .compatible = "qcom,cpu-vendor-hooks" }, + { } +}; +MODULE_DEVICE_TABLE(of, cpu_vendor_hooks_of_match); + +static struct platform_driver cpu_vendor_hooks_driver = { + .driver = { + .name = "qcom-cpu-vendor-hooks", + .of_match_table = cpu_vendor_hooks_of_match, + }, + .probe = cpu_vendor_hooks_driver_probe, + .remove = cpu_vendor_hooks_driver_remove, +}; + +static int __init qcom_vendor_hook_driver_init(void) +{ + return platform_driver_register(&cpu_vendor_hooks_driver); +} +#if IS_MODULE(CONFIG_QCOM_CPU_VENDOR_HOOKS) +module_init(qcom_vendor_hook_driver_init); +#else +pure_initcall(qcom_vendor_hook_driver_init); +#endif + +static void __exit qcom_vendor_hook_driver_exit(void) +{ + return platform_driver_unregister(&cpu_vendor_hooks_driver); +} +module_exit(qcom_vendor_hook_driver_exit); + +MODULE_DESCRIPTION("QCOM CPU Vendor Hooks Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/qcom_logbuf_vh.c b/drivers/soc/qcom/qcom_logbuf_vh.c new file mode 100644 index 000000000000..72c52d004ab4 --- /dev/null +++ b/drivers/soc/qcom/qcom_logbuf_vh.c @@ -0,0 +1,390 @@ +// 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) "logbuf_vh: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../../../kernel/printk/printk_ringbuffer.h" + +#define BOOT_LOG_SIZE SZ_512K +#define LOG_NEWLINE 2 + +static char *boot_log_buf; +static unsigned int boot_log_buf_size; +static bool copy_early_boot_log = true; +static unsigned int off; + +static size_t print_time(u64 ts, char *buf, size_t buf_sz) +{ + unsigned long rem_nsec = do_div(ts, 1000000000); + + return scnprintf(buf, buf_sz, "[%5lu.%06lu]", + (unsigned long)ts, rem_nsec / 1000); +} + +#ifdef CONFIG_PRINTK_CALLER +#define PREFIX_MAX 48 +static size_t print_caller(u32 id, char *buf, size_t buf_sz) +{ + char caller[12]; + + snprintf(caller, sizeof(caller), "%c%u", + id & 0x80000000 ? 'C' : 'T', id & ~0x80000000); + return scnprintf(buf, buf_sz, "[%6s]", caller); +} +#else +#define PREFIX_MAX 32 +#define print_caller(id, buf) 0 +#endif + +static size_t info_print_prefix(const struct printk_info *info, char *buf, + size_t buf_sz) +{ + size_t len = 0; + + len = print_time(info->ts_nsec, buf + len, buf_sz); + len += print_caller(info->caller_id, buf + len, buf_sz - len); + buf[len++] = ' '; + buf[len] = '\0'; + return len; +} + +static size_t record_print_text(struct printk_info *pinfo, char *r_text, size_t buf_size) +{ + size_t text_len = pinfo->text_len; + char prefix[PREFIX_MAX]; + size_t prefix_len; + char *text = r_text; + size_t line_len; + size_t len = 0; + char *next; + + prefix_len = info_print_prefix(pinfo, prefix, PREFIX_MAX); + + /* + * @text_len: bytes of unprocessed text + * @line_len: bytes of current line _without_ newline + * @text: pointer to beginning of current line + * @len: number of bytes prepared in r->text_buf + */ + for (;;) { + next = memchr(text, '\n', text_len); + if (next) + line_len = next - text; + else + line_len = text_len; + + /* + * Truncate the text if there is not enough space to add the + * prefix and a trailing newline and a terminator. + */ + if ((len + prefix_len + text_len + 1 + 1) > buf_size) + break; + + memmove(text + prefix_len, text, text_len); + memcpy(text, prefix, prefix_len); + + /* + * Increment the prepared length to include the text and + * prefix that were just moved+copied. Also increment for the + * newline at the end of this line. If this is the last line, + * there is no newline, but it will be added immediately below. + */ + len += prefix_len + line_len + 1; + if (text_len == line_len) { + /* + * This is the last line. Add the trailing newline + * removed in vprintk_store(). + */ + text[prefix_len + line_len] = '\n'; + break; + } + + /* + * Advance beyond the added prefix and the related line with + * its newline. + */ + + text += prefix_len + line_len + 1; + + /* + * The remaining text has only decreased by the line with its + * newline. + * + * Note that @text_len can become zero. It happens when @text + * ended with a newline (either due to truncation or the + * original string ending with "\n\n"). The loop is correctly + * repeated and (if not truncated) an empty line with a prefix + * will be prepared. + */ + + text_len -= line_len + 1; + } + + /* + * If a buffer was provided, it will be terminated. Space for the + * string terminator is guaranteed to be available. The terminator is + * not counted in the return value. + */ + + if (buf_size > 0) + r_text[len] = 0; + + return len; +} + +static void register_log_minidump(struct printk_ringbuffer *prb) +{ + struct prb_desc_ring descring = prb->desc_ring; + struct prb_data_ring textdata_ring = prb->text_data_ring; + struct prb_desc *descaddr = descring.descs; + struct printk_info *p_infos = descring.infos; + struct md_region md_entry; + int ret; + + strscpy(md_entry.name, "KLOGBUF", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)textdata_ring.data; + md_entry.phys_addr = virt_to_phys(textdata_ring.data); + md_entry.size = _DATA_SIZE(textdata_ring.size_bits); + ret = msm_minidump_add_region(&md_entry); + if (ret < 0) + pr_err("Failed to add log_text entry in minidump table\n"); + + strscpy(md_entry.name, "LOG_DESC", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)descaddr; + md_entry.phys_addr = virt_to_phys(descaddr); + md_entry.size = sizeof(struct prb_desc) * _DESCS_COUNT(descring.count_bits); + ret = msm_minidump_add_region(&md_entry); + if (ret < 0) + pr_err("Failed to add log_desc entry in minidump table\n"); + + strscpy(md_entry.name, "LOG_INFO", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)p_infos; + md_entry.phys_addr = virt_to_phys(p_infos); + md_entry.size = sizeof(struct printk_info) * _DESCS_COUNT(descring.count_bits); + ret = msm_minidump_add_region(&md_entry); + if (ret < 0) + pr_err("Failed to add log_info entry in minidump table\n"); +} + +static void copy_boot_log_pr_cont(void *unused, struct printk_record *r, size_t text_len) +{ + if (copy_early_boot_log) + return; + + if (!r->info->text_len || !off || + ((off + r->info->text_len + 1 + 1) > boot_log_buf_size)) + return; + + if (boot_log_buf[off - 1] == '\n') + off = off - 1; + + memcpy(&boot_log_buf[off], &r->text_buf[r->info->text_len - text_len], text_len); + off += text_len; + if (r->info->flags & LOG_NEWLINE) { + boot_log_buf[off] = '\n'; + boot_log_buf[off + 1] = 0; + off += 1; + } +} + +static void copy_boot_log(void *unused, struct printk_ringbuffer *prb, + struct printk_record *r) +{ + struct prb_desc_ring descring = prb->desc_ring; + struct prb_data_ring textdata_ring = prb->text_data_ring; + struct prb_desc *descaddr = descring.descs; + struct printk_info *p_infos = descring.infos; + atomic_long_t headid, tailid; + unsigned long did, ind, sv; + unsigned int textdata_size = _DATA_SIZE(textdata_ring.size_bits); + unsigned long begin, end; + enum desc_state state; + size_t rem_buf_sz; + + tailid = descring.tail_id; + headid = descring.head_id; + + if (!copy_early_boot_log) { + if (!r->info->text_len) + return; + + /* + * Check whether remaining buffer has enough space + * for record meta data size + newline + terminator + * if not, let's reject the record. + */ + if ((off + r->info->text_len + PREFIX_MAX + 1 + 1) > boot_log_buf_size) + return; + + rem_buf_sz = boot_log_buf_size - off; + if (!rem_buf_sz) + return; + + memcpy(&boot_log_buf[off], &r->text_buf[0], r->info->text_len); + off += record_print_text(r->info, &boot_log_buf[off], rem_buf_sz); + return; + } + + copy_early_boot_log = false; + register_log_minidump(prb); + did = atomic_long_read(&tailid); + while (true) { + ind = did % _DESCS_COUNT(descring.count_bits); + sv = atomic_long_read(&descaddr[ind].state_var); + state = DESC_STATE(sv); + /* skip non-committed record */ + if ((state != desc_committed) && (state != desc_finalized)) { + if (did == atomic_long_read(&headid)) + break; + + did = DESC_ID(did + 1); + continue; + } + + begin = (descaddr[ind].text_blk_lpos.begin) % textdata_size; + end = (descaddr[ind].text_blk_lpos.next) % textdata_size; + if ((begin & 1) != 1) { + unsigned long text_start; + u16 textlen; + + if (begin > end) + begin = 0; + + text_start = begin + sizeof(unsigned long); + textlen = p_infos[ind].text_len; + if (end - text_start < textlen) + textlen = end - text_start; + + /* + * Check whether remaining buffer has enough space + * for record meta data size + newline + terminator + * if not, let's reject the record. + */ + if ((off + textlen + PREFIX_MAX + 1 + 1) > boot_log_buf_size) + break; + + rem_buf_sz = boot_log_buf_size - off; + + memcpy(&boot_log_buf[off], &textdata_ring.data[text_start], + textlen); + off += record_print_text(&p_infos[ind], + &boot_log_buf[off], rem_buf_sz); + } + + if (did == atomic_long_read(&headid)) + break; + + did = DESC_ID(did + 1); + } +} + +static int boot_log_init(void) +{ + void *start; + int ret = 0; + unsigned int size = BOOT_LOG_SIZE; + struct md_region md_entry; + + start = kzalloc(size, GFP_KERNEL); + if (!start) { + ret = -ENOMEM; + goto out; + } + + strscpy(md_entry.name, "KBOOT_LOG", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)start; + md_entry.phys_addr = virt_to_phys(start); + md_entry.size = size; + ret = msm_minidump_add_region(&md_entry); + if (ret < 0) { + pr_err("Failed to add boot_log entry in minidump table\n"); + kfree(start); + goto out; + } + + boot_log_buf_size = size; + boot_log_buf = start; + + /* + * Ensure boot_log_buf and boot_log_buf initialization + * is visible to other CPU's + */ + smp_mb(); + +out: + return ret; +} + +static void release_boot_log_buf(void) +{ + if (!boot_log_buf) + return; + + kfree(boot_log_buf); +} + +static int logbuf_vh_driver_probe(struct platform_device *pdev) +{ + int ret; + + ret = boot_log_init(); + if (ret < 0) + return ret; + + ret = register_trace_android_vh_logbuf(copy_boot_log, NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register android_vh_logbuf hook\n"); + kfree(boot_log_buf); + return ret; + } + + ret = register_trace_android_vh_logbuf_pr_cont(copy_boot_log_pr_cont, NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register android_vh_logbuf_pr_cont hook\n"); + unregister_trace_android_vh_logbuf(copy_boot_log, NULL); + kfree(boot_log_buf); + } + + return ret; +} + +static int logbuf_vh_driver_remove(struct platform_device *pdev) +{ + unregister_trace_android_vh_logbuf_pr_cont(copy_boot_log_pr_cont, NULL); + unregister_trace_android_vh_logbuf(copy_boot_log, NULL); + release_boot_log_buf(); + return 0; +} + +static const struct of_device_id logbuf_vh_of_match[] = { + { .compatible = "qcom,logbuf-vendor-hooks" }, + { } +}; +MODULE_DEVICE_TABLE(of, logbuf_vh_of_match); + +static struct platform_driver logbuf_vh_driver = { + .driver = { + .name = "qcom-logbuf-vh", + .of_match_table = logbuf_vh_of_match, + }, + .probe = logbuf_vh_driver_probe, + .remove = logbuf_vh_driver_remove, +}; +module_platform_driver(logbuf_vh_driver); + +MODULE_DESCRIPTION("QCOM Logbuf Vendor Hooks Driver"); +MODULE_LICENSE("GPL v2");