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@xxxxxxxxx> Signed-off-by: Abhinav Kumar <abhinavk@xxxxxxxxxxxxxx> --- 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 --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(®_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(®_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, ®_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@xxxxxxxxx> */ @@ -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; } -- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project