1 /*
2 * Copyright © 2022 Intel Corporation
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
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 */
24
25 #include <assert.h>
26 #include <getopt.h>
27 #include <inttypes.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdarg.h>
33 #include <zlib.h>
34
35 #include "util/list.h"
36
37 #include "common/intel_hang_dump.h"
38
39 #include "drm-uapi/i915_drm.h"
40
41 static inline void
_fail(const char * prefix,const char * format,...)42 _fail(const char *prefix, const char *format, ...)
43 {
44 va_list args;
45
46 va_start(args, format);
47 if (prefix)
48 fprintf(stderr, "%s: ", prefix);
49 vfprintf(stderr, format, args);
50 va_end(args);
51
52 abort();
53 }
54
55 #define _fail_if(cond, prefix, ...) do { \
56 if (cond) \
57 _fail(prefix, __VA_ARGS__); \
58 } while (0)
59
60 #define fail_if(cond, ...) _fail_if(cond, NULL, __VA_ARGS__)
61
62 #define fail(...) fail_if(true, __VA_ARGS__)
63
zlib_inflate(uint32_t ** ptr,int len)64 static int zlib_inflate(uint32_t **ptr, int len)
65 {
66 struct z_stream_s zstream;
67 void *out;
68 const uint32_t out_size = 128*4096; /* approximate obj size */
69
70 memset(&zstream, 0, sizeof(zstream));
71
72 zstream.next_in = (unsigned char *)*ptr;
73 zstream.avail_in = 4*len;
74
75 if (inflateInit(&zstream) != Z_OK)
76 return 0;
77
78 out = malloc(out_size);
79 zstream.next_out = out;
80 zstream.avail_out = out_size;
81
82 do {
83 switch (inflate(&zstream, Z_SYNC_FLUSH)) {
84 case Z_STREAM_END:
85 goto end;
86 case Z_OK:
87 break;
88 default:
89 inflateEnd(&zstream);
90 return 0;
91 }
92
93 if (zstream.avail_out)
94 break;
95
96 out = realloc(out, 2*zstream.total_out);
97 if (out == NULL) {
98 inflateEnd(&zstream);
99 return 0;
100 }
101
102 zstream.next_out = (unsigned char *)out + zstream.total_out;
103 zstream.avail_out = zstream.total_out;
104 } while (1);
105 end:
106 inflateEnd(&zstream);
107 free(*ptr);
108 *ptr = out;
109 return zstream.total_out / 4;
110 }
111
ascii85_decode(const char * in,uint32_t ** out,bool inflate)112 static int ascii85_decode(const char *in, uint32_t **out, bool inflate)
113 {
114 int len = 0, size = 1024;
115
116 *out = realloc(*out, sizeof(uint32_t)*size);
117 if (*out == NULL)
118 return 0;
119
120 while (*in >= '!' && *in <= 'z') {
121 uint32_t v = 0;
122
123 if (len == size) {
124 size *= 2;
125 *out = realloc(*out, sizeof(uint32_t)*size);
126 if (*out == NULL)
127 return 0;
128 }
129
130 if (*in == 'z') {
131 in++;
132 } else {
133 v += in[0] - 33; v *= 85;
134 v += in[1] - 33; v *= 85;
135 v += in[2] - 33; v *= 85;
136 v += in[3] - 33; v *= 85;
137 v += in[4] - 33;
138 in += 5;
139 }
140 (*out)[len++] = v;
141 }
142
143 if (!inflate)
144 return len;
145
146 return zlib_inflate(out, len);
147 }
148
149 static void
print_help(const char * progname,FILE * file)150 print_help(const char *progname, FILE *file)
151 {
152 fprintf(file,
153 "Usage: %s [OPTION]... [FILE]\n"
154 "Convert an Intel GPU i915 error state to a hang dump file, replayable with intel_hang_replay.\n"
155 " -h, --help display this help and exit\n"
156 " -o, --output=FILE the output dump file (default FILE.dmp)\n",
157 progname);
158 }
159
160 struct bo {
161 enum address_space {
162 PPGTT,
163 GGTT,
164 } gtt;
165 enum bo_type {
166 BO_TYPE_UNKNOWN = 0,
167 BO_TYPE_BATCH,
168 BO_TYPE_USER,
169 BO_TYPE_CONTEXT,
170 BO_TYPE_RINGBUFFER,
171 BO_TYPE_STATUS,
172 BO_TYPE_CONTEXT_WA,
173 } type;
174 const char *name;
175 uint64_t addr;
176 uint8_t *data;
177 uint64_t size;
178
179 enum drm_i915_gem_engine_class engine_class;
180 int engine_instance;
181
182 struct list_head link;
183 };
184
185 static struct bo *
find_or_create(struct list_head * bo_list,uint64_t addr,enum address_space gtt,enum drm_i915_gem_engine_class engine_class,int engine_instance)186 find_or_create(struct list_head *bo_list, uint64_t addr,
187 enum address_space gtt,
188 enum drm_i915_gem_engine_class engine_class,
189 int engine_instance)
190 {
191 list_for_each_entry(struct bo, bo_entry, bo_list, link) {
192 if (bo_entry->addr == addr &&
193 bo_entry->gtt == gtt &&
194 bo_entry->engine_class == engine_class &&
195 bo_entry->engine_instance == engine_instance)
196 return bo_entry;
197 }
198
199 struct bo *new_bo = calloc(1, sizeof(*new_bo));
200 new_bo->addr = addr;
201 new_bo->gtt = gtt;
202 new_bo->engine_class = engine_class;
203 new_bo->engine_instance = engine_instance;
204 list_addtail(&new_bo->link, bo_list);
205
206 return new_bo;
207 }
208
209 static void
engine_from_name(const char * engine_name,enum drm_i915_gem_engine_class * engine_class,int * engine_instance)210 engine_from_name(const char *engine_name,
211 enum drm_i915_gem_engine_class *engine_class,
212 int *engine_instance)
213 {
214 const struct {
215 const char *match;
216 enum drm_i915_gem_engine_class engine_class;
217 bool parse_instance;
218 } rings[] = {
219 { "rcs", I915_ENGINE_CLASS_RENDER, true },
220 { "vcs", I915_ENGINE_CLASS_VIDEO, true },
221 { "vecs", I915_ENGINE_CLASS_VIDEO_ENHANCE, true },
222 { "bcs", I915_ENGINE_CLASS_COPY, true },
223 { "global", I915_ENGINE_CLASS_INVALID, false },
224 { "render command stream", I915_ENGINE_CLASS_RENDER, false },
225 { "blt command stream", I915_ENGINE_CLASS_COPY, false },
226 { "bsd command stream", I915_ENGINE_CLASS_VIDEO, false },
227 { "vebox command stream", I915_ENGINE_CLASS_VIDEO_ENHANCE, false },
228 { NULL, I915_ENGINE_CLASS_INVALID },
229 }, *r;
230
231 for (r = rings; r->match; r++) {
232 if (strncasecmp(engine_name, r->match, strlen(r->match)) == 0) {
233 *engine_class = r->engine_class;
234 if (r->parse_instance)
235 *engine_instance = strtol(engine_name + strlen(r->match), NULL, 10);
236 else
237 *engine_instance = 0;
238 return;
239 }
240 }
241
242 fail("Unknown engine %s\n", engine_name);
243 }
244
245 static void
write_header(FILE * f)246 write_header(FILE *f)
247 {
248 struct intel_hang_dump_block_header header = {
249 .base = {
250 .type = INTEL_HANG_DUMP_BLOCK_TYPE_HEADER,
251 },
252 .magic = INTEL_HANG_DUMP_MAGIC,
253 .version = INTEL_HANG_DUMP_VERSION,
254 };
255
256 fwrite(&header, sizeof(header), 1, f);
257 }
258
259 static void
write_buffer(FILE * f,uint64_t offset,const void * data,uint64_t size,const char * name)260 write_buffer(FILE *f,
261 uint64_t offset,
262 const void *data,
263 uint64_t size,
264 const char *name)
265 {
266 struct intel_hang_dump_block_bo header = {
267 .base = {
268 .type = INTEL_HANG_DUMP_BLOCK_TYPE_BO,
269 },
270 .offset = offset,
271 .size = size,
272 };
273 snprintf(header.name, sizeof(header.name), "%s", name);
274
275 fwrite(&header, sizeof(header), 1, f);
276 fwrite(data, size, 1, f);
277 }
278
279 static void
write_hw_image_buffer(FILE * f,const void * data,uint64_t size)280 write_hw_image_buffer(FILE *f, const void *data, uint64_t size)
281 {
282 struct intel_hang_dump_block_hw_image header = {
283 .base = {
284 .type = INTEL_HANG_DUMP_BLOCK_TYPE_HW_IMAGE,
285 },
286 .size = size,
287 };
288
289 fwrite(&header, sizeof(header), 1, f);
290 fwrite(data, size, 1, f);
291 }
292
293 static void
write_exec(FILE * f,uint64_t offset)294 write_exec(FILE *f, uint64_t offset)
295 {
296 struct intel_hang_dump_block_exec header = {
297 .base = {
298 .type = INTEL_HANG_DUMP_BLOCK_TYPE_EXEC,
299 },
300 .offset = offset,
301 };
302
303 fwrite(&header, sizeof(header), 1, f);
304 }
305
306 int
main(int argc,char * argv[])307 main(int argc, char *argv[])
308 {
309 int i, c;
310 bool help = false, verbose = false;
311 char *out_filename = NULL, *in_filename = NULL, *capture_engine_name = "rcs";
312 const struct option aubinator_opts[] = {
313 { "help", no_argument, NULL, 'h' },
314 { "output", required_argument, NULL, 'o' },
315 { "verbose", no_argument, NULL, 'v' },
316 { "engine", required_argument, NULL, 'e' },
317 { NULL, 0, NULL, 0 }
318 };
319
320 i = 0;
321 while ((c = getopt_long(argc, argv, "ho:v", aubinator_opts, &i)) != -1) {
322 switch (c) {
323 case 'h':
324 help = true;
325 break;
326 case 'o':
327 out_filename = strdup(optarg);
328 break;
329 case 'v':
330 verbose = true;
331 break;
332 case 'e':
333 capture_engine_name = optarg;
334 break;
335 default:
336 break;
337 }
338 }
339
340 if (optind < argc)
341 in_filename = argv[optind++];
342
343 if (help || argc == 1 || !in_filename) {
344 print_help(argv[0], stderr);
345 return in_filename ? EXIT_SUCCESS : EXIT_FAILURE;
346 }
347
348 enum drm_i915_gem_engine_class capture_engine;
349 engine_from_name(capture_engine_name, &capture_engine, &c);
350
351 if (out_filename == NULL) {
352 int out_filename_size = strlen(in_filename) + 5;
353 out_filename = malloc(out_filename_size);
354 snprintf(out_filename, out_filename_size, "%s.dmp", in_filename);
355 }
356
357 FILE *err_file = fopen(in_filename, "r");
358 fail_if(!err_file, "Failed to open error file \"%s\": %m\n", in_filename);
359
360 FILE *hang_file = fopen(out_filename, "w");
361 fail_if(!hang_file, "Failed to open aub file \"%s\": %m\n", in_filename);
362
363 enum address_space active_gtt = PPGTT;
364 enum address_space default_gtt = PPGTT;
365
366 int num_ring_bos = 0;
367
368 struct list_head bo_list;
369 list_inithead(&bo_list);
370
371 struct bo *last_bo = NULL;
372
373 enum drm_i915_gem_engine_class active_engine_class = I915_ENGINE_CLASS_INVALID;
374 int active_engine_instance = -1;
375
376 char *line = NULL;
377 size_t line_size;
378 while (getline(&line, &line_size, err_file) > 0) {
379 if (strstr(line, " command stream:")) {
380 engine_from_name(line, &active_engine_class, &active_engine_instance);
381 continue;
382 }
383
384 if (num_ring_bos > 0) {
385 unsigned hi, lo, size;
386 if (sscanf(line, " %x_%x %d", &hi, &lo, &size) == 3) {
387 struct bo *bo_entry = find_or_create(&bo_list, ((uint64_t)hi) << 32 | lo,
388 active_gtt,
389 active_engine_class,
390 active_engine_instance);
391 bo_entry->size = size;
392 num_ring_bos--;
393 } else {
394 fail("Not enough BO entries in the active table\n");
395 }
396 continue;
397 }
398
399 if (line[0] == ':' || line[0] == '~') {
400 if (!last_bo || last_bo->type == BO_TYPE_UNKNOWN)
401 continue;
402
403 int count = ascii85_decode(line+1, (uint32_t **) &last_bo->data, line[0] == ':');
404 fail_if(count == 0, "ASCII85 decode failed.\n");
405 last_bo->size = count * 4;
406 continue;
407 }
408
409 char *dashes = strstr(line, " --- ");
410 if (dashes) {
411 dashes += 5;
412
413 engine_from_name(line, &active_engine_class, &active_engine_instance);
414
415 uint32_t hi, lo;
416 char *bo_address_str = strchr(dashes, '=');
417 if (!bo_address_str || sscanf(bo_address_str, "= 0x%08x %08x\n", &hi, &lo) != 2)
418 continue;
419
420 const struct {
421 const char *match;
422 enum bo_type type;
423 enum address_space gtt;
424 } bo_types[] = {
425 { "gtt_offset", BO_TYPE_BATCH, default_gtt },
426 { "batch", BO_TYPE_BATCH, default_gtt },
427 { "user", BO_TYPE_USER, default_gtt },
428 { "HW context", BO_TYPE_CONTEXT, GGTT },
429 { "ringbuffer", BO_TYPE_RINGBUFFER, GGTT },
430 { "HW Status", BO_TYPE_STATUS, GGTT },
431 { "WA context", BO_TYPE_CONTEXT_WA, GGTT },
432 { "unknown", BO_TYPE_UNKNOWN, GGTT },
433 }, *b;
434
435 for (b = bo_types; b->type != BO_TYPE_UNKNOWN; b++) {
436 if (strncasecmp(dashes, b->match, strlen(b->match)) == 0)
437 break;
438 }
439
440 last_bo = find_or_create(&bo_list, ((uint64_t) hi) << 32 | lo,
441 b->gtt,
442 active_engine_class, active_engine_instance);
443
444 /* The batch buffer will appear twice as gtt_offset and user. Only
445 * keep the batch type.
446 */
447 if (last_bo->type == BO_TYPE_UNKNOWN) {
448 last_bo->type = b->type;
449 last_bo->name = b->match;
450 }
451
452 continue;
453 }
454 }
455
456 if (verbose) {
457 fprintf(stdout, "BOs found:\n");
458 list_for_each_entry(struct bo, bo_entry, &bo_list, link) {
459 fprintf(stdout, "\t type=%i addr=0x%016" PRIx64 " size=%" PRIu64 "\n",
460 bo_entry->type, bo_entry->addr, bo_entry->size);
461 }
462 }
463
464 /* Find the batch that trigger the hang */
465 struct bo *batch_bo = NULL, *hw_image_bo = NULL;
466 list_for_each_entry(struct bo, bo_entry, &bo_list, link) {
467 if (batch_bo != NULL && hw_image_bo != NULL)
468 break;
469
470 if (bo_entry->engine_class != capture_engine)
471 continue;
472
473 switch (bo_entry->type) {
474 case BO_TYPE_BATCH:
475 batch_bo = bo_entry;
476 break;
477 case BO_TYPE_CONTEXT:
478 hw_image_bo = bo_entry;
479 break;
480 default:
481 break;
482 }
483 }
484 fail_if(!batch_bo, "Failed to find batch buffer.\n");
485 fail_if(!hw_image_bo, "Failed to find HW image buffer.\n");
486
487 /* Add all the user BOs to the aub file */
488 list_for_each_entry(struct bo, bo_entry, &bo_list, link) {
489 if (bo_entry->type == BO_TYPE_USER && bo_entry->gtt == PPGTT)
490 write_buffer(hang_file, bo_entry->addr, bo_entry->data, bo_entry->size, "user");
491 }
492
493 write_buffer(hang_file, batch_bo->addr, batch_bo->data, batch_bo->size, "batch");
494 fprintf(stderr, "writing image buffer 0x%016"PRIx64" size=0x%016"PRIx64"\n",
495 hw_image_bo->addr, hw_image_bo->size);
496 write_hw_image_buffer(hang_file, hw_image_bo->data, hw_image_bo->size);
497 write_exec(hang_file, batch_bo->addr);
498
499 /* Cleanup */
500 list_for_each_entry_safe(struct bo, bo_entry, &bo_list, link) {
501 list_del(&bo_entry->link);
502 free(bo_entry->data);
503 free(bo_entry);
504 }
505
506 free(out_filename);
507 free(line);
508 if (err_file)
509 fclose(err_file);
510 fclose(hang_file);
511
512 return EXIT_SUCCESS;
513 }
514
515 /* vim: set ts=8 sw=8 tw=0 cino=:0,(0 noet :*/
516