1 | /* PCI (Auto) configuration Library. Setup PCI configuration space and IRQ. |
---|
2 | * |
---|
3 | * COPYRIGHT (c) 2010 Cobham Gaisler AB. |
---|
4 | * |
---|
5 | * The license and distribution terms for this file may be |
---|
6 | * found in the file LICENSE in this distribution or at |
---|
7 | * http://www.rtems.org/license/LICENSE. |
---|
8 | */ |
---|
9 | |
---|
10 | #include <rtems.h> |
---|
11 | #include <stdlib.h> |
---|
12 | #include <rtems/bspIo.h> |
---|
13 | #include <string.h> |
---|
14 | |
---|
15 | /* Configure headers */ |
---|
16 | #define PCI_CFG_AUTO_LIB |
---|
17 | |
---|
18 | #include <pci.h> |
---|
19 | #include <pci/access.h> |
---|
20 | #include <pci/cfg.h> |
---|
21 | |
---|
22 | #include "pci_internal.h" |
---|
23 | |
---|
24 | /* #define DEBUG */ |
---|
25 | |
---|
26 | #ifdef DEBUG |
---|
27 | #define DBG(x...) printk(x) |
---|
28 | #else |
---|
29 | #define DBG(x...) |
---|
30 | #endif |
---|
31 | |
---|
32 | /* PCI Library |
---|
33 | * (For debugging it might be good to use other functions or the driver's |
---|
34 | * directly) |
---|
35 | */ |
---|
36 | #define PCI_CFG_R8(dev, args...) pci_cfg_r8(dev, args) |
---|
37 | #define PCI_CFG_R16(dev, args...) pci_cfg_r16(dev, args) |
---|
38 | #define PCI_CFG_R32(dev, args...) pci_cfg_r32(dev, args) |
---|
39 | #define PCI_CFG_W8(dev, args...) pci_cfg_w8(dev, args) |
---|
40 | #define PCI_CFG_W16(dev, args...) pci_cfg_w16(dev, args) |
---|
41 | #define PCI_CFG_W32(dev, args...) pci_cfg_w32(dev, args) |
---|
42 | |
---|
43 | int pci_config_auto_initialized = 0; |
---|
44 | |
---|
45 | /* Configuration setup */ |
---|
46 | struct pci_auto_setup pci_auto_cfg; |
---|
47 | |
---|
48 | /* Insert BAR into the sorted resources list. The BARs are sorted on the |
---|
49 | * BAR size/alignment need. |
---|
50 | */ |
---|
51 | static void pci_res_insert(struct pci_res **root, struct pci_res *res) |
---|
52 | { |
---|
53 | struct pci_res *curr, *last; |
---|
54 | unsigned long curr_size_resulting_boundary, size_resulting_boundary; |
---|
55 | unsigned long boundary, size; |
---|
56 | |
---|
57 | res->start = 0; |
---|
58 | res->end = 0; |
---|
59 | boundary = res->boundary; |
---|
60 | size = res->size; |
---|
61 | |
---|
62 | /* Insert the resources depending on the boundary needs |
---|
63 | * Normally the boundary=size of the BAR, however when |
---|
64 | * PCI bridges are involved the bridge's boundary may be |
---|
65 | * smaller than the size due to the fact that a bridge |
---|
66 | * may have different-sized BARs behind, the largest BAR |
---|
67 | * (also the BAR with the largest boundary) will decide |
---|
68 | * the alignment need. |
---|
69 | */ |
---|
70 | last = NULL; |
---|
71 | curr = *root; |
---|
72 | |
---|
73 | /* Order List after boundary, the boundary is maintained |
---|
74 | * when the size is on an equal boundary, normally it is |
---|
75 | * but may not be with bridges. So in second hand it is |
---|
76 | * sorted after resulting boundary - the boundary after |
---|
77 | * the resource. |
---|
78 | */ |
---|
79 | while (curr && (curr->boundary >= boundary)) { |
---|
80 | if (curr->boundary == boundary) { |
---|
81 | /* Find Resulting boundary of size */ |
---|
82 | size_resulting_boundary = 1; |
---|
83 | while ((size & size_resulting_boundary) == 0) |
---|
84 | size_resulting_boundary = |
---|
85 | size_resulting_boundary << 1; |
---|
86 | |
---|
87 | /* Find Resulting boundary of curr->size */ |
---|
88 | curr_size_resulting_boundary = 1; |
---|
89 | while ((curr->size & curr_size_resulting_boundary) == 0) |
---|
90 | curr_size_resulting_boundary = |
---|
91 | curr_size_resulting_boundary << 1; |
---|
92 | |
---|
93 | if (size_resulting_boundary >= |
---|
94 | curr_size_resulting_boundary) |
---|
95 | break; |
---|
96 | } |
---|
97 | last = curr; |
---|
98 | curr = curr->next; |
---|
99 | } |
---|
100 | |
---|
101 | if (last == NULL) { |
---|
102 | /* Insert first in list */ |
---|
103 | res->next = *root; |
---|
104 | *root = res; |
---|
105 | } else { |
---|
106 | last->next = res; |
---|
107 | res->next = curr; |
---|
108 | } |
---|
109 | } |
---|
110 | |
---|
111 | #ifdef DEBUG |
---|
112 | void pci_res_list_print(struct pci_res *root) |
---|
113 | { |
---|
114 | if (root == NULL) |
---|
115 | return; |
---|
116 | |
---|
117 | printf("RESOURCE LIST:\n"); |
---|
118 | while (root) { |
---|
119 | printf(" SIZE: 0x%08x, BOUNDARY: 0x%08x\n", root->size, |
---|
120 | root->boundary); |
---|
121 | root = root->next; |
---|
122 | } |
---|
123 | } |
---|
124 | #endif |
---|
125 | |
---|
126 | /* Reorder a size/alignment ordered resources list. The idea is to |
---|
127 | * avoid unused due to alignment/size restriction. |
---|
128 | * |
---|
129 | * NOTE: The first element is always untouched. |
---|
130 | * NOTE: If less than three elements in list, nothing will be done |
---|
131 | * |
---|
132 | * Normally a BAR has the same alignment requirements as the size of the |
---|
133 | * BAR. However, when bridges are involved the alignment need may be smaller |
---|
134 | * than the size, because a bridge resource consist or multiple BARs. |
---|
135 | * For example, say that a bridge with a 256Mb and a 16Mb BAR is found, then |
---|
136 | * the alignment is required to be 256Mb but the size 256+16Mb. |
---|
137 | * |
---|
138 | * In order to minimize dead space on the bus, the boundary ordered list |
---|
139 | * is reordered, example: |
---|
140 | * BUS0 |
---|
141 | * | BUS1 |
---|
142 | * |------------| |
---|
143 | * | |-- BAR0: SIZE=256Mb, ALIGNMENT=256MB |
---|
144 | * | |-- BAR1: SIZE=16Mb, ALIGNMENT=16MB |
---|
145 | * | | |
---|
146 | * | | |
---|
147 | * | | |
---|
148 | * | | BUS2 (BAR_BRIDGE1: SIZE=256+16, ALIGNEMENT=256) |
---|
149 | * | |----------| |
---|
150 | * | | |-- BAR2: SIZE=256Mb, ALIGNMENT=256Mb |
---|
151 | * | | |-- BAR3: SIZE=16Mb, ALIGNMENT=16MB |
---|
152 | * |
---|
153 | * A alignment/boundary ordered list of BUS1 will look like: |
---|
154 | * - BAR_BRIDGE1 |
---|
155 | * - BAR0 (ALIGMENT NEED 256Mb) |
---|
156 | * - BAR1 |
---|
157 | * |
---|
158 | * However, Between BAR_BRIDGE1 and BAR0 will be a unused hole of 256-16Mb. |
---|
159 | * We can put BAR1 before BAR0 to avoid the problem. |
---|
160 | */ |
---|
161 | static void pci_res_reorder(struct pci_res *root) |
---|
162 | { |
---|
163 | struct pci_res *curr, *last, *curr2, *last2; |
---|
164 | unsigned int start, start_next, hole_size, hole_boundary; |
---|
165 | |
---|
166 | if (root == NULL) |
---|
167 | return; |
---|
168 | |
---|
169 | /* Make up a start address with the boundary of the |
---|
170 | * First element. |
---|
171 | */ |
---|
172 | start = root->boundary + root->size; |
---|
173 | last = root; |
---|
174 | curr = root->next; |
---|
175 | while (curr) { |
---|
176 | |
---|
177 | /* Find start address of resource */ |
---|
178 | start_next = (start + (curr->boundary - 1)) & |
---|
179 | ~(curr->boundary - 1); |
---|
180 | |
---|
181 | /* Find hole size, the unsed space in between last resource |
---|
182 | * and next */ |
---|
183 | hole_size = start_next - start; |
---|
184 | |
---|
185 | /* Find Boundary of START */ |
---|
186 | hole_boundary = 1; |
---|
187 | while ((start & hole_boundary) == 0) |
---|
188 | hole_boundary = hole_boundary<<1; |
---|
189 | |
---|
190 | /* Detect dead hole */ |
---|
191 | if (hole_size > 0) { |
---|
192 | /* Step through list and try to find a resource that |
---|
193 | * can fit into hole. Take into account hole start |
---|
194 | * boundary and hole size. |
---|
195 | */ |
---|
196 | last2 = curr; |
---|
197 | curr2 = curr->next; |
---|
198 | while (curr2) { |
---|
199 | if ((curr2->boundary <= hole_boundary) && |
---|
200 | (curr2->size <= hole_size)) { |
---|
201 | /* Found matching resource. Move it |
---|
202 | * first in the hole. Then rescan, now |
---|
203 | * that the hole has changed in |
---|
204 | * size/boundary. |
---|
205 | */ |
---|
206 | last2->next = curr2->next; |
---|
207 | curr2->next = curr; |
---|
208 | last->next = curr2; |
---|
209 | |
---|
210 | /* New Start address */ |
---|
211 | start_next = (start + |
---|
212 | (curr2->boundary - 1)) & |
---|
213 | ~(curr2->boundary - 1); |
---|
214 | /* Since we inserted the resource before |
---|
215 | * curr we need to re-evaluate curr one |
---|
216 | * more, more resources may fit into the |
---|
217 | * shrunken hole. |
---|
218 | */ |
---|
219 | curr = curr2; |
---|
220 | break; |
---|
221 | } |
---|
222 | last2 = curr2; |
---|
223 | curr2 = curr2->next; |
---|
224 | } |
---|
225 | } |
---|
226 | |
---|
227 | /* No hole or nothing fit into hole. */ |
---|
228 | start = start_next; |
---|
229 | |
---|
230 | last = curr; |
---|
231 | curr = curr->next; |
---|
232 | } |
---|
233 | } |
---|
234 | |
---|
235 | /* Find the total size required in PCI address space needed by a resource list*/ |
---|
236 | static unsigned int pci_res_size(struct pci_res *root) |
---|
237 | { |
---|
238 | struct pci_res *curr; |
---|
239 | unsigned int size; |
---|
240 | |
---|
241 | /* Get total size of all resources */ |
---|
242 | size = 0; |
---|
243 | curr = root; |
---|
244 | while (curr) { |
---|
245 | size = (size + (curr->boundary - 1)) & ~(curr->boundary - 1); |
---|
246 | size += curr->size; |
---|
247 | curr = curr->next; |
---|
248 | } |
---|
249 | |
---|
250 | return size; |
---|
251 | } |
---|
252 | |
---|
253 | #if 0 /* not used for now */ |
---|
254 | /* Free a device and secondary bus if device is a bridge */ |
---|
255 | static void pci_dev_free(struct pci_dev *dev) |
---|
256 | { |
---|
257 | struct pci_dev *subdev; |
---|
258 | struct pci_bus *bus; |
---|
259 | |
---|
260 | if (dev->flags & PCI_DEV_BRIDGE) { |
---|
261 | bus = (struct pci_bus *)dev; |
---|
262 | for (subdev = bus->devs; subdev ; subdev = subdev->next) |
---|
263 | pci_dev_free(dev); |
---|
264 | } |
---|
265 | |
---|
266 | free(dev); |
---|
267 | } |
---|
268 | #endif |
---|
269 | |
---|
270 | static struct pci_dev *pci_dev_create(int isbus) |
---|
271 | { |
---|
272 | void *ptr; |
---|
273 | int size; |
---|
274 | |
---|
275 | if (isbus) |
---|
276 | size = sizeof(struct pci_bus); |
---|
277 | else |
---|
278 | size = sizeof(struct pci_dev); |
---|
279 | |
---|
280 | ptr = calloc(1, size); |
---|
281 | if (!ptr) |
---|
282 | rtems_fatal_error_occurred(RTEMS_NO_MEMORY); |
---|
283 | return ptr; |
---|
284 | } |
---|
285 | |
---|
286 | static void pci_find_devs(struct pci_bus *bus) |
---|
287 | { |
---|
288 | uint32_t id, tmp; |
---|
289 | uint8_t header; |
---|
290 | int slot, func, fail; |
---|
291 | struct pci_dev *dev, **listptr; |
---|
292 | struct pci_bus *bridge; |
---|
293 | pci_dev_t pcidev; |
---|
294 | |
---|
295 | DBG("Scanning bus %d\n", bus->num); |
---|
296 | |
---|
297 | listptr = &bus->devs; |
---|
298 | for (slot = 0; slot <= PCI_SLOTMAX; slot++) { |
---|
299 | |
---|
300 | /* Slot address */ |
---|
301 | pcidev = PCI_DEV(bus->num, slot, 0); |
---|
302 | |
---|
303 | for (func = 0; func <= PCI_FUNCMAX; func++, pcidev++) { |
---|
304 | |
---|
305 | fail = PCI_CFG_R32(pcidev, PCIR_VENDOR, &id); |
---|
306 | if (fail || id == 0xffffffff || id == 0) { |
---|
307 | /* |
---|
308 | * This slot is empty |
---|
309 | */ |
---|
310 | if (func == 0) |
---|
311 | break; |
---|
312 | else |
---|
313 | continue; |
---|
314 | } |
---|
315 | |
---|
316 | DBG("Found PCIDEV 0x%x at (bus %x, slot %x, func %x)\n", |
---|
317 | id, bus, slot, func); |
---|
318 | |
---|
319 | /* Set command to reset values, it disables bus |
---|
320 | * mastering and address responses. |
---|
321 | */ |
---|
322 | PCI_CFG_W16(pcidev, PCIR_COMMAND, 0); |
---|
323 | |
---|
324 | /* Clear any already set status bits */ |
---|
325 | PCI_CFG_W16(pcidev, PCIR_STATUS, 0xf900); |
---|
326 | |
---|
327 | /* Set latency timer to 64 */ |
---|
328 | PCI_CFG_W8(pcidev, PCIR_LATTIMER, 64); |
---|
329 | |
---|
330 | PCI_CFG_R32(pcidev, PCIR_REVID, &tmp); |
---|
331 | tmp >>= 16; |
---|
332 | dev = pci_dev_create(tmp == PCID_PCI2PCI_BRIDGE); |
---|
333 | *listptr = dev; |
---|
334 | listptr = &dev->next; |
---|
335 | |
---|
336 | dev->busdevfun = pcidev; |
---|
337 | dev->bus = bus; |
---|
338 | PCI_CFG_R16(pcidev, PCIR_VENDOR, &dev->vendor); |
---|
339 | PCI_CFG_R16(pcidev, PCIR_DEVICE, &dev->device); |
---|
340 | PCI_CFG_R32(pcidev, PCIR_REVID, &dev->classrev); |
---|
341 | |
---|
342 | if (tmp == PCID_PCI2PCI_BRIDGE) { |
---|
343 | DBG("Found PCI-PCI Bridge 0x%x at " |
---|
344 | "(bus %x, slot %x, func %x)\n", |
---|
345 | id, bus, slot, func); |
---|
346 | dev->flags = PCI_DEV_BRIDGE; |
---|
347 | dev->subvendor = 0; |
---|
348 | dev->subdevice = 0; |
---|
349 | bridge = (struct pci_bus *)dev; |
---|
350 | bridge->num = bus->sord + 1; |
---|
351 | bridge->pri = bus->num; |
---|
352 | bridge->sord = bus->sord + 1; |
---|
353 | |
---|
354 | /* Configure bridge (no support for 64-bit) */ |
---|
355 | PCI_CFG_W32(pcidev, 0x28, 0); |
---|
356 | PCI_CFG_W32(pcidev, 0x2C, 0); |
---|
357 | tmp = (64 << 24) | (0xff << 16) | |
---|
358 | (bridge->num << 8) | bridge->pri; |
---|
359 | PCI_CFG_W32(pcidev, PCIR_PRIBUS_1, tmp); |
---|
360 | |
---|
361 | /* Scan Secondary Bus */ |
---|
362 | pci_find_devs(bridge); |
---|
363 | |
---|
364 | /* sord might have been updated */ |
---|
365 | PCI_CFG_W8(pcidev, 0x1a, bridge->sord); |
---|
366 | bus->sord = bridge->sord; |
---|
367 | |
---|
368 | DBG("PCI-PCI BRIDGE: Primary %x, Secondary %x, " |
---|
369 | "Subordinate %x\n", |
---|
370 | bridge->pri, bridge->num, bridge->sord); |
---|
371 | } else { |
---|
372 | /* Disable Cardbus CIS Pointer */ |
---|
373 | PCI_CFG_W32(pcidev, PCIR_CIS, 0); |
---|
374 | |
---|
375 | /* Devices have subsytem device and vendor ID */ |
---|
376 | PCI_CFG_R16(pcidev, PCIR_SUBVEND_0, |
---|
377 | &dev->subvendor); |
---|
378 | PCI_CFG_R16(pcidev, PCIR_SUBDEV_0, |
---|
379 | &dev->subdevice); |
---|
380 | } |
---|
381 | |
---|
382 | /* Stop if not a multi-function device */ |
---|
383 | if (func == 0) { |
---|
384 | pci_cfg_r8(pcidev, PCIR_HDRTYPE, &header); |
---|
385 | if ((header & PCIM_MFDEV) == 0) |
---|
386 | break; |
---|
387 | } |
---|
388 | } |
---|
389 | } |
---|
390 | } |
---|
391 | |
---|
392 | static void pci_find_bar(struct pci_dev *dev, int bar) |
---|
393 | { |
---|
394 | uint32_t size, disable, mask; |
---|
395 | struct pci_res *res = &dev->resources[bar]; |
---|
396 | pci_dev_t pcidev = dev->busdevfun; |
---|
397 | int ofs; |
---|
398 | #ifdef DEBUG |
---|
399 | char *str; |
---|
400 | #define DBG_SET_STR(str, val) str = (val) |
---|
401 | #else |
---|
402 | #define DBG_SET_STR(str, val) |
---|
403 | #endif |
---|
404 | |
---|
405 | DBG("Bus: %x, Slot: %x, function: %x, bar%d\n", |
---|
406 | PCI_DEV_EXPAND(pcidev), bar); |
---|
407 | |
---|
408 | res->bar = bar; |
---|
409 | if (bar == DEV_RES_ROM) { |
---|
410 | if (dev->flags & PCI_DEV_BRIDGE) |
---|
411 | ofs = PCIR_BIOS_1; |
---|
412 | else |
---|
413 | ofs = PCIR_BIOS; |
---|
414 | disable = 0; /* ROM BARs have a unique enable bit per BAR */ |
---|
415 | } else { |
---|
416 | ofs = PCIR_BAR(0) + (bar << 2); |
---|
417 | disable = pci_invalid_address; |
---|
418 | } |
---|
419 | |
---|
420 | PCI_CFG_W32(pcidev, ofs, 0xffffffff); |
---|
421 | PCI_CFG_R32(pcidev, ofs, &size); |
---|
422 | PCI_CFG_W32(pcidev, ofs, disable); |
---|
423 | |
---|
424 | if (size == 0 || size == 0xffffffff) |
---|
425 | return; |
---|
426 | if (bar == DEV_RES_ROM) { |
---|
427 | mask = PCIM_BIOS_ADDR_MASK; |
---|
428 | DBG_SET_STR(str, "ROM"); |
---|
429 | if (dev->bus->flags & PCI_BUS_MEM) |
---|
430 | res->flags = PCI_RES_MEM; |
---|
431 | else |
---|
432 | res->flags = PCI_RES_MEMIO; |
---|
433 | } else if (((size & 0x1) == 0) && (size & 0x6)) { |
---|
434 | /* unsupported Memory type */ |
---|
435 | PCI_CFG_W32(pcidev, ofs, 0); |
---|
436 | return; |
---|
437 | } else { |
---|
438 | mask = ~0xf; |
---|
439 | if (size & 0x1) { |
---|
440 | /* I/O */ |
---|
441 | mask = ~0x3; |
---|
442 | res->flags = PCI_RES_IO; |
---|
443 | DBG_SET_STR(str, "I/O"); |
---|
444 | if (size & 0xffff0000) |
---|
445 | res->flags |= PCI_RES_IO32; |
---|
446 | /* Limit size of I/O space to 256 byte */ |
---|
447 | size |= 0xffffff00; |
---|
448 | if ((dev->bus->flags & PCI_BUS_IO) == 0) { |
---|
449 | res->flags |= PCI_RES_FAIL; |
---|
450 | dev->flags |= PCI_DEV_RES_FAIL; |
---|
451 | } |
---|
452 | } else { |
---|
453 | /* Memory. We convert Prefetchable Memory BARs to Memory |
---|
454 | * BARs in case the Bridge does not support prefetchable |
---|
455 | * memory. |
---|
456 | */ |
---|
457 | if ((size & 0x8) && (dev->bus->flags & PCI_BUS_MEM)) { |
---|
458 | /* Prefetchable and Bus supports it */ |
---|
459 | res->flags = PCI_RES_MEM; |
---|
460 | DBG_SET_STR(str, "MEM"); |
---|
461 | } else { |
---|
462 | res->flags = PCI_RES_MEMIO; |
---|
463 | DBG_SET_STR(str, "MEMIO"); |
---|
464 | } |
---|
465 | } |
---|
466 | } |
---|
467 | size &= mask; |
---|
468 | res->size = ~size + 1; |
---|
469 | res->boundary = ~size + 1; |
---|
470 | |
---|
471 | DBG("Bus: %x, Slot: %x, function: %x, %s bar%d size: %x\n", |
---|
472 | PCI_DEV_EXPAND(pcidev), str, bar, res->size); |
---|
473 | } |
---|
474 | |
---|
475 | static int pci_find_res_dev(struct pci_dev *dev, void *unused) |
---|
476 | { |
---|
477 | struct pci_bus *bridge; |
---|
478 | uint32_t tmp; |
---|
479 | uint16_t tmp16; |
---|
480 | pci_dev_t pcidev = dev->busdevfun; |
---|
481 | int i, maxbars; |
---|
482 | |
---|
483 | if (dev->flags & PCI_DEV_BRIDGE) { |
---|
484 | /* PCI-PCI Bridge */ |
---|
485 | bridge = (struct pci_bus *)dev; |
---|
486 | |
---|
487 | /* Only 2 Bridge BARs */ |
---|
488 | maxbars = 2; |
---|
489 | |
---|
490 | /* Probe Bridge Spaces (MEMIO space always implemented), the |
---|
491 | * probe disables all space-decoding at the same time |
---|
492 | */ |
---|
493 | PCI_CFG_W32(pcidev, 0x30, 0); |
---|
494 | PCI_CFG_W16(pcidev, 0x1c, 0x00f0); |
---|
495 | PCI_CFG_R16(pcidev, 0x1c, &tmp16); |
---|
496 | if (tmp16 != 0) { |
---|
497 | bridge->flags |= PCI_BUS_IO; |
---|
498 | if (tmp16 & 0x1) |
---|
499 | bridge->flags |= PCI_BUS_IO32; |
---|
500 | } |
---|
501 | |
---|
502 | PCI_CFG_W32(pcidev, 0x24, 0x0000ffff); |
---|
503 | PCI_CFG_R32(pcidev, 0x24, &tmp); |
---|
504 | if (tmp != 0) |
---|
505 | bridge->flags |= PCI_BUS_MEM; |
---|
506 | |
---|
507 | PCI_CFG_W32(pcidev, 0x20, 0x0000ffff); |
---|
508 | bridge->flags |= PCI_BUS_MEMIO; |
---|
509 | } else { |
---|
510 | /* Normal PCI Device as max 6 BARs */ |
---|
511 | maxbars = 6; |
---|
512 | } |
---|
513 | |
---|
514 | /* Probe BARs */ |
---|
515 | for (i = 0; i < maxbars; i++) |
---|
516 | pci_find_bar(dev, i); |
---|
517 | pci_find_bar(dev, DEV_RES_ROM); |
---|
518 | |
---|
519 | return 0; |
---|
520 | } |
---|
521 | |
---|
522 | static int pci_add_res_dev(struct pci_dev *dev, void *arg); |
---|
523 | |
---|
524 | static void pci_add_res_bus(struct pci_bus *bus, int type) |
---|
525 | { |
---|
526 | int tindex = type - 1; |
---|
527 | |
---|
528 | /* Clear old resources */ |
---|
529 | bus->busres[tindex] = NULL; |
---|
530 | |
---|
531 | /* Add resources of devices behind bridge if bridge supports |
---|
532 | * resource type. If MEM space not supported by bridge, they are |
---|
533 | * converted to MEMIO in the process. |
---|
534 | */ |
---|
535 | if (!((type == PCI_BUS_IO) && ((bus->flags & PCI_BUS_IO) == 0))) { |
---|
536 | pci_for_each_child(bus, pci_add_res_dev, (void *)type, 0); |
---|
537 | |
---|
538 | /* Reorder Bus resources to fit more optimally (avoid dead |
---|
539 | * PCI space). Currently they are sorted by boundary and size. |
---|
540 | * |
---|
541 | * This is especially important when multiple buses (bridges) |
---|
542 | * are present. |
---|
543 | */ |
---|
544 | pci_res_reorder(bus->busres[tindex]); |
---|
545 | } |
---|
546 | } |
---|
547 | |
---|
548 | static int pci_add_res_dev(struct pci_dev *dev, void *arg) |
---|
549 | { |
---|
550 | int tindex, type = (int)arg; |
---|
551 | struct pci_bus *bridge; |
---|
552 | struct pci_res *res, *first_busres; |
---|
553 | int i; |
---|
554 | uint32_t bbound; |
---|
555 | |
---|
556 | /* Type index in Bus resource */ |
---|
557 | tindex = type - 1; |
---|
558 | |
---|
559 | if (dev->flags & PCI_DEV_BRIDGE) { |
---|
560 | /* PCI-PCI Bridge. Add all sub-bus resources first */ |
---|
561 | bridge = (struct pci_bus *)dev; |
---|
562 | |
---|
563 | /* Add all child device's resources to this type */ |
---|
564 | pci_add_res_bus(bridge, type); |
---|
565 | |
---|
566 | /* Propagate the resources from child bus to BAR on |
---|
567 | * this bus, by adding a "fake" BAR per type. |
---|
568 | */ |
---|
569 | res = &bridge->dev.resources[BUS_RES_START + tindex]; |
---|
570 | res->bar = BUS_RES_START + tindex; |
---|
571 | res->start = 0; |
---|
572 | res->end = 0; |
---|
573 | res->flags = 0; /* mark BAR resource not available */ |
---|
574 | first_busres = bridge->busres[tindex]; |
---|
575 | if (first_busres) { |
---|
576 | res->flags = type; |
---|
577 | res->size = pci_res_size(first_busres); |
---|
578 | res->boundary = first_busres->boundary; |
---|
579 | if (type == PCI_RES_IO) { |
---|
580 | bbound = 0x1000; /* Bridge I/O min 4KB */ |
---|
581 | } else { |
---|
582 | bbound = 0x100000; /* Bridge MEM min 1MB */ |
---|
583 | |
---|
584 | /* Convert MEM to MEMIO if not supported by |
---|
585 | * this bridge |
---|
586 | */ |
---|
587 | if ((bridge->flags & PCI_BUS_MEM) == 0) |
---|
588 | res->flags = PCI_RES_MEMIO; |
---|
589 | } |
---|
590 | /* Fulfil minimum bridge boundary */ |
---|
591 | if (res->boundary < bbound) |
---|
592 | res->boundary = bbound; |
---|
593 | /* Make sure that size is atleast bridge boundary */ |
---|
594 | if (res->size > bbound && (res->size & (bbound-1))) |
---|
595 | res->size = (res->size | (bbound-1)) + 1; |
---|
596 | } |
---|
597 | } |
---|
598 | |
---|
599 | /* Normal PCI Device as max 6 BARs and a ROM Bar. |
---|
600 | * Insert BARs into the sorted resource list. |
---|
601 | */ |
---|
602 | for (i = 0; i < DEV_RES_CNT; i++) { |
---|
603 | res = &dev->resources[i]; |
---|
604 | if ((res->flags & PCI_RES_TYPE_MASK) != type) |
---|
605 | continue; |
---|
606 | pci_res_insert(&dev->bus->busres[tindex], res); |
---|
607 | } |
---|
608 | |
---|
609 | return 0; |
---|
610 | } |
---|
611 | |
---|
612 | /* Function assumes that base is properly aligned to the requirement of the |
---|
613 | * largest BAR in the system. |
---|
614 | */ |
---|
615 | static uint32_t pci_alloc_res(struct pci_bus *bus, int type, |
---|
616 | uint32_t start, uint32_t end) |
---|
617 | { |
---|
618 | struct pci_dev *dev; |
---|
619 | struct pci_res *res, **prev_next; |
---|
620 | unsigned long starttmp; |
---|
621 | struct pci_bus *bridge; |
---|
622 | int removed, sec_type; |
---|
623 | |
---|
624 | /* The resources are sorted on their size (size and alignment is the |
---|
625 | * same) |
---|
626 | */ |
---|
627 | prev_next = &bus->busres[type - 1]; |
---|
628 | while ((res = *prev_next) != NULL) { |
---|
629 | |
---|
630 | dev = RES2DEV(res); |
---|
631 | removed = 0; |
---|
632 | |
---|
633 | /* Align start to this reource's need, only needed after |
---|
634 | * a bridge resource has been allocated. |
---|
635 | */ |
---|
636 | starttmp = (start + (res->boundary-1)) & ~(res->boundary-1); |
---|
637 | |
---|
638 | if ((starttmp + res->size - 1) > end) { |
---|
639 | /* Not enough memory available for this resource */ |
---|
640 | printk("PCI[%x:%x:%x]: DEV BAR%d (%d): no resource " |
---|
641 | "assigned\n", |
---|
642 | PCI_DEV_EXPAND(dev->busdevfun), |
---|
643 | res->bar, res->flags & PCI_RES_TYPE_MASK); |
---|
644 | res->start = res->end = 0; |
---|
645 | |
---|
646 | /* If this resources is a bridge window to the |
---|
647 | * secondary bus, the secondary resources are not |
---|
648 | * changed which has the following effect: |
---|
649 | * I/O : Will never be assigned |
---|
650 | * MEMIO : Will never be assigned |
---|
651 | * MEM : Will stay marked as MEM, but bridge window |
---|
652 | * is changed into MEMIO, when the window is |
---|
653 | * assigned a MEMIO address the secondary |
---|
654 | * resources will also be assigned. |
---|
655 | */ |
---|
656 | |
---|
657 | if (type == PCI_RES_MEM) { |
---|
658 | /* Try prefetchable as non-prefetchable mem */ |
---|
659 | res->flags &= ~PCI_RES_MEM_PREFETCH; |
---|
660 | /* Remove resource from MEM list, ideally we |
---|
661 | * should regenerate this list in order to fit |
---|
662 | * the comming BARs more optimially... |
---|
663 | */ |
---|
664 | *prev_next = res->next; |
---|
665 | /* We should not update prev_next here since |
---|
666 | * we just removed the resource from the list |
---|
667 | */ |
---|
668 | removed = 1; |
---|
669 | } else { |
---|
670 | res->flags |= PCI_RES_FAIL; |
---|
671 | dev->flags |= PCI_DEV_RES_FAIL; |
---|
672 | } |
---|
673 | } else { |
---|
674 | start = starttmp; |
---|
675 | |
---|
676 | res->start = start; |
---|
677 | res->end = start + res->size; |
---|
678 | |
---|
679 | /* "Virtual BAR" on a bridge? A bridge resource need all |
---|
680 | * its child devices resources allocated |
---|
681 | */ |
---|
682 | if ((res->bar != DEV_RES_ROM) && |
---|
683 | (dev->flags & PCI_DEV_BRIDGE) && |
---|
684 | (res->bar >= BUS_RES_START)) { |
---|
685 | bridge = (struct pci_bus *)dev; |
---|
686 | /* If MEM bar was changed into a MEMIO the |
---|
687 | * secondary MEM resources are still set to MEM, |
---|
688 | */ |
---|
689 | if (type == PCI_BUS_MEMIO && |
---|
690 | res->bar == BRIDGE_RES_MEM) |
---|
691 | sec_type = PCI_RES_MEM; |
---|
692 | else |
---|
693 | sec_type = type; |
---|
694 | |
---|
695 | pci_alloc_res(bridge, sec_type, res->start, |
---|
696 | res->end); |
---|
697 | } |
---|
698 | |
---|
699 | start += res->size; |
---|
700 | } |
---|
701 | if (removed == 0) |
---|
702 | prev_next = &res->next; |
---|
703 | } |
---|
704 | |
---|
705 | return start; |
---|
706 | } |
---|
707 | |
---|
708 | static void pci_set_bar(struct pci_dev *dev, int residx) |
---|
709 | { |
---|
710 | uint32_t tmp; |
---|
711 | uint16_t tmp16; |
---|
712 | pci_dev_t pcidev; |
---|
713 | struct pci_res *res; |
---|
714 | int is_bridge, ofs; |
---|
715 | |
---|
716 | res = &dev->resources[residx]; |
---|
717 | pcidev = dev->busdevfun; |
---|
718 | |
---|
719 | if ((res->flags == 0) || (res->flags & PCI_RES_FAIL)) |
---|
720 | return; |
---|
721 | |
---|
722 | is_bridge = dev->flags & PCI_DEV_BRIDGE; |
---|
723 | |
---|
724 | if (res->bar == DEV_RES_ROM) { |
---|
725 | /* ROM: 32-bit prefetchable memory BAR */ |
---|
726 | if (is_bridge) |
---|
727 | ofs = PCIR_BIOS_1; |
---|
728 | else |
---|
729 | ofs = PCIR_BIOS; |
---|
730 | PCI_CFG_W32(pcidev, ofs, res->start | PCIM_BIOS_ENABLE); |
---|
731 | DBG("PCI[%x:%x:%x]: ROM BAR: 0x%x-0x%x\n", |
---|
732 | PCI_DEV_EXPAND(pcidev), res->start, res->end); |
---|
733 | } else if (is_bridge && (res->bar == BRIDGE_RES_IO)) { |
---|
734 | /* PCI Bridge I/O BAR */ |
---|
735 | DBG("PCI[%x:%x:%x]: BAR 1C: 0x%x-0x%x\n", |
---|
736 | PCI_DEV_EXPAND(pcidev), res->start, res->end); |
---|
737 | |
---|
738 | /* Limit and Base */ |
---|
739 | tmp16 = ((res->end-1) & 0x0000f000) | |
---|
740 | ((res->start & 0x0000f000) >> 8); |
---|
741 | tmp = ((res->end-1) & 0xffff0000) | (res->start >> 16); |
---|
742 | |
---|
743 | DBG("PCI[%x:%x:%x]: BRIDGE BAR 0x%x: 0x%08x [0x30: 0x%x]\n", |
---|
744 | PCI_DEV_EXPAND(pcidev), 0x1C, tmp, tmp2); |
---|
745 | PCI_CFG_W16(pcidev, 0x1C, tmp16); |
---|
746 | PCI_CFG_W32(pcidev, 0x30, tmp); |
---|
747 | } else if (is_bridge && (res->bar >= BRIDGE_RES_MEMIO)) { |
---|
748 | /* PCI Bridge MEM and MEMIO Space */ |
---|
749 | |
---|
750 | /* Limit and Base */ |
---|
751 | tmp = ((res->end-1) & 0xfff00000) | (res->start >> 16); |
---|
752 | |
---|
753 | DBG("PCI[%x:%x:%x]: BRIDGE BAR 0x%x: 0x%08x\n", |
---|
754 | PCI_DEV_EXPAND(pcidev), |
---|
755 | 0x20 + (res->bar-BRIDGE_RES_MEMIO)*4, tmp); |
---|
756 | PCI_CFG_W32(pcidev, 0x20+(res->bar-BRIDGE_RES_MEMIO)*4, tmp); |
---|
757 | } else { |
---|
758 | /* PCI Device */ |
---|
759 | DBG("PCI[%x:%x:%x]: DEV BAR%d: 0x%08x\n", |
---|
760 | PCI_DEV_EXPAND(pcidev), res->bar, res->start); |
---|
761 | ofs = PCIR_BAR(0) + res->bar*4; |
---|
762 | PCI_CFG_W32(pcidev, ofs, res->start); |
---|
763 | } |
---|
764 | |
---|
765 | /* Enable Memory or I/O responses */ |
---|
766 | if ((res->flags & PCI_RES_TYPE_MASK) == PCI_RES_IO) |
---|
767 | pci_io_enable(pcidev); |
---|
768 | else |
---|
769 | pci_mem_enable(pcidev); |
---|
770 | |
---|
771 | /* Enable Master if bridge */ |
---|
772 | if (is_bridge) |
---|
773 | pci_master_enable(pcidev); |
---|
774 | } |
---|
775 | |
---|
776 | static int pci_set_res_dev(struct pci_dev *dev, void *unused) |
---|
777 | { |
---|
778 | int i, maxbars; |
---|
779 | |
---|
780 | if (dev->flags & PCI_DEV_BRIDGE) |
---|
781 | maxbars = 2 + 3; /* 2 BARs + 3 Bridge-Windows "Virtual BARs" */ |
---|
782 | else |
---|
783 | maxbars = 6; /* Normal PCI Device as max 6 BARs. */ |
---|
784 | |
---|
785 | /* Set BAR resources with previous allocated values */ |
---|
786 | for (i = 0; i < maxbars; i++) |
---|
787 | pci_set_bar(dev, i); |
---|
788 | pci_set_bar(dev, DEV_RES_ROM); |
---|
789 | |
---|
790 | return 0; |
---|
791 | } |
---|
792 | |
---|
793 | /* Route IRQ through PCI-PCI Bridges */ |
---|
794 | static int pci_route_irq(pci_dev_t dev, int irq_pin) |
---|
795 | { |
---|
796 | int slot_grp; |
---|
797 | |
---|
798 | if (PCI_DEV_BUS(dev) == 0) |
---|
799 | return irq_pin; |
---|
800 | |
---|
801 | slot_grp = PCI_DEV_SLOT(dev) & 0x3; |
---|
802 | |
---|
803 | return (((irq_pin - 1) + slot_grp) & 0x3) + 1; |
---|
804 | } |
---|
805 | |
---|
806 | /* Put assigned system IRQ into PCI interrupt line information field. |
---|
807 | * This is to make it possible for drivers to read system IRQ / Vector from |
---|
808 | * configuration space later on. |
---|
809 | * |
---|
810 | * 1. Get Interrupt PIN |
---|
811 | * 2. Route PIN to host bridge |
---|
812 | * 3. Get System interrupt number assignment for PIN |
---|
813 | * 4. Set Interrupt LINE |
---|
814 | */ |
---|
815 | static int pci_set_irq_dev(struct pci_dev *dev, void *cfg) |
---|
816 | { |
---|
817 | struct pci_auto_setup *autocfg = cfg; |
---|
818 | uint8_t irq_pin, irq_line, *psysirq; |
---|
819 | pci_dev_t pcidev; |
---|
820 | |
---|
821 | psysirq = &dev->sysirq; |
---|
822 | pcidev = dev->busdevfun; |
---|
823 | PCI_CFG_R8(pcidev, PCIR_INTPIN, &irq_pin); |
---|
824 | |
---|
825 | /* perform IRQ routing until we reach host bridge */ |
---|
826 | while (dev->bus && irq_pin != 0) { |
---|
827 | irq_pin = autocfg->irq_route(dev->busdevfun, irq_pin); |
---|
828 | dev = &dev->bus->dev; |
---|
829 | } |
---|
830 | |
---|
831 | /* Get IRQ from PIN on PCI bus0 */ |
---|
832 | if (irq_pin != 0 && autocfg->irq_map) |
---|
833 | irq_line = autocfg->irq_map(dev->busdevfun, irq_pin); |
---|
834 | else |
---|
835 | irq_line = 0; |
---|
836 | |
---|
837 | *psysirq = irq_line; |
---|
838 | |
---|
839 | /* Set System Interrupt/Vector for device. 0 means no-IRQ */ |
---|
840 | PCI_CFG_W8(pcidev, PCIR_INTLINE, irq_line); |
---|
841 | |
---|
842 | return 0; |
---|
843 | } |
---|
844 | |
---|
845 | /* This routine assumes that PCI access library has been successfully |
---|
846 | * initialized. All information about the PCI bus needed is found in |
---|
847 | * the pci_auto_cfg structure passed on by pci_config_register(). |
---|
848 | * |
---|
849 | * The PCI buses are enumerated as bridges are found, PCI devices are |
---|
850 | * setup with BARs and IRQs, etc. |
---|
851 | */ |
---|
852 | int pci_config_auto(void) |
---|
853 | { |
---|
854 | uint32_t end; |
---|
855 | uint32_t startmemio, startmem, startio; |
---|
856 | struct pci_auto_setup *autocfg = &pci_auto_cfg; |
---|
857 | #ifdef DEBUG |
---|
858 | uint32_t endmemio, endmem, endio; |
---|
859 | uint32_t start; |
---|
860 | #endif |
---|
861 | |
---|
862 | if (pci_config_auto_initialized == 0) |
---|
863 | return -1; /* no config given to library */ |
---|
864 | |
---|
865 | #ifdef DEBUG |
---|
866 | DBG("\n--- PCI MEMORY AVAILABLE ---\n"); |
---|
867 | if (autocfg->mem_size) { |
---|
868 | start = autocfg->mem_start; |
---|
869 | end = autocfg->mem_start + autocfg->mem_size - 1; |
---|
870 | DBG(" MEM AVAIL [0x%08x-0x%08x]\n", start, end); |
---|
871 | } else { |
---|
872 | /* One big memory space */ |
---|
873 | DBG(" MEM share the space with MEMIO\n"); |
---|
874 | } |
---|
875 | /* no-prefetchable memory space need separate memory space. |
---|
876 | * For example PCI controller maps this region non-cachable. |
---|
877 | */ |
---|
878 | start = autocfg->memio_start; |
---|
879 | end = autocfg->memio_start + autocfg->memio_size - 1; |
---|
880 | DBG(" MEMIO AVAIL [0x%08x-0x%08x]\n", start, end); |
---|
881 | if (autocfg->io_size) { |
---|
882 | start = autocfg->io_start; |
---|
883 | end = autocfg->io_start + autocfg->io_size - 1; |
---|
884 | DBG(" I/O AVAIL [0x%08x-0x%08x]\n", start, end); |
---|
885 | } else { |
---|
886 | DBG(" I/O Space not available\n"); |
---|
887 | } |
---|
888 | #endif |
---|
889 | |
---|
890 | /* Init Host-Bridge */ |
---|
891 | memset(&pci_hb, 0, sizeof(pci_hb)); |
---|
892 | pci_hb.dev.flags = PCI_DEV_BRIDGE; |
---|
893 | if (autocfg->memio_size <= 0) |
---|
894 | return -1; |
---|
895 | pci_hb.flags = PCI_BUS_MEMIO; |
---|
896 | if (autocfg->mem_size) |
---|
897 | pci_hb.flags |= PCI_BUS_MEM; |
---|
898 | if (autocfg->io_size) |
---|
899 | pci_hb.flags |= PCI_BUS_IO; |
---|
900 | |
---|
901 | /* Find all PCI devices/functions on all buses. The buses will be |
---|
902 | * enumrated (assigned a unique PCI Bus ID 0..255). |
---|
903 | */ |
---|
904 | DBG("\n--- PCI SCANNING ---\n"); |
---|
905 | pci_find_devs(&pci_hb); |
---|
906 | pci_bus_cnt = pci_hb.sord + 1; |
---|
907 | if (pci_hb.devs == NULL) |
---|
908 | return 0; |
---|
909 | |
---|
910 | pci_system_type = PCI_SYSTEM_HOST; |
---|
911 | |
---|
912 | /* Find all resources (MEM/MEMIO/IO BARs) of all devices/functions |
---|
913 | * on all buses. |
---|
914 | * |
---|
915 | * Device resources behind bridges which does not support prefetchable |
---|
916 | * memory are already marked as non-prefetchable memory. |
---|
917 | * Devices which as I/O resources behind a bridge that do not support |
---|
918 | * I/O space are marked DISABLED. |
---|
919 | * |
---|
920 | * All BARs and Bridge Spaces are disabled after this. Only the ones |
---|
921 | * that are allocated an address are initilized later on. |
---|
922 | */ |
---|
923 | DBG("\n\n--- PCI RESOURCES ---\n"); |
---|
924 | pci_for_each_dev(pci_find_res_dev, 0); |
---|
925 | |
---|
926 | /* Add all device's resources to bus and sort them to fit in the PCI |
---|
927 | * Window. The device resources are propagated upwards through bridges |
---|
928 | * by adding a "virtual" BAR (boundary != BAR size). |
---|
929 | * |
---|
930 | * We wait with MEMIO (non-prefetchable memory) resources to after MEM |
---|
931 | * resources have been allocated, so that MEM resources can be changed |
---|
932 | * into MEMIO resources if not enough space. |
---|
933 | */ |
---|
934 | pci_add_res_bus(&pci_hb, PCI_RES_IO); |
---|
935 | pci_add_res_bus(&pci_hb, PCI_RES_MEM); |
---|
936 | |
---|
937 | /* Start assigning found resource according to the sorted order. */ |
---|
938 | |
---|
939 | /* Allocate resources to I/O areas */ |
---|
940 | if (pci_hb.busres[BUS_RES_IO]) { |
---|
941 | startio = autocfg->io_start; |
---|
942 | end = startio + autocfg->io_size; |
---|
943 | #ifdef DEBUG |
---|
944 | endio = |
---|
945 | #endif |
---|
946 | pci_alloc_res(&pci_hb, PCI_RES_IO, startio, end); |
---|
947 | } |
---|
948 | |
---|
949 | /* Allocate resources to prefetchable memory */ |
---|
950 | if (pci_hb.busres[BUS_RES_MEM]) { |
---|
951 | startmem = autocfg->mem_start; |
---|
952 | end = startmem + autocfg->mem_size; |
---|
953 | #ifdef DEBUG |
---|
954 | endmem = |
---|
955 | #endif |
---|
956 | pci_alloc_res(&pci_hb, PCI_RES_MEM, startmem, end); |
---|
957 | } |
---|
958 | |
---|
959 | /* Add non-prefetchable memory resources and not fitting prefetchable |
---|
960 | * memory resources. |
---|
961 | * |
---|
962 | * Some prefetchable memory resources may not have fitted into PCI |
---|
963 | * window. Prefetchable memory can be mapped into non-prefetchable |
---|
964 | * memory window. The failing BARs have been marked as MEMIO instead. |
---|
965 | */ |
---|
966 | pci_add_res_bus(&pci_hb, PCI_RES_MEMIO); |
---|
967 | |
---|
968 | /* Allocate resources to non-prefetchable memory */ |
---|
969 | if (pci_hb.busres[BUS_RES_MEMIO]) { |
---|
970 | startmemio = autocfg->memio_start; |
---|
971 | end = startmemio + autocfg->memio_size; |
---|
972 | #ifdef DEBUG |
---|
973 | endmemio = |
---|
974 | #endif |
---|
975 | pci_alloc_res(&pci_hb, PCI_RES_MEMIO, startmemio, end); |
---|
976 | } |
---|
977 | |
---|
978 | DBG("\n--- PCI ALLOCATED SPACE RANGES ---\n"); |
---|
979 | DBG(" MEM NON-PREFETCHABLE: [0x%08x-0x%08x]\n", startmemio, endmemio); |
---|
980 | DBG(" MEM PREFETCHABLE: [0x%08x-0x%08x]\n", startmem, endmem); |
---|
981 | DBG(" I/O: [0x%08x-0x%08x]\n", startio, endio); |
---|
982 | |
---|
983 | /* Set all allocated BARs and Bridge Windows */ |
---|
984 | pci_for_each_dev(pci_set_res_dev, NULL); |
---|
985 | |
---|
986 | /* Initialize IRQs of all devices. According to the PCI-PCI bridge |
---|
987 | * specification the IRQs are routed differently depending on slot |
---|
988 | * number. Drivers can override the default routing if a motherboard |
---|
989 | * requires it. |
---|
990 | */ |
---|
991 | if ((autocfg->options & CFGOPT_NOSETUP_IRQ) == 0) { |
---|
992 | if (autocfg->irq_route == NULL) /* use standard irq routing */ |
---|
993 | autocfg->irq_route = pci_route_irq; |
---|
994 | pci_for_each_dev(pci_set_irq_dev, autocfg); |
---|
995 | } |
---|
996 | |
---|
997 | DBG("PCI resource allocation done\n"); |
---|
998 | |
---|
999 | return 0; |
---|
1000 | } |
---|
1001 | |
---|
1002 | void pci_config_auto_register(void *config) |
---|
1003 | { |
---|
1004 | pci_config_auto_initialized = 1; |
---|
1005 | memcpy(&pci_auto_cfg, config, sizeof(struct pci_auto_setup)); |
---|
1006 | } |
---|