1 /*
2 * Copyright (c) 2007 Bruce M. Simpson.
3 * All rights reserved
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27 #include <sys/types.h>
28 #include <sys/ioctl.h>
29 #include <sys/pciio.h>
30 #include <sys/mman.h>
31 #include <sys/memrange.h>
32 #include <sys/stat.h>
33 #include <machine/endian.h>
34
35 #include <stddef.h>
36 #include <inttypes.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <libgen.h>
40 #include <fcntl.h>
41 #include <string.h>
42 #include <unistd.h>
43
44 #define _PATH_DEVPCI "/dev/pci"
45 #define _PATH_DEVMEM "/dev/mem"
46
47 #define PCI_CFG_CMD 0x04 /* command register */
48 #define PCI_CFG_ROM_BAR 0x30 /* rom base register */
49
50 #define PCI_ROM_ADDR_MASK 0xFFFFFC00 /* the 21 MSBs form the BAR */
51 #define PCI_ROM_RESERVED_MASK 0x03FE /* mask for reserved bits */
52 #define PCI_ROM_ACTIVATE 0x01 /* mask for activation bit */
53
54 #define PCI_CMD_MEM_SPACE 0x02 /* memory space bit */
55 #define PCI_HDRTYPE_MFD 0x80 /* MFD bit in HDRTYPE reg. */
56
57 #define MAX_PCI_DEVS 64 /* # of devices in system */
58
59 typedef enum {
60 PRINT = 0,
61 SAVE = 1
62 } action_t;
63
64 /*
65 * This is set to a safe physical base address in PCI range for my Vaio.
66 * YOUR MACHINE *WILL* VARY, I SUGGEST YOU LOOK UP YOUR MACHINE'S MEMORY
67 * MAP IN DETAIL IF YOU PLAN ON SAVING ROMS.
68 *
69 * This is the hole between the APIC and the BIOS (FED00000-FEDFFFFF);
70 * should be a safe range on the i815 Solano chipset.
71 */
72 #define PCI_DEFAULT_ROM_ADDR 0xFED00000
73
74 static char *progname = NULL;
75 static uintptr_t base_addr = PCI_DEFAULT_ROM_ADDR;
76
77 static void usage(void);
78 static void banner(void);
79 static void pci_enum_devs(int pci_fd, action_t action);
80 static uint32_t pci_testrombar(int pci_fd, struct pci_conf *dev);
81 static int pci_enable_bars(int pci_fd, struct pci_conf *dev,
82 uint16_t *oldcmd);
83 static int pci_disable_bars(int pci_fd, struct pci_conf *dev,
84 uint16_t *oldcmd);
85 static int pci_save_rom(char *filename, int romsize);
86
87 int
main(int argc,char * argv[])88 main(int argc, char *argv[])
89 {
90 int pci_fd;
91 int err;
92 int ch;
93 action_t action;
94 char *base_addr_string;
95 char *ep;
96
97 err = -1;
98 pci_fd = -1;
99 action = PRINT;
100 base_addr_string = NULL;
101 ep = NULL;
102 progname = basename(argv[0]);
103
104 while ((ch = getopt(argc, argv, "sb:h")) != -1)
105 switch (ch) {
106 case 's':
107 action = SAVE;
108 break;
109 case 'b':
110 base_addr_string = optarg;
111 break;
112 case 'h':
113 default:
114 usage();
115 }
116 argc -= optind;
117 argv += optind;
118
119 if (base_addr_string != NULL) {
120 uintmax_t base_addr_max;
121
122 base_addr_max = strtoumax(base_addr_string, &ep, 16);
123 if (*ep != '\0') {
124 fprintf(stderr, "Invalid base address.\r\n");
125 usage();
126 }
127 /* XXX: TODO: deal with 64-bit PCI. */
128 base_addr = (uintptr_t)base_addr_max;
129 base_addr &= ~PCI_ROM_RESERVED_MASK;
130 }
131
132 if (argc > 0)
133 usage();
134
135 if ((pci_fd = open(_PATH_DEVPCI, O_RDWR)) == -1) {
136 perror("open");
137 goto cleanup;
138 }
139
140 banner();
141 pci_enum_devs(pci_fd, action);
142
143 err = 0;
144 cleanup:
145 if (pci_fd != -1)
146 close(pci_fd);
147
148 exit ((err == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
149 }
150
151 static void
usage(void)152 usage(void)
153 {
154
155 fprintf(stderr, "usage: %s [-s] [-b <base-address>]\r\n", progname);
156 exit(EXIT_FAILURE);
157 }
158
159 static void
banner(void)160 banner(void)
161 {
162
163 fprintf(stderr,
164 "WARNING: You are advised to run this program in single\r\n"
165 "user mode, with few or no processes running.\r\n\r\n");
166 }
167
168 /*
169 * Enumerate PCI device list to a limit of MAX_PCI_DEVS devices.
170 */
171 static void
pci_enum_devs(int pci_fd,action_t action)172 pci_enum_devs(int pci_fd, action_t action)
173 {
174 struct pci_conf devs[MAX_PCI_DEVS];
175 char filename[16];
176 struct pci_conf_io pc;
177 struct pci_conf *p;
178 int result;
179 int romsize;
180 uint16_t oldcmd;
181
182 result = -1;
183 romsize = 0;
184
185 bzero(&pc, sizeof(pc));
186 pc.match_buf_len = sizeof(devs);
187 pc.matches = devs;
188
189 if (ioctl(pci_fd, PCIOCGETCONF, &pc) == -1) {
190 perror("ioctl PCIOCGETCONF");
191 return;
192 }
193
194 if (pc.status == PCI_GETCONF_ERROR) {
195 fprintf(stderr,
196 "Error fetching PCI device list from kernel.\r\n");
197 return;
198 }
199
200 if (pc.status == PCI_GETCONF_MORE_DEVS) {
201 fprintf(stderr,
202 "More than %d devices exist. Only the first %d will be inspected.\r\n",
203 MAX_PCI_DEVS, MAX_PCI_DEVS);
204 }
205
206 for (p = devs ; p < &devs[pc.num_matches]; p++) {
207
208 /* No PCI bridges; only PCI devices. */
209 if (p->pc_hdr != 0x00)
210 continue;
211
212 romsize = pci_testrombar(pci_fd, p);
213
214 switch (action) {
215 case PRINT:
216 printf(
217 "Domain %04Xh Bus %02Xh Device %02Xh Function %02Xh: ",
218 p->pc_sel.pc_domain, p->pc_sel.pc_bus,
219 p->pc_sel.pc_dev, p->pc_sel.pc_func);
220 printf((romsize ? "%dKB ROM aperture detected."
221 : "No ROM present."), romsize/1024);
222 printf("\r\n");
223 break;
224 case SAVE:
225 if (romsize == 0)
226 continue; /* XXX */
227
228 snprintf(filename, sizeof(filename), "%08X.rom",
229 ((p->pc_device << 16) | p->pc_vendor));
230
231 fprintf(stderr, "Saving %dKB ROM image to %s...\r\n",
232 romsize, filename);
233
234 if (pci_enable_bars(pci_fd, p, &oldcmd) == 0)
235 result = pci_save_rom(filename, romsize);
236
237 pci_disable_bars(pci_fd, p, &oldcmd);
238
239 if (result == 0) {
240 fprintf(stderr, "Done.\r\n");
241 } else {
242 fprintf(stderr,
243 "An error occurred whilst saving the ROM.\r\n");
244 }
245 break;
246 } /* switch */
247 } /* for */
248 }
249
250 /*
251 * Return: size of ROM aperture off dev, 0 if no ROM exists.
252 */
253 static uint32_t
pci_testrombar(int pci_fd,struct pci_conf * dev)254 pci_testrombar(int pci_fd, struct pci_conf *dev)
255 {
256 struct pci_io io;
257 uint32_t romsize;
258
259 romsize = 0;
260
261 /*
262 * Only attempt to discover ROMs on Header Type 0x00 devices.
263 */
264 if (dev->pc_hdr != 0x00)
265 return romsize;
266
267 /*
268 * Activate ROM BAR
269 */
270 io.pi_sel = dev->pc_sel;
271 io.pi_reg = PCI_CFG_ROM_BAR;
272 io.pi_width = 4;
273 io.pi_data = 0xFFFFFFFF;
274 if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
275 return romsize;
276
277 /*
278 * Read back ROM BAR and compare with mask
279 */
280 if (ioctl(pci_fd, PCIOCREAD, &io) == -1)
281 return 0;
282
283 /*
284 * Calculate ROM aperture if one was set.
285 */
286 if (io.pi_data & PCI_ROM_ADDR_MASK)
287 romsize = -(io.pi_data & PCI_ROM_ADDR_MASK);
288
289 /*
290 * Disable the ROM BAR when done.
291 */
292 io.pi_data = 0;
293 if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
294 return 0;
295
296 return romsize;
297 }
298
299 static int
pci_save_rom(char * filename,int romsize)300 pci_save_rom(char *filename, int romsize)
301 {
302 int fd, mem_fd, err;
303 void *map_addr;
304
305 fd = err = mem_fd = -1;
306 map_addr = MAP_FAILED;
307
308 if ((mem_fd = open(_PATH_DEVMEM, O_RDONLY)) == -1) {
309 perror("open");
310 return -1;
311 }
312
313 map_addr = mmap(NULL, romsize, PROT_READ, MAP_SHARED|MAP_NOCORE,
314 mem_fd, base_addr);
315
316 /* Dump ROM aperture to a file. */
317 if ((fd = open(filename, O_CREAT|O_RDWR|O_TRUNC|O_NOFOLLOW,
318 S_IRUSR|S_IWUSR)) == -1) {
319 perror("open");
320 goto cleanup;
321 }
322
323 if (write(fd, map_addr, romsize) != romsize)
324 perror("write");
325
326 err = 0;
327 cleanup:
328 if (fd != -1)
329 close(fd);
330
331 if (map_addr != MAP_FAILED)
332 munmap((void *)base_addr, romsize);
333
334 if (mem_fd != -1)
335 close(mem_fd);
336
337 return err;
338 }
339
340 static int
pci_enable_bars(int pci_fd,struct pci_conf * dev,uint16_t * oldcmd)341 pci_enable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd)
342 {
343 struct pci_io io;
344
345 /* Don't grok bridges. */
346 if (dev->pc_hdr != 0x00)
347 return -1;
348
349 /* Save command register. */
350 io.pi_sel = dev->pc_sel;
351 io.pi_reg = PCI_CFG_CMD;
352 io.pi_width = 2;
353 if (ioctl(pci_fd, PCIOCREAD, &io) == -1)
354 return -1;
355 *oldcmd = (uint16_t)io.pi_data;
356
357 io.pi_data |= PCI_CMD_MEM_SPACE;
358 if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
359 return -1;
360
361 /*
362 * Activate ROM BAR and map at the specified base address.
363 */
364 io.pi_sel = dev->pc_sel;
365 io.pi_reg = PCI_CFG_ROM_BAR;
366 io.pi_width = 4;
367 io.pi_data = (base_addr | PCI_ROM_ACTIVATE);
368 if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
369 return -1;
370
371 return 0;
372 }
373
374 static int
pci_disable_bars(int pci_fd,struct pci_conf * dev,uint16_t * oldcmd)375 pci_disable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd)
376 {
377 struct pci_io io;
378
379 /*
380 * Clear ROM BAR to deactivate the mapping.
381 */
382 io.pi_sel = dev->pc_sel;
383 io.pi_reg = PCI_CFG_ROM_BAR;
384 io.pi_width = 4;
385 io.pi_data = 0;
386 if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
387 return 0;
388
389 /*
390 * Restore state of the command register.
391 */
392 io.pi_sel = dev->pc_sel;
393 io.pi_reg = PCI_CFG_CMD;
394 io.pi_width = 2;
395 io.pi_data = *oldcmd;
396 if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) {
397 perror("ioctl");
398 return 0;
399 }
400
401 return 0;
402 }
403