xref: /illumos-gate/usr/src/uts/i86pc/os/pci_cfgacc_x86.c (revision 69a119caa6570c7077699161b7c28b6ee9f8b0f4)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <sys/systm.h>
27 #include <sys/pci_cfgacc.h>
28 #include <sys/pci_cfgspace.h>
29 #include <sys/pci_cfgspace_impl.h>
30 #include <sys/sunddi.h>
31 #include <sys/sysmacros.h>
32 #include <sys/x86_archext.h>
33 #include <sys/pci.h>
34 #include <sys/cmn_err.h>
35 #include <vm/hat_i86.h>
36 #include <vm/seg_kmem.h>
37 #include <vm/kboot_mmu.h>
38 
39 #define	PCIE_CFG_SPACE_SIZE	(PCI_CONF_HDR_SIZE << 4)
40 #define	PCI_BDF_BUS(bdf)	((((uint16_t)bdf) & 0xff00) >> 8)
41 #define	PCI_BDF_DEV(bdf)	((((uint16_t)bdf) & 0xf8) >> 3)
42 #define	PCI_BDF_FUNC(bdf)	(((uint16_t)bdf) & 0x7)
43 
44 /* patchable variables */
45 volatile boolean_t pci_cfgacc_force_io = B_FALSE;
46 
47 extern uintptr_t alloc_vaddr(size_t, paddr_t);
48 
49 void pci_cfgacc_acc(pci_cfgacc_req_t *);
50 
51 boolean_t pci_cfgacc_find_workaround(uint16_t);
52 /*
53  * IS_P2ALIGNED() is used to make sure offset is 'size'-aligned, so
54  * it's guaranteed that the access will not cross 4k page boundary.
55  * Thus only 1 page is allocated for all config space access, and the
56  * virtual address of that page is cached in pci_cfgacc_virt_base.
57  */
58 static caddr_t pci_cfgacc_virt_base = NULL;
59 
60 static caddr_t
61 pci_cfgacc_map(paddr_t phys_addr)
62 {
63 #ifdef __xpv
64 	phys_addr = pfn_to_pa(xen_assign_pfn(mmu_btop(phys_addr))) |
65 	    (phys_addr & MMU_PAGEOFFSET);
66 #endif
67 	if (khat_running) {
68 		pfn_t pfn = mmu_btop(phys_addr);
69 		/*
70 		 * pci_cfgacc_virt_base may hold address left from early
71 		 * boot, which points to low mem. Realloc virtual address
72 		 * in kernel space since it's already late in boot now.
73 		 * Note: no need to unmap first, clear_boot_mappings() will
74 		 * do that for us.
75 		 */
76 		if (pci_cfgacc_virt_base < (caddr_t)kernelbase)
77 			pci_cfgacc_virt_base = vmem_alloc(heap_arena,
78 			    MMU_PAGESIZE, VM_SLEEP);
79 
80 		hat_devload(kas.a_hat, pci_cfgacc_virt_base,
81 		    MMU_PAGESIZE, pfn, PROT_READ | PROT_WRITE |
82 		    HAT_STRICTORDER, HAT_LOAD_LOCK);
83 	} else {
84 		paddr_t	pa_base = P2ALIGN(phys_addr, MMU_PAGESIZE);
85 
86 		if (pci_cfgacc_virt_base == NULL)
87 			pci_cfgacc_virt_base =
88 			    (caddr_t)alloc_vaddr(MMU_PAGESIZE, MMU_PAGESIZE);
89 
90 		kbm_map((uintptr_t)pci_cfgacc_virt_base, pa_base, 0, 0);
91 	}
92 
93 	return (pci_cfgacc_virt_base + (phys_addr & MMU_PAGEOFFSET));
94 }
95 
96 static void
97 pci_cfgacc_unmap()
98 {
99 	if (khat_running)
100 		hat_unload(kas.a_hat, pci_cfgacc_virt_base, MMU_PAGESIZE,
101 		    HAT_UNLOAD_UNLOCK);
102 }
103 
104 static void
105 pci_cfgacc_io(pci_cfgacc_req_t *req)
106 {
107 	uint8_t bus, dev, func;
108 	uint16_t ioacc_offset;	/* 4K config access with IO ECS */
109 
110 	bus = PCI_BDF_BUS(req->bdf);
111 	dev = PCI_BDF_DEV(req->bdf);
112 	func = PCI_BDF_FUNC(req->bdf);
113 	ioacc_offset = req->offset;
114 
115 	switch (req->size) {
116 	case 1:
117 		if (req->write)
118 			(*pci_putb_func)(bus, dev, func,
119 			    ioacc_offset, VAL8(req));
120 		else
121 			VAL8(req) = (*pci_getb_func)(bus, dev, func,
122 			    ioacc_offset);
123 		break;
124 	case 2:
125 		if (req->write)
126 			(*pci_putw_func)(bus, dev, func,
127 			    ioacc_offset, VAL16(req));
128 		else
129 			VAL16(req) = (*pci_getw_func)(bus, dev, func,
130 			    ioacc_offset);
131 		break;
132 	case 4:
133 		if (req->write)
134 			(*pci_putl_func)(bus, dev, func,
135 			    ioacc_offset, VAL32(req));
136 		else
137 			VAL32(req) = (*pci_getl_func)(bus, dev, func,
138 			    ioacc_offset);
139 		break;
140 	case 8:
141 		if (req->write) {
142 			(*pci_putl_func)(bus, dev, func,
143 			    ioacc_offset, VAL64(req) & 0xffffffff);
144 			(*pci_putl_func)(bus, dev, func,
145 			    ioacc_offset + 4, VAL64(req) >> 32);
146 		} else {
147 			VAL64(req) = (*pci_getl_func)(bus, dev, func,
148 			    ioacc_offset);
149 			VAL64(req) |= (uint64_t)(*pci_getl_func)(bus, dev, func,
150 			    ioacc_offset + 4) << 32;
151 		}
152 		break;
153 	}
154 }
155 
156 static void
157 pci_cfgacc_mmio(pci_cfgacc_req_t *req)
158 {
159 	caddr_t vaddr;
160 	paddr_t paddr;
161 
162 	paddr = (paddr_t)req->bdf << 12;
163 	paddr += mcfg_mem_base + req->offset;
164 
165 	mutex_enter(&pcicfg_mmio_mutex);
166 	vaddr = pci_cfgacc_map(paddr);
167 
168 	switch (req->size) {
169 	case 1:
170 		if (req->write)
171 			*((uint8_t *)vaddr) = VAL8(req);
172 		else
173 			VAL8(req) = *((uint8_t *)vaddr);
174 		break;
175 	case 2:
176 		if (req->write)
177 			*((uint16_t *)vaddr) = VAL16(req);
178 		else
179 			VAL16(req) = *((uint16_t *)vaddr);
180 		break;
181 	case 4:
182 		if (req->write)
183 			*((uint32_t *)vaddr) = VAL32(req);
184 		else
185 			VAL32(req) = *((uint32_t *)vaddr);
186 		break;
187 	case 8:
188 		if (req->write)
189 			*((uint64_t *)vaddr) = VAL64(req);
190 		else
191 			VAL64(req) = *((uint64_t *)vaddr);
192 		break;
193 	}
194 	pci_cfgacc_unmap();
195 	mutex_exit(&pcicfg_mmio_mutex);
196 }
197 
198 static boolean_t
199 pci_cfgacc_valid(pci_cfgacc_req_t *req, uint16_t cfgspc_size)
200 {
201 	int sz = req->size;
202 
203 	if (IS_P2ALIGNED(req->offset, sz) &&
204 	    (req->offset + sz - 1 < cfgspc_size) &&
205 	    ((sz & 0xf) && ISP2(sz)))
206 		return (B_TRUE);
207 
208 	cmn_err(CE_WARN, "illegal PCI request: offset = %x, size = %d",
209 	    req->offset, sz);
210 	return (B_FALSE);
211 }
212 
213 void
214 pci_cfgacc_check_io(pci_cfgacc_req_t *req)
215 {
216 	uint8_t bus;
217 
218 	bus = PCI_BDF_BUS(req->bdf);
219 
220 	if (pci_cfgacc_force_io || (mcfg_mem_base == NULL) ||
221 	    (bus < mcfg_bus_start) || (bus > mcfg_bus_end) ||
222 	    pci_cfgacc_find_workaround(req->bdf))
223 		req->ioacc = B_TRUE;
224 }
225 
226 void
227 pci_cfgacc_acc(pci_cfgacc_req_t *req)
228 {
229 	extern uint_t pci_iocfg_max_offset;
230 
231 	if (!req->write)
232 		VAL64(req) = (uint64_t)-1;
233 
234 	pci_cfgacc_check_io(req);
235 
236 	if (req->ioacc) {
237 		if (pci_cfgacc_valid(req, pci_iocfg_max_offset + 1))
238 			pci_cfgacc_io(req);
239 	} else {
240 		if (pci_cfgacc_valid(req, PCIE_CFG_SPACE_SIZE))
241 			pci_cfgacc_mmio(req);
242 	}
243 }
244 
245 typedef	struct cfgacc_bus_range {
246 	struct cfgacc_bus_range *next;
247 	uint16_t bdf;
248 	uchar_t	secbus;
249 	uchar_t	subbus;
250 } cfgacc_bus_range_t;
251 
252 cfgacc_bus_range_t *pci_cfgacc_bus_head = NULL;
253 
254 #define	BUS_INSERT(prev, el) \
255 	el->next = *prev; \
256 	*prev = el;
257 
258 #define	BUS_REMOVE(prev, el) \
259 	*prev = el->next;
260 
261 /*
262  * This function is only supposed to be called in device tree setup time,
263  * thus no lock is needed.
264  */
265 void
266 pci_cfgacc_add_workaround(uint16_t bdf, uchar_t secbus, uchar_t subbus)
267 {
268 	cfgacc_bus_range_t	*entry;
269 
270 	entry = kmem_zalloc(sizeof (cfgacc_bus_range_t), KM_SLEEP);
271 	entry->bdf = bdf;
272 	entry->secbus = secbus;
273 	entry->subbus = subbus;
274 	BUS_INSERT(&pci_cfgacc_bus_head, entry);
275 }
276 
277 boolean_t
278 pci_cfgacc_find_workaround(uint16_t bdf)
279 {
280 	cfgacc_bus_range_t	*entry;
281 	uchar_t			bus;
282 
283 	for (entry = pci_cfgacc_bus_head; entry != NULL;
284 	    entry = entry->next) {
285 		if (bdf == entry->bdf) {
286 			/* found a device which is known to be broken */
287 			return (B_TRUE);
288 		}
289 
290 		bus = PCI_BDF_BUS(bdf);
291 		if ((bus != 0) && (bus >= entry->secbus) &&
292 		    (bus <= entry->subbus)) {
293 			/*
294 			 * found a device whose parent/grandparent is
295 			 * known to be broken.
296 			 */
297 			return (B_TRUE);
298 		}
299 	}
300 
301 	return (B_FALSE);
302 }
303