summaryrefslogtreecommitdiff
path: root/drivers/soc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc')
-rw-r--r--drivers/soc/qcom/Kconfig16
-rw-r--r--drivers/soc/qcom/Makefile3
-rw-r--r--drivers/soc/qcom/memory_dump.c95
-rw-r--r--drivers/soc/qcom/memory_dump_v2.c193
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