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