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."); | |||||
| } | |||||