diff options
Diffstat (limited to 'drivers/soc')
-rw-r--r-- | drivers/soc/qcom/Kconfig | 16 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 3 | ||||
-rw-r--r-- | drivers/soc/qcom/memory_dump.c | 95 | ||||
-rw-r--r-- | drivers/soc/qcom/memory_dump_v2.c | 193 |
4 files changed, 307 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 2578c21b47f8..eb7b7ee7b3c4 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -99,4 +99,20 @@ config QCOM_WATCHDOG_V2 deadlocks. It does not run during the bootup process, so it will not catch any early lockups. +config QCOM_MEMORY_DUMP + bool "Qualcomm Memory Dump Support" + help + This enables memory dump feature. It allows various client + subsystems to register respective dump regions. At the time + 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_MEMORY_DUMP_V2 + bool "QCOM Memory Dump V2 Support" + help + This enables memory dump feature. It allows various client + subsystems to register respective dump regions. At the time + of deadlocks or cpu hangs these dump regions are captured to + give a snapshot of the system at the time of the crash. + endif # ARCH_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 8306ee2f2664..ae2ad0d6929a 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -9,3 +9,6 @@ obj-$(CONFIG_QCOM_SCM_ERRATA) += scm-errata.o obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o 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_WATCHDOG_V2) += watchdog_v2.o diff --git a/drivers/soc/qcom/memory_dump.c b/drivers/soc/qcom/memory_dump.c new file mode 100644 index 000000000000..5390994926be --- /dev/null +++ b/drivers/soc/qcom/memory_dump.c @@ -0,0 +1,95 @@ +/* Copyright (c) 2012-2014, 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. + */ +#include <asm/cacheflush.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/export.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <soc/qcom/memory_dump.h> + +#define MSM_DUMP_TABLE_VERSION MSM_DUMP_MAKE_VERSION(1, 0) + +struct msm_dump_table { + u32 version; + u32 num_entries; + struct msm_client_dump client_entries[MAX_NUM_CLIENTS]; +}; + +struct msm_memory_dump { + unsigned long dump_table_phys; + struct msm_dump_table *dump_table_ptr; +}; + +static struct msm_memory_dump mem_dump_data; + +uint32_t msm_dump_table_version(void) +{ + return MSM_DUMP_TABLE_VERSION; +} +EXPORT_SYMBOL(msm_dump_table_version); + +int msm_dump_tbl_register(struct msm_client_dump *client_entry) +{ + struct msm_client_dump *entry; + struct msm_dump_table *table = mem_dump_data.dump_table_ptr; + + if (!table || table->num_entries >= MAX_NUM_CLIENTS) + return -EINVAL; + entry = &table->client_entries[table->num_entries]; + entry->id = client_entry->id; + entry->start_addr = client_entry->start_addr; + entry->end_addr = client_entry->end_addr; + table->num_entries++; + /* flush cache */ + dmac_flush_range(table, (void *)table + sizeof(struct msm_dump_table)); + return 0; +} +EXPORT_SYMBOL(msm_dump_tbl_register); + +static int __init init_memory_dump(void) +{ + struct msm_dump_table *table; + struct device_node *np; + static void __iomem *imem_base; + + np = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-mem_dump_table"); + if (!np) { + pr_err("unable to find DT imem dump table node\n"); + return -ENODEV; + } + imem_base = of_iomap(np, 0); + if (!imem_base) { + pr_err("unable to map imem dump table offset\n"); + return -ENOMEM; + } + + mem_dump_data.dump_table_ptr = kzalloc(sizeof(struct msm_dump_table), + GFP_KERNEL); + if (!mem_dump_data.dump_table_ptr) { + iounmap(imem_base); + printk(KERN_ERR "unable to allocate memory for dump table\n"); + return -ENOMEM; + } + table = mem_dump_data.dump_table_ptr; + table->version = MSM_DUMP_TABLE_VERSION; + mem_dump_data.dump_table_phys = virt_to_phys(table); + writel_relaxed(mem_dump_data.dump_table_phys, imem_base); + printk(KERN_INFO "MSM Memory Dump table set up\n"); + iounmap(imem_base); + + return 0; +} + +early_initcall(init_memory_dump); + diff --git a/drivers/soc/qcom/memory_dump_v2.c b/drivers/soc/qcom/memory_dump_v2.c new file mode 100644 index 000000000000..861e6650448f --- /dev/null +++ b/drivers/soc/qcom/memory_dump_v2.c @@ -0,0 +1,193 @@ +/* Copyright (c) 2014, 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. + */ +#include <asm/cacheflush.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <soc/qcom/memory_dump.h> +#include <soc/qcom/scm.h> + +#define MSM_DUMP_TABLE_VERSION MSM_DUMP_MAKE_VERSION(2, 0) + +#define SCM_CMD_DEBUG_LAR_UNLOCK 0x00001003 + +struct msm_dump_table { + uint32_t version; + uint32_t num_entries; + struct msm_dump_entry entries[MAX_NUM_ENTRIES]; +}; + +struct msm_memory_dump { + uint64_t table_phys; + struct msm_dump_table *table; +}; + +static struct msm_memory_dump memdump; + +uint32_t msm_dump_table_version(void) +{ + return MSM_DUMP_TABLE_VERSION; +} +EXPORT_SYMBOL(msm_dump_table_version); + +static int msm_dump_table_register(struct msm_dump_entry *entry) +{ + struct msm_dump_entry *e; + struct msm_dump_table *table = memdump.table; + + if (!table || table->num_entries >= MAX_NUM_ENTRIES) + return -EINVAL; + + e = &table->entries[table->num_entries]; + e->id = entry->id; + e->type = MSM_DUMP_TYPE_TABLE; + e->addr = entry->addr; + table->num_entries++; + + dmac_flush_range(table, (void *)table + sizeof(struct msm_dump_table)); + return 0; +} + +static struct msm_dump_table *msm_dump_get_table(enum msm_dump_table_ids id) +{ + struct msm_dump_table *table = memdump.table; + int i; + + if (!table) { + pr_err("mem dump base table does not exist\n"); + return ERR_PTR(-EINVAL); + } + + for (i = 0; i < MAX_NUM_ENTRIES; i++) { + if (table->entries[i].id == id) + break; + } + if (i == MAX_NUM_ENTRIES || !table->entries[i].addr) { + pr_err("mem dump base table entry %d invalid\n", id); + return ERR_PTR(-EINVAL); + } + + /* Get the apps table pointer */ + table = phys_to_virt(table->entries[i].addr); + + return table; +} + +int msm_dump_data_register(enum msm_dump_table_ids id, + struct msm_dump_entry *entry) +{ + struct msm_dump_entry *e; + struct msm_dump_table *table; + + table = msm_dump_get_table(id); + if (IS_ERR(table)) + return PTR_ERR(table); + + if (!table || table->num_entries >= MAX_NUM_ENTRIES) + return -EINVAL; + + e = &table->entries[table->num_entries]; + e->id = entry->id; + e->type = MSM_DUMP_TYPE_DATA; + e->addr = entry->addr; + table->num_entries++; + + dmac_flush_range(table, (void *)table + sizeof(struct msm_dump_table)); + return 0; +} +EXPORT_SYMBOL(msm_dump_data_register); + +static int __init init_memory_dump(void) +{ + struct msm_dump_table *table; + struct msm_dump_entry entry; + struct device_node *np; + void __iomem *imem_base; + int ret; + + np = of_find_compatible_node(NULL, NULL, + "qcom,msm-imem-mem_dump_table"); + if (!np) { + pr_err("mem dump base table DT node does not exist\n"); + return -ENODEV; + } + + imem_base = of_iomap(np, 0); + if (!imem_base) { + pr_err("mem dump base table imem offset mapping failed\n"); + return -ENOMEM; + } + + memdump.table = kzalloc(sizeof(struct msm_dump_table), GFP_KERNEL); + if (!memdump.table) { + pr_err("mem dump base table allocation failed\n"); + ret = -ENOMEM; + goto err0; + } + memdump.table->version = MSM_DUMP_TABLE_VERSION; + memdump.table_phys = virt_to_phys(memdump.table); + writel_relaxed(memdump.table_phys, imem_base); + /* Ensure write to imem_base is complete before unmapping */ + mb(); + pr_info("MSM Memory Dump base table set up\n"); + + iounmap(imem_base); + + table = kzalloc(sizeof(struct msm_dump_table), GFP_KERNEL); + if (!table) { + pr_err("mem dump apps data table allocation failed\n"); + ret = -ENOMEM; + goto err1; + } + table->version = MSM_DUMP_TABLE_VERSION; + + entry.id = MSM_DUMP_TABLE_APPS; + entry.addr = virt_to_phys(table); + ret = msm_dump_table_register(&entry); + if (ret) { + pr_info("mem dump apps data table register failed\n"); + goto err2; + } + pr_info("MSM Memory Dump apps data table set up\n"); + + return 0; +err2: + kfree(table); +err1: + kfree(memdump.table); + return ret; +err0: + iounmap(imem_base); + return ret; +} +early_initcall(init_memory_dump); + +#ifdef CONFIG_MSM_DEBUG_LAR_UNLOCK +static int __init init_debug_lar_unlock(void) +{ + int ret; + uint32_t argument = 0; + + ret = scm_call(SCM_SVC_TZ, SCM_CMD_DEBUG_LAR_UNLOCK, &argument, + sizeof(argument), NULL, 0); + if (ret) + pr_info("Core Debug Lock unlock failed, ret: %d\n", ret); + else + pr_info("Core Debug Lock unlocked\n"); + return ret; +} +early_initcall(init_debug_lar_unlock); +#endif |