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 <clingutla@codeaurora.org>
This commit is contained in:
Lingutla Chandrasekhar 2017-01-20 13:46:34 +05:30
parent 7514c164f1
commit 3906d34c64
14 changed files with 597 additions and 12 deletions

View file

@ -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 * 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 * 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.addr = virt_to_phys(baddr);
drvdata->reg_data.len = size; 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.id = MSM_DUMP_DATA_ETM_REG + drvdata->cpu;
dump_entry.addr = virt_to_phys(&drvdata->reg_data); dump_entry.addr = virt_to_phys(&drvdata->reg_data);

View file

@ -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 * 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 * 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.addr = virt_to_phys(drvdata->buf);
drvdata->buf_data.len = drvdata->size; 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.id = MSM_DUMP_DATA_TMC_ETF + count;
dump_entry.addr = virt_to_phys(&drvdata->buf_data); 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.addr = virt_to_phys(drvdata->reg_buf);
drvdata->reg_data.len = size; 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.id = MSM_DUMP_DATA_TMC_REG + count;
dump_entry.addr = virt_to_phys(&drvdata->reg_data); dump_entry.addr = virt_to_phys(&drvdata->reg_data);

View file

@ -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.addr = virt_to_phys(ctx->ramdump_addr);
ctx->dump_data.len = WIGIG_RAMDUMP_SIZE; 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.id = MSM_DUMP_DATA_WIGIG;
dump_entry.addr = virt_to_phys(&ctx->dump_data); dump_entry.addr = virt_to_phys(&ctx->dump_data);

View file

@ -33,6 +33,7 @@
#include <soc/qcom/scm.h> #include <soc/qcom/scm.h>
#include <soc/qcom/restart.h> #include <soc/qcom/restart.h>
#include <soc/qcom/watchdog.h> #include <soc/qcom/watchdog.h>
#include <soc/qcom/minidump.h>
#define EMERGENCY_DLOAD_MAGIC1 0x322A4F99 #define EMERGENCY_DLOAD_MAGIC1 0x322A4F99
#define EMERGENCY_DLOAD_MAGIC2 0xC67E4350 #define EMERGENCY_DLOAD_MAGIC2 0xC67E4350
@ -42,9 +43,10 @@
#define SCM_IO_DISABLE_PMIC_ARBITER 1 #define SCM_IO_DISABLE_PMIC_ARBITER 1
#define SCM_IO_DEASSERT_PS_HOLD 2 #define SCM_IO_DEASSERT_PS_HOLD 2
#define SCM_WDOG_DEBUG_BOOT_PART 0x9 #define SCM_WDOG_DEBUG_BOOT_PART 0x9
#define SCM_DLOAD_MODE 0X10 #define SCM_DLOAD_FULLDUMP 0X10
#define SCM_EDLOAD_MODE 0X01 #define SCM_EDLOAD_MODE 0X01
#define SCM_DLOAD_CMD 0x10 #define SCM_DLOAD_CMD 0x10
#define SCM_DLOAD_MINIDUMP 0X20
static int restart_mode; static int restart_mode;
@ -69,6 +71,7 @@ static void scm_disable_sdi(void);
#endif #endif
static int in_panic; static int in_panic;
static int dload_type = SCM_DLOAD_FULLDUMP;
static int download_mode = 1; static int download_mode = 1;
static struct kobject dload_kobj; static struct kobject dload_kobj;
static void *dload_mode_addr, *dload_type_addr; static void *dload_mode_addr, *dload_type_addr;
@ -142,7 +145,7 @@ static void set_dload_mode(int on)
mb(); mb();
} }
ret = scm_set_dload_mode(on ? SCM_DLOAD_MODE : 0, 0); ret = scm_set_dload_mode(on ? dload_type : 0, 0);
if (ret) if (ret)
pr_err("Failed to set secure DLOAD mode: %d\n", 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; int old_val = download_mode;
ret = param_set_int(val, kp); ret = param_set_int(val, kp);
if (ret) if (ret)
return ret; return ret;
@ -454,7 +456,7 @@ static ssize_t show_emmc_dload(struct kobject *kobj, struct attribute *attr,
else else
show_val = 0; 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, 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; 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); RESET_ATTR(emmc_dload, 0644, show_emmc_dload, store_emmc_dload);
static struct attribute *reset_attrs[] = { static struct attribute *reset_attrs[] = {
&reset_attr_emmc_dload.attr, &reset_attr_emmc_dload.attr,
#ifdef CONFIG_QCOM_MINIDUMP
&reset_attr_dload_mode.attr,
#endif
NULL NULL
}; };

View file

@ -441,6 +441,23 @@ config QCOM_MEMORY_DUMP_V2
of deadlocks or cpu hangs these dump regions are captured to of deadlocks or cpu hangs these dump regions are captured to
give a snapshot of the system at the time of the crash. 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 config ICNSS
tristate "Platform driver for Q6 integrated connectivity" tristate "Platform driver for Q6 integrated connectivity"
---help--- ---help---

View file

@ -66,6 +66,7 @@ obj-$(CONFIG_QCOM_SCM_XPU) += scm-xpu.o
obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o
obj-$(CONFIG_QCOM_MEMORY_DUMP) += memory_dump.o obj-$(CONFIG_QCOM_MEMORY_DUMP) += memory_dump.o
obj-$(CONFIG_QCOM_MEMORY_DUMP_V2) += memory_dump_v2.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_DCC) += dcc.o
obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o
obj-$(CONFIG_QCOM_COMMON_LOG) += common_log.o obj-$(CONFIG_QCOM_COMMON_LOG) += common_log.o

View file

@ -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 * 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 * it under the terms of the GNU General Public License version 2 and
@ -18,6 +18,8 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/kmemleak.h> #include <linux/kmemleak.h>
#include <soc/qcom/memory_dump.h> #include <soc/qcom/memory_dump.h>
#include <soc/qcom/minidump.h>
#include <asm/sections.h>
#define MISC_DUMP_DATA_LEN 4096 #define MISC_DUMP_DATA_LEN 4096
#define PMIC_DUMP_DATA_LEN (64 * 1024) #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); misc_buf = kzalloc(MISC_DUMP_DATA_LEN, GFP_KERNEL);
if (!misc_buf) if (!misc_buf)
goto err0; goto err0;
strlcpy(misc_data->name, "KMISC", sizeof(misc_data->name));
misc_data->addr = virt_to_phys(misc_buf); misc_data->addr = virt_to_phys(misc_buf);
misc_data->len = MISC_DUMP_DATA_LEN; misc_data->len = MISC_DUMP_DATA_LEN;
dump_entry.id = MSM_DUMP_DATA_MISC; dump_entry.id = MSM_DUMP_DATA_MISC;
@ -70,6 +74,7 @@ static void register_pmic_dump(void)
if (!dump_addr) if (!dump_addr)
goto err0; goto err0;
strlcpy(dump_data->name, "KPMIC", sizeof(dump_data->name));
dump_data->addr = virt_to_phys(dump_addr); dump_data->addr = virt_to_phys(dump_addr);
dump_data->len = PMIC_DUMP_DATA_LEN; dump_data->len = PMIC_DUMP_DATA_LEN;
dump_entry.id = MSM_DUMP_DATA_PMIC; dump_entry.id = MSM_DUMP_DATA_PMIC;
@ -104,6 +109,8 @@ static void register_vsense_dump(void)
if (!dump_addr) if (!dump_addr)
goto err0; goto err0;
strlcpy(dump_data->name, "KVSENSE",
sizeof(dump_data->name));
dump_data->addr = virt_to_phys(dump_addr); dump_data->addr = virt_to_phys(dump_addr);
dump_data->len = VSENSE_DUMP_DATA_LEN; dump_data->len = VSENSE_DUMP_DATA_LEN;
dump_entry.id = MSM_DUMP_DATA_VSENSE; dump_entry.id = MSM_DUMP_DATA_VSENSE;
@ -136,6 +143,7 @@ void register_rpm_dump(void)
if (!dump_addr) if (!dump_addr)
goto err0; goto err0;
strlcpy(dump_data->name, "KRPM", sizeof(dump_data->name));
dump_data->addr = virt_to_phys(dump_addr); dump_data->addr = virt_to_phys(dump_addr);
dump_data->len = RPM_DUMP_DATA_LEN; dump_data->len = RPM_DUMP_DATA_LEN;
dump_entry.id = MSM_DUMP_DATA_RPM; 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) static int __init msm_common_log_init(void)
{ {
register_kernel_sections();
common_log_register_log_buf(); common_log_register_log_buf();
register_misc_dump(); register_misc_dump();
register_pmic_dump(); register_pmic_dump();

View file

@ -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 * 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 * 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->addr = dump_addr;
dump_data->len = size; dump_data->len = size;
scnprintf(dump_data->name, sizeof(dump_data->name),
"KCPUSS%X", id);
dump_entry.id = id; dump_entry.id = id;
dump_entry.addr = virt_to_phys(dump_data); dump_entry.addr = virt_to_phys(dump_data);
ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry);

View file

@ -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 * 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 * 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 */ /* Allocate memory for dcc reg dump */
drvdata->reg_buf = devm_kzalloc(dev, drvdata->reg_size, GFP_KERNEL); drvdata->reg_buf = devm_kzalloc(dev, drvdata->reg_size, GFP_KERNEL);
if (drvdata->reg_buf) { 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.addr = virt_to_phys(drvdata->reg_buf);
drvdata->reg_data.len = drvdata->reg_size; drvdata->reg_data.len = drvdata->reg_size;
reg_dump_entry.id = MSM_DUMP_DATA_DCC_REG; 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 */ /* Allocate memory for dcc sram dump */
drvdata->sram_buf = devm_kzalloc(dev, drvdata->ram_size, GFP_KERNEL); drvdata->sram_buf = devm_kzalloc(dev, drvdata->ram_size, GFP_KERNEL);
if (drvdata->sram_buf) { 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.addr = virt_to_phys(drvdata->sram_buf);
drvdata->sram_data.len = drvdata->ram_size; drvdata->sram_data.len = drvdata->ram_size;
sram_dump_entry.id = MSM_DUMP_DATA_DCC_SRAM; sram_dump_entry.id = MSM_DUMP_DATA_DCC_SRAM;

View file

@ -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 * 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 * it under the terms of the GNU General Public License version 2 and
@ -20,6 +20,7 @@
#include <linux/kmemleak.h> #include <linux/kmemleak.h>
#include <soc/qcom/memory_dump.h> #include <soc/qcom/memory_dump.h>
#include <soc/qcom/scm.h> #include <soc/qcom/scm.h>
#include <soc/qcom/minidump.h>
#define MSM_DUMP_TABLE_VERSION MSM_DUMP_MAKE_VERSION(2, 0) #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; 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, int msm_dump_data_register(enum msm_dump_table_ids id,
struct msm_dump_entry *entry) struct msm_dump_entry *entry)
{ {
@ -107,6 +131,10 @@ int msm_dump_data_register(enum msm_dump_table_ids id,
table->num_entries++; table->num_entries++;
dmac_flush_range(table, (void *)table + sizeof(struct msm_dump_table)); 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; return 0;
} }
EXPORT_SYMBOL(msm_dump_data_register); EXPORT_SYMBOL(msm_dump_data_register);

View file

@ -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 <linux/init.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/elf.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <soc/qcom/smem.h>
#include <soc/qcom/scm.h>
#include <soc/qcom/minidump.h>
#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)

View file

@ -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 * 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 * it under the terms of the GNU General Public License version 2 and
@ -29,6 +29,7 @@
#include <linux/wait.h> #include <linux/wait.h>
#include <soc/qcom/scm.h> #include <soc/qcom/scm.h>
#include <soc/qcom/memory_dump.h> #include <soc/qcom/memory_dump.h>
#include <soc/qcom/minidump.h>
#include <soc/qcom/watchdog.h> #include <soc/qcom/watchdog.h>
#define MODULE_NAME "msm_watchdog" #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->addr = virt_to_phys(dump_addr);
dump_data->len = wdog_dd->scandump_size; 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.id = MSM_DUMP_DATA_SCANDUMP;
dump_entry.addr = virt_to_phys(dump_data); dump_entry.addr = virt_to_phys(dump_data);
ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry); 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_data[cpu].addr = virt_to_phys(cpu_buf +
cpu * MAX_CPU_CTX_SIZE); cpu * MAX_CPU_CTX_SIZE);
cpu_data[cpu].len = 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.id = MSM_DUMP_DATA_CPU_CTX + cpu;
dump_entry.addr = virt_to_phys(&cpu_data[cpu]); dump_entry.addr = virt_to_phys(&cpu_data[cpu]);
ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS,
@ -820,6 +826,7 @@ static int msm_watchdog_probe(struct platform_device *pdev)
{ {
int ret; int ret;
struct msm_watchdog_data *wdog_dd; struct msm_watchdog_data *wdog_dd;
struct md_region md_entry;
if (!pdev->dev.of_node || !enable) if (!pdev->dev.of_node || !enable)
return -ENODEV; return -ENODEV;
@ -841,6 +848,15 @@ static int msm_watchdog_probe(struct platform_device *pdev)
goto err; goto err;
} }
init_watchdog_data(wdog_dd); 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; return 0;
err: err:
kzfree(wdog_dd); kzfree(wdog_dd);

View file

@ -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

View file

@ -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 * 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 * it under the terms of the GNU General Public License version 2 and
@ -28,6 +28,7 @@
#include <asm-generic/sizes.h> #include <asm-generic/sizes.h>
#include <linux/msm_rtb.h> #include <linux/msm_rtb.h>
#include <asm/timex.h> #include <asm/timex.h>
#include <soc/qcom/minidump.h>
#define SENTINEL_BYTE_1 0xFF #define SENTINEL_BYTE_1 0xFF
#define SENTINEL_BYTE_2 0xAA #define SENTINEL_BYTE_2 0xAA
@ -243,6 +244,7 @@ EXPORT_SYMBOL(uncached_logk);
static int msm_rtb_probe(struct platform_device *pdev) static int msm_rtb_probe(struct platform_device *pdev)
{ {
struct msm_rtb_platform_data *d = pdev->dev.platform_data; struct msm_rtb_platform_data *d = pdev->dev.platform_data;
struct md_region md_entry;
#if defined(CONFIG_QCOM_RTB_SEPARATE_CPUS) #if defined(CONFIG_QCOM_RTB_SEPARATE_CPUS)
unsigned int cpu; unsigned int cpu;
#endif #endif
@ -294,6 +296,12 @@ static int msm_rtb_probe(struct platform_device *pdev)
memset(msm_rtb.rtb, 0, msm_rtb.size); 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) #if defined(CONFIG_QCOM_RTB_SEPARATE_CPUS)
for_each_possible_cpu(cpu) { for_each_possible_cpu(cpu) {