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