1167167Sbms/*
2167167Sbms * Copyright (c) 2007 Bruce M. Simpson.
3167167Sbms * All rights reserved
4167167Sbms *
5167167Sbms * Redistribution and use in source and binary forms, with or without
6167167Sbms * modification, are permitted provided that the following conditions
7167167Sbms * are met:
8167167Sbms * 1. Redistributions of source code must retain the above copyright
9167167Sbms *    notice, this list of conditions and the following disclaimer.
10167167Sbms * 2. Redistributions in binary form must reproduce the above copyright
11167167Sbms *    notice, this list of conditions and the following disclaimer in the
12167167Sbms *    documentation and/or other materials provided with the distribution.
13167167Sbms *
14251475Semaste * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15251475Semaste * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16251475Semaste * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17251475Semaste * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18251475Semaste * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19251475Semaste * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20251475Semaste * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21251475Semaste * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22251475Semaste * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23251475Semaste * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24251475Semaste * SUCH DAMAGE.
25167167Sbms */
26167167Sbms
27167167Sbms#include <sys/cdefs.h>
28167167Sbms__FBSDID("$FreeBSD$");
29167167Sbms
30167167Sbms#include <sys/types.h>
31167167Sbms#include <sys/ioctl.h>
32167167Sbms#include <sys/pciio.h>
33167167Sbms#include <sys/mman.h>
34167167Sbms#include <sys/memrange.h>
35167167Sbms#include <sys/stat.h>
36167167Sbms#include <machine/endian.h>
37167167Sbms
38167167Sbms#include <stddef.h>
39167167Sbms#include <inttypes.h>
40167167Sbms#include <stdio.h>
41167167Sbms#include <stdlib.h>
42167167Sbms#include <libgen.h>
43167167Sbms#include <fcntl.h>
44167167Sbms#include <string.h>
45167167Sbms#include <unistd.h>
46167167Sbms
47167167Sbms#define	_PATH_DEVPCI	"/dev/pci"
48167167Sbms#define	_PATH_DEVMEM	"/dev/mem"
49167167Sbms
50167167Sbms#define	PCI_CFG_CMD		0x04		/* command register */
51167167Sbms#define	PCI_CFG_ROM_BAR		0x30		/* rom base register */
52167167Sbms
53167167Sbms#define	PCI_ROM_ADDR_MASK	0xFFFFFC00	/* the 21 MSBs form the BAR */
54167167Sbms#define	PCI_ROM_RESERVED_MASK	0x03FE		/* mask for reserved bits */
55167167Sbms#define	PCI_ROM_ACTIVATE	0x01		/* mask for activation bit */
56167167Sbms
57167167Sbms#define	PCI_CMD_MEM_SPACE	0x02		/* memory space bit */
58167167Sbms#define	PCI_HDRTYPE_MFD		0x80		/* MFD bit in HDRTYPE reg. */
59167167Sbms
60167167Sbms#define	MAX_PCI_DEVS		64		/* # of devices in system */
61167167Sbms
62167167Sbmstypedef enum {
63167167Sbms	PRINT = 0,
64167167Sbms	SAVE = 1
65167167Sbms} action_t;
66167167Sbms
67167167Sbms/*
68167167Sbms * This is set to a safe physical base address in PCI range for my Vaio.
69167167Sbms * YOUR MACHINE *WILL* VARY, I SUGGEST YOU LOOK UP YOUR MACHINE'S MEMORY
70167167Sbms * MAP IN DETAIL IF YOU PLAN ON SAVING ROMS.
71167167Sbms *
72167167Sbms * This is the hole between the APIC and the BIOS (FED00000-FEDFFFFF);
73167167Sbms * should be a safe range on the i815 Solano chipset.
74167167Sbms */
75167167Sbms#define PCI_DEFAULT_ROM_ADDR	0xFED00000
76167167Sbms
77167167Sbmsstatic char *progname = NULL;
78167167Sbmsstatic uintptr_t base_addr = PCI_DEFAULT_ROM_ADDR;
79167167Sbms
80167167Sbmsstatic void	usage(void);
81167167Sbmsstatic void	banner(void);
82167167Sbmsstatic void	pci_enum_devs(int pci_fd, action_t action);
83167167Sbmsstatic uint32_t	pci_testrombar(int pci_fd, struct pci_conf *dev);
84167167Sbmsstatic int	pci_enable_bars(int pci_fd, struct pci_conf *dev,
85167167Sbms    uint16_t *oldcmd);
86167167Sbmsstatic int	pci_disable_bars(int pci_fd, struct pci_conf *dev,
87167167Sbms    uint16_t *oldcmd);
88167167Sbmsstatic int	pci_save_rom(char *filename, int romsize);
89167167Sbms
90167167Sbmsint
91167167Sbmsmain(int argc, char *argv[])
92167167Sbms{
93167167Sbms	int		 pci_fd;
94167167Sbms	int		 err;
95167167Sbms	int		 ch;
96167167Sbms	action_t	 action;
97167167Sbms	char		*base_addr_string;
98167167Sbms	char		*ep;
99167167Sbms
100167167Sbms	err = -1;
101167167Sbms	pci_fd = -1;
102167167Sbms	action = PRINT;
103167167Sbms	base_addr_string = NULL;
104167167Sbms	ep = NULL;
105167167Sbms	progname = basename(argv[0]);
106167167Sbms
107167167Sbms	while ((ch = getopt(argc, argv, "sb:h")) != -1)
108167167Sbms		switch (ch) {
109167167Sbms		case 's':
110167167Sbms			action = SAVE;
111167167Sbms			break;
112167167Sbms		case 'b':
113167167Sbms			base_addr_string = optarg;
114167167Sbms			break;
115167167Sbms		case 'h':
116167167Sbms		default:
117167167Sbms		     usage();
118167167Sbms	}
119167167Sbms	argc -= optind;
120167167Sbms	argv += optind;
121167167Sbms
122167167Sbms	if (base_addr_string != NULL) {
123167167Sbms		uintmax_t base_addr_max;
124167167Sbms
125167167Sbms		base_addr_max = strtoumax(base_addr_string, &ep, 16);
126167167Sbms		if (*ep != '\0') {
127167167Sbms			fprintf(stderr, "Invalid base address.\r\n");
128167167Sbms			usage();
129167167Sbms		}
130167167Sbms		/* XXX: TODO: deal with 64-bit PCI. */
131167167Sbms		base_addr = (uintptr_t)base_addr_max;
132167167Sbms		base_addr &= ~PCI_ROM_RESERVED_MASK;
133167167Sbms	}
134167167Sbms
135167167Sbms	if (argc > 0)
136167167Sbms		usage();
137167167Sbms
138167167Sbms	if ((pci_fd = open(_PATH_DEVPCI, O_RDWR)) == -1) {
139167167Sbms		perror("open");
140167167Sbms		goto cleanup;
141167167Sbms	}
142167167Sbms
143167167Sbms	banner();
144167167Sbms	pci_enum_devs(pci_fd, action);
145167167Sbms
146167167Sbms	err = 0;
147167167Sbmscleanup:
148167167Sbms	if (pci_fd != -1)
149167167Sbms		close(pci_fd);
150167167Sbms
151167167Sbms	exit ((err == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
152167167Sbms}
153167167Sbms
154167167Sbmsstatic void
155167167Sbmsusage(void)
156167167Sbms{
157167167Sbms
158167167Sbms	fprintf(stderr, "usage: %s [-s] [-b <base-address>]\r\n", progname);
159167167Sbms	exit(EXIT_FAILURE);
160167167Sbms}
161167167Sbms
162167167Sbmsstatic void
163167167Sbmsbanner(void)
164167167Sbms{
165167167Sbms
166167167Sbms	fprintf(stderr,
167167167Sbms		"WARNING: You are advised to run this program in single\r\n"
168167167Sbms		"user mode, with few or no processes running.\r\n\r\n");
169167167Sbms}
170167167Sbms
171167167Sbms/*
172167167Sbms * Enumerate PCI device list to a limit of MAX_PCI_DEVS devices.
173167167Sbms */
174167167Sbmsstatic void
175167167Sbmspci_enum_devs(int pci_fd, action_t action)
176167167Sbms{
177167167Sbms	struct pci_conf		 devs[MAX_PCI_DEVS];
178167167Sbms	char			 filename[16];
179167167Sbms	struct pci_conf_io	 pc;
180167167Sbms	struct pci_conf		*p;
181167167Sbms	int			 result;
182167167Sbms	int			 romsize;
183167167Sbms	uint16_t		 oldcmd;
184167167Sbms
185167167Sbms	result = -1;
186167167Sbms	romsize = 0;
187167167Sbms
188167167Sbms	bzero(&pc, sizeof(pc));
189167167Sbms	pc.match_buf_len = sizeof(devs);
190167167Sbms	pc.matches = devs;
191167167Sbms
192167167Sbms	if (ioctl(pci_fd, PCIOCGETCONF, &pc) == -1) {
193167167Sbms		perror("ioctl PCIOCGETCONF");
194167167Sbms		return;
195167167Sbms	}
196167167Sbms
197167167Sbms	if (pc.status == PCI_GETCONF_ERROR) {
198167167Sbms		fprintf(stderr,
199167167Sbms		    "Error fetching PCI device list from kernel.\r\n");
200167167Sbms		return;
201167167Sbms	}
202167167Sbms
203167167Sbms	if (pc.status == PCI_GETCONF_MORE_DEVS) {
204167167Sbms		fprintf(stderr,
205167167Sbms"More than %d devices exist. Only the first %d will be inspected.\r\n",
206167167Sbms		    MAX_PCI_DEVS, MAX_PCI_DEVS);
207167167Sbms	}
208167167Sbms
209167167Sbms	for (p = devs ; p < &devs[pc.num_matches]; p++) {
210167167Sbms
211167167Sbms		/* No PCI bridges; only PCI devices. */
212167167Sbms		if (p->pc_hdr != 0x00)
213167167Sbms			continue;
214167167Sbms
215167167Sbms		romsize = pci_testrombar(pci_fd, p);
216167167Sbms
217167167Sbms		switch (action) {
218167167Sbms		case PRINT:
219172394Smarius			printf(
220172394Smarius"Domain %04Xh Bus %02Xh Device %02Xh Function %02Xh: ",
221172394Smarius				p->pc_sel.pc_domain, p->pc_sel.pc_bus,
222172394Smarius				p->pc_sel.pc_dev, p->pc_sel.pc_func);
223167167Sbms			printf((romsize ? "%dKB ROM aperture detected."
224167167Sbms					: "No ROM present."), romsize/1024);
225167167Sbms			printf("\r\n");
226167167Sbms			break;
227167167Sbms		case SAVE:
228167167Sbms			if (romsize == 0)
229167167Sbms				continue;	/* XXX */
230167167Sbms
231167167Sbms			snprintf(filename, sizeof(filename), "%08X.rom",
232167167Sbms			    ((p->pc_device << 16) | p->pc_vendor));
233167167Sbms
234167167Sbms			fprintf(stderr, "Saving %dKB ROM image to %s...\r\n",
235167167Sbms			    romsize, filename);
236167167Sbms
237167167Sbms			if (pci_enable_bars(pci_fd, p, &oldcmd) == 0)
238167167Sbms				result = pci_save_rom(filename, romsize);
239167167Sbms
240167167Sbms			pci_disable_bars(pci_fd, p, &oldcmd);
241167167Sbms
242167167Sbms			if (result == 0)  {
243167167Sbms				fprintf(stderr, "Done.\r\n");
244167167Sbms			} else  {
245167167Sbms				fprintf(stderr,
246167167Sbms"An error occurred whilst saving the ROM.\r\n");
247167167Sbms			}
248167167Sbms			break;
249167167Sbms		} /* switch */
250167167Sbms	} /* for */
251167167Sbms}
252167167Sbms
253167167Sbms/*
254167167Sbms * Return: size of ROM aperture off dev, 0 if no ROM exists.
255167167Sbms */
256167167Sbmsstatic uint32_t
257167167Sbmspci_testrombar(int pci_fd, struct pci_conf *dev)
258167167Sbms{
259167167Sbms	struct pci_io	 io;
260167167Sbms	uint32_t	 romsize;
261167167Sbms
262167167Sbms	romsize = 0;
263167167Sbms
264167167Sbms	/*
265167167Sbms	 * Only attempt to discover ROMs on Header Type 0x00 devices.
266167167Sbms	 */
267167167Sbms	if (dev->pc_hdr != 0x00)
268167167Sbms		return romsize;
269167167Sbms
270167167Sbms	/*
271167167Sbms	 * Activate ROM BAR
272167167Sbms	 */
273167167Sbms	io.pi_sel = dev->pc_sel;
274167167Sbms	io.pi_reg = PCI_CFG_ROM_BAR;
275167167Sbms	io.pi_width = 4;
276167167Sbms	io.pi_data = 0xFFFFFFFF;
277167167Sbms	if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
278167167Sbms		return romsize;
279167167Sbms
280167167Sbms	/*
281167167Sbms	 * Read back ROM BAR and compare with mask
282167167Sbms	 */
283167167Sbms	if (ioctl(pci_fd, PCIOCREAD, &io) == -1)
284167167Sbms		return 0;
285167167Sbms
286167167Sbms	/*
287167167Sbms	 * Calculate ROM aperture if one was set.
288167167Sbms	 */
289167167Sbms	if (io.pi_data & PCI_ROM_ADDR_MASK)
290167167Sbms		romsize = -(io.pi_data & PCI_ROM_ADDR_MASK);
291167167Sbms
292167167Sbms	/*
293167167Sbms	 * Disable the ROM BAR when done.
294167167Sbms	 */
295167167Sbms	io.pi_data = 0;
296167167Sbms	if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
297167167Sbms		return 0;
298167167Sbms
299167167Sbms	return romsize;
300167167Sbms}
301167167Sbms
302167167Sbmsstatic int
303167167Sbmspci_save_rom(char *filename, int romsize)
304167167Sbms{
305167167Sbms	int	 fd, mem_fd, err;
306167167Sbms	void	*map_addr;
307167167Sbms
308167167Sbms	fd = err = mem_fd = -1;
309167167Sbms	map_addr = MAP_FAILED;
310167167Sbms
311167167Sbms	if ((mem_fd = open(_PATH_DEVMEM, O_RDONLY)) == -1) {
312167167Sbms		perror("open");
313167167Sbms		return -1;
314167167Sbms	}
315167167Sbms
316167167Sbms	map_addr = mmap(NULL, romsize, PROT_READ, MAP_SHARED|MAP_NOCORE,
317167167Sbms	    mem_fd, base_addr);
318167167Sbms
319167167Sbms	/* Dump ROM aperture to a file. */
320167167Sbms	if ((fd = open(filename, O_CREAT|O_RDWR|O_TRUNC|O_NOFOLLOW,
321167167Sbms	    S_IRUSR|S_IWUSR)) == -1) {
322167167Sbms		perror("open");
323167167Sbms		goto cleanup;
324167167Sbms	}
325167167Sbms
326167167Sbms	if (write(fd, map_addr, romsize) != romsize)
327167167Sbms		perror("write");
328167167Sbms
329167167Sbms	err = 0;
330167167Sbmscleanup:
331167167Sbms	if (fd != -1)
332167167Sbms		close(fd);
333167167Sbms
334167167Sbms	if (map_addr != MAP_FAILED)
335167167Sbms		munmap((void *)base_addr, romsize);
336167167Sbms
337167167Sbms	if (mem_fd != -1)
338167167Sbms		close(mem_fd);
339167167Sbms
340167167Sbms	return err;
341167167Sbms}
342167167Sbms
343167167Sbmsstatic int
344167167Sbmspci_enable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd)
345167167Sbms{
346167167Sbms	struct pci_io io;
347167167Sbms
348167167Sbms	/* Don't grok bridges. */
349167167Sbms	if (dev->pc_hdr != 0x00)
350167167Sbms		return -1;
351167167Sbms
352167167Sbms	/* Save command register. */
353167167Sbms	io.pi_sel = dev->pc_sel;
354167167Sbms	io.pi_reg = PCI_CFG_CMD;
355167167Sbms	io.pi_width = 2;
356167167Sbms	if (ioctl(pci_fd, PCIOCREAD, &io) == -1)
357167167Sbms		return -1;
358167167Sbms	*oldcmd = (uint16_t)io.pi_data;
359167167Sbms
360167167Sbms	io.pi_data |= PCI_CMD_MEM_SPACE;
361167167Sbms	if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
362167167Sbms		return -1;
363167167Sbms
364167167Sbms	/*
365167167Sbms	 * Activate ROM BAR and map at the specified base address.
366167167Sbms	 */
367167167Sbms	io.pi_sel = dev->pc_sel;
368167167Sbms	io.pi_reg = PCI_CFG_ROM_BAR;
369167167Sbms	io.pi_width = 4;
370167167Sbms	io.pi_data = (base_addr | PCI_ROM_ACTIVATE);
371167167Sbms	if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
372167167Sbms		return -1;
373167167Sbms
374167167Sbms	return 0;
375167167Sbms}
376167167Sbms
377167167Sbmsstatic int
378167167Sbmspci_disable_bars(int pci_fd, struct pci_conf *dev, uint16_t *oldcmd)
379167167Sbms{
380167167Sbms	struct pci_io	 io;
381167167Sbms
382167167Sbms	/*
383167167Sbms	 * Clear ROM BAR to deactivate the mapping.
384167167Sbms	 */
385167167Sbms	io.pi_sel = dev->pc_sel;
386167167Sbms	io.pi_reg = PCI_CFG_ROM_BAR;
387167167Sbms	io.pi_width = 4;
388167167Sbms	io.pi_data = 0;
389167167Sbms	if (ioctl(pci_fd, PCIOCWRITE, &io) == -1)
390167167Sbms		return 0;
391167167Sbms
392167167Sbms	/*
393167167Sbms	 * Restore state of the command register.
394167167Sbms	 */
395167167Sbms	io.pi_sel = dev->pc_sel;
396167167Sbms	io.pi_reg = PCI_CFG_CMD;
397167167Sbms	io.pi_width = 2;
398167167Sbms	io.pi_data = *oldcmd;
399167167Sbms	if (ioctl(pci_fd, PCIOCWRITE, &io) == -1) {
400167167Sbms		perror("ioctl");
401167167Sbms		return 0;
402167167Sbms	}
403167167Sbms
404167167Sbms	return 0;
405167167Sbms}
406