/* * Copyright (c) 2007 Bruce M. Simpson. * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _PATH_DEVPCI "/dev/pci" #define _PATH_DEVMEM "/dev/mem" #define PCI_CFG_CMD 0x04 /* command register */ #define PCI_CFG_ROM_BAR 0x30 /* rom base register */ #define PCI_ROM_ADDR_MASK 0xFFFFFC00 /* the 21 MSBs form the BAR */ #define PCI_ROM_RESERVED_MASK 0x03FE /* mask for reserved bits */ #define PCI_ROM_ACTIVATE 0x01 /* mask for activation bit */ #define PCI_CMD_MEM_SPACE 0x02 /* memory space bit */ #define PCI_HDRTYPE_MFD 0x80 /* MFD bit in HDRTYPE reg. */ #define MAX_PCI_DEVS 64 /* # of devices in system */ typedef enum { PRINT = 0, SAVE = 1 } action_t; /* * This is set to a safe physical base address in PCI range for my Vaio. * YOUR MACHINE *WILL* VARY, I SUGGEST YOU LOOK UP YOUR MACHINE'S MEMORY * MAP IN DETAIL IF YOU PLAN ON SAVING ROMS. * * This is the hole between the APIC and the BIOS (FED00000-FEDFFFFF); * should be a safe range on the i815 Solano chipset. */ #define PCI_DEFAULT_ROM_ADDR 0xFED00000 static char *progname = NULL; static uintptr_t base_addr = PCI_DEFAULT_ROM_ADDR; static void usage(void); static void banner(void); static void pci_enum_devs(int pci_fd, action_t action); static uint32_t pci_testrombar(int pci_fd, struct pci_conf *dev); static int pci_enable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd); static int pci_disable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd); static int pci_save_rom(char *filename, int romsize); int main(int argc, char *argv[]) { int pci_fd; int err; int ch; action_t action; char *base_addr_string; char *ep; err = -1; pci_fd = -1; action = PRINT; base_addr_string = NULL; ep = NULL; progname = basename(argv[0]); while ((ch = getopt(argc, argv, "sb:h")) != -1) switch (ch) { case 's': action = SAVE; break; case 'b': base_addr_string = optarg; break; case 'h': default: usage(); } argc -= optind; argv += optind; if (base_addr_string != NULL) { uintmax_t base_addr_max; base_addr_max = strtoumax(base_addr_string, &ep, 16); if (*ep != '\0') { fprintf(stderr, "Invalid base address.\r\n"); usage(); } /* XXX: TODO: deal with 64-bit PCI. */ base_addr = (uintptr_t)base_addr_max; base_addr &= ~PCI_ROM_RESERVED_MASK; } if (argc > 0) usage(); if ((pci_fd = open(_PATH_DEVPCI, O_RDWR)) == -1) { perror("open"); goto cleanup; } banner(); pci_enum_devs(pci_fd, action); err = 0; cleanup: if (pci_fd != -1) close(pci_fd); exit ((err == 0) ? EXIT_SUCCESS : EXIT_FAILURE); } static void usage(void) { fprintf(stderr, "usage: %s [-s] [-b ]\r\n", progname); exit(EXIT_FAILURE); } static void banner(void) { fprintf(stderr, "WARNING: You are advised to run this program in single\r\n" "user mode, with few or no processes running.\r\n\r\n"); } /* * Enumerate PCI device list to a limit of MAX_PCI_DEVS devices. */ static void pci_enum_devs(int pci_fd, action_t action) { struct pci_conf devs[MAX_PCI_DEVS]; char filename[16]; struct pci_conf_io pc; struct pci_conf *p; int result; int romsize; uint16_t oldcmd; result = -1; romsize = 0; bzero(&pc, sizeof(pc)); pc.match_buf_len = sizeof(devs); pc.matches = devs; if (ioctl(pci_fd, PCIOCGETCONF, &pc) == -1) { perror("ioctl PCIOCGETCONF"); return; } if (pc.status == PCI_GETCONF_ERROR) { fprintf(stderr, "Error fetching PCI device list from kernel.\r\n"); return; } if (pc.status == PCI_GETCONF_MORE_DEVS) { fprintf(stderr, "More than %d devices exist. Only the first %d will be inspected.\r\n", MAX_PCI_DEVS, MAX_PCI_DEVS); } for (p = devs ; p < &devs[pc.num_matches]; p++) { /* No PCI bridges; only PCI devices. */ if (p->pc_hdr != 0x00) continue; romsize = pci_testrombar(pci_fd, p); switch (action) { case PRINT: printf( "Domain %04Xh Bus %02Xh Device %02Xh Function %02Xh: ", p->pc_sel.pc_domain, p->pc_sel.pc_bus, p->pc_sel.pc_dev, p->pc_sel.pc_func); printf((romsize ? "%dKB ROM aperture detected." : "No ROM present."), romsize/1024); printf("\r\n"); break; case SAVE: if (romsize == 0) continue; /* XXX */ snprintf(filename, sizeof(filename), "%08X.rom", ((p->pc_device << 16) | p->pc_vendor)); fprintf(stderr, "Saving %dKB ROM image to %s...\r\n", romsize, filename); if (pci_enable_bars(pci_fd, p, &oldcmd) == 0) result = pci_save_rom(filename, romsize); pci_disable_bars(pci_fd, p, &oldcmd); if (result == 0) { fprintf(stderr, "Done.\r\n"); } else { fprintf(stderr, "An error occurred whilst saving the ROM.\r\n"); } break; } /* switch */ } /* for */ } /* * Return: size of ROM aperture off dev, 0 if no ROM exists. */ static uint32_t pci_testrombar(int pci_fd, struct pci_conf *dev) { struct pci_io io; uint32_t romsize; romsize = 0; /* * Only attempt to discover ROMs on Header Type 0x00 devices. */ if (dev->pc_hdr != 0x00) return romsize; /* * Activate ROM BAR */ io.pi_sel = dev->pc_sel; io.pi_reg = PCI_CFG_ROM_BAR; io.pi_width = 4; io.pi_data = 0xFFFFFFFF; if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) return romsize; /* * Read back ROM BAR and compare with mask */ if (ioctl(pci_fd, PCIOCREAD, &io) == -1) return 0; /* * Calculate ROM aperture if one was set. */ if (io.pi_data & PCI_ROM_ADDR_MASK) romsize = -(io.pi_data & PCI_ROM_ADDR_MASK); /* * Disable the ROM BAR when done. */ io.pi_data = 0; if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) return 0; return romsize; } static int pci_save_rom(char *filename, int romsize) { int fd, mem_fd, err; void *map_addr; fd = err = mem_fd = -1; map_addr = MAP_FAILED; if ((mem_fd = open(_PATH_DEVMEM, O_RDONLY)) == -1) { perror("open"); return -1; } map_addr = mmap(NULL, romsize, PROT_READ, MAP_SHARED|MAP_NOCORE, mem_fd, base_addr); /* Dump ROM aperture to a file. */ if ((fd = open(filename, O_CREAT|O_RDWR|O_TRUNC|O_NOFOLLOW, S_IRUSR|S_IWUSR)) == -1) { perror("open"); goto cleanup; } if (write(fd, map_addr, romsize) != romsize) perror("write"); err = 0; cleanup: if (fd != -1) close(fd); if (map_addr != MAP_FAILED) munmap((void *)base_addr, romsize); if (mem_fd != -1) close(mem_fd); return err; } static int pci_enable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd) { struct pci_io io; /* Don't grok bridges. */ if (dev->pc_hdr != 0x00) return -1; /* Save command register. */ io.pi_sel = dev->pc_sel; io.pi_reg = PCI_CFG_CMD; io.pi_width = 2; if (ioctl(pci_fd, PCIOCREAD, &io) == -1) return -1; *oldcmd = (uint16_t)io.pi_data; io.pi_data |= PCI_CMD_MEM_SPACE; if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) return -1; /* * Activate ROM BAR and map at the specified base address. */ io.pi_sel = dev->pc_sel; io.pi_reg = PCI_CFG_ROM_BAR; io.pi_width = 4; io.pi_data = (base_addr | PCI_ROM_ACTIVATE); if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) return -1; return 0; } static int pci_disable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd) { struct pci_io io; /* * Clear ROM BAR to deactivate the mapping. */ io.pi_sel = dev->pc_sel; io.pi_reg = PCI_CFG_ROM_BAR; io.pi_width = 4; io.pi_data = 0; if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) return 0; /* * Restore state of the command register. */ io.pi_sel = dev->pc_sel; io.pi_reg = PCI_CFG_CMD; io.pi_width = 2; io.pi_data = *oldcmd; if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) { perror("ioctl"); return 0; } return 0; }