1/*	$NetBSD: pci_addr_fixup.c,v 1.8 2011/08/28 06:08:15 dyoung Exp $	*/
2
3/*-
4 * Copyright (c) 2000 UCHIYAMA Yasushi.  All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
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
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: pci_addr_fixup.c,v 1.8 2011/08/28 06:08:15 dyoung Exp $");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/malloc.h>
35#include <sys/kernel.h>
36#include <sys/device.h>
37#include <sys/extent.h>
38
39#include <sys/bus.h>
40
41#include <dev/pci/pcireg.h>
42#include <dev/pci/pcivar.h>
43#include <dev/pci/pcidevs.h>
44
45#include <x86/pci/pci_addr_fixup.h>
46
47struct pciaddr pciaddr;
48
49static void	pciaddr_resource_reserve(pci_chipset_tag_t, pcitag_t, void *);
50static int	pciaddr_do_resource_reserve(pci_chipset_tag_t, pcitag_t, int,
51    				    void *, int, bus_addr_t *, bus_size_t);
52static void	pciaddr_resource_allocate(pci_chipset_tag_t, pcitag_t, void *);
53static int	pciaddr_do_resource_allocate(pci_chipset_tag_t, pcitag_t, int,
54				     void *, int, bus_addr_t *, bus_size_t);
55static int	device_is_agp(pci_chipset_tag_t, pcitag_t);
56
57#define PCIADDR_MEM_START	0x0
58#define PCIADDR_MEM_END		0xffffffff
59#define PCIADDR_PORT_START	0x0
60#define PCIADDR_PORT_END	0xffff
61
62/* for ISA devices */
63#define PCIADDR_ISAPORT_RESERVE	0x5800 /* empirical value */
64#define PCIADDR_ISAMEM_RESERVE	(16 * 1024 * 1024)
65
66void
67pci_addr_fixup(pci_chipset_tag_t pc, int maxbus)
68{
69	extern paddr_t avail_end;
70	const char *verbose_header =
71		"[%s]-----------------------\n"
72		"  device vendor product\n"
73		"  register space address    size\n"
74		"--------------------------------------------\n";
75	const char *verbose_footer =
76		"--------------------------[%3d devices bogus]\n";
77	const struct {
78		bus_addr_t start;
79		bus_size_t size;
80		const char *name;
81	} system_reserve [] = {
82		{ 0xfec00000, 0x100000, "I/O APIC" },
83		{ 0xfee00000, 0x100000, "Local APIC" },
84		{ 0xfffe0000, 0x20000, "BIOS PROM" },
85		{ 0, 0, 0 }, /* terminator */
86	}, *srp;
87	paddr_t start;
88	int error;
89
90	pciaddr.extent_mem = extent_create("PCI I/O memory space",
91					   PCIADDR_MEM_START,
92					   PCIADDR_MEM_END,
93					   0, 0, EX_NOWAIT);
94	KASSERT(pciaddr.extent_mem);
95	pciaddr.extent_port = extent_create("PCI I/O port space",
96					    PCIADDR_PORT_START,
97					    PCIADDR_PORT_END,
98					    0, 0, EX_NOWAIT);
99	KASSERT(pciaddr.extent_port);
100
101	/*
102	 * 1. check & reserve system BIOS setting.
103	 */
104	aprint_debug(verbose_header, "System BIOS Setting");
105	pci_device_foreach(pc, maxbus, pciaddr_resource_reserve, NULL);
106	aprint_debug(verbose_footer, pciaddr.nbogus);
107
108	/*
109	 * 2. reserve non-PCI area.
110	 */
111	for (srp = system_reserve; srp->size; srp++) {
112		error = extent_alloc_region(pciaddr.extent_mem, srp->start,
113					    srp->size,
114					    EX_NOWAIT| EX_MALLOCOK);
115		if (error != 0) {
116			aprint_error("WARNING: can't reserve area for %s.\n",
117			       srp->name);
118		}
119	}
120
121	/*
122	 * 3. determine allocation space
123	 */
124	start = x86_round_page(avail_end + 1);
125	if (start < PCIADDR_ISAMEM_RESERVE)
126		start = PCIADDR_ISAMEM_RESERVE;
127	pciaddr.mem_alloc_start = (start + 0x100000 + 1) & ~(0x100000 - 1);
128	pciaddr.port_alloc_start = PCIADDR_ISAPORT_RESERVE;
129	aprint_debug(" Physical memory end: 0x%08x\n PCI memory mapped I/O "
130			"space start: 0x%08x\n", (unsigned)avail_end,
131			(unsigned)pciaddr.mem_alloc_start);
132
133	if (pciaddr.nbogus == 0)
134		return; /* no need to fixup */
135
136	/*
137	 * 4. do fixup
138	 */
139	aprint_debug(verbose_header, "PCIBIOS fixup stage");
140	pciaddr.nbogus = 0;
141	pci_device_foreach_min(pc, 0, maxbus, pciaddr_resource_allocate, NULL);
142	aprint_debug(verbose_footer, pciaddr.nbogus);
143
144}
145
146static void
147pciaddr_resource_reserve(pci_chipset_tag_t pc, pcitag_t tag,
148    void *context)
149{
150	pciaddr_print_devid(pc, tag);
151	pciaddr_resource_manage(pc, tag,
152				pciaddr_do_resource_reserve,
153				&pciaddr);
154}
155
156static void
157pciaddr_resource_allocate(pci_chipset_tag_t pc, pcitag_t tag,
158    void *context)
159{
160	pciaddr_print_devid(pc, tag);
161	pciaddr_resource_manage(pc, tag,
162				pciaddr_do_resource_allocate,
163				&pciaddr);
164}
165
166void
167pciaddr_resource_manage(pci_chipset_tag_t pc, pcitag_t tag,
168    pciaddr_resource_manage_func_t func, void *ctx)
169{
170	pcireg_t val, mask;
171	bus_addr_t addr;
172	bus_size_t size;
173	int error, useport, usemem, mapreg, type, reg_start, reg_end, width;
174
175	val = pci_conf_read(pc, tag, PCI_BHLC_REG);
176	switch (PCI_HDRTYPE_TYPE(val)) {
177	default:
178		aprint_error("WARNING: unknown PCI device header.");
179		pciaddr.nbogus++;
180		return;
181	case PCI_HDRTYPE_DEVICE:
182		reg_start = PCI_MAPREG_START;
183		reg_end   = PCI_MAPREG_END;
184		break;
185	case PCI_HDRTYPE_PPB: /* PCI-PCI bridge */
186		reg_start = PCI_MAPREG_START;
187		reg_end   = PCI_MAPREG_PPB_END;
188		break;
189	case PCI_HDRTYPE_PCB: /* PCI-CardBus bridge */
190		reg_start = PCI_MAPREG_START;
191		reg_end   = PCI_MAPREG_PCB_END;
192		break;
193	}
194	error = useport = usemem = 0;
195
196	for (mapreg = reg_start; mapreg < reg_end; mapreg += width) {
197		/* inquire PCI device bus space requirement */
198		val = pci_conf_read(pc, tag, mapreg);
199		pci_conf_write(pc, tag, mapreg, ~0);
200
201		mask = pci_conf_read(pc, tag, mapreg);
202		pci_conf_write(pc, tag, mapreg, val);
203
204		type = PCI_MAPREG_TYPE(val);
205		width = 4;
206		if (type == PCI_MAPREG_TYPE_MEM) {
207			if (PCI_MAPREG_MEM_TYPE(val) ==
208			    PCI_MAPREG_MEM_TYPE_64BIT) {
209				/* XXX We could examine the upper 32 bits
210				 * XXX of the BAR here, but we are totally
211				 * XXX unprepared to handle a non-zero value,
212				 * XXX either here or anywhere else in
213				 * XXX i386-land.
214				 * XXX So just arrange to not look at the
215				 * XXX upper 32 bits, lest we misinterpret
216				 * XXX it as a 32-bit BAR set to zero.
217				 */
218			    width = 8;
219			}
220			size = PCI_MAPREG_MEM_SIZE(mask);
221		} else {
222			size = PCI_MAPREG_IO_SIZE(mask);
223		}
224		addr = pciaddr_ioaddr(val);
225
226		if (size == 0) /* unused register */
227			continue;
228
229		if (type == PCI_MAPREG_TYPE_MEM)
230			++usemem;
231		else
232			++useport;
233
234		/* reservation/allocation phase */
235		error += (*func) (pc, tag, mapreg, ctx, type, &addr, size);
236
237		aprint_debug("\n\t%02xh %s 0x%08x 0x%08x",
238				mapreg, type ? "port" : "mem ",
239				(unsigned int)addr, (unsigned int)size);
240	}
241
242	/* enable/disable PCI device */
243	val = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG);
244	if (error == 0)
245		val |= (PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE |
246			PCI_COMMAND_MASTER_ENABLE);
247	else
248		val &= ~(PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE |
249			 PCI_COMMAND_MASTER_ENABLE);
250	pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, val);
251
252	if (error != 0)
253		pciaddr.nbogus++;
254
255	aprint_debug("\n\t\t[%s]\n", error ? "NG" : "OK");
256}
257
258static int
259pciaddr_do_resource_allocate(pci_chipset_tag_t pc, pcitag_t tag,
260    int mapreg, void *ctx, int type, bus_addr_t *addr, bus_size_t size)
261{
262 	struct pciaddr *pciaddrmap = (struct pciaddr *)ctx;
263	bus_addr_t start;
264	int error;
265 	struct extent *ex;
266
267	if (*addr != 0) /* no need to allocate */
268		return 0;
269
270 	ex = (type == PCI_MAPREG_TYPE_MEM ?
271 	      pciaddrmap->extent_mem : pciaddrmap->extent_port);
272
273	/* XXX Don't allocate if device is AGP device to avoid conflict. */
274	if (device_is_agp(pc, tag))
275		return 0;
276
277	start = (type == PCI_MAPREG_TYPE_MEM ?
278		 pciaddrmap->mem_alloc_start : pciaddrmap->port_alloc_start);
279
280	if (start < ex->ex_start || start + size - 1 >= ex->ex_end) {
281		aprint_debug("No available resources. fixup failed\n");
282		return 1;
283	}
284	error = extent_alloc_subregion(ex, start, ex->ex_end, size,
285				       size, 0,
286				       EX_FAST|EX_NOWAIT|EX_MALLOCOK,
287				       (u_long *)addr);
288	if (error) {
289		aprint_debug("No available resources. fixup failed\n");
290		return 1;
291	}
292
293	/* write new address to PCI device configuration header */
294	pci_conf_write(pc, tag, mapreg, *addr);
295	/* check */
296	aprint_debug("pci_addr_fixup: ");
297	pciaddr_print_devid(pc, tag);
298	if (pciaddr_ioaddr(pci_conf_read(pc, tag, mapreg)) != *addr) {
299		pci_conf_write(pc, tag, mapreg, 0); /* clear */
300		aprint_error("fixup failed. (new address=%#x)\n", (unsigned)*addr);
301		return 1;
302	}
303	aprint_debug("new address 0x%08x\n", (unsigned)*addr);
304
305	return 0;
306}
307
308int
309pciaddr_do_resource_reserve(pci_chipset_tag_t pc, pcitag_t tag,
310    int mapreg, void *ctx, int type, bus_addr_t *addr, bus_size_t size)
311{
312	struct extent *ex;
313	struct pciaddr *pciaddrmap = (struct pciaddr *)ctx;
314	int error;
315
316	if (*addr == 0)
317		return 1;
318
319	ex = (type == PCI_MAPREG_TYPE_MEM ?
320	      pciaddrmap->extent_mem : pciaddrmap->extent_port);
321
322	error = extent_alloc_region(ex, *addr, size, EX_NOWAIT| EX_MALLOCOK);
323	if (error) {
324		aprint_debug("Resource conflict.\n");
325		pci_conf_write(pc, tag, mapreg, 0); /* clear */
326		return 1;
327	}
328
329	return 0;
330}
331
332bus_addr_t
333pciaddr_ioaddr(uint32_t val)
334{
335	return (PCI_MAPREG_TYPE(val) == PCI_MAPREG_TYPE_MEM)
336		? PCI_MAPREG_MEM_ADDR(val)
337		: PCI_MAPREG_IO_ADDR(val);
338}
339
340void
341pciaddr_print_devid(pci_chipset_tag_t pc, pcitag_t tag)
342{
343	int bus, device, function;
344	pcireg_t id;
345
346	id = pci_conf_read(pc, tag, PCI_ID_REG);
347	pci_decompose_tag(pc, tag, &bus, &device, &function);
348	aprint_debug("%03d:%02d:%d 0x%04x 0x%04x ", bus, device, function,
349	       PCI_VENDOR(id), PCI_PRODUCT(id));
350}
351
352static int
353device_is_agp(pci_chipset_tag_t pc, pcitag_t tag)
354{
355	pcireg_t class, status, rval;
356	int off;
357
358	/* Check AGP device. */
359	class = pci_conf_read(pc, tag, PCI_CLASS_REG);
360	if (PCI_CLASS(class) == PCI_CLASS_DISPLAY) {
361		status = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG);
362		if (status & PCI_STATUS_CAPLIST_SUPPORT) {
363			rval = pci_conf_read(pc, tag, PCI_CAPLISTPTR_REG);
364			for (off = PCI_CAPLIST_PTR(rval);
365			    off != 0;
366			    off = PCI_CAPLIST_NEXT(rval) ) {
367				rval = pci_conf_read(pc, tag, off);
368				if (PCI_CAPLIST_CAP(rval) == PCI_CAP_AGP)
369					return 1;
370			}
371		}
372	}
373	return 0;
374}
375