Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/msm: add support to take dpu snapshot

Add the msm_disp_snapshot module which adds supports to dump dpu
registers and capture the drm atomic state which can be used in
case of error conditions.

changes in v5:
- start storing disp_state in msm_kms instead of dpu_kms
- get rid of MSM_DISP_SNAPSHOT_IN_* enum by simplifying the functions
- move snprintf inside the snapshot core by using varargs
- get rid of some stale code comments
- allow snapshot module for non-DPU targets

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Abhinav Kumar <abhinavk@codeaurora.org>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://lore.kernel.org/r/1618606645-19695-3-git-send-email-abhinavk@codeaurora.org
Signed-off-by: Rob Clark <robdclark@chromium.org>

authored by

Abhinav Kumar and committed by
Rob Clark
98659487 a4324a7a

+529 -2
+2
drivers/gpu/drm/msm/Makefile
··· 77 77 disp/dpu1/dpu_plane.o \ 78 78 disp/dpu1/dpu_rm.o \ 79 79 disp/dpu1/dpu_vbif.o \ 80 + disp/msm_disp_snapshot.o \ 81 + disp/msm_disp_snapshot_util.o \ 80 82 msm_atomic.o \ 81 83 msm_atomic_tracepoints.o \ 82 84 msm_debugfs.o \
+1 -1
drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
··· 1 1 /* SPDX-License-Identifier: GPL-2.0-only */ 2 - /* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. 2 + /* Copyright (c) 2015-2018, 2020 The Linux Foundation. All rights reserved. 3 3 */ 4 4 5 5 #ifndef _DPU_HW_CATALOG_H
+161
drivers/gpu/drm/msm/disp/msm_disp_snapshot.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. 4 + */ 5 + 6 + #define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ 7 + 8 + #include "msm_disp_snapshot.h" 9 + 10 + #ifdef CONFIG_DEV_COREDUMP 11 + static ssize_t disp_devcoredump_read(char *buffer, loff_t offset, 12 + size_t count, void *data, size_t datalen) 13 + { 14 + struct drm_print_iterator iter; 15 + struct drm_printer p; 16 + struct msm_disp_state *disp_state; 17 + 18 + disp_state = data; 19 + 20 + iter.data = buffer; 21 + iter.offset = 0; 22 + iter.start = offset; 23 + iter.remain = count; 24 + 25 + p = drm_coredump_printer(&iter); 26 + 27 + msm_disp_state_print(disp_state, &p); 28 + 29 + return count - iter.remain; 30 + } 31 + 32 + static void disp_devcoredump_free(void *data) 33 + { 34 + struct msm_disp_state *disp_state; 35 + 36 + disp_state = data; 37 + 38 + msm_disp_state_free(disp_state); 39 + 40 + disp_state->coredump_pending = false; 41 + } 42 + #endif /* CONFIG_DEV_COREDUMP */ 43 + 44 + static void _msm_disp_snapshot_work(struct kthread_work *work) 45 + { 46 + struct msm_disp_state *disp_state = container_of(work, struct msm_disp_state, dump_work); 47 + struct drm_printer p; 48 + 49 + mutex_lock(&disp_state->mutex); 50 + 51 + msm_disp_snapshot_capture_state(disp_state); 52 + 53 + if (MSM_DISP_SNAPSHOT_DUMP_IN_CONSOLE) { 54 + p = drm_info_printer(disp_state->drm_dev->dev); 55 + msm_disp_state_print(disp_state, &p); 56 + } 57 + 58 + /* 59 + * if devcoredump is not defined free the state immediately 60 + * otherwise it will be freed in the free handler. 61 + */ 62 + #ifdef CONFIG_DEV_COREDUMP 63 + dev_coredumpm(disp_state->dev, THIS_MODULE, disp_state, 0, GFP_KERNEL, 64 + disp_devcoredump_read, disp_devcoredump_free); 65 + disp_state->coredump_pending = true; 66 + #else 67 + msm_disp_state_free(disp_state); 68 + #endif 69 + 70 + mutex_unlock(&disp_state->mutex); 71 + } 72 + 73 + void msm_disp_snapshot_state(struct drm_device *drm_dev) 74 + { 75 + struct msm_drm_private *priv; 76 + struct msm_kms *kms; 77 + struct msm_disp_state *disp_state; 78 + 79 + if (!drm_dev) { 80 + DRM_ERROR("invalid params\n"); 81 + return; 82 + } 83 + 84 + priv = drm_dev->dev_private; 85 + kms = priv->kms; 86 + disp_state = kms->disp_state; 87 + 88 + if (!disp_state) { 89 + DRM_ERROR("invalid params\n"); 90 + return; 91 + } 92 + 93 + /* 94 + * if there is a coredump pending return immediately till dump 95 + * if read by userspace or timeout happens 96 + */ 97 + if (disp_state->coredump_pending) { 98 + DRM_DEBUG("coredump is pending read\n"); 99 + return; 100 + } 101 + 102 + kthread_queue_work(disp_state->dump_worker, 103 + &disp_state->dump_work); 104 + } 105 + 106 + int msm_disp_snapshot_init(struct drm_device *drm_dev) 107 + { 108 + struct msm_drm_private *priv; 109 + struct msm_disp_state *disp_state; 110 + struct msm_kms *kms; 111 + 112 + if (!drm_dev) { 113 + DRM_ERROR("invalid params\n"); 114 + return -EINVAL; 115 + } 116 + 117 + priv = drm_dev->dev_private; 118 + kms = priv->kms; 119 + 120 + disp_state = devm_kzalloc(drm_dev->dev, sizeof(struct msm_disp_state), GFP_KERNEL); 121 + 122 + mutex_init(&disp_state->mutex); 123 + 124 + disp_state->dev = drm_dev->dev; 125 + disp_state->drm_dev = drm_dev; 126 + 127 + INIT_LIST_HEAD(&disp_state->blocks); 128 + 129 + disp_state->dump_worker = kthread_create_worker(0, "%s", "disp_snapshot"); 130 + if (IS_ERR(disp_state->dump_worker)) 131 + DRM_ERROR("failed to create disp state task\n"); 132 + 133 + kthread_init_work(&disp_state->dump_work, _msm_disp_snapshot_work); 134 + 135 + kms->disp_state = disp_state; 136 + 137 + return 0; 138 + } 139 + 140 + void msm_disp_snapshot_destroy(struct drm_device *drm_dev) 141 + { 142 + struct msm_kms *kms; 143 + struct msm_drm_private *priv; 144 + struct msm_disp_state *disp_state; 145 + 146 + if (!drm_dev) { 147 + DRM_ERROR("invalid params\n"); 148 + return; 149 + } 150 + 151 + priv = drm_dev->dev_private; 152 + kms = priv->kms; 153 + disp_state = kms->disp_state; 154 + 155 + if (disp_state->dump_worker) 156 + kthread_destroy_worker(disp_state->dump_worker); 157 + 158 + list_del(&disp_state->blocks); 159 + 160 + mutex_destroy(&disp_state->mutex); 161 + }
+153
drivers/gpu/drm/msm/disp/msm_disp_snapshot.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + /* 3 + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. 4 + */ 5 + 6 + #ifndef MSM_DISP_SNAPSHOT_H_ 7 + #define MSM_DISP_SNAPSHOT_H_ 8 + 9 + #include <drm/drm_atomic_helper.h> 10 + #include <drm/drm_device.h> 11 + #include "../../../drm_crtc_internal.h" 12 + #include <drm/drm_print.h> 13 + #include <drm/drm_atomic.h> 14 + #include <linux/debugfs.h> 15 + #include <linux/list.h> 16 + #include <linux/delay.h> 17 + #include <linux/spinlock.h> 18 + #include <linux/ktime.h> 19 + #include <linux/debugfs.h> 20 + #include <linux/uaccess.h> 21 + #include <linux/dma-buf.h> 22 + #include <linux/slab.h> 23 + #include <linux/list_sort.h> 24 + #include <linux/pm.h> 25 + #include <linux/pm_runtime.h> 26 + #include <linux/kthread.h> 27 + #include <linux/devcoredump.h> 28 + #include <stdarg.h> 29 + #include "msm_kms.h" 30 + 31 + #define MSM_DISP_SNAPSHOT_MAX_BLKS 10 32 + 33 + /* debug option to print the registers in logs */ 34 + #define MSM_DISP_SNAPSHOT_DUMP_IN_CONSOLE 0 35 + 36 + /* print debug ranges in groups of 4 u32s */ 37 + #define REG_DUMP_ALIGN 16 38 + 39 + /** 40 + * struct msm_disp_state - structure to store current dpu state 41 + * @dev: device pointer 42 + * @drm_dev: drm device pointer 43 + * @mutex: mutex to serialize access to serialze dumps, debugfs access 44 + * @coredump_pending: coredump is pending read from userspace 45 + * @atomic_state: atomic state duplicated at the time of the error 46 + * @dump_worker: kworker thread which runs the dump work 47 + * @dump_work: kwork which dumps the registers and drm state 48 + * @timestamp: timestamp at which the coredump was captured 49 + */ 50 + struct msm_disp_state { 51 + struct device *dev; 52 + struct drm_device *drm_dev; 53 + struct mutex mutex; 54 + 55 + bool coredump_pending; 56 + 57 + struct list_head blocks; 58 + 59 + struct drm_atomic_state *atomic_state; 60 + 61 + struct kthread_worker *dump_worker; 62 + struct kthread_work dump_work; 63 + ktime_t timestamp; 64 + }; 65 + 66 + /** 67 + * struct msm_disp_state_block - structure to store each hardware block state 68 + * @name: name of the block 69 + * @drm_dev: handle to the linked list head 70 + * @size: size of the register space of this hardware block 71 + * @state: array holding the register dump of this hardware block 72 + * @base_addr: starting address of this hardware block's register space 73 + */ 74 + struct msm_disp_state_block { 75 + char name[SZ_128]; 76 + struct list_head node; 77 + unsigned int size; 78 + u32 *state; 79 + void __iomem *base_addr; 80 + }; 81 + 82 + /** 83 + * msm_disp_snapshot_init - initialize display snapshot 84 + * @drm_dev: drm device handle 85 + * 86 + * Returns: 0 or -ERROR 87 + */ 88 + int msm_disp_snapshot_init(struct drm_device *drm_dev); 89 + 90 + /** 91 + * msm_disp_snapshot_destroy - destroy the display snapshot 92 + * @drm_dev: drm device handle 93 + * 94 + * Returns: none 95 + */ 96 + void msm_disp_snapshot_destroy(struct drm_device *drm_dev); 97 + 98 + /** 99 + * msm_disp_snapshot_state - trigger to dump the display snapshot 100 + * @drm_dev: handle to drm device 101 + 102 + * Returns: none 103 + */ 104 + void msm_disp_snapshot_state(struct drm_device *drm_dev); 105 + 106 + /** 107 + * msm_disp_state_get - get the handle to msm_disp_state struct from the drm device 108 + * @drm: handle to drm device 109 + 110 + * Returns: handle to the msm_disp_state struct 111 + */ 112 + struct msm_disp_state *msm_disp_state_get(struct drm_device *drm); 113 + 114 + /** 115 + * msm_disp_state_print - print out the current dpu state 116 + * @disp_state: handle to drm device 117 + * @p: handle to drm printer 118 + * 119 + * Returns: none 120 + */ 121 + void msm_disp_state_print(struct msm_disp_state *disp_state, struct drm_printer *p); 122 + 123 + /** 124 + * msm_disp_snapshot_capture_state - utility to capture atomic state and hw registers 125 + * @disp_state: handle to msm_disp_state struct 126 + 127 + * Returns: none 128 + */ 129 + void msm_disp_snapshot_capture_state(struct msm_disp_state *disp_state); 130 + 131 + /** 132 + * msm_disp_state_free - free the memory after the coredump has been read 133 + * @disp_state: handle to struct msm_disp_state 134 + 135 + * Returns: none 136 + */ 137 + void msm_disp_state_free(struct msm_disp_state *disp_state); 138 + 139 + /** 140 + * msm_disp_snapshot_add_block - add a hardware block with its register dump 141 + * @disp_state: handle to struct msm_disp_state 142 + * @name: name of the hardware block 143 + * @len: size of the register space of the hardware block 144 + * @base_addr: starting address of the register space of the hardware block 145 + * @fmt: format in which the block names need to be printed 146 + * 147 + * Returns: none 148 + */ 149 + __printf(4, 5) 150 + void msm_disp_snapshot_add_block(struct msm_disp_state *disp_state, u32 len, 151 + void __iomem *base_addr, const char *fmt, ...); 152 + 153 + #endif /* MSM_DISP_SNAPSHOT_H_ */
+179
drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. 4 + */ 5 + 6 + #define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ 7 + 8 + #include "msm_disp_snapshot.h" 9 + 10 + static void msm_disp_state_dump_regs(u32 **reg, u32 aligned_len, void __iomem *base_addr) 11 + { 12 + u32 len_padded; 13 + u32 num_rows; 14 + u32 x0, x4, x8, xc; 15 + void __iomem *addr; 16 + u32 *dump_addr = NULL; 17 + void __iomem *end_addr; 18 + int i; 19 + 20 + len_padded = aligned_len * REG_DUMP_ALIGN; 21 + num_rows = aligned_len / REG_DUMP_ALIGN; 22 + 23 + addr = base_addr; 24 + end_addr = base_addr + aligned_len; 25 + 26 + if (!(*reg)) 27 + *reg = kzalloc(len_padded, GFP_KERNEL); 28 + 29 + if (*reg) 30 + dump_addr = *reg; 31 + 32 + for (i = 0; i < num_rows; i++) { 33 + x0 = (addr < end_addr) ? readl_relaxed(addr + 0x0) : 0; 34 + x4 = (addr + 0x4 < end_addr) ? readl_relaxed(addr + 0x4) : 0; 35 + x8 = (addr + 0x8 < end_addr) ? readl_relaxed(addr + 0x8) : 0; 36 + xc = (addr + 0xc < end_addr) ? readl_relaxed(addr + 0xc) : 0; 37 + 38 + if (dump_addr) { 39 + dump_addr[i * 4] = x0; 40 + dump_addr[i * 4 + 1] = x4; 41 + dump_addr[i * 4 + 2] = x8; 42 + dump_addr[i * 4 + 3] = xc; 43 + } 44 + 45 + addr += REG_DUMP_ALIGN; 46 + } 47 + } 48 + 49 + static void msm_disp_state_print_regs(u32 **reg, u32 len, void __iomem *base_addr, 50 + struct drm_printer *p) 51 + { 52 + int i; 53 + u32 *dump_addr = NULL; 54 + void __iomem *addr; 55 + u32 num_rows; 56 + 57 + addr = base_addr; 58 + num_rows = len / REG_DUMP_ALIGN; 59 + 60 + if (*reg) 61 + dump_addr = *reg; 62 + 63 + for (i = 0; i < num_rows; i++) { 64 + drm_printf(p, "0x%lx : %08x %08x %08x %08x\n", 65 + (unsigned long)(addr - base_addr), 66 + dump_addr[i * 4], dump_addr[i * 4 + 1], 67 + dump_addr[i * 4 + 2], dump_addr[i * 4 + 3]); 68 + addr += REG_DUMP_ALIGN; 69 + } 70 + } 71 + 72 + struct msm_disp_state *msm_disp_state_get(struct drm_device *drm) 73 + { 74 + struct msm_drm_private *priv; 75 + struct msm_kms *kms; 76 + 77 + priv = drm->dev_private; 78 + kms = priv->kms; 79 + 80 + return kms->disp_state; 81 + } 82 + 83 + void msm_disp_state_print(struct msm_disp_state *state, struct drm_printer *p) 84 + { 85 + struct msm_disp_state_block *block, *tmp; 86 + 87 + if (!p) { 88 + DRM_ERROR("invalid drm printer\n"); 89 + return; 90 + } 91 + 92 + drm_printf(p, "---\n"); 93 + 94 + drm_printf(p, "module: " KBUILD_MODNAME "\n"); 95 + drm_printf(p, "dpu devcoredump\n"); 96 + drm_printf(p, "timestamp %lld\n", ktime_to_ns(state->timestamp)); 97 + 98 + list_for_each_entry_safe(block, tmp, &state->blocks, node) { 99 + drm_printf(p, "====================%s================\n", block->name); 100 + msm_disp_state_print_regs(&block->state, block->size, block->base_addr, p); 101 + } 102 + 103 + drm_printf(p, "===================dpu drm state================\n"); 104 + 105 + if (state->atomic_state) 106 + drm_atomic_print_new_state(state->atomic_state, p); 107 + } 108 + 109 + static void msm_disp_capture_atomic_state(struct msm_disp_state *disp_state) 110 + { 111 + struct drm_device *ddev; 112 + struct drm_modeset_acquire_ctx ctx; 113 + 114 + disp_state->timestamp = ktime_get(); 115 + 116 + ddev = disp_state->drm_dev; 117 + 118 + drm_modeset_acquire_init(&ctx, 0); 119 + 120 + while (drm_modeset_lock_all_ctx(ddev, &ctx) != 0) 121 + drm_modeset_backoff(&ctx); 122 + 123 + disp_state->atomic_state = drm_atomic_helper_duplicate_state(ddev, 124 + &ctx); 125 + drm_modeset_drop_locks(&ctx); 126 + drm_modeset_acquire_fini(&ctx); 127 + } 128 + 129 + void msm_disp_snapshot_capture_state(struct msm_disp_state *disp_state) 130 + { 131 + struct msm_drm_private *priv; 132 + struct drm_device *drm_dev; 133 + 134 + drm_dev = disp_state->drm_dev; 135 + priv = drm_dev->dev_private; 136 + 137 + msm_disp_capture_atomic_state(disp_state); 138 + } 139 + 140 + void msm_disp_state_free(struct msm_disp_state *disp_state) 141 + { 142 + struct msm_disp_state_block *block, *tmp; 143 + 144 + if (disp_state->atomic_state) { 145 + drm_atomic_state_put(disp_state->atomic_state); 146 + disp_state->atomic_state = NULL; 147 + } 148 + 149 + list_for_each_entry_safe(block, tmp, &disp_state->blocks, node) { 150 + list_del(&block->node); 151 + kfree(block->state); 152 + kfree(block); 153 + } 154 + } 155 + 156 + void msm_disp_snapshot_add_block(struct msm_disp_state *disp_state, u32 len, 157 + void __iomem *base_addr, const char *fmt, ...) 158 + { 159 + struct msm_disp_state_block *new_blk; 160 + struct va_format vaf; 161 + va_list va; 162 + 163 + new_blk = kzalloc(sizeof(struct msm_disp_state_block), GFP_KERNEL); 164 + 165 + va_start(va, fmt); 166 + 167 + vaf.fmt = fmt; 168 + vaf.va = &va; 169 + snprintf(new_blk->name, sizeof(new_blk->name), "%pV", &vaf); 170 + 171 + va_end(va); 172 + 173 + INIT_LIST_HEAD(&new_blk->node); 174 + new_blk->size = ALIGN(len, REG_DUMP_ALIGN); 175 + new_blk->base_addr = base_addr; 176 + 177 + msm_disp_state_dump_regs(&new_blk->state, new_blk->size, base_addr); 178 + list_add(&new_blk->node, &disp_state->blocks); 179 + }
+1
drivers/gpu/drm/msm/dp/dp_display.h
··· 8 8 9 9 #include "dp_panel.h" 10 10 #include <sound/hdmi-codec.h> 11 + #include "disp/msm_disp_snapshot.h" 11 12 12 13 struct msm_dp { 13 14 struct drm_device *drm_dev;
+1
drivers/gpu/drm/msm/dsi/dsi.c
··· 266 266 return ret; 267 267 } 268 268 269 +
+1
drivers/gpu/drm/msm/dsi/dsi_host.c
··· 2487 2487 2488 2488 return of_drm_find_bridge(msm_host->device_node); 2489 2489 } 2490 +
+26 -1
drivers/gpu/drm/msm/msm_drv.c
··· 1 1 // SPDX-License-Identifier: GPL-2.0-only 2 2 /* 3 - * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. 3 + * Copyright (c) 2016-2018, 2020-2021 The Linux Foundation. All rights reserved. 4 4 * Copyright (C) 2013 Red Hat 5 5 * Author: Rob Clark <robdclark@gmail.com> 6 6 */ ··· 19 19 #include <drm/drm_of.h> 20 20 #include <drm/drm_vblank.h> 21 21 22 + #include "disp/msm_disp_snapshot.h" 22 23 #include "msm_drv.h" 23 24 #include "msm_debugfs.h" 24 25 #include "msm_fence.h" ··· 168 167 return _msm_ioremap(pdev, name, dbgname, true); 169 168 } 170 169 170 + unsigned long msm_iomap_size(struct platform_device *pdev, const char *name) 171 + { 172 + struct resource *res; 173 + 174 + if (name) 175 + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); 176 + else 177 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 178 + 179 + if (!res) { 180 + dev_dbg(&pdev->dev, "failed to get memory resource: %s\n", 181 + name); 182 + return 0; 183 + } 184 + 185 + return resource_size(res); 186 + } 187 + 171 188 void msm_writel(u32 data, void __iomem *addr) 172 189 { 173 190 if (reglog) ··· 296 277 if (fbdev && priv->fbdev) 297 278 msm_fbdev_free(ddev); 298 279 #endif 280 + 281 + msm_disp_snapshot_destroy(ddev); 299 282 300 283 drm_mode_config_cleanup(ddev); 301 284 ··· 570 549 ret = drm_dev_register(ddev, 0); 571 550 if (ret) 572 551 goto err_msm_uninit; 552 + 553 + ret = msm_disp_snapshot_init(ddev); 554 + if (ret) 555 + DRM_DEV_ERROR(dev, "msm_disp_snapshot_init failed ret = %d\n", ret); 573 556 574 557 drm_mode_config_reset(ddev); 575 558
+1
drivers/gpu/drm/msm/msm_drv.h
··· 450 450 const char *dbgname); 451 451 void __iomem *msm_ioremap_quiet(struct platform_device *pdev, const char *name, 452 452 const char *dbgname); 453 + unsigned long msm_iomap_size(struct platform_device *pdev, const char *name); 453 454 void msm_writel(u32 data, void __iomem *addr); 454 455 u32 msm_readl(const void __iomem *addr); 455 456 void msm_rmw(void __iomem *addr, u32 mask, u32 or);
+3
drivers/gpu/drm/msm/msm_kms.h
··· 152 152 /* mapper-id used to request GEM buffer mapped for scanout: */ 153 153 struct msm_gem_address_space *aspace; 154 154 155 + /* handle to disp snapshot state */ 156 + struct msm_disp_state *disp_state; 157 + 155 158 /* 156 159 * For async commit, where ->flush_commit() and later happens 157 160 * from the crtc's pending_timer close to end of the frame: