pci_cfgreg.c revision 182910
1/*-
2 * Copyright (c) 1997, Stefan Esser <se@freebsd.org>
3 * Copyright (c) 2000, Michael Smith <msmith@freebsd.org>
4 * Copyright (c) 2000, BSDi
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice unmodified, this list of conditions, and the following
12 *    disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: head/sys/amd64/pci/pci_cfgreg.c 182910 2008-09-10 18:06:08Z jhb $");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/bus.h>
35#include <sys/lock.h>
36#include <sys/mutex.h>
37#include <dev/pci/pcivar.h>
38#include <dev/pci/pcireg.h>
39#include <vm/vm.h>
40#include <vm/pmap.h>
41#include <machine/pci_cfgreg.h>
42
43enum {
44	CFGMECH_NONE = 0,
45	CFGMECH_1,
46	CFGMECH_PCIE,
47};
48
49static uint32_t	pci_docfgregread(int bus, int slot, int func, int reg,
50		    int bytes);
51static int	pciereg_cfgread(int bus, unsigned slot, unsigned func,
52		    unsigned reg, unsigned bytes);
53static void	pciereg_cfgwrite(int bus, unsigned slot, unsigned func,
54		    unsigned reg, int data, unsigned bytes);
55static int	pcireg_cfgread(int bus, int slot, int func, int reg, int bytes);
56static void	pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes);
57
58static int cfgmech;
59static vm_offset_t pcie_base;
60static int pcie_minbus, pcie_maxbus;
61static uint32_t pcie_badslots;
62static struct mtx pcicfg_mtx;
63
64/*
65 * Initialise access to PCI configuration space
66 */
67int
68pci_cfgregopen(void)
69{
70	static int once = 0;
71	uint64_t pciebar;
72	uint16_t did, vid;
73
74	if (!once) {
75		mtx_init(&pcicfg_mtx, "pcicfg", NULL, MTX_SPIN);
76		once = 1;
77	}
78
79	if (cfgmech != CFGMECH_NONE)
80		return (1);
81	cfgmech = CFGMECH_1;
82
83	/*
84	 * Grope around in the PCI config space to see if this is a
85	 * chipset that is capable of doing memory-mapped config cycles.
86	 * This also implies that it can do PCIe extended config cycles.
87	 */
88
89	/* Check for supported chipsets */
90	vid = pci_cfgregread(0, 0, 0, PCIR_VENDOR, 2);
91	did = pci_cfgregread(0, 0, 0, PCIR_DEVICE, 2);
92	switch (vid) {
93	case 0x8086:
94		switch (did) {
95		case 0x3590:
96		case 0x3592:
97			/* Intel 7520 or 7320 */
98			pciebar = pci_cfgregread(0, 0, 0, 0xce, 2) << 16;
99			pcie_cfgregopen(pciebar, 0, 255);
100			break;
101		case 0x2580:
102		case 0x2584:
103		case 0x2590:
104			/* Intel 915, 925, or 915GM */
105			pciebar = pci_cfgregread(0, 0, 0, 0x48, 4);
106			pcie_cfgregopen(pciebar, 0, 255);
107			break;
108		}
109	}
110
111	return (1);
112}
113
114static uint32_t
115pci_docfgregread(int bus, int slot, int func, int reg, int bytes)
116{
117
118	if (cfgmech == CFGMECH_PCIE &&
119	    (bus != 0 || !(1 << slot & pcie_badslots)))
120		return (pciereg_cfgread(bus, slot, func, reg, bytes));
121	else
122		return (pcireg_cfgread(bus, slot, func, reg, bytes));
123}
124
125/*
126 * Read configuration space register
127 */
128u_int32_t
129pci_cfgregread(int bus, int slot, int func, int reg, int bytes)
130{
131	uint32_t line;
132
133	/*
134	 * Some BIOS writers seem to want to ignore the spec and put
135	 * 0 in the intline rather than 255 to indicate none.  Some use
136	 * numbers in the range 128-254 to indicate something strange and
137	 * apparently undocumented anywhere.  Assume these are completely bogus
138	 * and map them to 255, which the rest of the PCI code recognizes as
139	 * as an invalid IRQ.
140	 */
141	if (reg == PCIR_INTLINE && bytes == 1) {
142		line = pci_docfgregread(bus, slot, func, PCIR_INTLINE, 1);
143		if (line == 0 || line >= 128)
144			line = PCI_INVALID_IRQ;
145		return (line);
146	}
147	return (pci_docfgregread(bus, slot, func, reg, bytes));
148}
149
150/*
151 * Write configuration space register
152 */
153void
154pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes)
155{
156
157	if (cfgmech == CFGMECH_PCIE &&
158	    (bus != 0 || !(1 << slot & pcie_badslots)))
159		pciereg_cfgwrite(bus, slot, func, reg, data, bytes);
160	else
161		pcireg_cfgwrite(bus, slot, func, reg, data, bytes);
162}
163
164/*
165 * Configuration space access using direct register operations
166 */
167
168/* enable configuration space accesses and return data port address */
169static int
170pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes)
171{
172	int dataport = 0;
173
174	if (bus <= PCI_BUSMAX && slot < 32 && func <= PCI_FUNCMAX &&
175	    reg <= PCI_REGMAX && bytes != 3 && (unsigned) bytes <= 4 &&
176	    (reg & (bytes - 1)) == 0) {
177		outl(CONF1_ADDR_PORT, (1 << 31) | (bus << 16) | (slot << 11)
178		    | (func << 8) | (reg & ~0x03));
179		dataport = CONF1_DATA_PORT + (reg & 0x03);
180	}
181	return (dataport);
182}
183
184/* disable configuration space accesses */
185static void
186pci_cfgdisable(void)
187{
188
189	/*
190	 * Do nothing.  Writing a 0 to the address port can apparently
191	 * confuse some bridges and cause spurious access failures.
192	 */
193}
194
195static int
196pcireg_cfgread(int bus, int slot, int func, int reg, int bytes)
197{
198	int data = -1;
199	int port;
200
201	mtx_lock_spin(&pcicfg_mtx);
202	port = pci_cfgenable(bus, slot, func, reg, bytes);
203	if (port != 0) {
204		switch (bytes) {
205		case 1:
206			data = inb(port);
207			break;
208		case 2:
209			data = inw(port);
210			break;
211		case 4:
212			data = inl(port);
213			break;
214		}
215		pci_cfgdisable();
216	}
217	mtx_unlock_spin(&pcicfg_mtx);
218	return (data);
219}
220
221static void
222pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes)
223{
224	int port;
225
226	mtx_lock_spin(&pcicfg_mtx);
227	port = pci_cfgenable(bus, slot, func, reg, bytes);
228	if (port != 0) {
229		switch (bytes) {
230		case 1:
231			outb(port, data);
232			break;
233		case 2:
234			outw(port, data);
235			break;
236		case 4:
237			outl(port, data);
238			break;
239		}
240		pci_cfgdisable();
241	}
242	mtx_unlock_spin(&pcicfg_mtx);
243}
244
245int
246pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus)
247{
248	uint32_t val1, val2;
249	int slot;
250
251	if (minbus != 0)
252		return (0);
253
254	if (bootverbose)
255		printf("PCIe: Memory Mapped configuration base @ 0x%lx\n",
256		    base);
257
258	/* XXX: We should make sure this really fits into the direct map. */
259	pcie_base = (vm_offset_t)pmap_mapdev(base, (maxbus + 1) << 20);
260	pcie_minbus = minbus;
261	pcie_maxbus = maxbus;
262	cfgmech = CFGMECH_PCIE;
263
264	/*
265	 * On some AMD systems, some of the devices on bus 0 are
266	 * inaccessible using memory-mapped PCI config access.  Walk
267	 * bus 0 looking for such devices.  For these devices, we will
268	 * fall back to using type 1 config access instead.
269	 */
270	if (pci_cfgregopen() != 0) {
271		for (slot = 0; slot < 32; slot++) {
272			val1 = pcireg_cfgread(0, slot, 0, 0, 4);
273			if (val1 == 0xffffffff)
274				continue;
275
276			val2 = pciereg_cfgread(0, slot, 0, 0, 4);
277			if (val2 != val1)
278				pcie_badslots |= (1 << slot);
279		}
280	}
281
282	return (1);
283}
284
285#define PCIE_VADDR(base, reg, bus, slot, func)	\
286	((base)				+	\
287	((((bus) & 0xff) << 20)		|	\
288	(((slot) & 0x1f) << 15)		|	\
289	(((func) & 0x7) << 12)		|	\
290	((reg) & 0xfff)))
291
292static int
293pciereg_cfgread(int bus, unsigned slot, unsigned func, unsigned reg,
294    unsigned bytes)
295{
296	volatile vm_offset_t va;
297	int data = -1;
298
299	if (bus < pcie_minbus || bus > pcie_maxbus || slot >= 32 ||
300	    func > PCI_FUNCMAX || reg >= 0x1000)
301		return (-1);
302
303	va = PCIE_VADDR(pcie_base, reg, bus, slot, func);
304
305	switch (bytes) {
306	case 4:
307		data = *(volatile uint32_t *)(va);
308		break;
309	case 2:
310		data = *(volatile uint16_t *)(va);
311		break;
312	case 1:
313		data = *(volatile uint8_t *)(va);
314		break;
315	}
316
317	return (data);
318}
319
320static void
321pciereg_cfgwrite(int bus, unsigned slot, unsigned func, unsigned reg, int data,
322    unsigned bytes)
323{
324	volatile vm_offset_t va;
325
326	if (bus < pcie_minbus || bus > pcie_maxbus || slot >= 32 ||
327	    func > PCI_FUNCMAX || reg >= 0x1000)
328		return;
329
330	va = PCIE_VADDR(pcie_base, reg, bus, slot, func);
331
332	switch (bytes) {
333	case 4:
334		*(volatile uint32_t *)(va) = data;
335		break;
336	case 2:
337		*(volatile uint16_t *)(va) = data;
338		break;
339	case 1:
340		*(volatile uint8_t *)(va) = data;
341		break;
342	}
343}
344