Commit 730c2bf4 authored by Adrián Larumbe's avatar Adrián Larumbe Committed by Steven Price

drm/panfrost: Add support for devcoredump

In the event of a job timeout, debug dump information will be written into
/sys/class/devcoredump.

Inspired by etnaviv's similar feature.
Signed-off-by: default avatarAdrián Larumbe <adrian.larumbe@collabora.com>
Reviewed-by: default avatarSteven Price <steven.price@arm.com>
Signed-off-by: default avatarSteven Price <steven.price@arm.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20220729144610.2105223-3-adrian.larumbe@collabora.com
parent 6a3aaa2b
...@@ -11,6 +11,7 @@ config DRM_PANFROST ...@@ -11,6 +11,7 @@ config DRM_PANFROST
select DRM_GEM_SHMEM_HELPER select DRM_GEM_SHMEM_HELPER
select PM_DEVFREQ select PM_DEVFREQ
select DEVFREQ_GOV_SIMPLE_ONDEMAND select DEVFREQ_GOV_SIMPLE_ONDEMAND
select WANT_DEV_COREDUMP
help help
DRM driver for ARM Mali Midgard (T6xx, T7xx, T8xx) and DRM driver for ARM Mali Midgard (T6xx, T7xx, T8xx) and
Bifrost (G3x, G5x, G7x) GPUs. Bifrost (G3x, G5x, G7x) GPUs.
...@@ -9,6 +9,7 @@ panfrost-y := \ ...@@ -9,6 +9,7 @@ panfrost-y := \
panfrost_gpu.o \ panfrost_gpu.o \
panfrost_job.o \ panfrost_job.o \
panfrost_mmu.o \ panfrost_mmu.o \
panfrost_perfcnt.o panfrost_perfcnt.o \
panfrost_dump.o
obj-$(CONFIG_DRM_PANFROST) += panfrost.o obj-$(CONFIG_DRM_PANFROST) += panfrost.o
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2021 Collabora ltd. */
#include <linux/err.h>
#include <linux/device.h>
#include <linux/devcoredump.h>
#include <linux/moduleparam.h>
#include <linux/iosys-map.h>
#include <drm/panfrost_drm.h>
#include <drm/drm_device.h>
#include "panfrost_job.h"
#include "panfrost_gem.h"
#include "panfrost_regs.h"
#include "panfrost_dump.h"
#include "panfrost_device.h"
static bool panfrost_dump_core = true;
module_param_named(dump_core, panfrost_dump_core, bool, 0600);
struct panfrost_dump_iterator {
void *start;
struct panfrost_dump_object_header *hdr;
void *data;
};
static const unsigned short panfrost_dump_registers[] = {
SHADER_READY_LO,
SHADER_READY_HI,
TILER_READY_LO,
TILER_READY_HI,
L2_READY_LO,
L2_READY_HI,
JOB_INT_MASK,
JOB_INT_STAT,
JS_HEAD_LO(0),
JS_HEAD_HI(0),
JS_TAIL_LO(0),
JS_TAIL_HI(0),
JS_AFFINITY_LO(0),
JS_AFFINITY_HI(0),
JS_CONFIG(0),
JS_STATUS(0),
JS_HEAD_NEXT_LO(0),
JS_HEAD_NEXT_HI(0),
JS_AFFINITY_NEXT_LO(0),
JS_AFFINITY_NEXT_HI(0),
JS_CONFIG_NEXT(0),
MMU_INT_MASK,
MMU_INT_STAT,
AS_TRANSTAB_LO(0),
AS_TRANSTAB_HI(0),
AS_MEMATTR_LO(0),
AS_MEMATTR_HI(0),
AS_FAULTSTATUS(0),
AS_FAULTADDRESS_LO(0),
AS_FAULTADDRESS_HI(0),
AS_STATUS(0),
};
static void panfrost_core_dump_header(struct panfrost_dump_iterator *iter,
u32 type, void *data_end)
{
struct panfrost_dump_object_header *hdr = iter->hdr;
hdr->magic = cpu_to_le32(PANFROSTDUMP_MAGIC);
hdr->type = cpu_to_le32(type);
hdr->file_offset = cpu_to_le32(iter->data - iter->start);
hdr->file_size = cpu_to_le32(data_end - iter->data);
iter->hdr++;
iter->data += le32_to_cpu(hdr->file_size);
}
static void
panfrost_core_dump_registers(struct panfrost_dump_iterator *iter,
struct panfrost_device *pfdev,
u32 as_nr, int slot)
{
struct panfrost_dump_registers *dumpreg = iter->data;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(panfrost_dump_registers); i++, dumpreg++) {
unsigned int js_as_offset = 0;
unsigned int reg;
if (panfrost_dump_registers[i] >= JS_BASE &&
panfrost_dump_registers[i] <= JS_BASE + JS_SLOT_STRIDE)
js_as_offset = slot * JS_SLOT_STRIDE;
else if (panfrost_dump_registers[i] >= MMU_BASE &&
panfrost_dump_registers[i] <= MMU_BASE + MMU_AS_STRIDE)
js_as_offset = (as_nr << MMU_AS_SHIFT);
reg = panfrost_dump_registers[i] + js_as_offset;
dumpreg->reg = cpu_to_le32(reg);
dumpreg->value = cpu_to_le32(gpu_read(pfdev, reg));
}
panfrost_core_dump_header(iter, PANFROSTDUMP_BUF_REG, dumpreg);
}
void panfrost_core_dump(struct panfrost_job *job)
{
struct panfrost_device *pfdev = job->pfdev;
struct panfrost_dump_iterator iter;
struct drm_gem_object *dbo;
unsigned int n_obj, n_bomap_pages;
__le64 *bomap, *bomap_start;
size_t file_size;
u32 as_nr;
int slot;
int ret, i;
as_nr = job->mmu->as;
slot = panfrost_job_get_slot(job);
/* Only catch the first event, or when manually re-armed */
if (!panfrost_dump_core)
return;
panfrost_dump_core = false;
/* At least, we dump registers and end marker */
n_obj = 2;
n_bomap_pages = 0;
file_size = ARRAY_SIZE(panfrost_dump_registers) *
sizeof(struct panfrost_dump_registers);
/* Add in the active buffer objects */
for (i = 0; i < job->bo_count; i++) {
/*
* Even though the CPU could be configured to use 16K or 64K pages, this
* is a very unusual situation for most kernel setups on SoCs that have
* a Panfrost device. Also many places across the driver make the somewhat
* arbitrary assumption that Panfrost's MMU page size is the same as the CPU's,
* so let's have a sanity check to ensure that's always the case
*/
dbo = job->bos[i];
WARN_ON(!IS_ALIGNED(dbo->size, PAGE_SIZE));
file_size += dbo->size;
n_bomap_pages += dbo->size >> PAGE_SHIFT;
n_obj++;
}
/* If we have any buffer objects, add a bomap object */
if (n_bomap_pages) {
file_size += n_bomap_pages * sizeof(*bomap);
n_obj++;
}
/* Add the size of the headers */
file_size += sizeof(*iter.hdr) * n_obj;
/*
* Allocate the file in vmalloc memory, it's likely to be big.
* The reason behind these GFP flags is that we don't want to trigger the
* OOM killer in the event that not enough memory could be found for our
* dump file. We also don't want the allocator to do any error reporting,
* as the right behaviour is failing gracefully if a big enough buffer
* could not be allocated.
*/
iter.start = __vmalloc(file_size, GFP_KERNEL | __GFP_NOWARN |
__GFP_NORETRY);
if (!iter.start) {
dev_warn(pfdev->dev, "failed to allocate devcoredump file\n");
return;
}
/* Point the data member after the headers */
iter.hdr = iter.start;
iter.data = &iter.hdr[n_obj];
memset(iter.hdr, 0, iter.data - iter.start);
/*
* For now, we write the job identifier in the register dump header,
* so that we can decode the entire dump later with pandecode
*/
iter.hdr->reghdr.jc = cpu_to_le64(job->jc);
iter.hdr->reghdr.major = cpu_to_le32(PANFROSTDUMP_MAJOR);
iter.hdr->reghdr.minor = cpu_to_le32(PANFROSTDUMP_MINOR);
iter.hdr->reghdr.gpu_id = cpu_to_le32(pfdev->features.id);
iter.hdr->reghdr.nbos = cpu_to_le64(job->bo_count);
panfrost_core_dump_registers(&iter, pfdev, as_nr, slot);
/* Reserve space for the bomap */
if (job->bo_count) {
bomap_start = bomap = iter.data;
memset(bomap, 0, sizeof(*bomap) * n_bomap_pages);
panfrost_core_dump_header(&iter, PANFROSTDUMP_BUF_BOMAP,
bomap + n_bomap_pages);
}
for (i = 0; i < job->bo_count; i++) {
struct iosys_map map;
struct panfrost_gem_mapping *mapping;
struct panfrost_gem_object *bo;
struct sg_page_iter page_iter;
void *vaddr;
bo = to_panfrost_bo(job->bos[i]);
mapping = job->mappings[i];
if (!bo->base.sgt) {
dev_err(pfdev->dev, "Panfrost Dump: BO has no sgt, cannot dump\n");
iter.hdr->bomap.valid = 0;
goto dump_header;
}
ret = drm_gem_shmem_vmap(&bo->base, &map);
if (ret) {
dev_err(pfdev->dev, "Panfrost Dump: couldn't map Buffer Object\n");
iter.hdr->bomap.valid = 0;
goto dump_header;
}
WARN_ON(!mapping->active);
iter.hdr->bomap.data[0] = cpu_to_le32((bomap - bomap_start));
for_each_sgtable_page(bo->base.sgt, &page_iter, 0) {
struct page *page = sg_page_iter_page(&page_iter);
if (!IS_ERR(page)) {
*bomap++ = cpu_to_le64(page_to_phys(page));
} else {
dev_err(pfdev->dev, "Panfrost Dump: wrong page\n");
*bomap++ = ~cpu_to_le64(0);
}
}
iter.hdr->bomap.iova = cpu_to_le64(mapping->mmnode.start << PAGE_SHIFT);
vaddr = map.vaddr;
memcpy(iter.data, vaddr, bo->base.base.size);
drm_gem_shmem_vunmap(&bo->base, &map);
iter.hdr->bomap.valid = cpu_to_le32(1);
dump_header: panfrost_core_dump_header(&iter, PANFROSTDUMP_BUF_BO, iter.data +
bo->base.base.size);
}
panfrost_core_dump_header(&iter, PANFROSTDUMP_BUF_TRAILER, iter.data);
dev_coredumpv(pfdev->dev, iter.start, iter.data - iter.start, GFP_KERNEL);
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2021 Collabora ltd.
*/
#ifndef PANFROST_DUMP_H
#define PANFROST_DUMP_H
struct panfrost_job;
void panfrost_core_dump(struct panfrost_job *job);
#endif
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "panfrost_regs.h" #include "panfrost_regs.h"
#include "panfrost_gpu.h" #include "panfrost_gpu.h"
#include "panfrost_mmu.h" #include "panfrost_mmu.h"
#include "panfrost_dump.h"
#define JOB_TIMEOUT_MS 500 #define JOB_TIMEOUT_MS 500
...@@ -727,6 +728,8 @@ static enum drm_gpu_sched_stat panfrost_job_timedout(struct drm_sched_job ...@@ -727,6 +728,8 @@ static enum drm_gpu_sched_stat panfrost_job_timedout(struct drm_sched_job
job_read(pfdev, JS_TAIL_LO(js)), job_read(pfdev, JS_TAIL_LO(js)),
sched_job); sched_job);
panfrost_core_dump(job);
atomic_set(&pfdev->reset.pending, 1); atomic_set(&pfdev->reset.pending, 1);
panfrost_reset(pfdev, sched_job); panfrost_reset(pfdev, sched_job);
......
...@@ -224,6 +224,53 @@ struct drm_panfrost_madvise { ...@@ -224,6 +224,53 @@ struct drm_panfrost_madvise {
__u32 retained; /* out, whether backing store still exists */ __u32 retained; /* out, whether backing store still exists */
}; };
/* Definitions for coredump decoding in user space */
#define PANFROSTDUMP_MAJOR 1
#define PANFROSTDUMP_MINOR 0
#define PANFROSTDUMP_MAGIC 0x464E4150 /* PANF */
#define PANFROSTDUMP_BUF_REG 0
#define PANFROSTDUMP_BUF_BOMAP (PANFROSTDUMP_BUF_REG + 1)
#define PANFROSTDUMP_BUF_BO (PANFROSTDUMP_BUF_BOMAP + 1)
#define PANFROSTDUMP_BUF_TRAILER (PANFROSTDUMP_BUF_BO + 1)
struct panfrost_dump_object_header {
__le32 magic;
__le32 type;
__le32 file_size;
__le32 file_offset;
union {
struct pan_reg_hdr {
__le64 jc;
__le32 gpu_id;
__le32 major;
__le32 minor;
__le64 nbos;
} reghdr;
struct pan_bomap_hdr {
__le32 valid;
__le64 iova;
__le32 data[2];
} bomap;
/*
* Force same size in case we want to expand the header
* with new fields and also keep it 512-byte aligned
*/
__le32 sizer[496];
};
};
/* Registers object, an array of these */
struct panfrost_dump_registers {
__le32 reg;
__le32 value;
};
#if defined(__cplusplus) #if defined(__cplusplus)
} }
#endif #endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment