| // 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); | |
| } |