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

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
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ config DRM_PANFROST
	select DRM_GEM_SHMEM_HELPER
	select PM_DEVFREQ
	select DEVFREQ_GOV_SIMPLE_ONDEMAND
	select WANT_DEV_COREDUMP
	help
	  DRM driver for ARM Mali Midgard (T6xx, T7xx, T8xx) and
	  Bifrost (G3x, G5x, G7x) GPUs.
+2 −1
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ panfrost-y := \
	panfrost_gpu.o \
	panfrost_job.o \
	panfrost_mmu.o \
	panfrost_perfcnt.o
	panfrost_perfcnt.o \
	panfrost_dump.o

obj-$(CONFIG_DRM_PANFROST) += panfrost.o
+249 −0
Original line number Diff line number Diff line
// 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);
}
+12 −0
Original line number Diff line number Diff line
/* 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
+3 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include "panfrost_regs.h"
#include "panfrost_gpu.h"
#include "panfrost_mmu.h"
#include "panfrost_dump.h"

#define JOB_TIMEOUT_MS 500

@@ -727,6 +728,8 @@ static enum drm_gpu_sched_stat panfrost_job_timedout(struct drm_sched_job
		job_read(pfdev, JS_TAIL_LO(js)),
		sched_job);

	panfrost_core_dump(job);

	atomic_set(&pfdev->reset.pending, 1);
	panfrost_reset(pfdev, sched_job);

Loading