| /* |
| * Copyright 2015 Advanced Micro Devices, Inc. |
| * |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| #include "ac_debug.h" |
| #include "sid.h" |
| #include "sid_tables.h" |
| |
| #include "util/u_string.h" |
| |
| #include <inttypes.h> |
| |
| const struct si_reg *ac_find_register(enum amd_gfx_level gfx_level, enum radeon_family family, |
| unsigned offset) |
| { |
| const struct si_reg *table; |
| unsigned table_size; |
| |
| switch (gfx_level) { |
| case GFX12: |
| table = gfx12_reg_table; |
| table_size = ARRAY_SIZE(gfx12_reg_table); |
| break; |
| case GFX11_5: |
| table = gfx115_reg_table; |
| table_size = ARRAY_SIZE(gfx115_reg_table); |
| break; |
| case GFX11: |
| table = gfx11_reg_table; |
| table_size = ARRAY_SIZE(gfx11_reg_table); |
| break; |
| case GFX10_3: |
| table = gfx103_reg_table; |
| table_size = ARRAY_SIZE(gfx103_reg_table); |
| break; |
| case GFX10: |
| table = gfx10_reg_table; |
| table_size = ARRAY_SIZE(gfx10_reg_table); |
| break; |
| case GFX9: |
| if (family == CHIP_GFX940) { |
| table = gfx940_reg_table; |
| table_size = ARRAY_SIZE(gfx940_reg_table); |
| break; |
| } |
| table = gfx9_reg_table; |
| table_size = ARRAY_SIZE(gfx9_reg_table); |
| break; |
| case GFX8: |
| if (family == CHIP_STONEY) { |
| table = gfx81_reg_table; |
| table_size = ARRAY_SIZE(gfx81_reg_table); |
| break; |
| } |
| table = gfx8_reg_table; |
| table_size = ARRAY_SIZE(gfx8_reg_table); |
| break; |
| case GFX7: |
| table = gfx7_reg_table; |
| table_size = ARRAY_SIZE(gfx7_reg_table); |
| break; |
| case GFX6: |
| table = gfx6_reg_table; |
| table_size = ARRAY_SIZE(gfx6_reg_table); |
| break; |
| default: |
| return NULL; |
| } |
| |
| for (unsigned i = 0; i < table_size; i++) { |
| const struct si_reg *reg = &table[i]; |
| |
| if (reg->offset == offset) |
| return reg; |
| } |
| |
| return NULL; |
| } |
| |
| const char *ac_get_register_name(enum amd_gfx_level gfx_level, enum radeon_family family, |
| unsigned offset) |
| { |
| const struct si_reg *reg = ac_find_register(gfx_level, family, offset); |
| |
| return reg ? sid_strings + reg->name_offset : "(no name)"; |
| } |
| |
| bool ac_register_exists(enum amd_gfx_level gfx_level, enum radeon_family family, |
| unsigned offset) |
| { |
| return ac_find_register(gfx_level, family, offset) != NULL; |
| } |
| |
| /** |
| * Parse dmesg and return TRUE if a VM fault has been detected. |
| * |
| * \param gfx_level gfx level |
| * \param old_dmesg_timestamp previous dmesg timestamp parsed at init time |
| * \param out_addr detected VM fault addr |
| */ |
| bool ac_vm_fault_occurred(enum amd_gfx_level gfx_level, uint64_t *old_dmesg_timestamp, |
| uint64_t *out_addr) |
| { |
| #ifdef _WIN32 |
| return false; |
| #else |
| char line[2000]; |
| unsigned sec, usec; |
| int progress = 0; |
| uint64_t dmesg_timestamp = 0; |
| bool fault = false; |
| |
| FILE *p = popen("dmesg", "r"); |
| if (!p) |
| return false; |
| |
| while (fgets(line, sizeof(line), p)) { |
| char *msg, len; |
| |
| if (!line[0] || line[0] == '\n') |
| continue; |
| |
| /* Get the timestamp. */ |
| if (sscanf(line, "[%u.%u]", &sec, &usec) != 2) { |
| static bool hit = false; |
| if (!hit) { |
| fprintf(stderr, "%s: failed to parse line '%s'\n", __func__, line); |
| hit = true; |
| } |
| continue; |
| } |
| dmesg_timestamp = sec * 1000000ull + usec; |
| |
| /* If just updating the timestamp. */ |
| if (!out_addr) |
| continue; |
| |
| /* Process messages only if the timestamp is newer. */ |
| if (dmesg_timestamp <= *old_dmesg_timestamp) |
| continue; |
| |
| /* Only process the first VM fault. */ |
| if (fault) |
| continue; |
| |
| /* Remove trailing \n */ |
| len = strlen(line); |
| if (len && line[len - 1] == '\n') |
| line[len - 1] = 0; |
| |
| /* Get the message part. */ |
| msg = strchr(line, ']'); |
| if (!msg) |
| continue; |
| msg++; |
| |
| const char *header_line, *addr_line_prefix, *addr_line_format; |
| |
| if (gfx_level >= GFX9) { |
| /* Match this: |
| * ..: [gfxhub] VMC page fault (src_id:0 ring:158 vm_id:2 pas_id:0) |
| * ..: at page 0x0000000219f8f000 from 27 |
| * ..: VM_L2_PROTECTION_FAULT_STATUS:0x0020113C |
| */ |
| header_line = "VMC page fault"; |
| addr_line_prefix = " at page"; |
| addr_line_format = "%" PRIx64; |
| } else { |
| header_line = "GPU fault detected:"; |
| addr_line_prefix = "VM_CONTEXT1_PROTECTION_FAULT_ADDR"; |
| addr_line_format = "%" PRIX64; |
| } |
| |
| switch (progress) { |
| case 0: |
| if (strstr(msg, header_line)) |
| progress = 1; |
| break; |
| case 1: |
| msg = strstr(msg, addr_line_prefix); |
| if (msg) { |
| msg = strstr(msg, "0x"); |
| if (msg) { |
| msg += 2; |
| if (sscanf(msg, addr_line_format, out_addr) == 1) |
| fault = true; |
| } |
| } |
| progress = 0; |
| break; |
| default: |
| progress = 0; |
| } |
| } |
| pclose(p); |
| |
| if (dmesg_timestamp > *old_dmesg_timestamp) |
| *old_dmesg_timestamp = dmesg_timestamp; |
| |
| return fault; |
| #endif |
| } |
| |
| char * |
| ac_get_umr_waves(const struct radeon_info *info, enum amd_ip_type ring) |
| { |
| /* TODO: Dump compute ring. */ |
| if (ring != AMD_IP_GFX) |
| return NULL; |
| |
| #ifndef _WIN32 |
| char *data; |
| size_t size; |
| FILE *f = open_memstream(&data, &size); |
| if (!f) |
| return NULL; |
| |
| char cmd[256]; |
| sprintf(cmd, "umr --by-pci %04x:%02x:%02x.%01x -O bits,halt_waves -go 0 -wa %s -go 1 2>&1", info->pci.domain, |
| info->pci.bus, info->pci.dev, info->pci.func, info->gfx_level >= GFX10 ? "gfx_0.0.0" : "gfx"); |
| |
| char line[2048]; |
| FILE *p = popen(cmd, "r"); |
| if (p) { |
| while (fgets(line, sizeof(line), p)) |
| fputs(line, f); |
| fprintf(f, "\n"); |
| pclose(p); |
| } |
| |
| fclose(f); |
| |
| return data; |
| #else |
| return NULL; |
| #endif |
| } |
| |
| static int compare_wave(const void *p1, const void *p2) |
| { |
| struct ac_wave_info *w1 = (struct ac_wave_info *)p1; |
| struct ac_wave_info *w2 = (struct ac_wave_info *)p2; |
| |
| /* Sort waves according to PC and then SE, SH, CU, etc. */ |
| if (w1->pc < w2->pc) |
| return -1; |
| if (w1->pc > w2->pc) |
| return 1; |
| if (w1->se < w2->se) |
| return -1; |
| if (w1->se > w2->se) |
| return 1; |
| if (w1->sh < w2->sh) |
| return -1; |
| if (w1->sh > w2->sh) |
| return 1; |
| if (w1->cu < w2->cu) |
| return -1; |
| if (w1->cu > w2->cu) |
| return 1; |
| if (w1->simd < w2->simd) |
| return -1; |
| if (w1->simd > w2->simd) |
| return 1; |
| if (w1->wave < w2->wave) |
| return -1; |
| if (w1->wave > w2->wave) |
| return 1; |
| |
| return 0; |
| } |
| |
| #define AC_UMR_REGISTERS_LINE "Main Registers" |
| |
| static bool |
| ac_read_umr_register(const char **_scan, const char *name, uint32_t *value) |
| { |
| const char *scan = *_scan; |
| if (strncmp(scan, name, MIN2(strlen(scan), strlen(name)))) |
| return false; |
| |
| scan += strlen(name); |
| scan += strlen(": "); |
| |
| *value = strtoul(scan, NULL, 16); |
| *_scan = scan + 8; |
| return true; |
| } |
| |
| /* Return wave information. "waves" should be a large enough array. */ |
| unsigned ac_get_wave_info(enum amd_gfx_level gfx_level, const struct radeon_info *info, |
| const char *wave_dump, |
| struct ac_wave_info waves[AC_MAX_WAVES_PER_CHIP]) |
| { |
| #ifdef _WIN32 |
| return 0; |
| #else |
| char *dump = NULL; |
| if (!wave_dump) { |
| dump = ac_get_umr_waves(info, AMD_IP_GFX); |
| wave_dump = dump; |
| } |
| |
| unsigned num_waves = 0; |
| |
| while (true) { |
| const char *end = strchr(wave_dump, '\n'); |
| if (!end) |
| break; |
| |
| if (strncmp(wave_dump, AC_UMR_REGISTERS_LINE, strlen(AC_UMR_REGISTERS_LINE))) { |
| wave_dump = end + 1; |
| continue; |
| } |
| |
| assert(num_waves < AC_MAX_WAVES_PER_CHIP); |
| struct ac_wave_info *w = &waves[num_waves]; |
| memset(w, 0, sizeof(struct ac_wave_info)); |
| num_waves++; |
| |
| while (true) { |
| const char *end2 = strchr(wave_dump, '\n'); |
| if (!end2) |
| break; |
| if (end2 - wave_dump < 2) |
| break; |
| |
| const char *scan = wave_dump; |
| while (scan < end2) { |
| if (strncmp(scan, "ix", MIN2(strlen(scan), strlen("ix")))) { |
| scan++; |
| continue; |
| } |
| |
| scan += strlen("ix"); |
| |
| bool progress = false; |
| |
| progress |= ac_read_umr_register(&scan, "SQ_WAVE_STATUS", &w->status); |
| progress |= ac_read_umr_register(&scan, "SQ_WAVE_PC_LO", &w->pc_lo); |
| progress |= ac_read_umr_register(&scan, "SQ_WAVE_PC_HI", &w->pc_hi); |
| progress |= ac_read_umr_register(&scan, "SQ_WAVE_EXEC_LO", &w->exec_lo); |
| progress |= ac_read_umr_register(&scan, "SQ_WAVE_EXEC_HI", &w->exec_hi); |
| progress |= ac_read_umr_register(&scan, "SQ_WAVE_INST_DW0", &w->inst_dw0); |
| progress |= ac_read_umr_register(&scan, "SQ_WAVE_INST_DW1", &w->inst_dw1); |
| |
| uint32_t wave; |
| if (ac_read_umr_register(&scan, "SQ_WAVE_HW_ID", &wave)) { |
| w->se = G_000050_SE_ID(wave); |
| w->sh = G_000050_SH_ID(wave); |
| w->cu = G_000050_CU_ID(wave); |
| w->simd = G_000050_SIMD_ID(wave); |
| w->wave = G_000050_WAVE_ID(wave); |
| |
| progress = true; |
| } |
| |
| if (ac_read_umr_register(&scan, "SQ_WAVE_HW_ID1", &wave)) { |
| w->se = G_00045C_SE_ID(wave); |
| w->sh = G_00045C_SA_ID(wave); |
| w->cu = G_00045C_WGP_ID(wave); |
| w->simd = G_00045C_SIMD_ID(wave); |
| w->wave = G_00045C_WAVE_ID(wave); |
| |
| progress = true; |
| } |
| |
| /* Skip registers we do not handle. */ |
| if (!progress) { |
| while (scan < end2) { |
| if (*scan == '|') { |
| progress = true; |
| break; |
| } |
| scan++; |
| } |
| } |
| |
| if (!progress) |
| break; |
| } |
| |
| wave_dump = end2 + 1; |
| } |
| } |
| |
| qsort(waves, num_waves, sizeof(struct ac_wave_info), compare_wave); |
| |
| free(dump); |
| |
| return num_waves; |
| #endif |
| } |
| |
| /* List of GFXHUB clients from AMDGPU source code. */ |
| static const char *const gfx10_gfxhub_client_ids[] = { |
| "CB/DB", |
| "Reserved", |
| "GE1", |
| "GE2", |
| "CPF", |
| "CPC", |
| "CPG", |
| "RLC", |
| "TCP", |
| "SQC (inst)", |
| "SQC (data)", |
| "SQG", |
| "Reserved", |
| "SDMA0", |
| "SDMA1", |
| "GCR", |
| "SDMA2", |
| "SDMA3", |
| }; |
| |
| static const char * |
| ac_get_gfx10_gfxhub_client(unsigned cid) |
| { |
| if (cid >= ARRAY_SIZE(gfx10_gfxhub_client_ids)) |
| return "UNKNOWN"; |
| return gfx10_gfxhub_client_ids[cid]; |
| } |
| |
| void ac_print_gpuvm_fault_status(FILE *output, enum amd_gfx_level gfx_level, |
| uint32_t status) |
| { |
| if (gfx_level >= GFX10) { |
| const uint8_t cid = G_00A130_CID(status); |
| |
| fprintf(output, "GCVM_L2_PROTECTION_FAULT_STATUS: 0x%x\n", status); |
| fprintf(output, "\t CLIENT_ID: (%s) 0x%x\n", ac_get_gfx10_gfxhub_client(cid), cid); |
| fprintf(output, "\t MORE_FAULTS: %d\n", G_00A130_MORE_FAULTS(status)); |
| fprintf(output, "\t WALKER_ERROR: %d\n", G_00A130_WALKER_ERROR(status)); |
| fprintf(output, "\t PERMISSION_FAULTS: %d\n", G_00A130_PERMISSION_FAULTS(status)); |
| fprintf(output, "\t MAPPING_ERROR: %d\n", G_00A130_MAPPING_ERROR(status)); |
| fprintf(output, "\t RW: %d\n", G_00A130_RW(status)); |
| } else { |
| fprintf(output, "VM_CONTEXT1_PROTECTION_FAULT_STATUS: 0x%x\n", status); |
| } |
| } |