1// SPDX-License-Identifier: GPL-2.0
2/* Copyright 2021 Collabora ltd. */
3
4#include <linux/err.h>
5#include <linux/device.h>
6#include <linux/devcoredump.h>
7#include <linux/moduleparam.h>
8#include <linux/iosys-map.h>
9#include <drm/panfrost_drm.h>
10#include <drm/drm_device.h>
11
12#include "panfrost_job.h"
13#include "panfrost_gem.h"
14#include "panfrost_regs.h"
15#include "panfrost_dump.h"
16#include "panfrost_device.h"
17
18static bool panfrost_dump_core = true;
19module_param_named(dump_core, panfrost_dump_core, bool, 0600);
20
21struct panfrost_dump_iterator {
22 void *start;
23 struct panfrost_dump_object_header *hdr;
24 void *data;
25};
26
27static const unsigned short panfrost_dump_registers[] = {
28 SHADER_READY_LO,
29 SHADER_READY_HI,
30 TILER_READY_LO,
31 TILER_READY_HI,
32 L2_READY_LO,
33 L2_READY_HI,
34 JOB_INT_MASK,
35 JOB_INT_STAT,
36 JS_HEAD_LO(0),
37 JS_HEAD_HI(0),
38 JS_TAIL_LO(0),
39 JS_TAIL_HI(0),
40 JS_AFFINITY_LO(0),
41 JS_AFFINITY_HI(0),
42 JS_CONFIG(0),
43 JS_STATUS(0),
44 JS_HEAD_NEXT_LO(0),
45 JS_HEAD_NEXT_HI(0),
46 JS_AFFINITY_NEXT_LO(0),
47 JS_AFFINITY_NEXT_HI(0),
48 JS_CONFIG_NEXT(0),
49 MMU_INT_MASK,
50 MMU_INT_STAT,
51 AS_TRANSTAB_LO(0),
52 AS_TRANSTAB_HI(0),
53 AS_MEMATTR_LO(0),
54 AS_MEMATTR_HI(0),
55 AS_FAULTSTATUS(0),
56 AS_FAULTADDRESS_LO(0),
57 AS_FAULTADDRESS_HI(0),
58 AS_STATUS(0),
59};
60
61static void panfrost_core_dump_header(struct panfrost_dump_iterator *iter,
62 u32 type, void *data_end)
63{
64 struct panfrost_dump_object_header *hdr = iter->hdr;
65
66 hdr->magic = PANFROSTDUMP_MAGIC;
67 hdr->type = type;
68 hdr->file_offset = iter->data - iter->start;
69 hdr->file_size = data_end - iter->data;
70
71 iter->hdr++;
72 iter->data += hdr->file_size;
73}
74
75static void
76panfrost_core_dump_registers(struct panfrost_dump_iterator *iter,
77 struct panfrost_device *pfdev,
78 u32 as_nr, int slot)
79{
80 struct panfrost_dump_registers *dumpreg = iter->data;
81 unsigned int i;
82
83 for (i = 0; i < ARRAY_SIZE(panfrost_dump_registers); i++, dumpreg++) {
84 unsigned int js_as_offset = 0;
85 unsigned int reg;
86
87 if (panfrost_dump_registers[i] >= JS_BASE &&
88 panfrost_dump_registers[i] <= JS_BASE + JS_SLOT_STRIDE)
89 js_as_offset = slot * JS_SLOT_STRIDE;
90 else if (panfrost_dump_registers[i] >= MMU_BASE &&
91 panfrost_dump_registers[i] <= MMU_BASE + MMU_AS_STRIDE)
92 js_as_offset = (as_nr << MMU_AS_SHIFT);
93
94 reg = panfrost_dump_registers[i] + js_as_offset;
95
96 dumpreg->reg = reg;
97 dumpreg->value = gpu_read(pfdev, reg);
98 }
99
100 panfrost_core_dump_header(iter, PANFROSTDUMP_BUF_REG, data_end: dumpreg);
101}
102
103void panfrost_core_dump(struct panfrost_job *job)
104{
105 struct panfrost_device *pfdev = job->pfdev;
106 struct panfrost_dump_iterator iter;
107 struct drm_gem_object *dbo;
108 unsigned int n_obj, n_bomap_pages;
109 u64 *bomap, *bomap_start;
110 size_t file_size;
111 u32 as_nr;
112 int slot;
113 int ret, i;
114
115 as_nr = job->mmu->as;
116 slot = panfrost_job_get_slot(job);
117
118 /* Only catch the first event, or when manually re-armed */
119 if (!panfrost_dump_core)
120 return;
121 panfrost_dump_core = false;
122
123 /* At least, we dump registers and end marker */
124 n_obj = 2;
125 n_bomap_pages = 0;
126 file_size = ARRAY_SIZE(panfrost_dump_registers) *
127 sizeof(struct panfrost_dump_registers);
128
129 /* Add in the active buffer objects */
130 for (i = 0; i < job->bo_count; i++) {
131 /*
132 * Even though the CPU could be configured to use 16K or 64K pages, this
133 * is a very unusual situation for most kernel setups on SoCs that have
134 * a Panfrost device. Also many places across the driver make the somewhat
135 * arbitrary assumption that Panfrost's MMU page size is the same as the CPU's,
136 * so let's have a sanity check to ensure that's always the case
137 */
138 dbo = job->bos[i];
139 WARN_ON(!IS_ALIGNED(dbo->size, PAGE_SIZE));
140
141 file_size += dbo->size;
142 n_bomap_pages += dbo->size >> PAGE_SHIFT;
143 n_obj++;
144 }
145
146 /* If we have any buffer objects, add a bomap object */
147 if (n_bomap_pages) {
148 file_size += n_bomap_pages * sizeof(*bomap);
149 n_obj++;
150 }
151
152 /* Add the size of the headers */
153 file_size += sizeof(*iter.hdr) * n_obj;
154
155 /*
156 * Allocate the file in vmalloc memory, it's likely to be big.
157 * The reason behind these GFP flags is that we don't want to trigger the
158 * OOM killer in the event that not enough memory could be found for our
159 * dump file. We also don't want the allocator to do any error reporting,
160 * as the right behaviour is failing gracefully if a big enough buffer
161 * could not be allocated.
162 */
163 iter.start = __vmalloc(size: file_size, GFP_KERNEL | __GFP_NOWARN |
164 __GFP_NORETRY);
165 if (!iter.start) {
166 dev_warn(pfdev->dev, "failed to allocate devcoredump file\n");
167 return;
168 }
169
170 /* Point the data member after the headers */
171 iter.hdr = iter.start;
172 iter.data = &iter.hdr[n_obj];
173
174 memset(iter.hdr, 0, iter.data - iter.start);
175
176 /*
177 * For now, we write the job identifier in the register dump header,
178 * so that we can decode the entire dump later with pandecode
179 */
180 iter.hdr->reghdr.jc = job->jc;
181 iter.hdr->reghdr.major = PANFROSTDUMP_MAJOR;
182 iter.hdr->reghdr.minor = PANFROSTDUMP_MINOR;
183 iter.hdr->reghdr.gpu_id = pfdev->features.id;
184 iter.hdr->reghdr.nbos = job->bo_count;
185
186 panfrost_core_dump_registers(iter: &iter, pfdev, as_nr, slot);
187
188 /* Reserve space for the bomap */
189 if (job->bo_count) {
190 bomap_start = bomap = iter.data;
191 memset(bomap, 0, sizeof(*bomap) * n_bomap_pages);
192 panfrost_core_dump_header(iter: &iter, PANFROSTDUMP_BUF_BOMAP,
193 data_end: bomap + n_bomap_pages);
194 }
195
196 for (i = 0; i < job->bo_count; i++) {
197 struct iosys_map map;
198 struct panfrost_gem_mapping *mapping;
199 struct panfrost_gem_object *bo;
200 struct sg_page_iter page_iter;
201 void *vaddr;
202
203 bo = to_panfrost_bo(obj: job->bos[i]);
204 mapping = job->mappings[i];
205
206 if (!bo->base.sgt) {
207 dev_err(pfdev->dev, "Panfrost Dump: BO has no sgt, cannot dump\n");
208 iter.hdr->bomap.valid = 0;
209 goto dump_header;
210 }
211
212 ret = drm_gem_vmap_unlocked(obj: &bo->base.base, map: &map);
213 if (ret) {
214 dev_err(pfdev->dev, "Panfrost Dump: couldn't map Buffer Object\n");
215 iter.hdr->bomap.valid = 0;
216 goto dump_header;
217 }
218
219 WARN_ON(!mapping->active);
220
221 iter.hdr->bomap.data[0] = bomap - bomap_start;
222
223 for_each_sgtable_page(bo->base.sgt, &page_iter, 0) {
224 struct page *page = sg_page_iter_page(piter: &page_iter);
225
226 if (!IS_ERR(ptr: page)) {
227 *bomap++ = page_to_phys(page);
228 } else {
229 dev_err(pfdev->dev, "Panfrost Dump: wrong page\n");
230 *bomap++ = 0;
231 }
232 }
233
234 iter.hdr->bomap.iova = mapping->mmnode.start << PAGE_SHIFT;
235
236 vaddr = map.vaddr;
237 memcpy(iter.data, vaddr, bo->base.base.size);
238
239 drm_gem_vunmap_unlocked(obj: &bo->base.base, map: &map);
240
241 iter.hdr->bomap.valid = 1;
242
243dump_header: panfrost_core_dump_header(iter: &iter, PANFROSTDUMP_BUF_BO, data_end: iter.data +
244 bo->base.base.size);
245 }
246 panfrost_core_dump_header(iter: &iter, PANFROSTDUMP_BUF_TRAILER, data_end: iter.data);
247
248 dev_coredumpv(dev: pfdev->dev, data: iter.start, datalen: iter.data - iter.start, GFP_KERNEL);
249}
250

source code of linux/drivers/gpu/drm/panfrost/panfrost_dump.c