Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 55e13622 authored by Rama Aparna Mallavarapu's avatar Rama Aparna Mallavarapu
Browse files

soc: qcom: Add snapshot of the memory dump v2 driver



This is a snapshot of the memory dump_v2 driver as of msm-4.9
commit '04470f15eff0effa10b3d69d13d3bc061e951bd6'.("Add support
for generic memory dump entry allocator") Currently, each dump
table entry is being registered in several places in the kernel.
Add centralized support in memory dump driver to parse the device
tree to register and allocate dump table entries. We rely on CMA
memory pool to allocate memory for the dump entries. Clients can
either use the generic approach or configure the dump table entries
independently using the existing framework.

Change-Id: I6a2db1e4c86952db07dc39c8fbbbdc88768db999
Signed-off-by: default avatarRama Aparna Mallavarapu <aparnam@codeaurora.org>
parent a82af004
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
Qualcomm Technologies Inc. memory dump driver

QTI memory dump driver allows various client subsystems to register and
allocate 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.

Required properties:

-compatible: "qcom,mem-dump"
-memory-region: phandle to the CMA region. The size of the CMA region
		should be greater than sum of size of all child nodes
		to account for padding.

If any child nodes exist the following property are required:

-qcom,dump-size: The size of memory that needs to be allocated for the
		 particular node.
-qcom,dump-id: The ID within the data dump table where this entry needs
	       to be added.

Example:

	mem_dump {
		compatible = "qcom,mem-dump";
		memory-region = <&dump_mem>;

		rpmh_dump {
			qcom,dump-size = <0x2000000>;
			qcom,dump-id = <0xEC>;
		};
	};
+8 −0
Original line number Diff line number Diff line
@@ -92,6 +92,14 @@ config QCOM_SCM
	 world(EL2 and EL3) by using smc call.
	 SCM driver provides the support for atomic scm calls also.

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.

config QCOM_SMEM_STATE
	bool

+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o
obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o
obj-$(CONFIG_MSM_GLADIATOR_HANG_DETECT) += gladiator_hang_detect.o
obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o
obj-$(CONFIG_QCOM_MEMORY_DUMP_V2) += memory_dump_v2.o
obj-$(CONFIG_ICNSS) += icnss.o wlan_firmware_service_v01.o icnss_utils.o
obj-$(CONFIG_MSM_SERVICE_NOTIFIER) += service-notifier.o
obj-$(CONFIG_MSM_SERVICE_LOCATOR) += service-locator.o
+281 −0
Original line number Diff line number Diff line
/* 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
 * 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>
#include <linux/of_device.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>

#define MSM_DUMP_TABLE_VERSION		MSM_DUMP_MAKE_VERSION(2, 0)

#define SCM_CMD_DEBUG_LAR_UNLOCK	0x4

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) {
		ret = -ENOMEM;
		goto err0;
	}
	memdump.table->version = MSM_DUMP_TABLE_VERSION;
	memdump.table_phys = virt_to_phys(memdump.table);
	memcpy_toio(imem_base, &memdump.table_phys, sizeof(memdump.table_phys));
	/* 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) {
		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;
	struct scm_desc desc = {0};

	if (!is_scm_armv8())
		ret = scm_call(SCM_SVC_TZ, SCM_CMD_DEBUG_LAR_UNLOCK, &argument,
			       sizeof(argument), NULL, 0);
	else
		ret = scm_call2(SCM_SIP_FNID(SCM_SVC_TZ,
				SCM_CMD_DEBUG_LAR_UNLOCK), &desc);
	if (ret)
		pr_err("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

static int mem_dump_probe(struct platform_device *pdev)
{
	struct device_node *child_node;
	const struct device_node *node = pdev->dev.of_node;
	static dma_addr_t dump_addr;
	static void *dump_vaddr;
	struct msm_dump_data *dump_data;
	struct msm_dump_entry dump_entry;
	int ret;
	u32 size, id;

	for_each_available_child_of_node(node, child_node) {
		ret = of_property_read_u32(child_node, "qcom,dump-size", &size);
		if (ret) {
			dev_err(&pdev->dev, "Unable to find size for %s\n",
					child_node->name);
			continue;
		}

		ret = of_property_read_u32(child_node, "qcom,dump-id", &id);
		if (ret) {
			dev_err(&pdev->dev, "Unable to find id for %s\n",
					child_node->name);
			continue;
		}

		dump_vaddr = (void *) dma_alloc_coherent(&pdev->dev, size,
						&dump_addr, GFP_KERNEL);

		if (!dump_vaddr) {
			dev_err(&pdev->dev, "Couldn't get memory for dumping\n");
			continue;
		}

		memset(dump_vaddr, 0x0, size);

		dump_data = devm_kzalloc(&pdev->dev,
				sizeof(struct msm_dump_data), GFP_KERNEL);
		if (!dump_data) {
			dma_free_coherent(&pdev->dev, size, dump_vaddr,
					dump_addr);
			continue;
		}

		dump_data->addr = dump_addr;
		dump_data->len = size;
		dump_entry.id = id;
		dump_entry.addr = virt_to_phys(dump_data);
		ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS, &dump_entry);
		if (ret) {
			dev_err(&pdev->dev, "Data dump setup failed, id = %d\n",
				id);
			dma_free_coherent(&pdev->dev, size, dump_vaddr,
					dump_addr);
			devm_kfree(&pdev->dev, dump_data);
		}
	}
	return 0;
}

static const struct of_device_id mem_dump_match_table[] = {
	{.compatible = "qcom,mem-dump",},
	{}
};

static struct platform_driver mem_dump_driver = {
	.probe = mem_dump_probe,
	.driver = {
		.name = "msm_mem_dump",
		.owner = THIS_MODULE,
		.of_match_table = mem_dump_match_table,
	},
};

static int __init mem_dump_init(void)
{
	return platform_driver_register(&mem_dump_driver);
}

pure_initcall(mem_dump_init);
+132 −0
Original line number Diff line number Diff line
/* Copyright (c) 2012, 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
 * 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 __MSM_MEMORY_DUMP_H
#define __MSM_MEMORY_DUMP_H

#include <linux/types.h>

enum dump_client_type {
	MSM_CPU_CTXT = 0,
	MSM_L1_CACHE,
	MSM_L2_CACHE,
	MSM_OCMEM,
	MSM_TMC_ETFETB,
	MSM_ETM0_REG,
	MSM_ETM1_REG,
	MSM_ETM2_REG,
	MSM_ETM3_REG,
	MSM_TMC0_REG, /* TMC_ETR */
	MSM_TMC1_REG, /* TMC_ETF */
	MSM_LOG_BUF,
	MSM_LOG_BUF_FIRST_IDX,
	MAX_NUM_CLIENTS,
};

struct msm_client_dump {
	enum dump_client_type id;
	unsigned long start_addr;
	unsigned long end_addr;
};

#ifdef CONFIG_QCOM_MEMORY_DUMP
extern int msm_dump_tbl_register(struct msm_client_dump *client_entry);
#else
static inline int msm_dump_tbl_register(struct msm_client_dump *entry)
{
	return -EIO;
}
#endif


#if defined(CONFIG_QCOM_MEMORY_DUMP) || defined(CONFIG_QCOM_MEMORY_DUMP_V2)
extern uint32_t msm_dump_table_version(void);
#else
static inline uint32_t msm_dump_table_version(void)
{
	return 0;
}
#endif

#define MSM_DUMP_MAKE_VERSION(ma, mi)	((ma << 20) | mi)
#define MSM_DUMP_MAJOR(val)		(val >> 20)
#define MSM_DUMP_MINOR(val)		(val & 0xFFFFF)


#define MAX_NUM_ENTRIES		0x150

enum msm_dump_data_ids {
	MSM_DUMP_DATA_CPU_CTX = 0x00,
	MSM_DUMP_DATA_L1_INST_CACHE = 0x60,
	MSM_DUMP_DATA_L1_DATA_CACHE = 0x80,
	MSM_DUMP_DATA_ETM_REG = 0xA0,
	MSM_DUMP_DATA_L2_CACHE = 0xC0,
	MSM_DUMP_DATA_L3_CACHE = 0xD0,
	MSM_DUMP_DATA_OCMEM = 0xE0,
	MSM_DUMP_DATA_CNSS_WLAN = 0xE1,
	MSM_DUMP_DATA_WIGIG = 0xE2,
	MSM_DUMP_DATA_PMIC = 0xE4,
	MSM_DUMP_DATA_DBGUI_REG = 0xE5,
	MSM_DUMP_DATA_DCC_REG = 0xE6,
	MSM_DUMP_DATA_DCC_SRAM = 0xE7,
	MSM_DUMP_DATA_MISC = 0xE8,
	MSM_DUMP_DATA_VSENSE = 0xE9,
	MSM_DUMP_DATA_RPM = 0xEA,
	MSM_DUMP_DATA_SCANDUMP = 0xEB,
	MSM_DUMP_DATA_RPMH = 0xEC,
	MSM_DUMP_DATA_TMC_ETF = 0xF0,
	MSM_DUMP_DATA_TMC_REG = 0x100,
	MSM_DUMP_DATA_LOG_BUF = 0x110,
	MSM_DUMP_DATA_LOG_BUF_FIRST_IDX = 0x111,
	MSM_DUMP_DATA_SCANDUMP_PER_CPU = 0x130,
	MSM_DUMP_DATA_LLCC_PER_INSTANCE = 0x140,
	MSM_DUMP_DATA_MAX = MAX_NUM_ENTRIES,
};

enum msm_dump_table_ids {
	MSM_DUMP_TABLE_APPS,
	MSM_DUMP_TABLE_MAX = MAX_NUM_ENTRIES,
};

enum msm_dump_type {
	MSM_DUMP_TYPE_DATA,
	MSM_DUMP_TYPE_TABLE,
};

struct msm_dump_data {
	uint32_t version;
	uint32_t magic;
	char name[32];
	uint64_t addr;
	uint64_t len;
	uint32_t reserved;
};

struct msm_dump_entry {
	uint32_t id;
	char name[32];
	uint32_t type;
	uint64_t addr;
};

#ifdef CONFIG_QCOM_MEMORY_DUMP_V2
extern int msm_dump_data_register(enum msm_dump_table_ids id,
				  struct msm_dump_entry *entry);
#else
static inline int msm_dump_data_register(enum msm_dump_table_ids id,
					 struct msm_dump_entry *entry)
{
	return -EINVAL;
}
#endif

#endif