root/drivers/gpu/drm/panfrost/panfrost_dump.c
// 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 = PANFROSTDUMP_MAGIC;
        hdr->type = type;
        hdr->file_offset = iter->data - iter->start;
        hdr->file_size = data_end - iter->data;

        iter->hdr++;
        iter->data += 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 = reg;
                dumpreg->value = 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;
        u64 *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->base.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 = job->jc;
        iter.hdr->reghdr.major = PANFROSTDUMP_MAJOR;
        iter.hdr->reghdr.minor = PANFROSTDUMP_MINOR;
        iter.hdr->reghdr.gpu_id = pfdev->features.id;
        iter.hdr->reghdr.nbos = 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->base.dev, "Panfrost Dump: BO has no sgt, cannot dump\n");
                        iter.hdr->bomap.valid = 0;
                        goto dump_header;
                }

                ret = drm_gem_vmap(&bo->base.base, &map);
                if (ret) {
                        dev_err(pfdev->base.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] = bomap - bomap_start;

                for_each_sgtable_page(bo->base.sgt, &page_iter, 0)
                        *bomap++ = page_to_phys(sg_page_iter_page(&page_iter));

                iter.hdr->bomap.iova = mapping->mmnode.start << PAGE_SHIFT;

                vaddr = map.vaddr;
                memcpy(iter.data, vaddr, bo->base.base.size);

                drm_gem_vunmap(&bo->base.base, &map);

                iter.hdr->bomap.valid = 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->base.dev, iter.start, iter.data - iter.start, GFP_KERNEL);
}