From afb375e663ee1d2942abb8ee319528d74c9c3547 Mon Sep 17 00:00:00 2001 From: Huang Yiwei Date: Wed, 30 Mar 2022 22:31:31 +0800 Subject: [PATCH] soc: qcom: Add snapshot of qcom_logbuf_vendor_hooks This is a snapshot of the qcom_logbuf_vendor_hooks as of msm-5.15 commit <908a517b050d> ("Merge "sched/walt: remove duplicate definitions of rt_task_arrival_time""). Change-Id: I4e409c56acc59c38fcd7fdc10e03c3edb81ee1a3 Signed-off-by: Huang Yiwei --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qcom_logbuf_vh.c | 390 ++++++++++++++++++++++++++++++ 3 files changed, 400 insertions(+) create mode 100644 drivers/soc/qcom/qcom_logbuf_vh.c diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 7256b2c3f391..7bf182e91cd4 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 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 91aed0ce3a02..8dcd23aef5aa 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -32,3 +32,4 @@ 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_LOGBUF_VENDOR_HOOKS) += qcom_logbuf_vh.o 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");