/* pci-config-space.c: Read the PCI configuration space. Read the PCI configuration space using the Intel bus-bridge interface registers. This bypasses the BIOS32 entry, and thus we are not assured of working on all systems. Copyright 1998-2003 by Donald Becker. This software may be used and distributed according to the terms of the GNU General Public License (GPL), incorporated herein by reference. Contact the author for use under other terms. The author may be reached as becker@scyld.com, or C/O Scyld Computing Corporation 914 Bay Ridge Road, Suite 220 Annapolis MD 21403 Support and updates available at http://www.scyld.com/diag/index.html Common-sense licensing statement: Using any portion of this program in your own program means that you must give credit to the original author and release the resulting code under the GPL. */ static char *version_msg = "pci-config.c:v2.05 2/23/2005 Donald Becker (becker@scyld.com)\n" " http://www.scyld.com/diag/index.html\n"; static char *usage_msg = "Usage: pci-config [-aDfSvVW] [-# ]\n"; static char *long_usage_msg ="\ \n\ This program shows the contents of PCI configuration space.\n\ It reads the hardware registers, and thus must be run as 'root'.\n\ \n\ Running this program with no options shows the installed PCI devices.\n\ Each line is prefixed by its index which may be used with -#\n\ e.g. \"pci-config -#3\" to specify the device to operate on.\n\ \n\ Commonly use options are\n\ -# Operate only on DEVICE-INDEX e.g -#3\n\ \n\ The operations on the selected device are\n\ -a --show-addresses Show PCI address registers.\n\ -S --sleep Put device to sleep (ACPI D3 state)\n\ -W --wake Wake a sleeping device (ACPI D0 state)\n\ \n\ Less commonly used options are\n\ -B --bus Show only devices on BUS.\n\ -A --set-addresses Set PCI address register 1 to the ADDR.\n\ -D --debug Show details of operations\n\ -f --force Override checks and perform the operation\n\ -u --usage Show this long usage message\n\ -v --verbose Verbose mode\n\ -V --version Display this program's version information\n\ \n"; #include #include #include #include #include #include #include #if defined(__linux__) && __GNU_LIBRARY__ == 1 #include /* Newer libraries use instead. */ #else #include #endif #if !defined(__OPTIMIZE__) #error You must compile this driver with "-O"! #endif struct option longopts[] = { {"show-addresses", 0, 0, 'a'}, /* Show PCI address registers. */ {"set-addresses", 1, 0, 'A'}, /* Show PCI address registers. */ {"bus", 1, 0, 'B'}, /* Show only devices on BUS. */ {"debug", 0, 0, 'D'}, /* Increase debug level. */ {"force", 0, 0, 'f'}, /* Force operation. */ {"set-WOL", 0, 0, 'M'}, /* Set to Wake-On-LAN mode. */ {"sleep", 0, 0, 'S'}, /* Put device to sleep (ACPI D3 state). */ {"usage", 0, 0, 'u'}, /* Show the long usage message. */ {"verbose", 0, 0, 'v'}, /* Verbose mode */ {"version", 0, 0, 'V'}, /* Display version number */ {"wake-on-lan", 0, 0, 'W'}, /* Wake (set to D0 state) the device. */ {"device-index", 1, 0, '#'}, /* Operate only on device INDEX. */ { 0, 0, 0, 0 } }; static int verbose=1, opt_a=0, opt_f=0, opt_wake=0, opt_set_WOL=0, debug=0; static int opt_sleep = 0; static long set_address = -1; static int scan_one_bus(int pci_bus); static void show_addr_config(unsigned char pci_bus, unsigned char pci_dev_fn); static void show_ext_caps(unsigned int *cfg_space, unsigned char pci_bus, unsigned char pci_dev_fn); static void show_one_device(unsigned char pci_bus, unsigned char pci_dev_fn, int dev_num); static void cyclone_WOL(int pci_bus, int pci_dev_fn, void *pci_config_space); static void acpi_wake(unsigned char pci_bus, unsigned char pci_dev_fn, void *config); static void acpi_sleep(unsigned char bus, unsigned char devfn, void *pci_cfg); static int dump_mem_region(long addr); extern int pcibios_read_config_byte (unsigned char bus, unsigned char dev_fn, unsigned char where, unsigned char *val); int pcibios_read_config_word (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned short *val); extern int pcibios_read_config_dword (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned int *val); void pcibios_write_config_byte (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned char val); void pcibios_write_config_word (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned short val); void pcibios_write_config_dword (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned int val); /* Match values for specifying a single device. */ int card_num = 0; int match_busnum = -1, match_devfn = -1; int show_all_devs = 1; int main(int argc, char **argv) { int pci_bus = 0; int errflag = 0, show_version = 0; int c, longind; while ((c = getopt_long(argc, argv, "#:aA:b:B:DfMs:SuvVW", longopts, &longind)) != -1) switch (c) { case 'a': opt_a++; break; case 'A': set_address = strtol(optarg, NULL, 16); break; case 'b': printf("Setting bus to %s.\n", optarg); case 'B': pci_bus = strtol(optarg, NULL, 0); break; case 'D': debug++; break; case 'f': opt_f++; break; case 'M': opt_set_WOL++; break; case 's': { int match_cnt, match_bus, match_dev, match_fn = 0; match_cnt = sscanf(optarg, "%d:%d.%1d", &match_bus, &match_dev, &match_fn); if (match_cnt == 2 || match_cnt == 3) { match_busnum = match_bus; match_devfn = (match_dev << 3) | match_fn; } else { fprintf(stderr, "Invalid PCI bus:device number '%s'.\n", optarg); errflag++; } show_all_devs = 0; break; } case 'S': opt_sleep++; break; case 'u': printf("%s%s", usage_msg, long_usage_msg); return 0; case 'v': verbose++; break; case 'V': show_version++; break; case 'W': opt_wake++; break; case '#': card_num = atoi(optarg); show_all_devs = 0; break; case '?': errflag++; } if (errflag) { fprintf(stderr, "%s%s", usage_msg, " Use -u for more information.\n"); return 3; } if (verbose) printf(version_msg); /* Get access to all of I/O space. */ if (iopl(3) < 0) { perror("pci-config: iopl()"); fprintf(stderr, "This program must be run as root.\n"); return 2; } if (match_busnum >= 0) { if (verbose) fprintf(stderr, "Looking for device %d:%d.\n", match_busnum, match_devfn); scan_one_bus(match_busnum); } else scan_one_bus(pci_bus); return 0; } static int scan_one_bus(int pci_bus) { int pci_dev_fn; static int dev_num = 0; for (pci_dev_fn = 0; pci_dev_fn < 256; pci_dev_fn++) { /*unsigned char cb;*/ unsigned int pci_id; /* Read the PCI ID from offset 0. */ pcibios_read_config_dword(pci_bus, pci_dev_fn, 0, &pci_id); if (pci_id == 0xffffffff) continue; dev_num++; if (card_num == dev_num) { show_one_device(pci_bus, pci_dev_fn, dev_num); } else if (pci_bus == match_busnum && pci_dev_fn == match_devfn) { show_one_device(pci_bus, pci_dev_fn, dev_num); } else if (show_all_devs) { printf("Device #%d at bus %d device/function %d/%d, %8.8x.\n", dev_num, pci_bus, pci_dev_fn>>3, pci_dev_fn&7, pci_id); } /* Skip fn1-fn7 if this is not a multi-function device. */ if ((pci_dev_fn & 7) == 0) { unsigned int cdw; pcibios_read_config_dword(pci_bus, pci_dev_fn, 3*4, &cdw); if ((cdw & 0x00800000) == 0) pci_dev_fn += 7; } } return 0; } static void show_one_device(unsigned char pci_bus, unsigned char pci_dev_fn, int dev_num) { unsigned int config[64]; int i; int pci_id; printf("Device #%d at bus %d device/function %d/%d.", dev_num, pci_bus, pci_dev_fn>>3, pci_dev_fn&7); for (i = 0; i < 64; i++) { pcibios_read_config_dword(pci_bus, pci_dev_fn, i<<2, &config[i]); printf("%s%8.8x", i % 8 == 0 ? "\n " : " ", config[i]); } printf("\n"); for (i = 0; i < 5; i++) { unsigned int pciaddr = config[4 + i]; if (pciaddr) printf(" Base Address %d: %s at %8.8x.\n", i, pciaddr & 1 ? "I/O" : "Memory", pciaddr & ~1); } if (set_address >= 0) { fprintf(stderr, "Setting PCI address register 1 to 0x%lx.\n", set_address); pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x14, set_address); pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x04, config[1] | 3); } if (opt_a) show_addr_config(pci_bus, pci_dev_fn); pci_id = config[0]; if (config[1] & 0x00100000) show_ext_caps(config, pci_bus, pci_dev_fn); if (config[10]) { char *cis_addr_space[] = {"PCI configuration space", "BAR 0", "BAR 1", "BAR 2", "BAR 3", }; int space = config[10] & 7; printf(" CardBus CIS pointer 0x%4.4x (%s), address %x.\n", config[10], cis_addr_space[space], config[4 + (space-1)]); if (space > 0 && space < 4) dump_mem_region(config[4 + ((space-1))]); } if (opt_sleep) acpi_sleep(pci_bus, pci_dev_fn, config); if (opt_wake) acpi_wake(pci_bus, pci_dev_fn, config); if (opt_set_WOL) if (pci_id == 0x905510b7) cyclone_WOL(pci_bus, pci_dev_fn, config); } static void show_addr_config(unsigned char pci_bus, unsigned char pci_dev_fn) { int i; unsigned int pciaddr, cdw; for (i = 0; i < 5; i++) { int cfg_i = 0x10 + (i<<2); pcibios_read_config_dword(pci_bus, pci_dev_fn, cfg_i, &pciaddr); pcibios_write_config_dword(pci_bus, pci_dev_fn, cfg_i, 0xffffffff); pcibios_read_config_dword(pci_bus, pci_dev_fn, cfg_i, &cdw); if (cdw == 0) break; printf(" Address %d %s at %8.8x, decoded bits are %8.8x.\n", i, cdw & 1 ? "is I/O" : "memory", pciaddr & ~1, ~cdw); pcibios_write_config_dword(pci_bus, pci_dev_fn, cfg_i, pciaddr); } pcibios_read_config_dword(pci_bus, pci_dev_fn, 0x30, &pciaddr); pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, 0xfffffffe); pcibios_read_config_dword(pci_bus, pci_dev_fn, 0x30, &cdw); pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, pciaddr); if (cdw) printf(" BIOS ROM at %8.8x, decoded bits are %8.8x.\n", pciaddr, cdw); else printf(" No BIOS extension (boot ROM).\n"); return; } static void show_ext_caps(unsigned int *cfg_space, unsigned char pci_bus, unsigned char pci_dev_fn) { unsigned char *pcfg = (void *)cfg_space; int cap_idx = cfg_space[13] & 0xff; printf(" Extended capabilities, first structure at offset 0x%x.\n", cap_idx); for (; cap_idx; cap_idx = pcfg[cap_idx + 1]) { printf(" Extended PCI capability type %d at 0x%2.2x, next %d.\n", pcfg[cap_idx], cap_idx, pcfg[cap_idx + 1]); if (pcfg[cap_idx] == 1) { printf(" Power management entry ver. %d: Capabilities %2.2x%2.2x" ", Ctrl %2.2x%2.2x, Event %2.2x%2.2x.\n", pcfg[cap_idx + 2] & 7, pcfg[cap_idx + 3], pcfg[cap_idx + 2], pcfg[cap_idx + 5], pcfg[cap_idx + 4], pcfg[cap_idx + 7], pcfg[cap_idx + 6]); printf(" Power state D%d.\n", pcfg[cap_idx + 4] & 3); } } } static int acpi_find(unsigned char pci_bus, unsigned char pci_dev_fn, void *config) { unsigned char *pcfg = (void *)config; if (pcfg[6] & 0x10) { int cap_idx = pcfg[0x34]; printf(" Extended capabilities, first structure at offset 0x%x.\n", cap_idx); for (; cap_idx; cap_idx = pcfg[cap_idx + 1]) { if (pcfg[cap_idx] == 1) return cap_idx; } } return 0; } static void acpi_wake(unsigned char pci_bus, unsigned char pci_dev_fn, void *pci_config_space) { unsigned char *config = pci_config_space; unsigned short *configw = pci_config_space; unsigned int *configdw = pci_config_space; int pwr_idx = acpi_find(pci_bus, pci_dev_fn, config); unsigned short pwr_command = configw[(pwr_idx + 4)>>1]; int i; if (debug) printf("Power index is %#x.\n", pwr_idx); printf(" Waking up an ACPI device. Currently powered %s, " "I/O %#x IRQ %d.\n" " Updating the power state of %4.4x->%4.4x.\n", pwr_command & 3 ? "down" : "up", configdw[0x10>>2], config[0x3c], pwr_command, pwr_command & ~3); pcibios_write_config_word(pci_bus, pci_dev_fn, pwr_idx + 4, pwr_command & ~3); /* Many devices must have their PCI register state restored when changing from D3 state. */ for (i = 0x10; i <= 0x20; i+=4) pcibios_write_config_dword(pci_bus, pci_dev_fn, i, configdw[i >> 2]); /* PCI_ROM_ADDRESS, interrupt line, cache line size, latency timer */ pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, configdw[0x30 >> 2]); pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x3c, config[0x3c]); pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x0c, config[0x0c]); pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x0d, config[0x0d]); /* Finally, restore the command register. */ pcibios_write_config_word(pci_bus, pci_dev_fn, 0x04, configw[4>>1]); } static void acpi_sleep(unsigned char bus, unsigned char devfn, void *pci_config_space) { unsigned short *configw = pci_config_space; int pwr_idx = acpi_find(bus, devfn, pci_config_space); unsigned short pwr_command = configw[(pwr_idx + 4)>>1]; pcibios_write_config_word(bus, devfn, pwr_idx + 4, pwr_command | 0x0103); } /* Put the 3Com Cyclone e.g. 3c905B series into Wake On LAN mode. */ static void cyclone_WOL(int pci_bus, int pci_dev_fn, void *config) { int pwr_idx = acpi_find(pci_bus, pci_dev_fn, config); unsigned short pwr_command = ((unsigned short *)config)[(pwr_idx + 4)>>1]; long ioaddr = ((int *)config)[0x10]; acpi_wake(pci_bus, pci_dev_fn, config); outw(0x801f, ioaddr + 0x0e); /* Set RxFilter to accept frames. */ outw((1<<11) + 7, ioaddr + 0x0e); outw(7, ioaddr + 0x0c); printf(" Window 7 Power Management Event is %4.4x.\n", inw(ioaddr + 0x0c)); printf(" Changing the power state from %4.4x to 0103.\n", pwr_command); outw(0x2000, ioaddr + 0x0e); /* RxEnable. */ pcibios_write_config_word(pci_bus, pci_dev_fn, 0xe0, pwr_command | 0x8103); } #define PCI_CONFIG_ADDR 0x0cf8 #define PCI_CONFIG_DATA 0x0cfc int pcibios_read_config_byte (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned char *val) { outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), PCI_CONFIG_ADDR); *val = inb(PCI_CONFIG_DATA + (regnum & 3)); return 0; } int pcibios_read_config_word (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned short *val) { outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), PCI_CONFIG_ADDR); *val = inw(PCI_CONFIG_DATA + (regnum & 2)); return 0; } int pcibios_read_config_dword (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned int *val) { outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), PCI_CONFIG_ADDR); *val = inl(PCI_CONFIG_DATA); return 0; } void pcibios_write_config_byte (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned char val) { outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), PCI_CONFIG_ADDR); outb(val, PCI_CONFIG_DATA + (regnum & 3)); return; } void pcibios_write_config_word (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned short val) { outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), PCI_CONFIG_ADDR); outw(val, PCI_CONFIG_DATA + (regnum & 2)); return; } void pcibios_write_config_dword (unsigned char bus, unsigned char dev_fn, unsigned char regnum, unsigned int val) { outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), PCI_CONFIG_ADDR); outl(val, PCI_CONFIG_DATA); return; } /* Map the board shared memory into our address space -- this code is a good example of non-kernel access to devices on the PCI bus. */ static int dump_mem_region(long addr) { unsigned short *shared_mem; int i; int memfd = open("/dev/kmem", O_RDWR); if (memfd < 0) { perror("/dev/kmem (shared memory)"); return 2; } else printf("Opened /dev/kmem for PCI memory.\n"); shared_mem = mmap(0, 0x8000, PROT_READ|PROT_WRITE, MAP_SHARED, memfd, addr); printf("Shared memory at %#lx (%p).\n", (long)addr, shared_mem); for (i = 0; i < 100; i++) printf(" %4.4x", shared_mem[i]); close(memfd); printf(" ...\n"); return 0; } /* * Local variables: * compile-command: "cc -O -Wall -o pci-config pci-config.c" * tab-width: 4 * c-indent-level: 4 * c-basic-offset: 4 * End: */