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
62typedef 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
77static char *progname = NULL;
78static uintptr_t base_addr = PCI_DEFAULT_ROM_ADDR;
79
80static void	usage(void);
81static void	banner(void);
82static void	pci_enum_devs(int pci_fd, action_t action);
83static uint32_t	pci_testrombar(int pci_fd, struct pci_conf *dev);
84static int	pci_enable_bars(int pci_fd, struct pci_conf *dev,
85    uint16_t *oldcmd);
86static int	pci_disable_bars(int pci_fd, struct pci_conf *dev,
87    uint16_t *oldcmd);
88static int	pci_save_rom(char *filename, int romsize);
89
90int
91main(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;
147cleanup:
148	if (pci_fd != -1)
149		close(pci_fd);
150
151	exit ((err == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
152}
153
154static void
155usage(void)
156{
157
158	fprintf(stderr, "usage: %s [-s] [-b <base-address>]\r\n", progname);
159	exit(EXIT_FAILURE);
160}
161
162static void
163banner(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 */
174static void
175pci_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 */
256static uint32_t
257pci_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
302static int
303pci_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;
330cleanup:
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
343static int
344pci_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
377static int
378pci_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