/* * Copyright © 2012 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. * * Authors: * Ben Widawsky * */ #include #include #include #include #include #include #include #include #include #include "intel_chipset.h" #include "intel_io.h" #include "igt_sysfs.h" #include "drmtest.h" #include "config.h" #include #include #include "intel_l3_parity.h" static unsigned int devid; /* L3 size is always a function of banks. The number of banks cannot be * determined by number of slices however */ static inline int num_banks(void) { switch (intel_get_device_info(devid)->gt) { case 2: return 8; case 1: return 4; default: return 2; } } #define NUM_SUBBANKS 8 #define BYTES_PER_BANK (128 << 10) /* Each row addresses [up to] 4b. This multiplied by the number of subbanks * will give the L3 size per bank. * TODO: Row size is fixed on IVB, and variable on HSW.*/ #define MAX_ROW (1<<12) #define MAX_BANKS_PER_SLICE 4 #define NUM_REGS (MAX_BANKS_PER_SLICE * NUM_SUBBANKS) #define MAX_SLICES (intel_get_device_info(devid)->gt > 1 ? 2 : 1) #define REAL_MAX_SLICES 2 /* TODO support SLM config */ #define L3_SIZE ((MAX_ROW * 4) * NUM_SUBBANKS * num_banks()) struct __attribute__ ((__packed__)) l3_log_register { uint32_t row0_enable : 1; uint32_t rsvd2 : 4; uint32_t row0 : 11; uint32_t row1_enable : 1; uint32_t rsvd1 : 4; uint32_t row1 : 11; } l3logs[REAL_MAX_SLICES][MAX_BANKS_PER_SLICE][NUM_SUBBANKS]; static int which_slice = -1; #define for_each_slice(__i) \ for ((__i) = (which_slice == -1) ? 0 : which_slice; \ (__i) < ((which_slice == -1) ? MAX_SLICES : (which_slice + 1)); \ (__i)++) static void decode_dft(uint32_t dft) { if (IS_IVYBRIDGE(devid) || !(dft & 1)) { printf("Error injection disabled\n"); return; } printf("Error injection enabled\n"); printf(" Hang = %s\n", (dft >> 28) & 0x1 ? "yes" : "no"); printf(" Row = %d\n", (dft >> 7) & 0x7ff); printf(" Bank = %d\n", (dft >> 2) & 0x3); printf(" Subbank = %d\n", (dft >> 4) & 0x7); printf(" Slice = %d\n", (dft >> 1) & 0x1); } static void dumpit(int slice) { int i, j; for (i = 0; i < MAX_BANKS_PER_SLICE; i++) { for (j = 0; j < NUM_SUBBANKS; j++) { struct l3_log_register *reg = &l3logs[slice][i][j]; if (reg->row0_enable) printf("Slice %d, Row %d, Bank %d, Subbank %d is disabled\n", slice, reg->row0, i, j); if (reg->row1_enable) printf("Slice %d, Row %d, Bank %d, Subbank %d is disabled\n", slice, reg->row1, i, j); } } } static int disable_rbs(int row, int bank, int sbank, int slice) { struct l3_log_register *reg = &l3logs[slice][bank][sbank]; // can't map more than 2 rows if (reg->row0_enable && reg->row1_enable) return -1; // can't remap the same row twice if ((reg->row0_enable && reg->row0 == row) || (reg->row1_enable && reg->row1 == row)) { return -1; } if (reg->row0_enable) { reg->row1 = row; reg->row1_enable = 1; } else { reg->row0 = row; reg->row0_enable = 1; } return 0; } static void enables_rbs(int row, int bank, int sbank, int slice) { struct l3_log_register *reg = &l3logs[slice][bank][sbank]; if (!reg->row0_enable && !reg->row1_enable) return; if (reg->row1_enable && reg->row1 == row) reg->row1_enable = 0; else if (reg->row0_enable && reg->row0 == row) reg->row0_enable = 0; } static void usage(const char *name) { printf("usage: %s [OPTIONS] [ACTION]\n" "Operate on the i915 L3 GPU cache (should be run as root)\n\n" " OPTIONS:\n" " -r, --row=[row] The row to act upon (default 0)\n" " -b, --bank=[bank] The bank to act upon (default 0)\n" " -s, --subbank=[subbank] The subbank to act upon (default 0)\n" " -w, --slice=[slice] Which slice to act on (default: -1 [all])\n" " , --daemon Run the listener (-L) as a daemon\n" " ACTIONS (only 1 may be specified at a time):\n" " -h, --help Display this help\n" " -H, --hw-info Display the current L3 properties\n" " -l, --list List the current L3 logs\n" " -a, --clear-all Clear all disabled rows\n" " -e, --enable Enable row, bank, subbank (undo -d)\n" " -d, --disable= Disable row, bank, subbank (inline arguments are deprecated. Please use -r, -b, -s instead\n" " -i, --inject [HSW only] Cause hardware to inject a row errors\n" " -u, --uninject [HSW only] Turn off hardware error injectection (undo -i)\n" " -L, --listen Listen for uevent errors\n", name); } int main(int argc, char *argv[]) { struct intel_mmio_data mmio_data; const char *path[REAL_MAX_SLICES] = {"l3_parity", "l3_parity_slice_1"}; int row = 0, bank = 0, sbank = 0; int fd[REAL_MAX_SLICES] = {0}, ret, i; int exitcode = EXIT_FAILURE; int action = '0'; int daemonize = 0; int device, dir; uint32_t dft; device = drm_open_driver(DRIVER_INTEL); devid = intel_get_drm_devid(device); if (intel_gen(devid) < 7 || IS_VALLEYVIEW(devid)) exit(77); assert(intel_register_access_init(&mmio_data, intel_get_pci_device(), 0, device) == 0); dir = igt_sysfs_open(device); for_each_slice(i) { fd[i] = openat(dir, path[i], O_RDWR); if (fd[i] < 0) { if (i == 0) /* at least one slice must be supported */ goto skip; continue; } if (read(fd[i], l3logs[i], NUM_REGS * sizeof(uint32_t)) < 0) { perror(path[i]); goto skip; } assert(lseek(fd[i], 0, SEEK_SET) == 0); } close(dir); /* NB: It is potentially unsafe to read this register if the kernel is * actively using this register range, or we're running multiple * instances of this tool. Since neither of those cases should occur * (and the tool should be root only) we can safely ignore this for * now. Just be aware of this if for some reason a hang is reported * when using this tool. */ dft = intel_register_read(&mmio_data, 0xb038); while (1) { int c, option_index = 0; struct option long_options[] = { { "help", no_argument, 0, 'h' }, { "list", no_argument, 0, 'l' }, { "clear-all", no_argument, 0, 'a' }, { "enable", no_argument, 0, 'e' }, { "disable", optional_argument, 0, 'd' }, { "inject", no_argument, 0, 'i' }, { "uninject", no_argument, 0, 'u' }, { "hw-info", no_argument, 0, 'H' }, { "listen", no_argument, 0, 'L' }, { "row", required_argument, 0, 'r' }, { "bank", required_argument, 0, 'b' }, { "subbank", required_argument, 0, 's' }, { "slice", required_argument, 0, 'w' }, { "daemon", no_argument, &daemonize, 1 }, {0, 0, 0, 0} }; c = getopt_long(argc, argv, "hHr:b:s:w:aled::iuL", long_options, &option_index); if (c == -1) break; if (c == 0) continue; switch (c) { case '?': case 'h': usage(argv[0]); goto success; case 'H': printf("Number of slices: %d\n", MAX_SLICES); printf("Number of banks: %d\n", num_banks()); printf("Subbanks per bank: %d\n", NUM_SUBBANKS); printf("Max L3 size: %dK\n", L3_SIZE >> 10); printf("Has error injection: %s\n", IS_HASWELL(devid) ? "yes" : "no"); goto success; case 'r': row = atoi(optarg); if (row >= MAX_ROW) goto failure; break; case 'b': bank = atoi(optarg); if (bank >= num_banks() || bank >= MAX_BANKS_PER_SLICE) goto failure; break; case 's': sbank = atoi(optarg); if (sbank >= NUM_SUBBANKS) goto failure; break; case 'w': which_slice = atoi(optarg); if (which_slice >= MAX_SLICES) goto failure; break; case 'i': case 'u': if (!IS_HASWELL(devid)) { fprintf(stderr, "Error injection supported on HSW+ only\n"); goto failure; } case 'd': if (optarg) { ret = sscanf(optarg, "%d,%d,%d", &row, &bank, &sbank); if (ret != 3) goto failure; } case 'a': case 'l': case 'e': case 'L': if (action != '0') { fprintf(stderr, "Only one action may be specified\n"); goto failure; } action = c; break; default: goto failure; } } if (action == 'i') { if (((dft >> 1) & 1) != which_slice) { fprintf(stderr, "DFT register already has slice %d enabled, and we don't support multiple slices. Try modifying -w; but sometimes the register sticks in the wrong way\n", (dft >> 1) & 1); goto failure; } if (which_slice == -1) { fprintf(stderr, "Cannot inject errors to multiple slices (modify -w)\n"); goto failure; } if (dft & 1 && ((dft >> 1) && 1) == which_slice) printf("warning: overwriting existing injections. This is very dangerous.\n"); } /* Daemon doesn't work like the other commands */ if (action == 'L') { struct l3_parity par; struct l3_location loc; if (daemonize) { assert(daemon(0, 0) == 0); openlog(argv[0], LOG_CONS | LOG_PID, LOG_USER); } memset(&par, 0, sizeof(par)); assert(l3_uevent_setup(&par) == 0); assert(l3_listen(&par, daemonize == 1, &loc) == 0); goto success; } if (action == 'l') decode_dft(dft); /* Per slice operations */ for_each_slice(i) { if (fd[i] < 0) continue; switch (action) { case 'l': dumpit(i); break; case 'a': memset(l3logs[i], 0, NUM_REGS * sizeof(struct l3_log_register)); break; case 'e': enables_rbs(row, bank, sbank, i); break; case 'd': assert(disable_rbs(row, bank, sbank, i) == 0); break; case 'i': if (bank == 3) { fprintf(stderr, "The hardware does not support error inject on bank 3.\n"); goto failure; } dft |= row << 7; dft |= sbank << 4; dft |= bank << 2; assert(i < 2); dft |= i << 1; /* slice */ dft |= 1 << 0; /* enable */ intel_register_write(&mmio_data, 0xb038, dft); break; case 'u': intel_register_write(&mmio_data ,0xb038, dft & ~(1<<0)); break; case 'L': break; default: goto failure; } } if (action == 'l') goto success; for_each_slice(i) { if (fd[i] < 0) continue; ret = write(fd[i], l3logs[i], NUM_REGS * sizeof(uint32_t)); if (ret == -1) { perror("Writing sysfs"); goto failure; } close(fd[i]); } success: exitcode = EXIT_SUCCESS; failure: intel_register_access_fini(&mmio_data); return exitcode; skip: exitcode = 77; goto failure; }