Merge commit '5e2cca1843c61ee0ef1bb95c5dddc9b450b790c6'
[unleashed.git] / arch / x86 / kernel / os / pci_cfgacc_x86.c
blob8c0d80cbb8e0fd4679cd4c21e7d13824715e8cdb
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
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>
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)
44 /* patchable variables */
45 volatile boolean_t pci_cfgacc_force_io = B_FALSE;
47 extern uintptr_t alloc_vaddr(size_t, paddr_t);
49 void pci_cfgacc_acc(pci_cfgacc_req_t *);
51 boolean_t pci_cfgacc_find_workaround(uint16_t);
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.
58 static caddr_t pci_cfgacc_virt_base = NULL;
60 static caddr_t
61 pci_cfgacc_map(paddr_t phys_addr)
63 if (khat_running) {
64 pfn_t pfn = mmu_btop(phys_addr);
66 * pci_cfgacc_virt_base may hold address left from early
67 * boot, which points to low mem. Realloc virtual address
68 * in kernel space since it's already late in boot now.
69 * Note: no need to unmap first, clear_boot_mappings() will
70 * do that for us.
72 if (pci_cfgacc_virt_base < (caddr_t)kernelbase)
73 pci_cfgacc_virt_base = vmem_alloc(heap_arena,
74 MMU_PAGESIZE, VM_SLEEP);
76 hat_devload(kas.a_hat, pci_cfgacc_virt_base,
77 MMU_PAGESIZE, pfn, PROT_READ | PROT_WRITE |
78 HAT_STRICTORDER, HAT_LOAD_LOCK);
79 } else {
80 paddr_t pa_base = P2ALIGN(phys_addr, MMU_PAGESIZE);
82 if (pci_cfgacc_virt_base == NULL)
83 pci_cfgacc_virt_base =
84 (caddr_t)alloc_vaddr(MMU_PAGESIZE, MMU_PAGESIZE);
86 kbm_map((uintptr_t)pci_cfgacc_virt_base, pa_base, 0, 0);
89 return (pci_cfgacc_virt_base + (phys_addr & MMU_PAGEOFFSET));
92 static void
93 pci_cfgacc_unmap()
95 if (khat_running)
96 hat_unload(kas.a_hat, pci_cfgacc_virt_base, MMU_PAGESIZE,
97 HAT_UNLOAD_UNLOCK);
100 static void
101 pci_cfgacc_io(pci_cfgacc_req_t *req)
103 uint8_t bus, dev, func;
104 uint16_t ioacc_offset; /* 4K config access with IO ECS */
106 bus = PCI_BDF_BUS(req->bdf);
107 dev = PCI_BDF_DEV(req->bdf);
108 func = PCI_BDF_FUNC(req->bdf);
109 ioacc_offset = req->offset;
111 switch (req->size) {
112 case 1:
113 if (req->write)
114 (*pci_putb_func)(bus, dev, func,
115 ioacc_offset, VAL8(req));
116 else
117 VAL8(req) = (*pci_getb_func)(bus, dev, func,
118 ioacc_offset);
119 break;
120 case 2:
121 if (req->write)
122 (*pci_putw_func)(bus, dev, func,
123 ioacc_offset, VAL16(req));
124 else
125 VAL16(req) = (*pci_getw_func)(bus, dev, func,
126 ioacc_offset);
127 break;
128 case 4:
129 if (req->write)
130 (*pci_putl_func)(bus, dev, func,
131 ioacc_offset, VAL32(req));
132 else
133 VAL32(req) = (*pci_getl_func)(bus, dev, func,
134 ioacc_offset);
135 break;
136 case 8:
137 if (req->write) {
138 (*pci_putl_func)(bus, dev, func,
139 ioacc_offset, VAL64(req) & 0xffffffff);
140 (*pci_putl_func)(bus, dev, func,
141 ioacc_offset + 4, VAL64(req) >> 32);
142 } else {
143 VAL64(req) = (*pci_getl_func)(bus, dev, func,
144 ioacc_offset);
145 VAL64(req) |= (uint64_t)(*pci_getl_func)(bus, dev, func,
146 ioacc_offset + 4) << 32;
148 break;
152 static void
153 pci_cfgacc_mmio(pci_cfgacc_req_t *req)
155 caddr_t vaddr;
156 paddr_t paddr;
158 paddr = (paddr_t)req->bdf << 12;
159 paddr += mcfg_mem_base + req->offset;
161 mutex_enter(&pcicfg_mmio_mutex);
162 vaddr = pci_cfgacc_map(paddr);
164 switch (req->size) {
165 case 1:
166 if (req->write)
167 *((uint8_t *)vaddr) = VAL8(req);
168 else
169 VAL8(req) = *((uint8_t *)vaddr);
170 break;
171 case 2:
172 if (req->write)
173 *((uint16_t *)vaddr) = VAL16(req);
174 else
175 VAL16(req) = *((uint16_t *)vaddr);
176 break;
177 case 4:
178 if (req->write)
179 *((uint32_t *)vaddr) = VAL32(req);
180 else
181 VAL32(req) = *((uint32_t *)vaddr);
182 break;
183 case 8:
184 if (req->write)
185 *((uint64_t *)vaddr) = VAL64(req);
186 else
187 VAL64(req) = *((uint64_t *)vaddr);
188 break;
190 pci_cfgacc_unmap();
191 mutex_exit(&pcicfg_mmio_mutex);
194 static boolean_t
195 pci_cfgacc_valid(pci_cfgacc_req_t *req, uint16_t cfgspc_size)
197 int sz = req->size;
199 if (IS_P2ALIGNED(req->offset, sz) &&
200 (req->offset + sz - 1 < cfgspc_size) &&
201 ((sz & 0xf) && ISP2(sz)))
202 return (B_TRUE);
204 cmn_err(CE_WARN, "illegal PCI request: offset = %x, size = %d",
205 req->offset, sz);
206 return (B_FALSE);
209 void
210 pci_cfgacc_check_io(pci_cfgacc_req_t *req)
212 uint8_t bus;
214 bus = PCI_BDF_BUS(req->bdf);
216 if (pci_cfgacc_force_io || (mcfg_mem_base == (uintptr_t)NULL) ||
217 (bus < mcfg_bus_start) || (bus > mcfg_bus_end) ||
218 pci_cfgacc_find_workaround(req->bdf))
219 req->ioacc = B_TRUE;
222 void
223 pci_cfgacc_acc(pci_cfgacc_req_t *req)
225 extern uint_t pci_iocfg_max_offset;
227 if (!req->write)
228 VAL64(req) = (uint64_t)-1;
230 pci_cfgacc_check_io(req);
232 if (req->ioacc) {
233 if (pci_cfgacc_valid(req, pci_iocfg_max_offset + 1))
234 pci_cfgacc_io(req);
235 } else {
236 if (pci_cfgacc_valid(req, PCIE_CFG_SPACE_SIZE))
237 pci_cfgacc_mmio(req);
241 typedef struct cfgacc_bus_range {
242 struct cfgacc_bus_range *next;
243 uint16_t bdf;
244 uchar_t secbus;
245 uchar_t subbus;
246 } cfgacc_bus_range_t;
248 cfgacc_bus_range_t *pci_cfgacc_bus_head = NULL;
250 #define BUS_INSERT(prev, el) \
251 el->next = *prev; \
252 *prev = el;
254 #define BUS_REMOVE(prev, el) \
255 *prev = el->next;
258 * This function is only supposed to be called in device tree setup time,
259 * thus no lock is needed.
261 void
262 pci_cfgacc_add_workaround(uint16_t bdf, uchar_t secbus, uchar_t subbus)
264 cfgacc_bus_range_t *entry;
266 entry = kmem_zalloc(sizeof (cfgacc_bus_range_t), KM_SLEEP);
267 entry->bdf = bdf;
268 entry->secbus = secbus;
269 entry->subbus = subbus;
270 BUS_INSERT(&pci_cfgacc_bus_head, entry);
273 boolean_t
274 pci_cfgacc_find_workaround(uint16_t bdf)
276 cfgacc_bus_range_t *entry;
277 uchar_t bus;
279 for (entry = pci_cfgacc_bus_head; entry != NULL;
280 entry = entry->next) {
281 if (bdf == entry->bdf) {
282 /* found a device which is known to be broken */
283 return (B_TRUE);
286 bus = PCI_BDF_BUS(bdf);
287 if ((bus != 0) && (bus >= entry->secbus) &&
288 (bus <= entry->subbus)) {
290 * found a device whose parent/grandparent is
291 * known to be broken.
293 return (B_TRUE);
297 return (B_FALSE);