diff mbox series

[v2,2/4] drm/msm/dpu: add support to dump dpu registers

Message ID 20201030010101.4345-3-abhinavk@codeaurora.org
State New, archived
Headers show
Series [v2,1/4] drm: allow drm_atomic_print_state() to accept any drm_printer | expand

Commit Message

Abhinav Kumar Oct. 30, 2020, 1 a.m. UTC
Add the dpu_dbg module which adds supports to dump dpu registers
which can be used in case of error conditions.

changes in v2: Fix kbot errors

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Abhinav Kumar <abhinavk@codeaurora.org>
---
 drivers/gpu/drm/msm/Makefile                  |   2 +
 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c       | 316 ++++++++++++++++++
 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h       | 273 +++++++++++++++
 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c  | 314 +++++++++++++++++
 .../gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h    |   4 +-
 drivers/gpu/drm/msm/msm_drv.c                 |   6 +-
 6 files changed, 913 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
 create mode 100644 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
 create mode 100644 drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c
diff mbox series

Patch

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 340682cd0f32..96bd1398edac 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -54,6 +54,8 @@  msm-y := \
 	disp/dpu1/dpu_core_irq.o \
 	disp/dpu1/dpu_core_perf.o \
 	disp/dpu1/dpu_crtc.o \
+	disp/dpu1/dpu_dbg.o \
+	disp/dpu1/dpu_dbg_util.o \
 	disp/dpu1/dpu_encoder.o \
 	disp/dpu1/dpu_encoder_phys_cmd.o \
 	disp/dpu1/dpu_encoder_phys_vid.o \
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
new file mode 100644
index 000000000000..f83682668e87
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.c
@@ -0,0 +1,316 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2009-2020, The Linux Foundation. All rights reserved.
+ */
+
+#define pr_fmt(fmt)	"[drm:%s:%d] " fmt, __func__, __LINE__
+
+#include "dpu_dbg.h"
+#include "dpu_hw_catalog.h"
+
+/* global dpu debug base structure */
+static struct dpu_dbg_base dpu_dbg;
+
+
+#ifdef CONFIG_DEV_COREDUMP
+static ssize_t dpu_devcoredump_read(char *buffer, loff_t offset,
+		size_t count, void *data, size_t datalen)
+{
+	struct drm_print_iterator iter;
+	struct drm_printer p;
+
+	iter.data = buffer;
+	iter.offset = 0;
+	iter.start = offset;
+	iter.remain = count;
+
+	p = drm_coredump_printer(&iter);
+
+	drm_printf(&p, "---\n");
+
+	drm_printf(&p, "module: " KBUILD_MODNAME "\n");
+	drm_printf(&p, "dpu devcoredump\n");
+	drm_printf(&p, "timestamp %lld\n", ktime_to_ns(dpu_dbg.timestamp));
+
+	dpu_dbg.dpu_dbg_printer = &p;
+	dpu_dbg.enable_reg_dump = DPU_DBG_DUMP_IN_COREDUMP;
+
+	drm_printf(&p, "===================dpu regs================\n");
+
+	_dpu_dump_array(&dpu_dbg, dpu_dbg.req_dump_blks,
+		ARRAY_SIZE(dpu_dbg.req_dump_blks),
+		dpu_dbg.work_panic, "evtlog_workitem",
+		dpu_dbg.dump_all);
+
+	drm_printf(&p, "===================dpu drm state================\n");
+
+	if (dpu_dbg.atomic_state)
+		drm_atomic_print_new_state(dpu_dbg.atomic_state,
+				&p);
+
+	return count - iter.remain;
+}
+
+static void dpu_devcoredump_free(void *data)
+{
+	if (dpu_dbg.atomic_state) {
+		drm_atomic_state_put(dpu_dbg.atomic_state);
+		dpu_dbg.atomic_state = NULL;
+	}
+	dpu_dbg.coredump_pending = false;
+}
+
+static void dpu_devcoredump_capture_state(void)
+{
+	struct drm_device *ddev;
+	struct drm_modeset_acquire_ctx ctx;
+
+	dpu_dbg.timestamp = ktime_get();
+
+	ddev = dpu_dbg.drm_dev;
+
+	drm_modeset_acquire_init(&ctx, 0);
+
+	while (drm_modeset_lock_all_ctx(ddev, &ctx) != 0)
+		drm_modeset_backoff(&ctx);
+
+	dpu_dbg.atomic_state = drm_atomic_helper_duplicate_state(ddev,
+			&ctx);
+	drm_modeset_drop_locks(&ctx);
+	drm_modeset_acquire_fini(&ctx);
+}
+#else
+static void dpu_devcoredump_capture_state(void)
+{
+}
+#endif /* CONFIG_DEV_COREDUMP */
+
+/**
+ * _dpu_dump_work - deferred dump work function
+ * @work: work structure
+ */
+static void _dpu_dump_work(struct kthread_work *work)
+{
+	/* reset the enable_reg_dump to default before every dump */
+	dpu_dbg.enable_reg_dump = DEFAULT_REGDUMP;
+
+	_dpu_dump_array(&dpu_dbg, dpu_dbg.req_dump_blks,
+		ARRAY_SIZE(dpu_dbg.req_dump_blks),
+		dpu_dbg.work_panic, "evtlog_workitem",
+		dpu_dbg.dump_all);
+
+	dpu_devcoredump_capture_state();
+
+#ifdef CONFIG_DEV_COREDUMP
+	if (dpu_dbg.enable_reg_dump & DPU_DBG_DUMP_IN_MEM) {
+		dev_coredumpm(dpu_dbg.dev, THIS_MODULE, &dpu_dbg, 0, GFP_KERNEL,
+				dpu_devcoredump_read, dpu_devcoredump_free);
+		dpu_dbg.coredump_pending = true;
+	}
+#endif
+}
+
+void dpu_dbg_dump(enum dpu_dbg_dump_context dump_mode, const char *name, ...)
+{
+	int i, index = 0;
+	bool do_panic = false;
+	bool dump_all = false;
+	va_list args;
+	char *blk_name = NULL;
+	struct dpu_dbg_reg_base *blk_base = NULL;
+	struct dpu_dbg_reg_base **blk_arr;
+	u32 blk_len;
+
+	/*
+	 * if there is a coredump pending return immediately till dump
+	 * if read by userspace or timeout happens
+	 */
+	if (((dpu_dbg.enable_reg_dump == DPU_DBG_DUMP_IN_MEM) ||
+		 (dpu_dbg.enable_reg_dump == DPU_DBG_DUMP_IN_COREDUMP)) &&
+		dpu_dbg.coredump_pending) {
+		pr_debug("coredump is pending read\n");
+		return;
+	}
+
+	blk_arr = &dpu_dbg.req_dump_blks[0];
+	blk_len = ARRAY_SIZE(dpu_dbg.req_dump_blks);
+
+	memset(dpu_dbg.req_dump_blks, 0,
+			sizeof(dpu_dbg.req_dump_blks));
+	dpu_dbg.dump_all = false;
+	dpu_dbg.dump_mode = dump_mode;
+
+	va_start(args, name);
+	i = 0;
+	while ((blk_name = va_arg(args, char*))) {
+
+		if (IS_ERR_OR_NULL(blk_name))
+			break;
+
+		blk_base = _dpu_dump_get_blk_addr(&dpu_dbg, blk_name);
+		if (blk_base) {
+			if (index < blk_len) {
+				blk_arr[index] = blk_base;
+				index++;
+			} else {
+				pr_err("insufficient space to dump %s\n",
+						blk_name);
+			}
+		}
+
+		if (!strcmp(blk_name, "all"))
+			dump_all = true;
+
+		if (!strcmp(blk_name, "panic"))
+			do_panic = true;
+
+	}
+	va_end(args);
+
+	dpu_dbg.work_panic = do_panic;
+	dpu_dbg.dump_all = dump_all;
+
+	kthread_queue_work(dpu_dbg.dump_worker,
+			&dpu_dbg.dump_work);
+
+}
+
+int dpu_dbg_init(struct device *dev)
+{
+	if (!dev) {
+		pr_err("invalid params\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&dpu_dbg.mutex);
+	INIT_LIST_HEAD(&dpu_dbg.reg_base_list);
+	dpu_dbg.dev = dev;
+
+	dpu_dbg.work_panic = false;
+	dpu_dbg.enable_reg_dump = DEFAULT_REGDUMP;
+
+	dpu_dbg.dump_worker = kthread_create_worker(0, "%s", "dpu_dbg");
+	if (IS_ERR(dpu_dbg.dump_worker))
+		dev_err(dev, "failed to create dpu dbg task\n");
+
+	kthread_init_work(&dpu_dbg.dump_work, _dpu_dump_work);
+
+	pr_info("dump:%d\n", dpu_dbg.enable_reg_dump);
+
+	return 0;
+}
+
+void dpu_dbg_register_drm_dev(struct drm_device *ddev)
+{
+	dpu_dbg.drm_dev = ddev;
+}
+
+static void dpu_dbg_reg_base_destroy(void)
+{
+	struct dpu_dbg_reg_range *range_node, *range_tmp;
+	struct dpu_dbg_reg_base *blk_base, *blk_tmp;
+	struct dpu_dbg_base *dbg_base = &dpu_dbg;
+
+	/* if the dbg init failed or was never called */
+	if (!dbg_base || !dpu_dbg.dev)
+		return;
+
+	list_for_each_entry_safe(blk_base, blk_tmp, &dbg_base->reg_base_list,
+							reg_base_head) {
+		list_for_each_entry_safe(range_node, range_tmp,
+				&blk_base->sub_range_list, head) {
+			list_del(&range_node->head);
+			kfree(range_node);
+		}
+		list_del(&blk_base->reg_base_head);
+		kfree(blk_base);
+	}
+}
+
+/**
+ * dpu_dbg_destroy - destroy dpu debug facilities
+ */
+void dpu_dbg_destroy(void)
+{
+	if (dpu_dbg.dump_worker)
+		kthread_destroy_worker(dpu_dbg.dump_worker);
+	dpu_dbg_reg_base_destroy();
+	mutex_destroy(&dpu_dbg.mutex);
+}
+
+int dpu_dbg_reg_register_base(const char *name, void __iomem *base,
+		size_t max_offset)
+{
+	struct dpu_dbg_base *dbg_base = &dpu_dbg;
+	struct dpu_dbg_reg_base *reg_base;
+
+	if (!name || !strlen(name)) {
+		pr_err("no debug name provided\n");
+		return -EINVAL;
+	}
+
+	reg_base = kzalloc(sizeof(*reg_base), GFP_KERNEL);
+	if (!reg_base)
+		return -ENOMEM;
+
+	strlcpy(reg_base->name, name, sizeof(reg_base->name));
+	reg_base->base = base;
+	reg_base->max_offset = max_offset;
+	reg_base->off = 0;
+	reg_base->cnt = DEFAULT_BASE_REG_CNT;
+	reg_base->reg_dump = NULL;
+
+	/* Initialize list to make sure check for null list will be valid */
+	INIT_LIST_HEAD(&reg_base->sub_range_list);
+
+	pr_debug("%s base: %pK max_offset 0x%zX\n", reg_base->name,
+			reg_base->base, reg_base->max_offset);
+
+	list_add(&reg_base->reg_base_head, &dbg_base->reg_base_list);
+
+	return 0;
+}
+
+void dpu_dbg_reg_register_dump_range(const char *base_name,
+		const char *range_name, u32 offset_start, u32 offset_end,
+		uint32_t xin_id)
+{
+	struct dpu_dbg_reg_base *reg_base;
+	struct dpu_dbg_reg_range *range;
+
+	reg_base = _dpu_dump_get_blk_addr(&dpu_dbg, base_name);
+	if (!reg_base) {
+		pr_err("error: for range %s unable to locate base %s\n",
+				range_name, base_name);
+		return;
+	}
+
+	if (!range_name || strlen(range_name) == 0) {
+		pr_err("%pS: bad range name, base_name %s, offset_start 0x%X, end 0x%X\n",
+				__builtin_return_address(0), base_name,
+				offset_start, offset_end);
+		return;
+	}
+
+	if (offset_end - offset_start < REG_DUMP_ALIGN ||
+			offset_start > offset_end) {
+		pr_err("%pS: bad range, base_name %s, range_name %s, offset_start 0x%X, end 0x%X\n",
+				__builtin_return_address(0), base_name,
+				range_name, offset_start, offset_end);
+		return;
+	}
+
+	range = kzalloc(sizeof(*range), GFP_KERNEL);
+	if (!range)
+		return;
+
+	strlcpy(range->range_name, range_name, sizeof(range->range_name));
+	range->offset.start = offset_start;
+	range->offset.end = offset_end;
+	range->xin_id = xin_id;
+	list_add_tail(&range->head, &reg_base->sub_range_list);
+
+	pr_debug("base %s, range %s, start 0x%X, end 0x%X\n",
+			base_name, range->range_name,
+			range->offset.start, range->offset.end);
+}
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
new file mode 100644
index 000000000000..2cea13288cef
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg.h
@@ -0,0 +1,273 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef DPU_DBG_H_
+#define DPU_DBG_H_
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_device.h>
+#include "../../../drm_crtc_internal.h"
+#include <drm/drm_print.h>
+#include <drm/drm_atomic.h>
+#include <linux/debugfs.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/ktime.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/dma-buf.h>
+#include <linux/slab.h>
+#include <linux/list_sort.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/kthread.h>
+#include <linux/devcoredump.h>
+#include <stdarg.h>
+
+#define DPU_DBG_DUMP_DATA_LIMITER (NULL)
+
+enum dpu_dbg_dump_flag {
+	DPU_DBG_DUMP_IN_LOG = BIT(0),
+	DPU_DBG_DUMP_IN_MEM = BIT(1),
+	DPU_DBG_DUMP_IN_COREDUMP = BIT(2),
+};
+
+enum dpu_dbg_dump_context {
+	DPU_DBG_DUMP_PROC_CTX,
+	DPU_DBG_DUMP_CLK_ENABLED_CTX,
+};
+
+#define DPU_DBG_BASE_MAX		10
+
+#define DEFAULT_PANIC		0
+#define DEFAULT_REGDUMP		DPU_DBG_DUMP_IN_MEM
+#define DEFAULT_BASE_REG_CNT	DEFAULT_MDSS_HW_BLOCK_SIZE
+#define ROW_BYTES		16
+#define RANGE_NAME_LEN		40
+#define REG_BASE_NAME_LEN	80
+
+/* print debug ranges in groups of 4 u32s */
+#define REG_DUMP_ALIGN		16
+
+/**
+ * struct dpu_dbg_reg_offset - tracking for start and end of region
+ * @start: start offset
+ * @start: end offset
+ */
+struct dpu_dbg_reg_offset {
+	u32 start;
+	u32 end;
+};
+
+/**
+ * struct dpu_dbg_reg_range - register dumping named sub-range
+ * @head: head of this node
+ * @reg_dump: address for the mem dump
+ * @range_name: name of this range
+ * @offset: offsets for range to dump
+ * @xin_id: client xin id
+ */
+struct dpu_dbg_reg_range {
+	struct list_head head;
+	u32 *reg_dump;
+	char range_name[RANGE_NAME_LEN];
+	struct dpu_dbg_reg_offset offset;
+	uint32_t xin_id;
+};
+
+/**
+ * struct dpu_dbg_reg_base - register region base.
+ *	may sub-ranges: sub-ranges are used for dumping
+ *	or may not have sub-ranges: dumping is base -> max_offset
+ * @reg_base_head: head of this node
+ * @sub_range_list: head to the list with dump ranges
+ * @name: register base name
+ * @base: base pointer
+ * @off: cached offset of region for manual register dumping
+ * @cnt: cached range of region for manual register dumping
+ * @max_offset: length of region
+ * @buf: buffer used for manual register dumping
+ * @buf_len:  buffer length used for manual register dumping
+ * @reg_dump: address for the mem dump if no ranges used
+ * @cb: callback for external dump function, null if not defined
+ * @cb_ptr: private pointer to callback function
+ */
+struct dpu_dbg_reg_base {
+	struct list_head reg_base_head;
+	struct list_head sub_range_list;
+	char name[REG_BASE_NAME_LEN];
+	void __iomem *base;
+	size_t off;
+	size_t cnt;
+	size_t max_offset;
+	char *buf;
+	size_t buf_len;
+	u32 *reg_dump;
+	void (*cb)(void *ptr);
+	void *cb_ptr;
+};
+
+/**
+ * struct dpu_dbg_base - global sde debug base structure
+ * @evtlog: event log instance
+ * @reg_base_list: list of register dumping regions
+ * @dev: device pointer
+ * @drm_dev: drm device pointer
+ * @mutex: mutex to serialize access to serialze dumps, debugfs access
+ * @req_dump_blks: list of blocks requested for dumping
+ * @work_panic: panic after dump if internal user passed "panic" special region
+ * @enable_reg_dump: whether to dump registers into memory, kernel log, or both
+ * @dump_all: dump all entries in register dump
+ * @coredump_pending: coredump is pending read from userspace
+ * @atomic_state: atomic state duplicated at the time of the error
+ * @dump_worker: kworker thread which runs the dump work
+ * @dump_work: kwork which dumps the registers and drm state
+ * @timestamp: timestamp at which the coredump was captured
+ * @dpu_dbg_printer: drm printer handle used to take drm snapshot
+ * @dump_mode: decides whether the data is dumped in memory or logs
+ */
+struct dpu_dbg_base {
+	struct list_head reg_base_list;
+	struct device *dev;
+	struct drm_device *drm_dev;
+	struct mutex mutex;
+
+	struct dpu_dbg_reg_base *req_dump_blks[DPU_DBG_BASE_MAX];
+
+	bool work_panic;
+	u32 enable_reg_dump;
+
+	bool dump_all;
+	bool coredump_pending;
+
+	struct drm_atomic_state *atomic_state;
+
+	struct kthread_worker *dump_worker;
+	struct kthread_work dump_work;
+	ktime_t timestamp;
+
+	struct drm_printer *dpu_dbg_printer;
+
+	enum dpu_dbg_dump_context dump_mode;
+};
+
+struct dpu_dbg_power_ctrl {
+	void *handle;
+	void *client;
+	int (*enable_fn)(void *handle, void *client, bool enable);
+};
+
+
+/**
+ * DPU_DBG_DUMP - trigger dumping of all dpu_dbg facilities
+ * @va_args:	list of named register dump ranges and regions to dump, as
+ *		registered previously through dpu_dbg_reg_register_base and
+ *		dpu_dbg_reg_register_dump_range.
+ *		Including the special name "panic" will trigger a panic after
+ *		the dumping work has completed.
+ */
+#define DPU_DBG_DUMP(...) dpu_dbg_dump(DPU_DBG_DUMP_PROC_CTX, __func__, \
+		##__VA_ARGS__, DPU_DBG_DUMP_DATA_LIMITER)
+
+/**
+ * DPU_DBG_DUMP_CLK_EN - trigger dumping of all dpu_dbg facilities, without clk
+ * @va_args:	list of named register dump ranges and regions to dump, as
+ *		registered previously through dpu_dbg_reg_register_base and
+ *		dpu_dbg_reg_register_dump_range.
+ *		Including the special name "panic" will trigger a panic after
+ *		the dumping work has completed.
+ */
+#define DPU_DBG_DUMP_CLK_EN(...) dpu_dbg_dump(DPU_DBG_DUMP_CLK_ENABLED_CTX, \
+		__func__, ##__VA_ARGS__, DPU_DBG_DUMP_DATA_LIMITER)
+
+/**
+ * dpu_dbg_init - initialize global sde debug facilities: evtlog, regdump
+ * @dev:		device handle
+ * Returns:		0 or -ERROR
+ */
+int dpu_dbg_init(struct device *dev);
+
+/**
+ * dpu_dbg_register_drm_dev - register a drm device with the dpu dbg module
+ * @ddev:		drm device hangle
+ * Returns:		void
+ */
+void dpu_dbg_register_drm_dev(struct drm_device *ddev);
+
+/**
+ * dpu_dbg_destroy - destroy the global sde debug facilities
+ * Returns:	none
+ */
+void dpu_dbg_destroy(void);
+
+/**
+ * dpu_dbg_dump - trigger dumping of all dpu_dbg facilities
+ * @queue_work:	whether to queue the dumping work to the work_struct
+ * @name:	string indicating origin of dump
+ * @va_args:	list of named register dump ranges and regions to dump, as
+ *		registered previously through dpu_dbg_reg_register_base and
+ *		dpu_dbg_reg_register_dump_range.
+ *		Including the special name "panic" will trigger a panic after
+ *		the dumping work has completed.
+ * Returns:	none
+ */
+void dpu_dbg_dump(enum dpu_dbg_dump_context mode, const char *name, ...);
+
+/**
+ * dpu_dbg_reg_register_base - register a hw register address section for later
+ *	dumping. call this before calling dpu_dbg_reg_register_dump_range
+ *	to be able to specify sub-ranges within the base hw range.
+ * @name:	name of base region
+ * @base:	base pointer of region
+ * @max_offset:	length of region
+ * Returns:	0 or -ERROR
+ */
+int dpu_dbg_reg_register_base(const char *name, void __iomem *base,
+		size_t max_offset);
+
+/**
+ * dpu_dbg_reg_register_dump_range - register a hw register sub-region for
+ *	later register dumping associated with base specified by
+ *	dpu_dbg_reg_register_base
+ * @base_name:		name of base region
+ * @range_name:		name of sub-range within base region
+ * @offset_start:	sub-range's start offset from base's base pointer
+ * @offset_end:		sub-range's end offset from base's base pointer
+ * @xin_id:		xin id
+ * Returns:		none
+ */
+void dpu_dbg_reg_register_dump_range(const char *base_name,
+		const char *range_name, u32 offset_start, u32 offset_end,
+		uint32_t xin_id);
+
+/**
+ * dpu_dbg_set_sde_top_offset - set the target specific offset from mdss base
+ *	address of the top registers. Used for accessing debug bus controls.
+ * @blk_off: offset from mdss base of the top block
+ */
+void dpu_dbg_set_sde_top_offset(u32 blk_off);
+
+/**
+ * _dpu_dump_array - dump array of register bases
+ * @blk_arr: array of register base pointers
+ * @len: length of blk_arr
+ * @do_panic: whether to trigger a panic after dumping
+ * @name: string indicating origin of dump
+ * @dump_all: dump all regs
+ */
+void _dpu_dump_array(struct dpu_dbg_base *dbg_base,
+		struct dpu_dbg_reg_base *blk_arr[],
+		u32 len, bool do_panic, const char *name, bool dump_all);
+
+/**
+ * _dpu_dump_get_blk_addr - retrieve register block address by name
+ * @blk_name: register blk name
+ * @Return: register blk base, or NULL
+ */
+struct dpu_dbg_reg_base *_dpu_dump_get_blk_addr(struct dpu_dbg_base *dbg_base,
+		const char *blk_name);
+
+#endif /* DPU_DBG_H_ */
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c
new file mode 100644
index 000000000000..30e03674bfd6
--- /dev/null
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_dbg_util.c
@@ -0,0 +1,314 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ */
+
+#define pr_fmt(fmt)	"[drm:%s:%d] " fmt, __func__, __LINE__
+
+#include "dpu_dbg.h"
+#include "dpu_hw_catalog.h"
+
+/**
+ * _sde_power_check - check if power needs to enabled
+ * @dump_mode: to check if power need to be enabled
+ * Return: true if success; false otherwise
+ */
+static inline bool _dpu_power_check(enum dpu_dbg_dump_context dump_mode)
+{
+	return (dump_mode == DPU_DBG_DUMP_CLK_ENABLED_CTX) ? false : true;
+}
+
+/**
+ * _dpu_dump_reg - helper function for dumping rotator register set content
+ * @dump_name: register set name
+ * @reg_dump_flag: dumping flag controlling in-log/memory dump location
+ * @base_addr: starting address of io region for calculating offsets to print
+ * @addr: starting address offset for dumping
+ * @len_bytes: range of the register set
+ * @dump_mem: output buffer for memory dump location option
+ * @from_isr: whether being called from isr context
+ */
+static void _dpu_dump_reg(struct dpu_dbg_base *dbg_base,
+		const char *dump_name, u32 reg_dump_flag,
+		void __iomem *base_addr, void __iomem *addr,
+		size_t len_bytes, u32 **dump_mem)
+{
+	u32 in_log, in_mem, len_align, len_padded, in_dump;
+	u32 *dump_addr = NULL;
+	void __iomem *end_addr;
+	int i;
+	int rc;
+
+	if (!len_bytes)
+		return;
+
+	in_log = (reg_dump_flag & DPU_DBG_DUMP_IN_LOG);
+	in_mem = (reg_dump_flag & DPU_DBG_DUMP_IN_MEM);
+	in_dump = (reg_dump_flag & DPU_DBG_DUMP_IN_COREDUMP);
+
+	pr_debug("%s: reg_dump_flag=%d in_log=%d in_mem=%d\n",
+		dump_name, reg_dump_flag, in_log, in_mem);
+
+	if (!in_log && !in_mem && !in_dump)
+		return;
+
+	if (in_log)
+		dev_info(dbg_base->dev, "%s: start_offset 0x%lx len 0x%zx\n",
+				dump_name, (unsigned long)(addr - base_addr),
+					len_bytes);
+
+	len_align = (len_bytes + REG_DUMP_ALIGN - 1) / REG_DUMP_ALIGN;
+	len_padded = len_align * REG_DUMP_ALIGN;
+	end_addr = addr + len_bytes;
+
+	if (in_mem || in_dump) {
+		if (dump_mem && !(*dump_mem))
+			*dump_mem = devm_kzalloc(dbg_base->dev, len_padded,
+					GFP_KERNEL);
+
+		if (dump_mem && *dump_mem) {
+			dump_addr = *dump_mem;
+			dev_info(dbg_base->dev,
+				"%s: start_addr:0x%pK len:0x%x reg_offset=0x%lx\n",
+				dump_name, dump_addr, len_padded,
+				(unsigned long)(addr - base_addr));
+			if (in_dump)
+				drm_printf(dbg_base->dpu_dbg_printer,
+						"%s: start_addr:0x%pK len:0x%x reg_offset=0x%lx\n",
+						dump_name, dump_addr,
+						len_padded,
+						(unsigned long)(addr -
+						base_addr));
+		} else {
+			in_mem = 0;
+			pr_err("dump_mem: kzalloc fails!\n");
+		}
+	}
+
+	if (_dpu_power_check(dbg_base->dump_mode)) {
+		rc = pm_runtime_get_sync(dbg_base->dev);
+		if (rc < 0) {
+			pr_err("failed to enable power %d\n", rc);
+			return;
+		}
+	}
+
+	for (i = 0; i < len_align; i++) {
+		u32 x0, x4, x8, xc;
+
+		if (in_log || in_mem) {
+			x0 = (addr < end_addr) ? readl_relaxed(addr + 0x0) : 0;
+			x4 = (addr + 0x4 < end_addr) ? readl_relaxed(addr +
+					0x4) : 0;
+			x8 = (addr + 0x8 < end_addr) ? readl_relaxed(addr +
+					0x8) : 0;
+			xc = (addr + 0xc < end_addr) ? readl_relaxed(addr +
+					0xc) : 0;
+		}
+
+		if (in_log)
+			dev_info(dbg_base->dev,
+					"0x%lx : %08x %08x %08x %08x\n",
+					(unsigned long)(addr - base_addr),
+					x0, x4, x8, xc);
+
+		if (dump_addr && in_mem) {
+			dump_addr[i * 4] = x0;
+			dump_addr[i * 4 + 1] = x4;
+			dump_addr[i * 4 + 2] = x8;
+			dump_addr[i * 4 + 3] = xc;
+		}
+
+		if (in_dump) {
+			drm_printf(dbg_base->dpu_dbg_printer,
+					"0x%lx : %08x %08x %08x %08x\n",
+					(unsigned long)(addr - base_addr),
+					dump_addr[i * 4],
+					dump_addr[i * 4 + 1],
+					dump_addr[i * 4 + 2],
+					dump_addr[i * 4 + 3]);
+
+		}
+
+		addr += REG_DUMP_ALIGN;
+	}
+
+	if (_dpu_power_check(dbg_base->dump_mode))
+		pm_runtime_put_sync(dbg_base->dev);
+}
+
+/**
+ * _dpu_dbg_get_dump_range - helper to retrieve dump length for a range node
+ * @range_node: range node to dump
+ * @max_offset: max offset of the register base
+ * @Return: length
+ */
+static u32 _dpu_dbg_get_dump_range(struct dpu_dbg_reg_offset *range_node,
+		size_t max_offset)
+{
+	u32 length = 0;
+
+	if (range_node->start == 0 && range_node->end == 0) {
+		length = max_offset;
+	} else if (range_node->start < max_offset) {
+		if (range_node->end > max_offset)
+			length = max_offset - range_node->start;
+		else if (range_node->start < range_node->end)
+			length = range_node->end - range_node->start;
+	}
+
+	return length;
+}
+
+static int _dpu_dump_reg_range_cmp(void *priv, struct list_head *a,
+		struct list_head *b)
+{
+	struct dpu_dbg_reg_range *ar, *br;
+
+	if (!a || !b)
+		return 0;
+
+	ar = container_of(a, struct dpu_dbg_reg_range, head);
+	br = container_of(b, struct dpu_dbg_reg_range, head);
+
+	return ar->offset.start - br->offset.start;
+}
+
+/**
+ * _dpu_dump_reg_by_ranges - dump ranges or full range of the register blk base
+ * @dbg: register blk base structure
+ * @reg_dump_flag: dump target, memory, kernel log, or both
+ */
+static void _dpu_dump_reg_by_ranges(struct dpu_dbg_base *dbg_base,
+		struct dpu_dbg_reg_base *dbg,
+		u32 reg_dump_flag)
+{
+	void __iomem *addr;
+	size_t len;
+	struct dpu_dbg_reg_range *range_node;
+
+	if (!dbg || !(dbg->base || dbg->cb)) {
+		pr_err("dbg base is null!\n");
+		return;
+	}
+
+	dev_info(dbg_base->dev, "%s:=========%s DUMP=========\n", __func__,
+			dbg->name);
+
+	if (reg_dump_flag & DPU_DBG_DUMP_IN_COREDUMP)
+		drm_printf(dbg_base->dpu_dbg_printer,
+				"%s:=========%s DUMP=========\n",
+				__func__, dbg->name);
+
+	if (dbg->cb) {
+		dbg->cb(dbg->cb_ptr);
+	/* If there is a list to dump the registers by ranges, use the ranges */
+	} else if (!list_empty(&dbg->sub_range_list)) {
+		/* sort the list by start address first */
+		list_sort(NULL, &dbg->sub_range_list, _dpu_dump_reg_range_cmp);
+		list_for_each_entry(range_node, &dbg->sub_range_list, head) {
+			len = _dpu_dbg_get_dump_range(&range_node->offset,
+				dbg->max_offset);
+			addr = dbg->base + range_node->offset.start;
+
+			pr_debug("%s: range_base=0x%pK start=0x%x end=0x%x\n",
+				range_node->range_name,
+				addr, range_node->offset.start,
+				range_node->offset.end);
+
+			_dpu_dump_reg(dbg_base, range_node->range_name,
+					reg_dump_flag,
+					dbg->base, addr, len,
+					&range_node->reg_dump);
+		}
+	} else {
+		/* If there is no list to dump ranges, dump all registers */
+		dev_info(dbg_base->dev,
+				"Ranges not found, will dump full registers\n");
+		dev_info(dbg_base->dev, "base:0x%pK len:0x%zx\n", dbg->base,
+				dbg->max_offset);
+		addr = dbg->base;
+		len = dbg->max_offset;
+		_dpu_dump_reg(dbg_base, dbg->name, reg_dump_flag,
+				dbg->base, addr, len,
+				&dbg->reg_dump);
+	}
+}
+
+/**
+ * _dpu_dump_reg_by_blk - dump a named register base region
+ * @blk_name: register blk name
+ */
+static void _dpu_dump_reg_by_blk(struct dpu_dbg_base *dbg_base,
+		const char *blk_name)
+{
+	struct dpu_dbg_reg_base *blk_base;
+
+	if (!dbg_base)
+		return;
+
+	list_for_each_entry(blk_base, &dbg_base->reg_base_list, reg_base_head) {
+		if (strlen(blk_base->name) &&
+			!strcmp(blk_base->name, blk_name)) {
+			_dpu_dump_reg_by_ranges(dbg_base, blk_base,
+				dbg_base->enable_reg_dump);
+			break;
+		}
+	}
+}
+
+/**
+ * _dpu_dump_reg_all - dump all register regions
+ */
+static void _dpu_dump_reg_all(struct dpu_dbg_base *dbg_base)
+{
+	struct dpu_dbg_reg_base *blk_base;
+
+	if (!dbg_base)
+		return;
+
+	list_for_each_entry(blk_base, &dbg_base->reg_base_list, reg_base_head) {
+
+		if (!strlen(blk_base->name))
+			continue;
+
+		_dpu_dump_reg_by_blk(dbg_base, blk_base->name);
+	}
+}
+
+struct dpu_dbg_reg_base *_dpu_dump_get_blk_addr(struct dpu_dbg_base *dbg_base,
+		const char *blk_name)
+{
+	struct dpu_dbg_reg_base *blk_base;
+
+	list_for_each_entry(blk_base, &dbg_base->reg_base_list, reg_base_head)
+		if (strlen(blk_base->name) && !strcmp(blk_base->name, blk_name))
+			return blk_base;
+
+	return NULL;
+}
+
+void _dpu_dump_array(struct dpu_dbg_base *dbg_base,
+		struct dpu_dbg_reg_base *blk_arr[],
+		u32 len, bool do_panic, const char *name, bool dump_all)
+{
+	int i;
+
+	mutex_lock(&dbg_base->mutex);
+
+	if (dump_all || !blk_arr || !len) {
+		_dpu_dump_reg_all(dbg_base);
+	} else {
+		for (i = 0; i < len; i++) {
+			if (blk_arr[i] != NULL)
+				_dpu_dump_reg_by_ranges(dbg_base,
+						blk_arr[i],
+						dbg_base->enable_reg_dump);
+		}
+	}
+
+	if (do_panic)
+		panic(name);
+
+	mutex_unlock(&dbg_base->mutex);
+}
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
index 3544af1a45c5..b2ab22be4c55 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
@@ -1,5 +1,5 @@ 
 /* SPDX-License-Identifier: GPL-2.0-only */
-/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2018, 2020 The Linux Foundation. All rights reserved.
  */
 
 #ifndef _DPU_HW_CATALOG_H
@@ -52,6 +52,8 @@ 
 
 
 #define DPU_HW_BLK_NAME_LEN	16
+/* default size of valid register space for MDSS_HW block (offset 0) */
+#define DEFAULT_MDSS_HW_BLOCK_SIZE 0x5C
 
 #define MAX_IMG_WIDTH 0x3fff
 #define MAX_IMG_HEIGHT 0x3fff
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index 49685571dc0e..f6fb0187388f 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1,6 +1,6 @@ 
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2018, 2020 The Linux Foundation. All rights reserved.
  * Copyright (C) 2013 Red Hat
  * Author: Rob Clark <robdclark@gmail.com>
  */
@@ -17,6 +17,7 @@ 
 #include <drm/drm_prime.h>
 #include <drm/drm_of.h>
 #include <drm/drm_vblank.h>
+#include "dpu_dbg.h"
 
 #include "msm_drv.h"
 #include "msm_debugfs.h"
@@ -268,6 +269,8 @@  static int msm_drm_uninit(struct device *dev)
 		msm_fbdev_free(ddev);
 #endif
 
+	dpu_dbg_destroy();
+
 	drm_mode_config_cleanup(ddev);
 
 	pm_runtime_get_sync(dev);
@@ -1303,6 +1306,7 @@  static int msm_pdev_probe(struct platform_device *pdev)
 
 fail:
 	of_platform_depopulate(&pdev->dev);
+	dpu_dbg_destroy();
 	return ret;
 }