#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/pciio.h>
#include <sys/mman.h>
#include <sys/memrange.h>
#include <sys/stat.h>
#include <machine/endian.h>
#include <stddef.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#define _PATH_DEVPCI "/dev/pci"
#define _PATH_DEVMEM "/dev/mem"
#define PCI_CFG_CMD 0x04
#define PCI_CFG_ROM_BAR 0x30
#define PCI_ROM_ADDR_MASK 0xFFFFFC00
#define PCI_ROM_RESERVED_MASK 0x03FE
#define PCI_ROM_ACTIVATE 0x01
#define PCI_CMD_MEM_SPACE 0x02
#define PCI_HDRTYPE_MFD 0x80
#define MAX_PCI_DEVS 64
typedef enum {
PRINT = 0,
SAVE = 1
} action_t;
#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();
}
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 <base-address>]\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");
}
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++) {
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;
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;
}
}
}
static uint32_t
pci_testrombar(int pci_fd, struct pci_conf *dev)
{
struct pci_io io;
uint32_t romsize;
romsize = 0;
if (dev->pc_hdr != 0x00)
return romsize;
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;
if (ioctl(pci_fd, PCIOCREAD, &io) == -1)
return 0;
if (io.pi_data & PCI_ROM_ADDR_MASK)
romsize = -(io.pi_data & PCI_ROM_ADDR_MASK);
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);
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;
if (dev->pc_hdr != 0x00)
return -1;
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;
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;
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;
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;
}