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