xref: /freebsd/tools/tools/pciroms/pciroms.c (revision abd87254)
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
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
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
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
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
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
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
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
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