You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
11 KiB
400 lines
11 KiB
/*
|
|
* 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 <ben@bwidawsk.net>
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include "intel_chipset.h"
|
|
#include "intel_io.h"
|
|
#include "igt_sysfs.h"
|
|
#include "drmtest.h"
|
|
#include "config.h"
|
|
#include <libudev.h>
|
|
#include <syslog.h>
|
|
#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_gt(devid)) {
|
|
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_gt(devid) > 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=<row,bank,subbank> 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[])
|
|
{
|
|
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 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(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 */
|
|
exit(77);
|
|
continue;
|
|
}
|
|
|
|
if (read(fd[i], l3logs[i], NUM_REGS * sizeof(uint32_t)) < 0) {
|
|
perror(path[i]);
|
|
exit(77);
|
|
}
|
|
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(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]);
|
|
exit(EXIT_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");
|
|
exit(EXIT_SUCCESS);
|
|
case 'r':
|
|
row = atoi(optarg);
|
|
if (row >= MAX_ROW)
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
case 'b':
|
|
bank = atoi(optarg);
|
|
if (bank >= num_banks() || bank >= MAX_BANKS_PER_SLICE)
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
case 's':
|
|
sbank = atoi(optarg);
|
|
if (sbank >= NUM_SUBBANKS)
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
case 'w':
|
|
which_slice = atoi(optarg);
|
|
if (which_slice >= MAX_SLICES)
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
case 'i':
|
|
case 'u':
|
|
if (!IS_HASWELL(devid)) {
|
|
fprintf(stderr, "Error injection supported on HSW+ only\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
case 'd':
|
|
if (optarg) {
|
|
ret = sscanf(optarg, "%d,%d,%d", &row, &bank, &sbank);
|
|
if (ret != 3)
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
case 'a':
|
|
case 'l':
|
|
case 'e':
|
|
case 'L':
|
|
if (action != '0') {
|
|
fprintf(stderr, "Only one action may be specified\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
action = c;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
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);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (which_slice == -1) {
|
|
fprintf(stderr, "Cannot inject errors to multiple slices (modify -w)\n");
|
|
exit(EXIT_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);
|
|
exit(EXIT_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");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
dft |= row << 7;
|
|
dft |= sbank << 4;
|
|
dft |= bank << 2;
|
|
assert(i < 2);
|
|
dft |= i << 1; /* slice */
|
|
dft |= 1 << 0; /* enable */
|
|
intel_register_write(0xb038, dft);
|
|
break;
|
|
case 'u':
|
|
intel_register_write(0xb038, dft & ~(1<<0));
|
|
break;
|
|
case 'L':
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
intel_register_access_fini();
|
|
if (action == 'l')
|
|
exit(EXIT_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");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
close(fd[i]);
|
|
}
|
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|