// SPDX-License-Identifier: GPL-2.0 /* Copyright 2021 Collabora ltd. */ #include #include #include #include #include #include #include #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); }