Changeset View
Changeset View
Standalone View
Standalone View
tools/tools/pci/InjectAER.c
- This file was added.
/*- | |||||
* Copyright (c) 2016 Isilon LLC, EMC | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <stdio.h> | |||||
#include <dev/pci/pcireg.h> | |||||
#include <sys/types.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/pciio.h> | |||||
#include <sys/ioctl.h> | |||||
#include <sys/fcntl.h> /* O_RDWR */ | |||||
#include <sys/queue.h> /* STAILQ */ | |||||
#include <stdlib.h> /* malloc */ | |||||
#include <errno.h> /* strerror */ | |||||
#include <string.h> /* strcpy */ | |||||
#include <net/if.h> /* ifreq */ | |||||
#include <unistd.h> /* close */ | |||||
#define DEVICE_LIST_LENGTH 255 | |||||
#define INJ_SYSCTL_NAMELEN 8 | |||||
#define INJ_SYSCTL_PATH "hw.pci.pcib_probe" | |||||
#define PATH_DEVPCI "/dev/pci" | |||||
#define PCIEM_CAPID 0xFF | |||||
#define PCIR_EXTCAP_MAX 0xFFF | |||||
#define METHOD_RBER_AER 1 | |||||
#define METHOD_AER 2 | |||||
#define METHOD_NO_RBER 3 | |||||
#define METHOD_NICFLAG 4 | |||||
#define WIDTH_32B 4 | |||||
#define WIDTH_16B 2 | |||||
#define WIDTH_8B 1 | |||||
void usage(); | |||||
void inject_dev(int fatal); | |||||
void inject_if(); | |||||
void scan(); | |||||
void error_handler(int stage); | |||||
void record_initial(); | |||||
void restore_initial(); | |||||
int findPCIEOffs(struct pcisel *pi_sel); | |||||
int findAEROffs(struct pcisel *pi_sel); | |||||
struct config { | |||||
int bridgeControl; | |||||
int deviceControl; | |||||
int deviceUncErrMask; | |||||
int deviceUncErrSeve; | |||||
int deviceCErrMask; | |||||
int previousUP; | |||||
int previousBUSMAS; | |||||
}; | |||||
struct dev_loca_entry { | |||||
struct pcisel bridge_pi_sel; | |||||
char bridge_name[PCI_MAXNAMELEN + 1]; | |||||
int bridge_unit; | |||||
struct pcisel device_pi_sel; | |||||
char device_name[PCI_MAXNAMELEN + 1]; | |||||
int device_unit; | |||||
int method; | |||||
STAILQ_ENTRY(dev_loca_entry) next; | |||||
}; | |||||
static STAILQ_HEAD(,dev_loca_entry) dev_loca_head; | |||||
int fd; | |||||
/* Global VARs will be defined after selecting device */ | |||||
struct pcisel bridge_sel; | |||||
struct pcisel device_sel; | |||||
struct config initial_config; | |||||
char device_name[IFNAMSIZ]; | |||||
int PCIE_offs; | |||||
int AER_offs; | |||||
/* | |||||
* Read user input, process automatic error-injection, | |||||
* or guide user thorugh running error-injection. | |||||
*/ | |||||
int main(int argc, char **argv) | |||||
{ | |||||
char parm; | |||||
int automode; | |||||
int count, select_index, select_fatal, method; | |||||
struct dev_loca_entry *entry; | |||||
if (argc != 2) { | |||||
usage(0); | |||||
return 0; | |||||
} | |||||
while ((parm = getopt(argc, argv, "alh")) != -1) { | |||||
switch(parm) { | |||||
case 'a': | |||||
automode = 1; | |||||
break; | |||||
case 'l': | |||||
automode = 0; | |||||
break; | |||||
case 'h': | |||||
usage(); | |||||
return 0; | |||||
default: | |||||
usage(); | |||||
return 0; | |||||
} | |||||
} | |||||
bzero(&bridge_sel, sizeof(bridge_sel)); | |||||
bzero(&device_sel, sizeof(device_sel)); | |||||
fd = open(PATH_DEVPCI, O_RDWR); | |||||
if (fd < 0) { | |||||
printf("Unable to open %s.\n", PATH_DEVPCI); | |||||
error_handler (1); | |||||
} | |||||
STAILQ_INIT(&dev_loca_head); | |||||
scan(); | |||||
printf("Scan result:\n"); | |||||
if (!STAILQ_FIRST(&dev_loca_head)) { | |||||
printf("No potentially error-inject-avilabile device found\n"); | |||||
error_handler(0); | |||||
} | |||||
if (automode) { | |||||
entry = STAILQ_FIRST(&dev_loca_head); | |||||
printf("Performing auto error-injetion on: \n"); | |||||
printf("Bridge=%s%d@%d:%d:%d:%d ",entry->bridge_name, entry->bridge_unit, | |||||
entry->bridge_pi_sel.pc_domain,entry->bridge_pi_sel.pc_bus, | |||||
entry->bridge_pi_sel.pc_dev,entry->bridge_pi_sel.pc_func); | |||||
printf("Device=%s%d@%d:%d:%d:%d ",entry->device_name, entry->device_unit, | |||||
entry->device_pi_sel.pc_domain,entry->device_pi_sel.pc_bus, | |||||
entry->device_pi_sel.pc_dev,entry->device_pi_sel.pc_func); | |||||
printf("Method=%d\n", entry->method); | |||||
bridge_sel = entry->bridge_pi_sel; | |||||
device_sel = entry->device_pi_sel; | |||||
sprintf(device_name, "%s%d", entry->device_name, entry->device_unit); | |||||
select_fatal = 0; | |||||
PCIE_offs = findPCIEOffs(&device_sel); | |||||
AER_offs = findAEROffs(&device_sel); | |||||
if (entry->method == METHOD_NICFLAG) | |||||
inject_if(); | |||||
else | |||||
inject_dev(select_fatal); | |||||
close(fd); | |||||
printf("Error injection complete.\n"); | |||||
return 0; | |||||
} | |||||
count = 0; | |||||
STAILQ_FOREACH(entry, &dev_loca_head, next) { | |||||
printf("Index: %d ", count); | |||||
printf("Bridge: %s%d@%d:%d:%d:%d ",entry->bridge_name, entry->bridge_unit, | |||||
entry->bridge_pi_sel.pc_domain,entry->bridge_pi_sel.pc_bus, | |||||
entry->bridge_pi_sel.pc_dev,entry->bridge_pi_sel.pc_func); | |||||
printf("Device: %s%d@%d:%d:%d:%d ",entry->device_name, entry->device_unit, | |||||
entry->device_pi_sel.pc_domain,entry->device_pi_sel.pc_bus, | |||||
entry->device_pi_sel.pc_dev,entry->device_pi_sel.pc_func); | |||||
printf("Method="); | |||||
switch (entry->method) { | |||||
case (METHOD_RBER_AER): | |||||
printf ("Probing: COR/Fatal"); | |||||
break; | |||||
case (METHOD_AER): | |||||
printf ("Probing: non-Fatal/Fatal"); | |||||
break; | |||||
case (METHOD_NO_RBER): | |||||
printf ("Probing: non-fatal"); | |||||
break; | |||||
case (METHOD_NICFLAG): | |||||
printf ("NIC flag: non-fatal"); | |||||
break; | |||||
default: | |||||
error_handler(2); | |||||
} | |||||
printf("\n"); | |||||
count ++; | |||||
} | |||||
printf("Select a combination to try error injection.\n"); | |||||
select_index = 0; | |||||
while(1) { | |||||
printf("Input index (0~%d): ", count - 1); | |||||
scanf("%d", &select_index); | |||||
if (select_index < count && select_index >= 0) | |||||
break; | |||||
} | |||||
printf("Get: %d\n", select_index); | |||||
count = 0; | |||||
STAILQ_FOREACH(entry, &dev_loca_head, next) { | |||||
if (count == select_index) { | |||||
bridge_sel = entry->bridge_pi_sel; | |||||
device_sel = entry->device_pi_sel; | |||||
sprintf(device_name, "%s%d", entry->device_name, entry->device_unit); | |||||
method = entry->method; | |||||
break; | |||||
} | |||||
count ++; | |||||
} | |||||
printf("Selected: "); | |||||
printf("Index=%d ", count); | |||||
printf("DeviceName=%s ", device_name); | |||||
printf("Method=%d\n", method); | |||||
select_fatal = 0; | |||||
printf ("\n"); | |||||
printf ("Expected: "); | |||||
switch (method) { | |||||
case METHOD_RBER_AER: | |||||
while (1) { | |||||
printf ("Select error type "); | |||||
printf ("(0=correctable, 1=fatal): "); | |||||
scanf("%d", &select_fatal); | |||||
if (select_fatal == 0 | select_fatal == 1) | |||||
break; | |||||
} | |||||
if (select_fatal) | |||||
printf ("Uncorrectable fatal error "); | |||||
else | |||||
printf ("Correctable error "); | |||||
printf ("record on root port status "); | |||||
break; | |||||
case METHOD_AER: | |||||
while (1) { | |||||
printf ("Select error type "); | |||||
printf ("(0=non-fatal, 1=fatal): "); | |||||
scanf("%d", &select_fatal); | |||||
if (select_fatal == 0 | select_fatal == 1) | |||||
break; | |||||
} | |||||
if (select_fatal) | |||||
printf ("Uncorrectable fatal error "); | |||||
else | |||||
printf ("Uncorrectable non-fatal error "); | |||||
printf ("record on root port status "); | |||||
break; | |||||
case METHOD_NO_RBER: | |||||
printf ("Uncorrectable non-fatal error "); | |||||
printf ("record on root port status "); | |||||
break; | |||||
case METHOD_NICFLAG: | |||||
printf ("Uncorrectable non-fatal error "); | |||||
printf ("record on root port status, and\n"); | |||||
printf ("Error 'Unsupported Request' record on "); | |||||
printf ("bridge AER Uncorrectable error status\n"); | |||||
break; | |||||
default: | |||||
error_handler (2); | |||||
break; | |||||
} | |||||
printf ("after injection.\n"); | |||||
printf ("\n"); | |||||
PCIE_offs = findPCIEOffs(&device_sel); | |||||
AER_offs = findAEROffs(&device_sel); | |||||
printf("Device: PCI-E Extd Config Space @ %X \n", PCIE_offs); | |||||
if (AER_offs) | |||||
printf("Device: AER Cap Config Space @ %X \n", AER_offs); | |||||
else | |||||
printf("Device: AER Not supported\n"); | |||||
if (method == METHOD_NICFLAG) | |||||
inject_if(); | |||||
else | |||||
inject_dev(select_fatal); | |||||
close(fd); | |||||
printf("Error injection complete.\n"); | |||||
printf("\n"); | |||||
return 0; | |||||
} | |||||
/* | |||||
* Attempt to probe non-existent function to trigger UR error. | |||||
* error message is generated by device and forwarded by bridge. | |||||
*/ | |||||
void inject_dev(int fatal) | |||||
{ | |||||
int name[INJ_SYSCTL_NAMELEN]; | |||||
int buffer; | |||||
size_t len, size; | |||||
struct pci_io bridge_io; | |||||
struct pci_io device_io; | |||||
bzero(&bridge_io, sizeof(struct pci_io)); | |||||
bzero(&device_io, sizeof(struct pci_io)); | |||||
bridge_io.pi_sel = bridge_sel; | |||||
device_io.pi_sel = device_sel; | |||||
/* Record initial value */ | |||||
record_initial(); | |||||
/* Clear MasterAbort & Error Received */ | |||||
bridge_io.pi_width = WIDTH_16B; | |||||
bridge_io.pi_reg = PCIR_SECSTAT_1; | |||||
bridge_io.pi_data = PCIM_STATUS_RMABORT | PCIM_STATUS_SERR; | |||||
if (ioctl(fd, PCIOCWRITE, &bridge_io)) | |||||
error_handler(31); | |||||
/* Enable error forwarding */ | |||||
bridge_io.pi_width = WIDTH_8B; | |||||
bridge_io.pi_reg = PCIR_BRIDGECTL_1; | |||||
bridge_io.pi_data = initial_config.bridgeControl | PCIB_BCR_SERR_ENABLE; | |||||
if (ioctl(fd, PCIOCWRITE, &bridge_io)) | |||||
error_handler(31); | |||||
/* Enable error report */ | |||||
device_io.pi_width = WIDTH_8B; | |||||
device_io.pi_reg = PCIE_offs+PCIER_DEVICE_CTL; | |||||
device_io.pi_data = initial_config.deviceControl | | |||||
PCIEM_CTL_URR_ENABLE | | |||||
PCIEM_CTL_FER_ENABLE | | |||||
PCIEM_CTL_NFER_ENABLE | | |||||
PCIEM_CTL_COR_ENABLE; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(32); | |||||
/* AER registers */ | |||||
if (AER_offs) { | |||||
device_io.pi_width = WIDTH_32B; | |||||
/* Clear UR status */ | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_STATUS; | |||||
device_io.pi_data = PCIM_AER_UC_UNSUPPORTED_REQUEST; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(33); | |||||
/* Clear AdvsErr status */ | |||||
device_io.pi_reg = AER_offs + PCIR_AER_COR_STATUS; | |||||
device_io.pi_data = PCIM_AER_COR_ADVISORY_NF_ERROR; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(33); | |||||
/* Clear Uncorrectable error mask for UR */ | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_MASK; | |||||
device_io.pi_data = (initial_config.deviceCErrMask & | |||||
(~PCIM_AER_UC_UNSUPPORTED_REQUEST)); | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(33); | |||||
/* Clear Correctable error mask for advs. unc. error */ | |||||
device_io.pi_reg = AER_offs + PCIR_AER_COR_MASK; | |||||
device_io.pi_data = (initial_config.deviceCErrMask & | |||||
(~PCIM_AER_COR_ADVISORY_NF_ERROR)); | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(33); | |||||
/* Fatal or Non-fatal error */ | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_SEVERITY; | |||||
if (fatal) | |||||
device_io.pi_data = (initial_config.deviceUncErrSeve | | |||||
PCIM_AER_UC_UNSUPPORTED_REQUEST); | |||||
else | |||||
device_io.pi_data = (initial_config.deviceUncErrSeve & | |||||
(~PCIM_AER_UC_UNSUPPORTED_REQUEST)); | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(33); | |||||
printf("Uncorrectable Error Severity set to: %X\n", device_io.pi_data); | |||||
} | |||||
/* Probe non-existent function */ | |||||
len = INJ_SYSCTL_NAMELEN; | |||||
sysctlnametomib(INJ_SYSCTL_PATH, name, &len); | |||||
name[3] = device_io.pi_sel.pc_bus; | |||||
name[4] = device_io.pi_sel.pc_dev; | |||||
name[5] = PCI_FUNCMAX; | |||||
name[6] = 0; | |||||
name[7] = WIDTH_32B; | |||||
size = sizeof(int); | |||||
if (sysctl(name, INJ_SYSCTL_NAMELEN, &buffer, &size, NULL, 0)) | |||||
error_handler(34); | |||||
/* Verify MasterAbort & Error Received */ | |||||
bridge_io.pi_width = WIDTH_16B; | |||||
bridge_io.pi_reg = PCIR_SECSTAT_1; | |||||
if (ioctl(fd, PCIOCREAD, &bridge_io)) | |||||
error_handler(35); | |||||
printf("Bridge sec. status after injection: %X\n", bridge_io.pi_data); | |||||
if (AER_offs) { | |||||
device_io.pi_width = WIDTH_32B; | |||||
/* Clear UR status */ | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_STATUS; | |||||
device_io.pi_data = PCIM_AER_UC_UNSUPPORTED_REQUEST; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(35); | |||||
/* Clear AdvsErr status */ | |||||
device_io.pi_reg = AER_offs + PCIR_AER_COR_STATUS; | |||||
device_io.pi_data = PCIM_AER_COR_ADVISORY_NF_ERROR; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(35); | |||||
} | |||||
/* Clear MasterAbort & Error Received */ | |||||
bridge_io.pi_data = PCIM_STATUS_RMABORT | PCIM_STATUS_SERR; | |||||
if (ioctl(fd, PCIOCWRITE, &bridge_io)) | |||||
error_handler(35); | |||||
/* Restore */ | |||||
restore_initial(); | |||||
} | |||||
/* | |||||
* Disabling busmaster and set NIC flag to trigger UR errors. | |||||
* Error is generated by bridge. | |||||
*/ | |||||
void inject_if() | |||||
{ | |||||
int sockfd; | |||||
char input; | |||||
struct pci_io bridge_io; | |||||
struct ifreq ifr; | |||||
sockfd = socket(AF_INET, SOCK_DGRAM, 0); | |||||
if (sockfd < 0) | |||||
error_handler(51); | |||||
bzero(&initial_config, sizeof(initial_config)); | |||||
bzero(&bridge_io, sizeof(bridge_io)); | |||||
bridge_io.pi_sel = bridge_sel; | |||||
bzero(&ifr, sizeof(ifr)); | |||||
strcpy(ifr.ifr_name, device_name); | |||||
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr)) | |||||
error_handler (52); | |||||
printf("Device %s current lower-flags: %X\n", device_name, ifr.ifr_flags); | |||||
if ((ifr.ifr_flags & IFF_UP) == IFF_UP) { | |||||
initial_config.previousUP = 1; | |||||
printf("This device is currently in up state, "); | |||||
printf("continue may bring link down.\n"); | |||||
while (1) { | |||||
printf("Continue (y/n) ? "); | |||||
input = getchar(); | |||||
if (input == 'y') | |||||
break; | |||||
else if (input == 'n') | |||||
error_handler (0); | |||||
} | |||||
} | |||||
/* Disable NIC first */ | |||||
ifr.ifr_flags = ifr.ifr_flags & (~IFF_UP); | |||||
if (ioctl(sockfd, SIOCSIFFLAGS, &ifr)) | |||||
error_handler (53); | |||||
/* Disable Bridge's BusMaster */ | |||||
bridge_io.pi_width = WIDTH_8B; | |||||
bridge_io.pi_reg = PCIR_COMMAND; | |||||
if (ioctl(fd, PCIOCREAD, &bridge_io)) | |||||
error_handler(53); | |||||
initial_config.previousBUSMAS = bridge_io.pi_data; | |||||
if (!(initial_config.previousBUSMAS & PCIM_CMD_BUSMASTEREN)) { | |||||
printf ("BusMaster is previously disabled:"); | |||||
printf ("Unexpected settings. Quit\n"); | |||||
error_handler(0); | |||||
} | |||||
bridge_io.pi_data = bridge_io.pi_data & (~PCIM_CMD_BUSMASTEREN); | |||||
if (ioctl(fd, PCIOCWRITE, &bridge_io)) | |||||
error_handler(53); | |||||
/* Toggle NIC flag */ | |||||
ifr.ifr_flags = ifr.ifr_flags | IFF_UP; | |||||
if (ioctl(sockfd, SIOCSIFFLAGS, &ifr)) | |||||
error_handler (52); | |||||
ifr.ifr_flags = ifr.ifr_flags & (~IFF_UP); | |||||
if (ioctl(sockfd, SIOCSIFFLAGS, &ifr)) | |||||
error_handler (52); | |||||
/* Bring back BusMaster */ | |||||
bridge_io.pi_data = initial_config.previousBUSMAS; | |||||
if (ioctl(fd, PCIOCWRITE, &bridge_io)) | |||||
error_handler(53); | |||||
/* Restore NIC flag */ | |||||
if (initial_config.previousUP) { | |||||
ifr.ifr_flags = ifr.ifr_flags | IFF_UP; | |||||
if (ioctl(sockfd, SIOCSIFFLAGS, &ifr)) | |||||
error_handler (52); | |||||
} | |||||
} | |||||
/* | |||||
* Record settings before performing any modification. | |||||
*/ | |||||
void record_initial() | |||||
{ | |||||
struct pci_io bridge_io; | |||||
struct pci_io device_io; | |||||
bzero(&bridge_io, sizeof(bridge_io)); | |||||
bzero(&device_io, sizeof(device_io)); | |||||
bzero(&initial_config, sizeof(initial_config)); | |||||
bridge_io.pi_sel = bridge_sel; | |||||
device_io.pi_sel = device_sel; | |||||
bridge_io.pi_width = WIDTH_8B; | |||||
bridge_io.pi_reg = PCIR_BRIDGECTL_2; | |||||
if (ioctl(fd, PCIOCREAD, &bridge_io)) | |||||
error_handler(21); | |||||
initial_config.bridgeControl = bridge_io.pi_data; | |||||
bridge_io.pi_width = WIDTH_16B; | |||||
if (ioctl(fd, PCIOCREAD, &bridge_io)) | |||||
error_handler(21); | |||||
printf("Initial bridge control: %X\n",bridge_io.pi_data); | |||||
device_io.pi_width = WIDTH_8B; | |||||
device_io.pi_reg = PCIE_offs + PCIER_DEVICE_CTL; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(22); | |||||
initial_config.deviceControl = device_io.pi_data; | |||||
device_io.pi_width = WIDTH_16B; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(22); | |||||
printf("Initial device control: %X\n",device_io.pi_data); | |||||
bridge_io.pi_width = WIDTH_16B; | |||||
bridge_io.pi_reg = PCIR_SECSTAT_1; | |||||
if (ioctl(fd, PCIOCREAD, &bridge_io)) | |||||
error_handler(21); | |||||
printf("SecondaryStatus before inject: %X\n", bridge_io.pi_data); | |||||
if (AER_offs) { | |||||
device_io.pi_width = WIDTH_32B; | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_STATUS; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(23); | |||||
printf("AER Uncorrectable error status before inject: %X\n", | |||||
device_io.pi_data); | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_MASK; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(23); | |||||
initial_config.deviceUncErrMask = device_io.pi_data; | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_SEVERITY; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(23); | |||||
initial_config.deviceUncErrSeve = device_io.pi_data; | |||||
device_io.pi_reg = AER_offs + PCIR_AER_COR_STATUS; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(23); | |||||
printf("AER Correctable error status before inject: %X\n", | |||||
device_io.pi_data); | |||||
device_io.pi_reg = AER_offs + PCIR_AER_COR_MASK; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(23); | |||||
initial_config.deviceCErrMask = device_io.pi_data; | |||||
} | |||||
} | |||||
/* | |||||
* Restore settings after error-injection, or run-time error. | |||||
*/ | |||||
void restore_initial() | |||||
{ | |||||
struct pci_io bridge_io; | |||||
struct pci_io device_io; | |||||
bzero(&bridge_io, sizeof(bridge_io)); | |||||
bzero(&device_io, sizeof(device_io)); | |||||
bridge_io.pi_sel = bridge_sel; | |||||
device_io.pi_sel = device_sel; | |||||
bridge_io.pi_width = WIDTH_8B; | |||||
bridge_io.pi_reg = PCIR_BRIDGECTL_2; | |||||
bridge_io.pi_data = initial_config.bridgeControl; | |||||
if (ioctl(fd, PCIOCWRITE, &bridge_io)) | |||||
error_handler(41); | |||||
bridge_io.pi_width = WIDTH_16B; | |||||
if (ioctl(fd, PCIOCREAD, &bridge_io)) | |||||
error_handler(41); | |||||
printf("Restored bridge control: %X\n",bridge_io.pi_data); | |||||
device_io.pi_width = WIDTH_8B; | |||||
device_io.pi_reg = PCIE_offs + PCIER_DEVICE_CTL; | |||||
device_io.pi_data = initial_config.deviceControl; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(42); | |||||
device_io.pi_width = WIDTH_16B; | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(42); | |||||
printf("Restored device control: %X\n",device_io.pi_data); | |||||
if (AER_offs) { | |||||
device_io.pi_width = WIDTH_32B; | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_SEVERITY; | |||||
device_io.pi_data = initial_config.deviceUncErrSeve; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(43); | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(43); | |||||
printf("Restored Uncorrectable Error Severity: %X\n", | |||||
device_io.pi_data); | |||||
device_io.pi_reg = AER_offs + PCIR_AER_UC_MASK; | |||||
device_io.pi_data = initial_config.deviceUncErrMask; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(43); | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(43); | |||||
printf("Restored Uncorrectable Error Mask: %X\n", | |||||
device_io.pi_data); | |||||
device_io.pi_reg = AER_offs + PCIR_AER_COR_MASK; | |||||
device_io.pi_data = initial_config.deviceCErrMask; | |||||
if (ioctl(fd, PCIOCWRITE, &device_io)) | |||||
error_handler(43); | |||||
if (ioctl(fd, PCIOCREAD, &device_io)) | |||||
error_handler(43); | |||||
printf("Restored Correctable Error Mask: %X\n", | |||||
device_io.pi_data); | |||||
} | |||||
} | |||||
/* General helpers */ | |||||
/* | |||||
* Handles runtime errors. | |||||
* try restoring configuration if any modification is made, | |||||
* or print configuration if cannot restore. | |||||
*/ | |||||
void error_handler(int stage) | |||||
{ | |||||
if (stage) { | |||||
printf("\n"); | |||||
printf("ERROR! Error stage NO.: %d\n",stage); | |||||
printf("Translation (errno): %s\n",strerror(errno)); | |||||
} | |||||
printf("\n"); | |||||
switch (stage / 10) { | |||||
case 3: | |||||
printf("Try restoring initial configration\n"); | |||||
restore_initial(); | |||||
printf("Restore success\n"); | |||||
break; | |||||
case 4: | |||||
printf("Restore configration failed.\n"); | |||||
printf("Print stored initial configration:\n"); | |||||
printf("* bridge control [8 bits]: %X\n", initial_config.bridgeControl); | |||||
printf("* device control [8 bits]: %X\n", initial_config.deviceControl); | |||||
printf("* device UNC Mask [AER][32 bits]: %X\n", initial_config.deviceUncErrMask); | |||||
printf("* device UNC Severity [AER][32 bits]: %X\n", initial_config.deviceUncErrSeve); | |||||
printf("* device COR Mask [AER][32 bits]: %X\n", initial_config.deviceCErrMask); | |||||
break; | |||||
case 5: | |||||
printf("Status before error-injection:\n"); | |||||
printf("* NIC device flag UP: %d\n", initial_config.previousUP); | |||||
printf("* Bridge busMaster enable: %d\n", initial_config.previousBUSMAS); | |||||
break; | |||||
default: | |||||
printf("No configuration changed\n"); | |||||
break; | |||||
} | |||||
abort (); | |||||
} | |||||
/* | |||||
* Return the offset of PCI-Express capbility config space. | |||||
* Return 0 if not found. | |||||
*/ | |||||
int findPCIEOffs(struct pcisel *pi_sel) | |||||
{ | |||||
struct pci_io io; | |||||
bzero(&io, sizeof(io)); | |||||
int pointer; | |||||
int offset = 0; | |||||
io.pi_width = WIDTH_8B; | |||||
io.pi_reg = PCIR_CAP_PTR; | |||||
io.pi_sel = *pi_sel; | |||||
if (ioctl(fd, PCIOCREAD, &io)) | |||||
error_handler(101); | |||||
pointer = io.pi_data; | |||||
io.pi_width = WIDTH_16B; | |||||
while (pointer < PCIR_EXTCAP && pointer > 0) { | |||||
io.pi_reg = pointer; | |||||
if (ioctl(fd, PCIOCREAD, &io)) | |||||
error_handler(101); | |||||
if ((io.pi_data & PCIEM_CAPID) == PCIY_EXPRESS) { | |||||
offset = pointer; | |||||
break; | |||||
} | |||||
pointer = (io.pi_data) >> 8; | |||||
} | |||||
return pointer; | |||||
} | |||||
/* | |||||
* Return the offset of PCI-Express AER capbility configuration space. | |||||
* Return 0 if not found. | |||||
*/ | |||||
int findAEROffs(struct pcisel *pi_sel) | |||||
{ | |||||
struct pci_io io; | |||||
bzero(&io, sizeof(io)); | |||||
int pointer; | |||||
int offset = 0; | |||||
io.pi_width = WIDTH_32B; | |||||
io.pi_sel = *pi_sel; | |||||
pointer = PCIR_EXTCAP; | |||||
while (pointer < PCIR_EXTCAP_MAX && | |||||
pointer >= PCIR_EXTCAP) { | |||||
io.pi_reg = pointer; | |||||
if (ioctl(fd, PCIOCREAD, &io)) | |||||
error_handler(102); | |||||
if (PCI_EXTCAP_ID(io.pi_data) == PCIZ_AER) { | |||||
offset = pointer; | |||||
break; | |||||
} | |||||
pointer = PCI_EXTCAP_NEXTPTR(io.pi_data); | |||||
} | |||||
return offset; | |||||
} | |||||
/* Scan and scan helpers */ | |||||
/* | |||||
* Record any avaliable methods into the singly-linked tail queue. | |||||
*/ | |||||
void recordAvbl(int bgidx, int devidx, struct pci_conf *buffer, int method) | |||||
{ | |||||
struct dev_loca_entry *entry; | |||||
entry = malloc(sizeof(*entry)); | |||||
entry->bridge_pi_sel = buffer[bgidx].pc_sel; | |||||
strcpy(entry->bridge_name, buffer[bgidx].pd_name); | |||||
entry->bridge_unit = buffer[bgidx].pd_unit; | |||||
entry->device_pi_sel = buffer[devidx].pc_sel; | |||||
strcpy(entry->device_name, buffer[devidx].pd_name); | |||||
entry->device_unit = buffer[devidx].pd_unit; | |||||
entry->method = method; | |||||
if (method == METHOD_NICFLAG) | |||||
STAILQ_INSERT_TAIL(&dev_loca_head, entry, next); | |||||
else | |||||
STAILQ_INSERT_HEAD(&dev_loca_head, entry, next); | |||||
} | |||||
/* | |||||
* Check what kind of error-injection method can apply on certain bridge-device combination. | |||||
*/ | |||||
void detectAvbl(int bgidx, int devidx, struct pci_conf *buffer, int pcie_offs) | |||||
{ | |||||
struct pci_io io; | |||||
int RBER_enable; | |||||
/* Check1: Network Interface */ | |||||
if (buffer[devidx].pc_class == PCIC_NETWORK && | |||||
buffer[devidx].pc_subclass == PCIS_NETWORK_ETHERNET) | |||||
recordAvbl(bgidx, devidx, buffer, METHOD_NICFLAG); | |||||
/* Check2: Role-Based Error Reporting */ | |||||
RBER_enable = 0; | |||||
bzero(&io, sizeof(io)); | |||||
io.pi_sel = buffer[devidx].pc_sel; | |||||
io.pi_width = WIDTH_16B; | |||||
io.pi_reg = PCIER_DEVICE_CAP + pcie_offs; | |||||
if (ioctl(fd, PCIOCREAD, &io)) | |||||
error_handler(13); | |||||
RBER_enable = (io.pi_data & PCIEM_CAP_ROLE_ERR_RPT) >> 15; | |||||
/* If PCI_FUNCMAX is assigned, cannot take the device. */ | |||||
io.pi_sel.pc_func = PCI_FUNCMAX; | |||||
if (!ioctl(fd, PCIOCREAD, &io)) | |||||
return; | |||||
if (findAEROffs(&buffer[devidx].pc_sel)) { | |||||
if (RBER_enable) | |||||
recordAvbl(bgidx, devidx, buffer, METHOD_RBER_AER); | |||||
else | |||||
recordAvbl(bgidx, devidx, buffer, METHOD_AER); | |||||
} else if (!RBER_enable) | |||||
recordAvbl(bgidx, devidx, buffer, METHOD_NO_RBER); | |||||
} | |||||
/* | |||||
* Scan through the children of a bridge. | |||||
*/ | |||||
void scanChild(int bgidx, struct pci_conf *buffer, int num_matches) | |||||
{ | |||||
int pcie_offs, bus, i; | |||||
struct pci_io scan_io; | |||||
bzero(&scan_io, sizeof(scan_io)); | |||||
scan_io.pi_width = WIDTH_16B; | |||||
scan_io.pi_reg = PCIR_PRIBUS_1; | |||||
scan_io.pi_sel = buffer[bgidx].pc_sel; | |||||
if(ioctl(fd, PCIOCREAD, &scan_io)) | |||||
error_handler(12); | |||||
bus = scan_io.pi_data >> 8; | |||||
for (i = 0; i < num_matches; i++) { | |||||
if (buffer[i].pc_sel.pc_bus != bus) | |||||
continue; | |||||
pcie_offs = findPCIEOffs(&buffer[i].pc_sel); | |||||
if (pcie_offs) | |||||
detectAvbl(bgidx, i, buffer, pcie_offs); | |||||
} | |||||
} | |||||
/* | |||||
* Using ioctl to get the device list from kernel. | |||||
* Only search for bridges first. | |||||
*/ | |||||
void scan() | |||||
{ | |||||
int i; | |||||
struct pci_conf_io pciconfio; | |||||
struct pci_conf buffer[DEVICE_LIST_LENGTH]; | |||||
bzero(&pciconfio, sizeof(pciconfio)); | |||||
pciconfio.match_buf_len = sizeof(buffer); | |||||
pciconfio.matches = buffer; | |||||
printf("Perform device list scan..\n"); | |||||
if (ioctl(fd, PCIOCGETCONF, &pciconfio)) | |||||
error_handler(11); | |||||
switch (pciconfio.status) { | |||||
case PCI_GETCONF_LAST_DEVICE: | |||||
break; | |||||
case PCI_GETCONF_LIST_CHANGED: | |||||
printf("Device list changes, please retry\n"); | |||||
error_handler(0); | |||||
case PCI_GETCONF_MORE_DEVS: | |||||
printf("Increase DEVICE_LIST_LENGTH and retry\n"); | |||||
error_handler(0); | |||||
case PCI_GETCONF_ERROR: | |||||
error_handler(11); | |||||
break; | |||||
default: | |||||
error_handler(11); | |||||
} | |||||
for (i = 0; i < pciconfio.num_matches; i++) { | |||||
if (buffer[i].pc_sel.pc_bus != 0) | |||||
continue; | |||||
if (buffer[i].pc_class != PCIC_BRIDGE || | |||||
buffer[i].pc_subclass != PCIS_BRIDGE_PCI) | |||||
continue; | |||||
if (!findPCIEOffs(&buffer[i].pc_sel)) | |||||
continue; | |||||
if (!findAEROffs(&buffer[i].pc_sel)) | |||||
continue; | |||||
scanChild(i, buffer, pciconfio.num_matches); | |||||
} | |||||
} | |||||
/* Usage and description */ | |||||
void usage() | |||||
{ | |||||
fprintf(stderr, "%s\n%s\n%s\n", | |||||
"usage: InjectAER -a: automatically try error-injection on a device.", | |||||
" InjectAER -l: list available devices and methods and guide selection.", | |||||
" InjectAER -h: Usage."); | |||||
} |