From 3906d34c64e54e0656f23a4ec93062c6d872b4bc Mon Sep 17 00:00:00 2001 From: Lingutla Chandrasekhar Date: Fri, 20 Jan 2017 13:46:34 +0530 Subject: [PATCH] soc: qcom: Add Minidump support Add Minidump support for clients to get minimum required data at the time of system crash. The Minidump table resides in SMEM, BOOT(SBL) will iterate the table entries and dumps out (to USB/Flash) the data in address location. Any client can register to this table with static or known addresses, as currently Minidump doesn't support dumping of dynamic data structures. To simplify post processing, we create an ELF header, where each entry in the minidump table is a section in elf header. If Memory dump table enabled, Dump all data entries registered with MDT. Enable Minidump: echo mini > /sys/kernel/dload/dload_mode Change-Id: I0fc8d21aef71ded34a498426ee3d7f86b063a639 Signed-off-by: Lingutla Chandrasekhar --- drivers/hwtracing/coresight/coresight-etm4x.c | 4 +- drivers/hwtracing/coresight/coresight-tmc.c | 7 +- drivers/platform/msm/msm_11ad/msm_11ad.c | 2 + drivers/power/reset/msm-poweroff.c | 50 +++- drivers/soc/qcom/Kconfig | 17 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/common_log.c | 41 ++- drivers/soc/qcom/cpuss_dump.c | 4 +- drivers/soc/qcom/dcc.c | 6 +- drivers/soc/qcom/memory_dump_v2.c | 30 ++- drivers/soc/qcom/msm_minidump.c | 371 ++++++++++++++++++++++++++ drivers/soc/qcom/watchdog_v2.c | 18 +- include/soc/qcom/minidump.h | 48 ++++ kernel/trace/msm_rtb.c | 10 +- 14 files changed, 597 insertions(+), 12 deletions(-) create mode 100644 drivers/soc/qcom/msm_minidump.c create mode 100644 include/soc/qcom/minidump.h diff --git a/drivers/hwtracing/coresight/coresight-etm4x.c b/drivers/hwtracing/coresight/coresight-etm4x.c index 287d839f98d0..9f9dd574c8d0 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.c +++ b/drivers/hwtracing/coresight/coresight-etm4x.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014, 2016 The Linux Foundation. All rights reserved. +/* Copyright (c) 2014, 2016-2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -2557,6 +2557,8 @@ static int etm4_set_reg_dump(struct etmv4_drvdata *drvdata) drvdata->reg_data.addr = virt_to_phys(baddr); drvdata->reg_data.len = size; + scnprintf(drvdata->reg_data.name, sizeof(drvdata->reg_data.name), + "KETM_REG%d", drvdata->cpu); dump_entry.id = MSM_DUMP_DATA_ETM_REG + drvdata->cpu; dump_entry.addr = virt_to_phys(&drvdata->reg_data); diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c index 34b12e015768..c5998bd5ce02 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012, 2016 The Linux Foundation. All rights reserved. +/* Copyright (c) 2012, 2016-2017 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1726,6 +1726,9 @@ static int tmc_etf_set_buf_dump(struct tmc_drvdata *drvdata) drvdata->buf_data.addr = virt_to_phys(drvdata->buf); drvdata->buf_data.len = drvdata->size; + scnprintf(drvdata->buf_data.name, sizeof(drvdata->buf_data.name), + "KTMC_ETF%d", count); + dump_entry.id = MSM_DUMP_DATA_TMC_ETF + count; dump_entry.addr = virt_to_phys(&drvdata->buf_data); @@ -1817,6 +1820,8 @@ static int tmc_set_reg_dump(struct tmc_drvdata *drvdata) drvdata->reg_data.addr = virt_to_phys(drvdata->reg_buf); drvdata->reg_data.len = size; + scnprintf(drvdata->reg_data.name, sizeof(drvdata->reg_data.name), + "KTMC_REG%d", count); dump_entry.id = MSM_DUMP_DATA_TMC_REG + count; dump_entry.addr = virt_to_phys(&drvdata->reg_data); diff --git a/drivers/platform/msm/msm_11ad/msm_11ad.c b/drivers/platform/msm/msm_11ad/msm_11ad.c index c6009d767db5..ef733f7a71f9 100644 --- a/drivers/platform/msm/msm_11ad/msm_11ad.c +++ b/drivers/platform/msm/msm_11ad/msm_11ad.c @@ -864,6 +864,8 @@ static int msm_11ad_ssr_init(struct msm11ad_ctx *ctx) ctx->dump_data.addr = virt_to_phys(ctx->ramdump_addr); ctx->dump_data.len = WIGIG_RAMDUMP_SIZE; + strlcpy(ctx->dump_data.name, "KWIGIG", + sizeof(ctx->dump_data.name)); dump_entry.id = MSM_DUMP_DATA_WIGIG; dump_entry.addr = virt_to_phys(&ctx->dump_data); diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c index a30ed90d6e92..8d038ba0770d 100644 --- a/drivers/power/reset/msm-poweroff.c +++ b/drivers/power/reset/msm-poweroff.c @@ -33,6 +33,7 @@ #include #include #include +#include #define EMERGENCY_DLOAD_MAGIC1 0x322A4F99 #define EMERGENCY_DLOAD_MAGIC2 0xC67E4350 @@ -42,9 +43,10 @@ #define SCM_IO_DISABLE_PMIC_ARBITER 1 #define SCM_IO_DEASSERT_PS_HOLD 2 #define SCM_WDOG_DEBUG_BOOT_PART 0x9 -#define SCM_DLOAD_MODE 0X10 +#define SCM_DLOAD_FULLDUMP 0X10 #define SCM_EDLOAD_MODE 0X01 #define SCM_DLOAD_CMD 0x10 +#define SCM_DLOAD_MINIDUMP 0X20 static int restart_mode; @@ -69,6 +71,7 @@ static void scm_disable_sdi(void); #endif static int in_panic; +static int dload_type = SCM_DLOAD_FULLDUMP; static int download_mode = 1; static struct kobject dload_kobj; static void *dload_mode_addr, *dload_type_addr; @@ -142,7 +145,7 @@ static void set_dload_mode(int on) mb(); } - ret = scm_set_dload_mode(on ? SCM_DLOAD_MODE : 0, 0); + ret = scm_set_dload_mode(on ? dload_type : 0, 0); if (ret) pr_err("Failed to set secure DLOAD mode: %d\n", ret); @@ -185,7 +188,6 @@ static int dload_set(const char *val, struct kernel_param *kp) int old_val = download_mode; ret = param_set_int(val, kp); - if (ret) return ret; @@ -454,7 +456,7 @@ static ssize_t show_emmc_dload(struct kobject *kobj, struct attribute *attr, else show_val = 0; - return snprintf(buf, sizeof(show_val), "%u\n", show_val); + return scnprintf(buf, sizeof(show_val), "%u\n", show_val); } static size_t store_emmc_dload(struct kobject *kobj, struct attribute *attr, @@ -477,10 +479,50 @@ static size_t store_emmc_dload(struct kobject *kobj, struct attribute *attr, return count; } + +#ifdef CONFIG_QCOM_MINIDUMP + +static DEFINE_MUTEX(tcsr_lock); + +static ssize_t show_dload_mode(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "DLOAD dump type: %s\n", + (dload_type == SCM_DLOAD_MINIDUMP) ? "mini" : "full"); +} + +static size_t store_dload_mode(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + if (sysfs_streq(buf, "full")) { + dload_type = SCM_DLOAD_FULLDUMP; + } else if (sysfs_streq(buf, "mini")) { + if (!msm_minidump_enabled()) { + pr_info("Minidump is not enabled\n"); + return -ENODEV; + } + dload_type = SCM_DLOAD_MINIDUMP; + } else { + pr_info("Invalid value. Use 'full' or 'mini'\n"); + return -EINVAL; + } + + mutex_lock(&tcsr_lock); + /*Overwrite TCSR reg*/ + set_dload_mode(dload_type); + mutex_unlock(&tcsr_lock); + return count; +} +RESET_ATTR(dload_mode, 0644, show_dload_mode, store_dload_mode); +#endif + RESET_ATTR(emmc_dload, 0644, show_emmc_dload, store_emmc_dload); static struct attribute *reset_attrs[] = { &reset_attr_emmc_dload.attr, +#ifdef CONFIG_QCOM_MINIDUMP + &reset_attr_dload_mode.attr, +#endif NULL }; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 75bebf66376d..34b0adb108eb 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -441,6 +441,23 @@ config QCOM_MEMORY_DUMP_V2 of deadlocks or cpu hangs these dump regions are captured to give a snapshot of the system at the time of the crash. +config QCOM_MINIDUMP + bool "QCOM Minidump Support" + depends on MSM_SMEM && QCOM_DLOAD_MODE + help + This enables minidump feature. It allows various clients to + register to dump their state at system bad state (panic/WDT,etc.,). + This uses SMEM to store all registered client information. + This will dump all registered entries, only when DLOAD mode is enabled. + +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 SMEM table. + config ICNSS tristate "Platform driver for Q6 integrated connectivity" ---help--- diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index fa350d122384..87698b75d3b8 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_QCOM_SCM_XPU) += scm-xpu.o obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o obj-$(CONFIG_QCOM_MEMORY_DUMP) += memory_dump.o obj-$(CONFIG_QCOM_MEMORY_DUMP_V2) += memory_dump_v2.o +obj-$(CONFIG_QCOM_MINIDUMP) += msm_minidump.o obj-$(CONFIG_QCOM_DCC) += dcc.o obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o obj-$(CONFIG_QCOM_COMMON_LOG) += common_log.o diff --git a/drivers/soc/qcom/common_log.c b/drivers/soc/qcom/common_log.c index ecf89b2b3b37..f001e820b797 100644 --- a/drivers/soc/qcom/common_log.c +++ b/drivers/soc/qcom/common_log.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -18,6 +18,8 @@ #include #include #include +#include +#include #define MISC_DUMP_DATA_LEN 4096 #define PMIC_DUMP_DATA_LEN (64 * 1024) @@ -38,6 +40,8 @@ void register_misc_dump(void) misc_buf = kzalloc(MISC_DUMP_DATA_LEN, GFP_KERNEL); if (!misc_buf) goto err0; + + strlcpy(misc_data->name, "KMISC", sizeof(misc_data->name)); misc_data->addr = virt_to_phys(misc_buf); misc_data->len = MISC_DUMP_DATA_LEN; dump_entry.id = MSM_DUMP_DATA_MISC; @@ -70,6 +74,7 @@ static void register_pmic_dump(void) if (!dump_addr) goto err0; + strlcpy(dump_data->name, "KPMIC", sizeof(dump_data->name)); dump_data->addr = virt_to_phys(dump_addr); dump_data->len = PMIC_DUMP_DATA_LEN; dump_entry.id = MSM_DUMP_DATA_PMIC; @@ -104,6 +109,8 @@ static void register_vsense_dump(void) if (!dump_addr) goto err0; + strlcpy(dump_data->name, "KVSENSE", + sizeof(dump_data->name)); dump_data->addr = virt_to_phys(dump_addr); dump_data->len = VSENSE_DUMP_DATA_LEN; dump_entry.id = MSM_DUMP_DATA_VSENSE; @@ -136,6 +143,7 @@ void register_rpm_dump(void) if (!dump_addr) goto err0; + strlcpy(dump_data->name, "KRPM", sizeof(dump_data->name)); dump_data->addr = virt_to_phys(dump_addr); dump_data->len = RPM_DUMP_DATA_LEN; dump_entry.id = MSM_DUMP_DATA_RPM; @@ -217,8 +225,39 @@ static void __init common_log_register_log_buf(void) } } +static void __init register_kernel_sections(void) +{ + struct md_region ksec_entry; + char *data_name = "KDATABSS"; + const size_t static_size = __per_cpu_end - __per_cpu_start; + void __percpu *base = (void __percpu *)__per_cpu_start; + unsigned int cpu; + + strlcpy(ksec_entry.name, data_name, sizeof(ksec_entry.name)); + ksec_entry.virt_addr = (uintptr_t)_sdata; + ksec_entry.phys_addr = virt_to_phys(_sdata); + ksec_entry.size = roundup((__bss_stop - _sdata), 4); + if (msm_minidump_add_region(&ksec_entry)) + pr_err("Failed to add data 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)) + pr_err("Failed to add percpu sections in Minidump\n"); + } +} + static int __init msm_common_log_init(void) { + register_kernel_sections(); common_log_register_log_buf(); register_misc_dump(); register_pmic_dump(); diff --git a/drivers/soc/qcom/cpuss_dump.c b/drivers/soc/qcom/cpuss_dump.c index 93c876a7ad73..2e08b78dee94 100644 --- a/drivers/soc/qcom/cpuss_dump.c +++ b/drivers/soc/qcom/cpuss_dump.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -75,6 +75,8 @@ static int cpuss_dump_probe(struct platform_device *pdev) dump_data->addr = dump_addr; dump_data->len = size; + scnprintf(dump_data->name, sizeof(dump_data->name), + "KCPUSS%X", id); dump_entry.id = id; dump_entry.addr = virt_to_phys(dump_data); ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); diff --git a/drivers/soc/qcom/dcc.c b/drivers/soc/qcom/dcc.c index aced50bf7fda..e5f3f119065b 100644 --- a/drivers/soc/qcom/dcc.c +++ b/drivers/soc/qcom/dcc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1173,6 +1173,8 @@ static void dcc_allocate_dump_mem(struct dcc_drvdata *drvdata) /* Allocate memory for dcc reg dump */ drvdata->reg_buf = devm_kzalloc(dev, drvdata->reg_size, GFP_KERNEL); if (drvdata->reg_buf) { + strlcpy(drvdata->reg_data.name, "KDCC_REG", + sizeof(drvdata->reg_data.name)); drvdata->reg_data.addr = virt_to_phys(drvdata->reg_buf); drvdata->reg_data.len = drvdata->reg_size; reg_dump_entry.id = MSM_DUMP_DATA_DCC_REG; @@ -1190,6 +1192,8 @@ static void dcc_allocate_dump_mem(struct dcc_drvdata *drvdata) /* Allocate memory for dcc sram dump */ drvdata->sram_buf = devm_kzalloc(dev, drvdata->ram_size, GFP_KERNEL); if (drvdata->sram_buf) { + strlcpy(drvdata->sram_data.name, "KDCC_SRAM", + sizeof(drvdata->sram_data.name)); drvdata->sram_data.addr = virt_to_phys(drvdata->sram_buf); drvdata->sram_data.len = drvdata->ram_size; sram_dump_entry.id = MSM_DUMP_DATA_DCC_SRAM; diff --git a/drivers/soc/qcom/memory_dump_v2.c b/drivers/soc/qcom/memory_dump_v2.c index af141c808c81..092b1c1af44b 100644 --- a/drivers/soc/qcom/memory_dump_v2.c +++ b/drivers/soc/qcom/memory_dump_v2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2015, 2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -20,6 +20,7 @@ #include #include #include +#include #define MSM_DUMP_TABLE_VERSION MSM_DUMP_MAKE_VERSION(2, 0) @@ -87,6 +88,29 @@ static struct msm_dump_table *msm_dump_get_table(enum msm_dump_table_ids id) return table; } +int msm_dump_data_add_minidump(struct msm_dump_entry *entry) +{ + struct msm_dump_data *data; + struct md_region md_entry; + + data = (struct msm_dump_data *)(phys_to_virt(entry->addr)); + if (!strcmp(data->name, "")) { + pr_info("Entry name is NULL, Use ID %d for minidump\n", + entry->id); + snprintf(md_entry.name, sizeof(md_entry.name), "KMDT0x%X", + entry->id); + } else { + strlcpy(md_entry.name, data->name, sizeof(md_entry.name)); + } + + md_entry.phys_addr = data->addr; + md_entry.virt_addr = (uintptr_t)phys_to_virt(data->addr); + md_entry.size = data->len; + md_entry.id = entry->id; + + return msm_minidump_add_region(&md_entry); +} + int msm_dump_data_register(enum msm_dump_table_ids id, struct msm_dump_entry *entry) { @@ -107,6 +131,10 @@ int msm_dump_data_register(enum msm_dump_table_ids id, table->num_entries++; dmac_flush_range(table, (void *)table + sizeof(struct msm_dump_table)); + + if (msm_dump_data_add_minidump(entry)) + pr_info("Failed to add entry in Minidump table\n"); + return 0; } EXPORT_SYMBOL(msm_dump_data_register); diff --git a/drivers/soc/qcom/msm_minidump.c b/drivers/soc/qcom/msm_minidump.c new file mode 100644 index 000000000000..1cb36bf98555 --- /dev/null +++ b/drivers/soc/qcom/msm_minidump.c @@ -0,0 +1,371 @@ +/* Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define pr_fmt(fmt) "Minidump: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define MAX_NUM_ENTRIES (CONFIG_MINIDUMP_MAX_ENTRIES + 1) +#define SMEM_ENTRY_SIZE 32 +#define MAX_MEM_LENGTH (SMEM_ENTRY_SIZE * MAX_NUM_ENTRIES) +#define MAX_STRTBL_SIZE (MAX_NUM_ENTRIES * MAX_NAME_LENGTH) +#define SMEM_MINIDUMP_TABLE_ID 602 + +/* Bootloader Minidump table */ +struct md_smem_table { + u32 version; + u32 smem_length; + u64 next_avail_offset; + char reserved[MAX_NAME_LENGTH]; + u64 *region_start; +}; + +/* Bootloader Minidump region */ +struct md_smem_region { + char name[MAX_NAME_LENGTH]; + u64 address; + u64 size; +}; + +/* md_table: APPS minidump table + * @num_regions: Number of entries registered + * @region_base_offset: APPS region start offset smem table + * @md_smem_table: Pointer smem table + * @region: Pointer to APPS region in smem table + * @entry: All registered client entries. + */ + +struct md_table { + u32 num_regions; + u32 region_base_offset; + struct md_smem_table *md_smem_table; + struct md_smem_region *region; + struct md_region entry[MAX_NUM_ENTRIES]; +}; + +/* Protect elfheader and smem table from deferred calls contention */ +static DEFINE_SPINLOCK(mdt_lock); +static bool minidump_enabled; +static struct md_table minidump_table; +static unsigned int pendings; +static unsigned int region_idx = 1; /* First entry is ELF header*/ + +/* ELF Header */ +static struct elfhdr *md_ehdr; +/* ELF Program header */ +static struct elf_phdr *phdr; +/* ELF Section header */ +static struct elf_shdr *shdr; +/* Section offset in elf image */ +static u64 elf_offset; +/* String table index, first byte must be '\0' */ +static unsigned int stringtable_idx = 1; + +static inline struct elf_shdr *elf_sheader(struct elfhdr *hdr) +{ + return (struct elf_shdr *)((size_t)hdr + (size_t)hdr->e_shoff); +} + +static inline struct elf_shdr *elf_section(struct elfhdr *hdr, int idx) +{ + return &elf_sheader(hdr)[idx]; +} + +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; +} + +static inline char *elf_lookup_string(struct elfhdr *hdr, int offset) +{ + char *strtab = elf_str_table(hdr); + + if ((strtab == NULL) | (stringtable_idx < offset)) + return NULL; + return strtab + offset; +} + +static inline unsigned int set_section_name(const char *name) +{ + char *strtab = elf_str_table(md_ehdr); + int ret = 0; + + if ((strtab == NULL) | (name == NULL)) + return 0; + + ret = stringtable_idx; + stringtable_idx += strlcpy((strtab + stringtable_idx), + name, MAX_NAME_LENGTH); + stringtable_idx += 1; + return ret; +} + +/* return 1 if name already exists */ +static inline bool md_check_name(const char *name) +{ + struct md_region *mde = minidump_table.entry; + int i, regno = minidump_table.num_regions; + + for (i = 0; i < regno; i++, mde++) + if (!strcmp(mde->name, name)) + return true; + return false; +} + +/* Update Mini dump table in SMEM */ +static int md_update_smem_table(const struct md_region *entry) +{ + struct md_smem_region *mdr; + + if (!minidump_enabled) { + pr_info("Table in smem is not setup\n"); + return -ENODEV; + } + + mdr = &minidump_table.region[region_idx++]; + + strlcpy(mdr->name, entry->name, sizeof(mdr->name)); + mdr->address = entry->phys_addr; + mdr->size = entry->size; + + /* 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->size; + shdr->sh_flags = SHF_WRITE; + shdr->sh_offset = elf_offset; + shdr->sh_entsize = 0; + + phdr->p_type = PT_LOAD; + phdr->p_offset = elf_offset; + phdr->p_vaddr = entry->virt_addr; + phdr->p_paddr = entry->phys_addr; + phdr->p_filesz = phdr->p_memsz = mdr->size; + phdr->p_flags = PF_R | PF_W; + + md_ehdr->e_shnum += 1; + md_ehdr->e_phnum += 1; + elf_offset += shdr->sh_size; + shdr++; + phdr++; + + return 0; +} + +bool msm_minidump_enabled(void) +{ + bool ret; + + spin_lock(&mdt_lock); + ret = minidump_enabled; + spin_unlock(&mdt_lock); + return ret; +} +EXPORT_SYMBOL(msm_minidump_enabled); + +int msm_minidump_add_region(const struct md_region *entry) +{ + u32 entries; + struct md_region *mdr; + int ret = 0; + + if (!entry) + return -EINVAL; + + if (((strlen(entry->name) > MAX_NAME_LENGTH) || + md_check_name(entry->name)) && !entry->virt_addr) { + pr_info("Invalid entry details\n"); + return -EINVAL; + } + + if (!IS_ALIGNED(entry->size, 4)) { + pr_info("size should be 4 byte aligned\n"); + return -EINVAL; + } + + spin_lock(&mdt_lock); + entries = minidump_table.num_regions; + if (entries >= MAX_NUM_ENTRIES) { + pr_info("Maximum entries reached.\n"); + spin_unlock(&mdt_lock); + return -ENOMEM; + } + + mdr = &minidump_table.entry[entries]; + strlcpy(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 (minidump_enabled) + ret = md_update_smem_table(entry); + else + pendings++; + + spin_unlock(&mdt_lock); + + pr_debug("Minidump: added %s to %s list\n", + mdr->name, minidump_enabled ? "":"pending"); + return ret; +} +EXPORT_SYMBOL(msm_minidump_add_region); + +static int msm_minidump_add_header(void) +{ + struct md_smem_region *mdreg = &minidump_table.region[0]; + char *banner; + unsigned int strtbl_off, elfh_size, phdr_off; + + elfh_size = sizeof(*md_ehdr) + MAX_STRTBL_SIZE + MAX_MEM_LENGTH + + ((sizeof(*shdr) + sizeof(*phdr)) * (MAX_NUM_ENTRIES + 1)); + + md_ehdr = kzalloc(elfh_size, GFP_KERNEL); + if (!md_ehdr) + return -ENOMEM; + + strlcpy(mdreg->name, "KELF_HEADER", sizeof(mdreg->name)); + mdreg->address = virt_to_phys(md_ehdr); + mdreg->size = elfh_size; + + /* Section headers*/ + shdr = (struct elf_shdr *)(md_ehdr + 1); + phdr = (struct elf_phdr *)(shdr + MAX_NUM_ENTRIES); + phdr_off = sizeof(*md_ehdr) + (sizeof(*shdr) * MAX_NUM_ENTRIES); + + memcpy(md_ehdr->e_ident, ELFMAG, SELFMAG); + md_ehdr->e_ident[EI_CLASS] = ELF_CLASS; + md_ehdr->e_ident[EI_DATA] = ELF_DATA; + md_ehdr->e_ident[EI_VERSION] = EV_CURRENT; + md_ehdr->e_ident[EI_OSABI] = ELF_OSABI; + md_ehdr->e_type = ET_CORE; + md_ehdr->e_machine = ELF_ARCH; + md_ehdr->e_version = EV_CURRENT; + md_ehdr->e_ehsize = sizeof(*md_ehdr); + md_ehdr->e_phoff = phdr_off; + md_ehdr->e_phentsize = sizeof(*phdr); + md_ehdr->e_phnum = 1; + md_ehdr->e_shoff = sizeof(*md_ehdr); + md_ehdr->e_shentsize = sizeof(*shdr); + md_ehdr->e_shnum = 3; /* NULL, STR TABLE, Linux banner */ + md_ehdr->e_shstrndx = 1; + + elf_offset = elfh_size; + strtbl_off = sizeof(*md_ehdr) + + ((sizeof(*phdr) + sizeof(*shdr)) * MAX_NUM_ENTRIES); + /* First section header should be NULL + * 2nd entry for string table + */ + 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 entry for linux banner */ + banner = (char *)md_ehdr + strtbl_off + MAX_STRTBL_SIZE; + strlcpy(banner, linux_banner, MAX_MEM_LENGTH); + + 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"); + shdr++; + + 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; + + md_ehdr->e_phnum += 1; + phdr++; + + return 0; +} + +static int __init msm_minidump_init(void) +{ + unsigned int i, size; + struct md_region *mdr; + struct md_smem_table *smem_table; + + /* Get Minidump table */ + smem_table = smem_get_entry(SMEM_MINIDUMP_TABLE_ID, &size, 0, + SMEM_ANY_HOST_FLAG); + if (IS_ERR_OR_NULL(smem_table)) { + pr_info("SMEM is not initialized.\n"); + return -ENODEV; + } + + if ((smem_table->next_avail_offset + MAX_MEM_LENGTH) > + smem_table->smem_length) { + pr_info("SMEM memory not available.\n"); + return -ENOMEM; + } + + /* Get next_avail_offset and update it to reserve memory */ + minidump_table.region_base_offset = smem_table->next_avail_offset; + minidump_table.region = (struct md_smem_region *)((uintptr_t)smem_table + + minidump_table.region_base_offset); + + smem_table->next_avail_offset = + minidump_table.region_base_offset + MAX_MEM_LENGTH; + minidump_table.md_smem_table = smem_table; + + msm_minidump_add_header(); + + /* Add pending entries to smem table */ + spin_lock(&mdt_lock); + minidump_enabled = true; + + for (i = 0; i < pendings; i++) { + mdr = &minidump_table.entry[i]; + if (md_update_smem_table(mdr)) { + pr_info("Unable to add entry %s to smem table\n", + mdr->name); + spin_unlock(&mdt_lock); + return -ENODEV; + } + } + + pendings = 0; + spin_unlock(&mdt_lock); + + pr_info("Enabled, region base:%d, region 0x%pK\n", + minidump_table.region_base_offset, minidump_table.region); + + return 0; +} +subsys_initcall(msm_minidump_init) diff --git a/drivers/soc/qcom/watchdog_v2.c b/drivers/soc/qcom/watchdog_v2.c index 470ecfdd9f5e..745a069df88a 100644 --- a/drivers/soc/qcom/watchdog_v2.c +++ b/drivers/soc/qcom/watchdog_v2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -29,6 +29,7 @@ #include #include #include +#include #include #define MODULE_NAME "msm_watchdog" @@ -521,6 +522,8 @@ void register_scan_dump(struct msm_watchdog_data *wdog_dd) dump_data->addr = virt_to_phys(dump_addr); dump_data->len = wdog_dd->scandump_size; + strlcpy(dump_data->name, "KSCANDUMP", sizeof(dump_data->name)); + dump_entry.id = MSM_DUMP_DATA_SCANDUMP; dump_entry.addr = virt_to_phys(dump_data); ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); @@ -605,6 +608,9 @@ static void configure_bark_dump(struct msm_watchdog_data *wdog_dd) cpu_data[cpu].addr = virt_to_phys(cpu_buf + cpu * MAX_CPU_CTX_SIZE); cpu_data[cpu].len = MAX_CPU_CTX_SIZE; + snprintf(cpu_data[cpu].name, sizeof(cpu_data[cpu].name), + "KCPU_CTX%d", cpu); + dump_entry.id = MSM_DUMP_DATA_CPU_CTX + cpu; dump_entry.addr = virt_to_phys(&cpu_data[cpu]); ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, @@ -820,6 +826,7 @@ static int msm_watchdog_probe(struct platform_device *pdev) { int ret; struct msm_watchdog_data *wdog_dd; + struct md_region md_entry; if (!pdev->dev.of_node || !enable) return -ENODEV; @@ -841,6 +848,15 @@ static int msm_watchdog_probe(struct platform_device *pdev) goto err; } init_watchdog_data(wdog_dd); + + /* Add wdog info to minidump table */ + strlcpy(md_entry.name, "KWDOGDATA", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)wdog_dd; + md_entry.phys_addr = virt_to_phys(wdog_dd); + md_entry.size = sizeof(*wdog_dd); + if (msm_minidump_add_region(&md_entry)) + pr_info("Failed to add RTB in Minidump\n"); + return 0; err: kzfree(wdog_dd); diff --git a/include/soc/qcom/minidump.h b/include/soc/qcom/minidump.h new file mode 100644 index 000000000000..9d993a17fb89 --- /dev/null +++ b/include/soc/qcom/minidump.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2017 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MINIDUMP_H +#define __MINIDUMP_H + +#define MAX_NAME_LENGTH 16 +/* 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: + * Zero: on successful addition + * Negetive error number on failures + */ +#ifdef CONFIG_QCOM_MINIDUMP +extern int msm_minidump_add_region(const struct md_region *entry); +extern bool msm_minidump_enabled(void); +#else +static inline int msm_minidump_add_region(const struct md_region *entry) +{ + return -ENODEV; +} +static inline bool msm_minidump_enabled(void) { return false; } +#endif +#endif diff --git a/kernel/trace/msm_rtb.c b/kernel/trace/msm_rtb.c index 80058b544cb5..587082117842 100644 --- a/kernel/trace/msm_rtb.c +++ b/kernel/trace/msm_rtb.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -28,6 +28,7 @@ #include #include #include +#include #define SENTINEL_BYTE_1 0xFF #define SENTINEL_BYTE_2 0xAA @@ -243,6 +244,7 @@ EXPORT_SYMBOL(uncached_logk); static int msm_rtb_probe(struct platform_device *pdev) { struct msm_rtb_platform_data *d = pdev->dev.platform_data; + struct md_region md_entry; #if defined(CONFIG_QCOM_RTB_SEPARATE_CPUS) unsigned int cpu; #endif @@ -294,6 +296,12 @@ static int msm_rtb_probe(struct platform_device *pdev) memset(msm_rtb.rtb, 0, msm_rtb.size); + strlcpy(md_entry.name, "KRTB_BUF", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)msm_rtb.rtb; + md_entry.phys_addr = msm_rtb.phys; + md_entry.size = msm_rtb.size; + if (msm_minidump_add_region(&md_entry)) + pr_info("Failed to add RTB in Minidump\n"); #if defined(CONFIG_QCOM_RTB_SEPARATE_CPUS) for_each_possible_cpu(cpu) { -- 2.11.0