diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 18e264dc0f72..b5fa4ea3e806 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -831,6 +831,15 @@ config QCOM_IRQ_STAT This provides additional debug information for irq counts on cpu and ipi counts. +config QCOM_INITIAL_LOGBUF + bool "QCOM save initial log_buf" + depends on QGKI && QCOM_WDT_CORE + 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_FORCE_WDOG_BITE_ON_PANIC bool "QCOM force watchdog bite on panic" depends on QCOM_WDT_CORE diff --git a/drivers/soc/qcom/qcom_wdt_core.c b/drivers/soc/qcom/qcom_wdt_core.c index 349b77a2718f..e796e99d1b14 100644 --- a/drivers/soc/qcom/qcom_wdt_core.c +++ b/drivers/soc/qcom/qcom_wdt_core.c @@ -24,12 +24,20 @@ #include #include #include +#include #define MASK_SIZE 32 #define COMPARE_RET -1 typedef int (*compare_t) (const void *lhs, const void *rhs); +#ifdef CONFIG_QCOM_INITIAL_LOGBUF +#define BOOT_LOG_SIZE SZ_512K +char *boot_log_buf; +unsigned int boot_log_buf_size; +bool copy_early_boot_log = true; +#endif + static struct msm_watchdog_data *wdog_data; static void qcom_wdt_dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd) @@ -231,6 +239,63 @@ static void queue_irq_counts_work(struct work_struct *irq_counts_work) { } static void compute_irq_stat(struct work_struct *work) { } #endif +#ifdef CONFIG_QCOM_INITIAL_LOGBUF +static void boot_log_init(void) +{ + void *start; + unsigned int size; + struct md_region md_entry; + unsigned int *log_buf_size; + + log_buf_size = (unsigned int *)kallsyms_lookup_name("log_buf_len"); + if (!log_buf_size) { + dev_err(wdog_data->dev, "log_buf_len symbol not found\n"); + goto out; + } + + if (*log_buf_size >= BOOT_LOG_SIZE) + size = *log_buf_size; + else + size = BOOT_LOG_SIZE; + + start = kmalloc(size, GFP_KERNEL); + if (!start) + goto out; + + strlcpy(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; + if (msm_minidump_add_region(&md_entry) < 0) { + dev_err(wdog_data->dev, "Failed to add boot log entry\n"); + kfree(start); + goto out; + } + + boot_log_buf_size = size; + boot_log_buf = (char *)start; + + /* Ensure boot_log_buf and boot_log_buf initialization + * is visible to other CPU's + */ + smp_mb(); + +out: + return; +} + +static void release_boot_log_buf(void) +{ + if (!boot_log_buf) + return; + + kfree(boot_log_buf); +} +#else +static void boot_log_init(void) { } +static void release_boot_log_buf(void) { } +#endif + #ifdef CONFIG_PM_SLEEP /** * qcom_wdt_suspend() - Suspends qcom watchdog functionality. @@ -638,6 +703,7 @@ int qcom_wdt_remove(struct platform_device *pdev) wdog_dd->user_pet_complete = true; kthread_stop(wdog_dd->watchdog_task); flush_work(&wdog_dd->irq_counts_work); + release_boot_log_buf(); return 0; } EXPORT_SYMBOL(qcom_wdt_remove); @@ -825,6 +891,8 @@ int qcom_wdt_register(struct platform_device *pdev, if (ret) goto err; + boot_log_init(); + /* Add wdog info to minidump table */ strlcpy(md_entry.name, "KWDOGDATA", sizeof(md_entry.name)); md_entry.virt_addr = (uintptr_t)wdog_dd; diff --git a/include/linux/printk.h b/include/linux/printk.h index 2efa602092eb..8c07389b682e 100644 --- a/include/linux/printk.h +++ b/include/linux/printk.h @@ -11,6 +11,12 @@ extern const char linux_banner[]; extern const char linux_proc_banner[]; +#ifdef CONFIG_QCOM_INITIAL_LOGBUF +extern char *boot_log_buf; +extern unsigned int boot_log_buf_size; +extern bool copy_early_boot_log; +#endif + #define PRINTK_MAX_SINGLE_HEADER_LEN 2 static inline int printk_get_level(const char *buffer) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 6fa98f4eb5d1..9a439f5fa1ea 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -612,6 +612,47 @@ static u32 truncate_msg(u16 *text_len, u16 *trunc_msg_len, return msg_used_size(*text_len + *trunc_msg_len, 0, pad_len); } +#ifdef CONFIG_QCOM_INITIAL_LOGBUF +static inline void copy_boot_log(struct printk_log *msg) +{ + unsigned int bytes_to_copy; + unsigned int avail_buf; + static unsigned int boot_log_offset; + + if (!boot_log_buf) + goto out; + + avail_buf = boot_log_buf_size - boot_log_offset; + if (!avail_buf || (avail_buf < sizeof(*msg))) + goto out; + + if (copy_early_boot_log) { + bytes_to_copy = log_next_idx; + + if (avail_buf < bytes_to_copy) + bytes_to_copy = avail_buf; + + memcpy(boot_log_buf + boot_log_offset, log_buf, bytes_to_copy); + boot_log_offset += bytes_to_copy; + copy_early_boot_log = false; + goto out; + } + + bytes_to_copy = msg->len; + if (!bytes_to_copy) + bytes_to_copy = sizeof(*msg); + + if (avail_buf < bytes_to_copy) + bytes_to_copy = avail_buf; + + memcpy(boot_log_buf + boot_log_offset, msg, bytes_to_copy); + boot_log_offset += bytes_to_copy; + +out: + return; +} +#endif + /* insert record into the buffer, discard old ones, update heads */ static int log_store(u32 caller_id, int facility, int level, enum log_flags flags, u64 ts_nsec, @@ -670,6 +711,9 @@ static int log_store(u32 caller_id, int facility, int level, /* insert message */ log_next_idx += msg->len; log_next_seq++; +#ifdef CONFIG_QCOM_INITIAL_LOGBUF + copy_boot_log(msg); +#endif return msg->text_len; }