Module: Mesa Branch: main Commit: 03712579b04a063e2aac857dda98faa38ee3e760 URL: http://cgit.freedesktop.org/mesa/mesa/commit/?id=03712579b04a063e2aac857dda98faa38ee3e760
Author: Lionel Landwerlin <lionel.g.landwer...@intel.com> Date: Wed Feb 23 13:33:31 2022 +0200 intel/tools: add hang_replay tool Signed-off-by: Lionel Landwerlin <lionel.g.landwer...@intel.com> Acked-by: Tapani Pälli <tapani.pa...@intel.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/21167> --- src/intel/common/intel_hang_dump.h | 103 ++++++++ src/intel/tools/intel_hang_replay.c | 489 ++++++++++++++++++++++++++++++++++++ src/intel/tools/meson.build | 11 + 3 files changed, 603 insertions(+) diff --git a/src/intel/common/intel_hang_dump.h b/src/intel/common/intel_hang_dump.h new file mode 100644 index 00000000000..832225d472c --- /dev/null +++ b/src/intel/common/intel_hang_dump.h @@ -0,0 +1,103 @@ +/* + * Copyright © 2022 Intel Corporation + * + * 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. + */ +#ifndef INTEL_HANG_DUMP_H +#define INTEL_HANG_DUMP_H + +#include <stdint.h> + +/** + * This files contains a format description for data saved in a hang dump. + * This allows us to replay the dump later on simulation. + */ + +/* TODO: Consider compression? ZSTD_error_dstSize_tooSmall */ + +#define INTEL_HANG_DUMP_VERSION (1) +#define INTEL_HANG_DUMP_MAGIC (0x4245012345676463) + +enum intel_hang_dump_block_type { + INTEL_HANG_DUMP_BLOCK_TYPE_HEADER = 1, + INTEL_HANG_DUMP_BLOCK_TYPE_BO = 2, + INTEL_HANG_DUMP_BLOCK_TYPE_MAP = 3, + INTEL_HANG_DUMP_BLOCK_TYPE_EXEC = 4, +}; + +struct intel_hang_dump_block_base { + uint32_t type; /* enum intel_hang_dump_block_type */ + + uint32_t pad; +}; + +struct intel_hang_dump_block_header { + struct intel_hang_dump_block_base base; + + uint64_t magic; + + uint32_t version; + uint32_t pad; +}; + +struct intel_hang_dump_block_bo { + struct intel_hang_dump_block_base base; + + /* Helpful */ + char name[64]; + + /* PPGTT location */ + uint64_t offset; + + /* Buffer size */ + uint64_t size; + + /* Data follows */ +}; + +struct intel_hang_dump_block_map { + struct intel_hang_dump_block_base base; + + /* Helpful */ + char name[64]; + + /* PPGTT location */ + uint64_t offset; + + /* Buffer size */ + uint64_t size; +}; + +struct intel_hang_dump_block_exec { + struct intel_hang_dump_block_base base; + + /* PPGTT location */ + uint64_t offset; +}; + +union intel_hang_dump_block_all { + struct intel_hang_dump_block_base base; + struct intel_hang_dump_block_header header; + struct intel_hang_dump_block_bo bo; + struct intel_hang_dump_block_map map; + struct intel_hang_dump_block_exec exec; +}; + +#endif /* INTEL_HANG_DUMP_H */ diff --git a/src/intel/tools/intel_hang_replay.c b/src/intel/tools/intel_hang_replay.c new file mode 100644 index 00000000000..b4e270d1c6b --- /dev/null +++ b/src/intel/tools/intel_hang_replay.c @@ -0,0 +1,489 @@ +/* + * Copyright © 2022 Intel Corporation + * + * 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. + * + */ + +#include <fcntl.h> +#include <getopt.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <xf86drm.h> + +#include "common/intel_disasm.h" +#include "common/intel_gem.h" +#include "common/intel_hang_dump.h" +#include "dev/intel_device_info.h" + +#include "drm-uapi/i915_drm.h" + +#include "util/u_dynarray.h" +#include "util/u_math.h" + +static uint32_t +gem_create(int drm_fd, uint64_t size) +{ + struct drm_i915_gem_create gem_create = { + .size = size, + }; + + int ret = intel_ioctl(drm_fd, DRM_IOCTL_I915_GEM_CREATE, &gem_create); + if (ret != 0) { + /* FIXME: What do we do if this fails? */ + return 0; + } + + return gem_create.handle; +} + +static void* +gem_mmap_offset(int drm_fd, + uint32_t gem_handle, + uint64_t offset, + uint64_t size, + uint32_t flags) +{ + struct drm_i915_gem_mmap_offset gem_mmap = { + .handle = gem_handle, + .flags = I915_MMAP_OFFSET_WB, + }; + assert(offset == 0); + + /* Get the fake offset back */ + int ret = intel_ioctl(drm_fd, DRM_IOCTL_I915_GEM_MMAP_OFFSET, &gem_mmap); + if (ret != 0 && gem_mmap.flags == I915_MMAP_OFFSET_FIXED) { + gem_mmap.flags = + (flags & I915_MMAP_WC) ? I915_MMAP_OFFSET_WC : I915_MMAP_OFFSET_WB, + ret = intel_ioctl(drm_fd, DRM_IOCTL_I915_GEM_MMAP_OFFSET, &gem_mmap); + } + + if (ret != 0) + return MAP_FAILED; + + /* And map it */ + void *map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, + drm_fd, gem_mmap.offset); + return map; +} + +static void +write_gem_bo_data(int drm_fd, + uint32_t gem_handle, + int file_fd, + size_t size) +{ + void *map = gem_mmap_offset(drm_fd, gem_handle, 0, size, I915_MMAP_OFFSET_WB); + assert(map != MAP_FAILED); + + size_t total_read_len = 0; + ssize_t read_len; + while (total_read_len < size && + (read_len = read(file_fd, map + total_read_len, size - total_read_len)) > 0) { + total_read_len += read_len; + } + munmap(map, size); + + assert(total_read_len == size); +} + +static void +skip_data(int file_fd, size_t size) +{ + lseek(file_fd, size, SEEK_CUR); +} + +static int +get_drm_device(struct intel_device_info *devinfo) +{ + drmDevicePtr devices[8]; + int max_devices = drmGetDevices2(0, devices, 8); + + int i, fd = -1; + for (i = 0; i < max_devices; i++) { + if (devices[i]->available_nodes & 1 << DRM_NODE_RENDER && + devices[i]->bustype == DRM_BUS_PCI && + devices[i]->deviceinfo.pci->vendor_id == 0x8086) { + fd = open(devices[i]->nodes[DRM_NODE_RENDER], O_RDWR | O_CLOEXEC); + if (fd < 0) + continue; + + if (!intel_get_device_info_from_fd(fd, devinfo) || + devinfo->ver < 8) { + close(fd); + fd = -1; + continue; + } + + /* Found a device! */ + break; + } + } + + return fd; +} + +struct gem_bo { + off_t file_offset; + uint32_t gem_handle; + uint64_t offset; + uint64_t size; +}; + +static int +compare_bos(const void *b1, const void *b2) +{ + const struct gem_bo *gem_b1 = b1, *gem_b2 = b2; + + return gem_b2->size > gem_b1->size; +} + +static void +print_help(const char *filename, FILE *f) +{ + fprintf(f, "%s: %s [options]...\n", filename, filename); + fprintf(f, " -d, --dump FILE hang file to replay\n"); + fprintf(f, " -l, --list list content of hang file (no replay)\n"); + fprintf(f, " -s, --shader ADDR print shader at ADDR\n"); + fprintf(f, " -h, --help print this screen\n"); + fprintf(f, " -a, --address ADDR Find BO containing ADDR\n"); +} + +static int +execbuffer(int drm_fd, struct util_dynarray *execbuffer_bos, struct gem_bo *exec_bo, uint64_t exec_offset) +{ + struct drm_i915_gem_execbuffer2 execbuf = { + .buffers_ptr = (uintptr_t)(void *)util_dynarray_begin(execbuffer_bos), + .buffer_count = util_dynarray_num_elements(execbuffer_bos, + struct drm_i915_gem_exec_object2), + .batch_start_offset = exec_offset - exec_bo->offset, + .batch_len = exec_bo->size, + .flags = I915_EXEC_HANDLE_LUT | I915_EXEC_RENDER, + .rsvd1 = 0, + }; + + int ret = intel_ioctl(drm_fd, DRM_IOCTL_I915_GEM_EXECBUFFER2_WR, &execbuf); + if (ret == 0) { + struct drm_i915_gem_wait gem_wait = { + .bo_handle = exec_bo->gem_handle, + .timeout_ns = INT64_MAX, + }; + ret = intel_ioctl(drm_fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait); + if (ret) + fprintf(stderr, "wait failed: %m\n"); + } else { + fprintf(stderr, "execbuffer failed: %m\n"); + } + + return ret; +} + +int +main(int argc, char *argv[]) +{ + bool help = false, list = false; + const struct option aubinator_opts[] = { + { "address", required_argument, NULL, 'a' }, + { "dump", required_argument, NULL, 'd' }, + { "shader", required_argument, NULL, 's' }, + { "list", no_argument, NULL, 'l' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + void *mem_ctx = ralloc_context(NULL); + + struct util_dynarray shader_addresses; + + util_dynarray_init(&shader_addresses, mem_ctx); + + const char *file = NULL; + uint64_t check_addr = -1; + int c, i; + while ((c = getopt_long(argc, argv, "a:d:hls:", aubinator_opts, &i)) != -1) { + switch (c) { + case 'a': + check_addr = strtol(optarg, NULL, 0); + break; + case 'd': + file = optarg; + break; + case 's': { + uint64_t *addr = util_dynarray_grow(&shader_addresses, uint64_t, 1); + *addr = strtol(optarg, NULL, 0); + fprintf(stderr, "shader addr=0x%016lx\n", *addr); + break; + } + case 'h': + help = true; + break; + case 'l': + list = true; + break; + default: + break; + } + } + + if (help) { + print_help(argv[0], stderr); + exit(EXIT_SUCCESS); + } + + int file_fd = open(file, O_RDONLY); + if (file_fd < 0) + exit(EXIT_FAILURE); + + struct stat file_stats; + if (fstat(file_fd, &file_stats) != 0) + exit(EXIT_FAILURE); + + struct intel_device_info devinfo; + int drm_fd = get_drm_device(&devinfo); + if (drm_fd < 0) + exit(EXIT_FAILURE); + + struct util_dynarray buffers; + uint64_t total_vma = 0; + + util_dynarray_init(&buffers, mem_ctx); + + union intel_hang_dump_block_all block_header; + struct intel_hang_dump_block_exec init = {}, exec = {}; + + while (read(file_fd, &block_header.base, sizeof(block_header.base)) == + sizeof(block_header.base)) { + + static const size_t block_size[] = { + [INTEL_HANG_DUMP_BLOCK_TYPE_HEADER] = sizeof(struct intel_hang_dump_block_header), + [INTEL_HANG_DUMP_BLOCK_TYPE_BO] = sizeof(struct intel_hang_dump_block_bo), + [INTEL_HANG_DUMP_BLOCK_TYPE_MAP] = sizeof(struct intel_hang_dump_block_map), + [INTEL_HANG_DUMP_BLOCK_TYPE_EXEC] = sizeof(struct intel_hang_dump_block_exec), + }; + + assert(block_header.base.type < ARRAY_SIZE(block_size)); + + size_t remaining_size = block_size[block_header.base.type] - sizeof(block_header.base); + ssize_t ret = read(file_fd, &block_header.base + 1, remaining_size); + assert(ret == remaining_size); + + switch (block_header.base.type) { + case INTEL_HANG_DUMP_BLOCK_TYPE_HEADER: + assert(block_header.header.magic == INTEL_HANG_DUMP_MAGIC); + assert(block_header.header.version == INTEL_HANG_DUMP_VERSION); + break; + + case INTEL_HANG_DUMP_BLOCK_TYPE_BO: { + struct gem_bo *bo = util_dynarray_grow(&buffers, struct gem_bo, 1); + bo->file_offset = lseek(file_fd, 0, SEEK_CUR); + bo->offset = block_header.bo.offset; + bo->size = block_header.bo.size; + total_vma += bo->size; + skip_data(file_fd, bo->size); + if (list) { + fprintf(stderr, "buffer: offset=0x%016lx size=0x%016lx name=%s\n", + bo->offset, bo->size, block_header.bo.name); + } + break; + } + + case INTEL_HANG_DUMP_BLOCK_TYPE_MAP: { + struct gem_bo *bo = util_dynarray_grow(&buffers, struct gem_bo, 1); + bo->file_offset = 0; + bo->offset = block_header.map.offset; + bo->size = block_header.map.size; + total_vma += bo->size; + if (list) { + fprintf(stderr, "map : offset=0x%016lx size=0x%016lx name=%s\n", + bo->offset, bo->size, block_header.map.name); + } + break; + } + + case INTEL_HANG_DUMP_BLOCK_TYPE_EXEC: { + if (init.offset == 0) { + if (list) + fprintf(stderr, "init : offset=0x%016lx\n", block_header.exec.offset); + init = block_header.exec; + } else { + if (list) + fprintf(stderr, "exec : offset=0x%016lx\n", block_header.exec.offset); + exec = block_header.exec; + } + break; + } + + default: + unreachable("Invalid block type"); + } + } + + fprintf(stderr, "total_vma: 0x%016lx\n", total_vma); + + if (check_addr != -1) { + struct gem_bo *check_bo = NULL; + util_dynarray_foreach(&buffers, struct gem_bo, bo) { + if (check_addr >= bo->offset && check_addr < (bo->offset + bo->size)) { + check_bo = bo; + break; + } + } + + if (check_bo) { + fprintf(stderr, "address=0x%016lx found in buffer 0x%016lx size=0x%016lx\n", + check_addr, check_bo->offset, check_bo->size); + } else { + fprintf(stderr, "address=0x%016lx not found in buffer list\n", check_addr); + } + } + + util_dynarray_foreach(&shader_addresses, uint64_t, addr) { + bool found = false; + util_dynarray_foreach(&buffers, struct gem_bo, bo) { + if (*addr < bo->offset || *addr >= (bo->offset + bo->size)) + continue; + if (!bo->file_offset) + break; + + uint64_t aligned_offset = ROUND_DOWN_TO(bo->file_offset, 4096); + uint64_t remaining_length = file_stats.st_size - aligned_offset; + void *map = mmap(NULL, remaining_length, PROT_READ, MAP_PRIVATE, + file_fd, aligned_offset); + if (map == MAP_FAILED) + break; + + found = true; + fprintf(stderr, "shader at 0x%016lx file_offset=0%016lx addr_offset=%016lx:\n", *addr, + (bo->file_offset - aligned_offset), (*addr - bo->offset)); + struct brw_isa_info _isa, *isa = &_isa; + brw_init_isa_info(isa, &devinfo); + intel_disassemble(isa, + map + (bo->file_offset - aligned_offset) + (*addr - bo->offset), + 0, stderr); + + munmap(map, remaining_length); + } + + if (!found) + fprintf(stderr, "shader at 0x%016lx not found\n", *addr); + } + + if (!list && util_dynarray_num_elements(&shader_addresses, uint64_t) == 0) { + /* Sort buffers by size */ + qsort(util_dynarray_begin(&buffers), + util_dynarray_num_elements(&buffers, struct gem_bo), + sizeof(struct gem_bo), + compare_bos); + + /* Allocate BOs populate them */ + uint64_t gem_allocated = 0; + util_dynarray_foreach(&buffers, struct gem_bo, bo) { + bo->gem_handle = gem_create(drm_fd, bo->size); + if (bo->file_offset != 0) { + lseek(file_fd, bo->file_offset, SEEK_SET); + write_gem_bo_data(drm_fd, bo->gem_handle, file_fd, bo->size); + } + + gem_allocated += bo->size; + } + + struct util_dynarray execbuffer_bos; + util_dynarray_init(&execbuffer_bos, mem_ctx); + + struct gem_bo *init_bo = NULL, *batch_bo = NULL; + util_dynarray_foreach(&buffers, struct gem_bo, bo) { + if (bo->offset <= init.offset && + (bo->offset + bo->size) > init.offset) { + init_bo = bo; + continue; + } + + if (bo->offset <= exec.offset && + (bo->offset + bo->size) > exec.offset) { + batch_bo = bo; + continue; + } + + struct drm_i915_gem_exec_object2 *execbuf_bo = + util_dynarray_grow(&execbuffer_bos, struct drm_i915_gem_exec_object2, 1); + *execbuf_bo = (struct drm_i915_gem_exec_object2) { + .handle = bo->gem_handle, + .relocation_count = 0, + .relocs_ptr = 0, + .flags = EXEC_OBJECT_SUPPORTS_48B_ADDRESS | + EXEC_OBJECT_PINNED, + .offset = bo->offset, + }; + } + + assert(batch_bo != NULL); + + struct drm_i915_gem_exec_object2 *execbuf_bo = + util_dynarray_grow(&execbuffer_bos, struct drm_i915_gem_exec_object2, 1); + + int ret; + + fprintf(stderr, "init: 0x%016lx\n", init_bo->offset); + *execbuf_bo = (struct drm_i915_gem_exec_object2) { + .handle = init_bo->gem_handle, + .relocation_count = 0, + .relocs_ptr = 0, + .flags = EXEC_OBJECT_SUPPORTS_48B_ADDRESS | + EXEC_OBJECT_PINNED | + EXEC_OBJECT_WRITE /* to be able to wait on the BO */, + .offset = init_bo->offset, + }; + ret = execbuffer(drm_fd, &execbuffer_bos, init_bo, init.offset); + if (ret != 0) { + fprintf(stderr, "initialization buffer failed to execute errno=%i\n", errno); + exit(-1); + } + + fprintf(stderr, "exec: 0x%016lx aperture=%.2fMb\n", batch_bo->offset, + gem_allocated / 1024.0 / 1024.0); + *execbuf_bo = (struct drm_i915_gem_exec_object2) { + .handle = batch_bo->gem_handle, + .relocation_count = 0, + .relocs_ptr = 0, + .flags = EXEC_OBJECT_SUPPORTS_48B_ADDRESS | + EXEC_OBJECT_PINNED | + EXEC_OBJECT_WRITE /* to be able to wait on the BO */, + .offset = batch_bo->offset, + }; + ret = execbuffer(drm_fd, &execbuffer_bos, batch_bo, exec.offset); + if (ret != 0) { + fprintf(stderr, "replayed buffer failed to execute errno=%i\n", errno); + exit(-1); + } + } + + close(drm_fd); + close(file_fd); + + ralloc_free(mem_ctx); + + return EXIT_SUCCESS; +} diff --git a/src/intel/tools/meson.build b/src/intel/tools/meson.build index 44fa3f1d3e8..960545dffa0 100644 --- a/src/intel/tools/meson.build +++ b/src/intel/tools/meson.build @@ -72,6 +72,17 @@ error2aub = executable( install : true ) +intel_hang_replay = executable( + 'intel_hang_replay', + files('intel_hang_replay.c'), + dependencies : [idep_libintel_common, dep_libdrm, dep_dl, dep_thread, dep_m], + include_directories : [inc_include, inc_src, inc_intel], + link_with : [libintel_dev, libintel_compiler], + c_args : [no_override_init_args], + gnu_symbol_visibility : 'hidden', + install : true +) + sanitize_data = configuration_data() sanitize_data.set( 'install_libexecdir',