/* * Copyright (C) 2021 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ /* * Debug dump analyser for panfrost. In case of a gpu crash/hang, * the coredump should be found in: * * /sys/class/devcoredump/devcd/data * * The crashdump will hang around for 5min, it can be cleared by writing to * the file, ie: * * echo 1 > /sys/class/devcoredump/devcd/data * * (the driver won't log any new crashdumps until the previous one is cleared * or times out after 5min) */ #include #include #include #include #include #include #include #include #include #include #include #include "decode.h" /* Same as panfrost_dump_object_header, but with field * entries in host byte order */ struct panfrost_dump_object_header_ho { uint32_t magic; uint32_t type; uint32_t file_size; uint32_t file_offset; union { struct pan_reg_hdr_ho { uint64_t jc; uint32_t gpu_id; uint32_t major; uint32_t minor; uint64_t nbos; } reghdr; struct pan_bomap_hdr_ho { uint32_t valid; uint64_t iova; uint32_t data[2]; } bomap; uint32_t sizer[496]; }; }; #define MAX_BODUMP_FILENAME 32 #define GPU_PAGE_SIZE 4096 static bool read_header(FILE *fp, struct panfrost_dump_object_header_ho *pdoh) { /* Fields in the coredump file header structures * are found in little-endian order */ struct panfrost_dump_object_header doh_le; size_t nr; nr = fread(&doh_le, 1, sizeof(struct panfrost_dump_object_header), fp); if (nr < sizeof(struct panfrost_dump_object_header)) { fprintf(stderr, "Wrong header read\n"); return false; } /* Convert from little-endian to host byte order */ pdoh->magic = le32toh(doh_le.magic); if (pdoh->magic != PANFROSTDUMP_MAGIC) { fprintf(stderr, "Wrong header magic\n"); return false; } pdoh->type = le32toh(doh_le.type); pdoh->file_offset = le32toh(doh_le.file_offset); pdoh->file_size = le32toh(doh_le.file_size); switch (pdoh->type) { case PANFROSTDUMP_BUF_REG: pdoh->reghdr.jc = le64toh(doh_le.reghdr.jc); pdoh->reghdr.gpu_id = le32toh(doh_le.reghdr.gpu_id); pdoh->reghdr.major = le32toh(doh_le.reghdr.major); pdoh->reghdr.minor = le32toh(doh_le.reghdr.minor); pdoh->reghdr.nbos = le64toh(doh_le.reghdr.nbos); break; case PANFROSTDUMP_BUF_BO: pdoh->bomap.iova = le64toh(doh_le.bomap.iova); pdoh->bomap.valid = le32toh(doh_le.bomap.valid); pdoh->bomap.data[0] = le32toh(doh_le.bomap.data[0]); pdoh->bomap.data[1] = le32toh(doh_le.bomap.data[1]); } return true; } static bool read_register(uint32_t *ro, uint32_t *rv, FILE *fp) { /* Register pair we read form memory is * laid out in little-endian order */ struct panfrost_dump_registers reg_le; size_t nr; nr = fread(®_le, 1, sizeof(reg_le), fp); if (nr < sizeof(reg_le)) { fprintf(stderr, "Wrong register read\n"); return false; } *ro = le32toh(reg_le.reg); *rv = le32toh(reg_le.value); return true; } static bool read_page_addr(uint64_t *phys_page, FILE *fp) { uint64_t phys_addr_le; size_t nr; nr = fread(&phys_addr_le, 1, sizeof(uint64_t), fp); if (nr < sizeof(uint64_t)) { fprintf(stderr, "Wrong page address read\n"); /* Skip over to the next address */ if (fseek(fp, sizeof(uint64_t) - nr, SEEK_CUR)) { perror("fseek error"); return false; } return false; } *phys_page = le64toh(phys_addr_le); return true; } /* Keeping these definitions as global/static shouldn't be * an issue because this tool will always be single-threaded */ static FILE *hdr_fp; static FILE *data_fp; static char **bos; static uint32_t bo_num; static void cleanup(void) { if (hdr_fp != NULL) fclose(hdr_fp); if (data_fp != NULL) fclose(data_fp); if (bos != NULL) { for (int k = 0; k < bo_num; k++) free(bos[k]); free(bos); } } static void print_help(const char *progname, FILE *file) { fprintf(file, "Usage: %s [OPTION] inputfile\n" "Decode Panfrost coredump file.\n\n" " -h, --help display this help and exit\n" " -a, --addr print BO physical addresses\n" " -r, --regs print Panfrost HW registers\n" "Example:\n" " panfrostdump -a -r coredump.bin\n", progname); } int main(int argc, char *argv[]) { struct panfrost_dump_object_header_ho doh; bool print_addr = false; bool print_reg = false; uint32_t gpu_id = 0; uint64_t jc = 0; size_t nbytes; int i, j, k, c; if (argc < 2) { printf("Pass a coredump file\n"); return EXIT_FAILURE; } /* clang-format off */ const struct option longopts[] = { { "addr", no_argument, (int *) &print_addr, true }, { "regs", no_argument, (int *) &print_reg, true }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; /* clang-format on */ while ((c = getopt_long(argc, argv, "arh", longopts, NULL)) != -1) { switch (c) { case 'h': print_help(argv[0], stderr); return EXIT_SUCCESS; case 'a': print_addr = true; break; case 'r': print_reg = true; break; default: fprintf(stderr, "Unknown option\n"); print_help(argv[0], stderr); return EXIT_FAILURE; } } i = j = k = 0; atexit(cleanup); struct pandecode_context *ctx = pandecode_create_context(false); hdr_fp = fopen(argv[optind], "r"); if (!hdr_fp) { perror("failed to open file"); return EXIT_FAILURE; } data_fp = fopen(argv[optind], "r"); if (!data_fp) { perror("failed to open file"); return EXIT_FAILURE; } /* Read register header */ if (!read_header(hdr_fp, &doh)) return EXIT_FAILURE; if (fseek(data_fp, doh.file_offset, SEEK_SET)) { perror("fseek error"); return EXIT_FAILURE; } if (doh.type == PANFROSTDUMP_BUF_REG) { jc = doh.reghdr.jc; gpu_id = doh.reghdr.gpu_id; bo_num = doh.reghdr.nbos; bos = calloc(bo_num, sizeof(char *)); if (!bos) { fprintf(stderr, "Failed to allocate memory for BO pointer array\n"); return EXIT_FAILURE; } printf("JC: %" PRIX64 ", GPU_ID: %" PRIX32 "\n", jc, gpu_id); if (print_reg) { puts("GPU registers:"); for (i = 0; i < (doh.file_size / sizeof(struct panfrost_dump_registers)); i++) { uint32_t reg_offset; uint32_t reg_val; if (read_register(®_offset, ®_val, data_fp)) printf("0x%04X : 0x%08X\n", reg_offset, reg_val); } } } if (!read_header(hdr_fp, &doh)) return EXIT_FAILURE; if (doh.type == PANFROSTDUMP_BUF_BOMAP) { uint32_t bomap_offset = doh.file_offset; if (!jc || !gpu_id) { fprintf(stderr, "Missing initial dump header\n"); return EXIT_FAILURE; } if (!read_header(hdr_fp, &doh)) return EXIT_FAILURE; while (doh.type != PANFROSTDUMP_BUF_TRAILER) { if (doh.bomap.valid) { if (fseek(data_fp, bomap_offset + doh.bomap.data[0], SEEK_SET)) { perror("fseek error"); return EXIT_FAILURE; } if (print_addr) { printf("BO(%u) VA(%" PRIX64 ") SZ(%" PRIX32 ") page addresses:\n", j, doh.bomap.iova, doh.file_size); for (k = 0; k < (doh.file_size / GPU_PAGE_SIZE); k++) { uint64_t phys_addr; if (!read_page_addr(&phys_addr, data_fp)) continue; printf("%u: %" PRIX64 "\n", k, phys_addr); } } /* Copy the BO into external file */ char bodump_filename[MAX_BODUMP_FILENAME]; FILE *bodump; snprintf(bodump_filename, MAX_BODUMP_FILENAME, "bodump-%u.dump", j); if ((bodump = fopen(bodump_filename, "wb"))) { if (fseek(data_fp, doh.file_offset, SEEK_SET)) { perror("fseek error"); return EXIT_FAILURE; } bos[j] = malloc(doh.file_size); if (!bos[j]) { fprintf(stderr, "Failed to allocate memory for BO\n"); return EXIT_FAILURE; } fseek(data_fp, doh.file_offset, SEEK_SET); nbytes = fread(bos[j], 1, doh.file_size, data_fp); if (nbytes < doh.file_size) { fprintf(stderr, "Read less than BO size: %u\n", errno); return EXIT_FAILURE; } nbytes = fwrite(bos[j], 1, doh.file_size, bodump); if (nbytes < doh.file_size) { fprintf(stderr, "Failed to write BO contents into file: %u\n", errno); return EXIT_FAILURE; } fclose(bodump); pandecode_inject_mmap(ctx, doh.bomap.iova, bos[j], doh.file_size, NULL); } else { perror("failed to open BO dump file"); } } else { fprintf(stderr, "BO(%u) isn't valid\n", j); } if (!read_header(hdr_fp, &doh)) return EXIT_FAILURE; j++; } } else { if (!read_header(hdr_fp, &doh)) return EXIT_FAILURE; } if (doh.type != PANFROSTDUMP_BUF_TRAILER) fprintf(stderr, "Trailing header isn't right\n"); pandecode_jc(ctx, jc, gpu_id); pandecode_destroy_context(ctx); fclose(data_fp); fclose(hdr_fp); data_fp = hdr_fp = NULL; return EXIT_SUCCESS; }