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
59typedef 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
74static char *progname = NULL;
75static uintptr_t base_addr = PCI_DEFAULT_ROM_ADDR;
76
77static void	usage(void);
78static void	banner(void);
79static void	pci_enum_devs(int pci_fd, action_t action);
80static uint32_t	pci_testrombar(int pci_fd, struct pci_conf *dev);
81static int	pci_enable_bars(int pci_fd, struct pci_conf *dev,
82    uint16_t *oldcmd);
83static int	pci_disable_bars(int pci_fd, struct pci_conf *dev,
84    uint16_t *oldcmd);
85static int	pci_save_rom(char *filename, int romsize);
86
87int
88main(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;
144cleanup:
145	if (pci_fd != -1)
146		close(pci_fd);
147
148	exit ((err == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
149}
150
151static void
152usage(void)
153{
154
155	fprintf(stderr, "usage: %s [-s] [-b <base-address>]\r\n", progname);
156	exit(EXIT_FAILURE);
157}
158
159static void
160banner(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 */
171static void
172pci_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 */
253static uint32_t
254pci_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
299static int
300pci_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;
327cleanup:
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
340static int
341pci_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
374static int
375pci_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