1 /*
2 * Copyright (C) 2021 Collabora, Ltd.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 *
23 */
24
25 /*
26 * Debug dump analyser for panfrost. In case of a gpu crash/hang,
27 * the coredump should be found in:
28 *
29 * /sys/class/devcoredump/devcd<n>/data
30 *
31 * The crashdump will hang around for 5min, it can be cleared by writing to
32 * the file, ie:
33 *
34 * echo 1 > /sys/class/devcoredump/devcd<n>/data
35 *
36 * (the driver won't log any new crashdumps until the previous one is cleared
37 * or times out after 5min)
38 */
39
40 #include <endian.h>
41 #include <errno.h>
42 #include <getopt.h>
43 #include <inttypes.h>
44 #include <stdbool.h>
45 #include <stdint.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50
51 #include <drm-uapi/panfrost_drm.h>
52
53 #include "decode.h"
54
55 /* Same as panfrost_dump_object_header, but with field
56 * entries in host byte order
57 */
58 struct panfrost_dump_object_header_ho {
59 uint32_t magic;
60 uint32_t type;
61 uint32_t file_size;
62 uint32_t file_offset;
63
64 union {
65 struct pan_reg_hdr_ho {
66 uint64_t jc;
67 uint32_t gpu_id;
68 uint32_t major;
69 uint32_t minor;
70 uint64_t nbos;
71 } reghdr;
72
73 struct pan_bomap_hdr_ho {
74 uint32_t valid;
75 uint64_t iova;
76 uint32_t data[2];
77 } bomap;
78
79 uint32_t sizer[496];
80 };
81 };
82
83 #define MAX_BODUMP_FILENAME 32
84 #define GPU_PAGE_SIZE 4096
85
86 static bool
read_header(FILE * fp,struct panfrost_dump_object_header_ho * pdoh)87 read_header(FILE *fp, struct panfrost_dump_object_header_ho *pdoh)
88 {
89 /* Fields in the coredump file header structures
90 * are found in little-endian order
91 */
92 struct panfrost_dump_object_header doh_le;
93 size_t nr;
94
95 nr = fread(&doh_le, 1, sizeof(struct panfrost_dump_object_header), fp);
96 if (nr < sizeof(struct panfrost_dump_object_header)) {
97 fprintf(stderr, "Wrong header read\n");
98 return false;
99 }
100
101 /* Convert from little-endian to host byte order */
102 pdoh->magic = le32toh(doh_le.magic);
103 if (pdoh->magic != PANFROSTDUMP_MAGIC) {
104 fprintf(stderr, "Wrong header magic\n");
105 return false;
106 }
107
108 pdoh->type = le32toh(doh_le.type);
109 pdoh->file_offset = le32toh(doh_le.file_offset);
110 pdoh->file_size = le32toh(doh_le.file_size);
111
112 switch (pdoh->type) {
113 case PANFROSTDUMP_BUF_REG:
114 pdoh->reghdr.jc = le64toh(doh_le.reghdr.jc);
115 pdoh->reghdr.gpu_id = le32toh(doh_le.reghdr.gpu_id);
116 pdoh->reghdr.major = le32toh(doh_le.reghdr.major);
117 pdoh->reghdr.minor = le32toh(doh_le.reghdr.minor);
118 pdoh->reghdr.nbos = le64toh(doh_le.reghdr.nbos);
119 break;
120 case PANFROSTDUMP_BUF_BO:
121 pdoh->bomap.iova = le64toh(doh_le.bomap.iova);
122 pdoh->bomap.valid = le32toh(doh_le.bomap.valid);
123 pdoh->bomap.data[0] = le32toh(doh_le.bomap.data[0]);
124 pdoh->bomap.data[1] = le32toh(doh_le.bomap.data[1]);
125 }
126
127 return true;
128 }
129
130 static bool
read_register(uint32_t * ro,uint32_t * rv,FILE * fp)131 read_register(uint32_t *ro, uint32_t *rv, FILE *fp)
132 {
133 /* Register pair we read form memory is
134 * laid out in little-endian order
135 */
136 struct panfrost_dump_registers reg_le;
137 size_t nr;
138
139 nr = fread(®_le, 1, sizeof(reg_le), fp);
140 if (nr < sizeof(reg_le)) {
141 fprintf(stderr, "Wrong register read\n");
142 return false;
143 }
144
145 *ro = le32toh(reg_le.reg);
146 *rv = le32toh(reg_le.value);
147
148 return true;
149 }
150
151 static bool
read_page_addr(uint64_t * phys_page,FILE * fp)152 read_page_addr(uint64_t *phys_page, FILE *fp)
153 {
154 uint64_t phys_addr_le;
155 size_t nr;
156
157 nr = fread(&phys_addr_le, 1, sizeof(uint64_t), fp);
158 if (nr < sizeof(uint64_t)) {
159 fprintf(stderr, "Wrong page address read\n");
160
161 /* Skip over to the next address */
162 if (fseek(fp, sizeof(uint64_t) - nr, SEEK_CUR)) {
163 perror("fseek error");
164 return false;
165 }
166
167 return false;
168 }
169
170 *phys_page = le64toh(phys_addr_le);
171
172 return true;
173 }
174
175 /* Keeping these definitions as global/static shouldn't be
176 * an issue because this tool will always be single-threaded
177 */
178 static FILE *hdr_fp;
179 static FILE *data_fp;
180 static char **bos;
181 static uint32_t bo_num;
182
183 static void
cleanup(void)184 cleanup(void)
185 {
186 if (hdr_fp != NULL)
187 fclose(hdr_fp);
188 if (data_fp != NULL)
189 fclose(data_fp);
190 if (bos != NULL) {
191 for (int k = 0; k < bo_num; k++)
192 free(bos[k]);
193 free(bos);
194 }
195 }
196
197 static void
print_help(const char * progname,FILE * file)198 print_help(const char *progname, FILE *file)
199 {
200 fprintf(file,
201 "Usage: %s [OPTION] inputfile\n"
202 "Decode Panfrost coredump file.\n\n"
203 " -h, --help display this help and exit\n"
204 " -a, --addr print BO physical addresses\n"
205 " -r, --regs print Panfrost HW registers\n"
206 "Example:\n"
207 " panfrostdump -a -r coredump.bin\n",
208 progname);
209 }
210
211 int
main(int argc,char * argv[])212 main(int argc, char *argv[])
213 {
214 struct panfrost_dump_object_header_ho doh;
215 bool print_addr = false;
216 bool print_reg = false;
217 uint32_t gpu_id = 0;
218 uint64_t jc = 0;
219 size_t nbytes;
220 int i, j, k, c;
221
222 if (argc < 2) {
223 printf("Pass a coredump file\n");
224 return EXIT_FAILURE;
225 }
226
227 /* clang-format off */
228 const struct option longopts[] = {
229 { "addr", no_argument, (int *) &print_addr, true },
230 { "regs", no_argument, (int *) &print_reg, true },
231 { "help", no_argument, NULL, 'h' },
232 { NULL, 0, NULL, 0 }
233 };
234 /* clang-format on */
235
236 while ((c = getopt_long(argc, argv, "arh", longopts, NULL)) != -1) {
237 switch (c) {
238 case 'h':
239 print_help(argv[0], stderr);
240 return EXIT_SUCCESS;
241 case 'a':
242 print_addr = true;
243 break;
244 case 'r':
245 print_reg = true;
246 break;
247 default:
248 fprintf(stderr, "Unknown option\n");
249 print_help(argv[0], stderr);
250 return EXIT_FAILURE;
251 }
252 }
253
254 i = j = k = 0;
255
256 atexit(cleanup);
257 struct pandecode_context *ctx = pandecode_create_context(false);
258
259 hdr_fp = fopen(argv[optind], "r");
260 if (!hdr_fp) {
261 perror("failed to open file");
262 return EXIT_FAILURE;
263 }
264
265 data_fp = fopen(argv[optind], "r");
266 if (!data_fp) {
267 perror("failed to open file");
268 return EXIT_FAILURE;
269 }
270
271 /* Read register header */
272 if (!read_header(hdr_fp, &doh))
273 return EXIT_FAILURE;
274
275 if (fseek(data_fp, doh.file_offset, SEEK_SET)) {
276 perror("fseek error");
277 return EXIT_FAILURE;
278 }
279
280 if (doh.type == PANFROSTDUMP_BUF_REG) {
281 jc = doh.reghdr.jc;
282 gpu_id = doh.reghdr.gpu_id;
283 bo_num = doh.reghdr.nbos;
284
285 bos = calloc(bo_num, sizeof(char *));
286 if (!bos) {
287 fprintf(stderr, "Failed to allocate memory for BO pointer array\n");
288 return EXIT_FAILURE;
289 }
290
291 printf("JC: %" PRIX64 ", GPU_ID: %" PRIX32 "\n", jc, gpu_id);
292
293 if (print_reg) {
294 puts("GPU registers:");
295 for (i = 0;
296 i < (doh.file_size / sizeof(struct panfrost_dump_registers));
297 i++) {
298 uint32_t reg_offset;
299 uint32_t reg_val;
300
301 if (read_register(®_offset, ®_val, data_fp))
302 printf("0x%04X : 0x%08X\n", reg_offset, reg_val);
303 }
304 }
305 }
306
307 if (!read_header(hdr_fp, &doh))
308 return EXIT_FAILURE;
309
310 if (doh.type == PANFROSTDUMP_BUF_BOMAP) {
311 uint32_t bomap_offset = doh.file_offset;
312
313 if (!jc || !gpu_id) {
314 fprintf(stderr, "Missing initial dump header\n");
315 return EXIT_FAILURE;
316 }
317
318 if (!read_header(hdr_fp, &doh))
319 return EXIT_FAILURE;
320
321 while (doh.type != PANFROSTDUMP_BUF_TRAILER) {
322 if (doh.bomap.valid) {
323 if (fseek(data_fp, bomap_offset + doh.bomap.data[0], SEEK_SET)) {
324 perror("fseek error");
325 return EXIT_FAILURE;
326 }
327
328 if (print_addr) {
329 printf("BO(%u) VA(%" PRIX64 ") SZ(%" PRIX32
330 ") page addresses:\n",
331 j, doh.bomap.iova, doh.file_size);
332
333 for (k = 0; k < (doh.file_size / GPU_PAGE_SIZE); k++) {
334 uint64_t phys_addr;
335
336 if (!read_page_addr(&phys_addr, data_fp))
337 continue;
338
339 printf("%u: %" PRIX64 "\n", k, phys_addr);
340 }
341 }
342
343 /* Copy the BO into external file */
344 char bodump_filename[MAX_BODUMP_FILENAME];
345 FILE *bodump;
346
347 snprintf(bodump_filename, MAX_BODUMP_FILENAME, "bodump-%u.dump", j);
348
349 if ((bodump = fopen(bodump_filename, "wb"))) {
350 if (fseek(data_fp, doh.file_offset, SEEK_SET)) {
351 perror("fseek error");
352 return EXIT_FAILURE;
353 }
354
355 bos[j] = malloc(doh.file_size);
356 if (!bos[j]) {
357 fprintf(stderr, "Failed to allocate memory for BO\n");
358 return EXIT_FAILURE;
359 }
360
361 fseek(data_fp, doh.file_offset, SEEK_SET);
362
363 nbytes = fread(bos[j], 1, doh.file_size, data_fp);
364 if (nbytes < doh.file_size) {
365 fprintf(stderr, "Read less than BO size: %u\n", errno);
366 return EXIT_FAILURE;
367 }
368 nbytes = fwrite(bos[j], 1, doh.file_size, bodump);
369 if (nbytes < doh.file_size) {
370 fprintf(stderr, "Failed to write BO contents into file: %u\n",
371 errno);
372 return EXIT_FAILURE;
373 }
374
375 fclose(bodump);
376
377 pandecode_inject_mmap(ctx, doh.bomap.iova, bos[j], doh.file_size,
378 NULL);
379
380 } else {
381 perror("failed to open BO dump file");
382 }
383 } else {
384 fprintf(stderr, "BO(%u) isn't valid\n", j);
385 }
386
387 if (!read_header(hdr_fp, &doh))
388 return EXIT_FAILURE;
389
390 j++;
391 }
392 } else {
393 if (!read_header(hdr_fp, &doh))
394 return EXIT_FAILURE;
395 }
396
397 if (doh.type != PANFROSTDUMP_BUF_TRAILER)
398 fprintf(stderr, "Trailing header isn't right\n");
399
400 pandecode_jc(ctx, jc, gpu_id);
401 pandecode_destroy_context(ctx);
402
403 fclose(data_fp);
404 fclose(hdr_fp);
405 data_fp = hdr_fp = NULL;
406
407 return EXIT_SUCCESS;
408 }
409