diff options
Diffstat (limited to 'cpukit/libpci/pci_cfg_auto.c')
-rw-r--r-- | cpukit/libpci/pci_cfg_auto.c | 1014 |
1 files changed, 1014 insertions, 0 deletions
diff --git a/cpukit/libpci/pci_cfg_auto.c b/cpukit/libpci/pci_cfg_auto.c new file mode 100644 index 0000000000..548dd90185 --- /dev/null +++ b/cpukit/libpci/pci_cfg_auto.c @@ -0,0 +1,1014 @@ +/* PCI (Auto) configuration Library. Setup PCI configuration space and IRQ. + * + * COPYRIGHT (c) 2010. + * Cobham Gaisler AB. + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + */ + +#include <rtems.h> +#include <stdlib.h> +#include <rtems/bspIo.h> +#include <string.h> + +/* Configure headers */ +#define PCI_CFG_AUTO_LIB + +#include <pci.h> +#include <pci/access.h> +#include <pci/cfg.h> + +/* Define PCI_INFO_ON_STARTUP to get a listing of configured devices at boot + * time + */ +#undef PCI_INFO_ON_STARTUP + +/* #define DEBUG */ + +#ifdef DEBUG +#define DBG(x...) printk(x) +#else +#define DBG(x...) +#endif + +/* PCI Library + * (For debugging it might be good to use other functions or the driver's + * directly) + */ +#define PCI_CFG_R8(dev, args...) pci_cfg_r8(dev, args) +#define PCI_CFG_R16(dev, args...) pci_cfg_r16(dev, args) +#define PCI_CFG_R32(dev, args...) pci_cfg_r32(dev, args) +#define PCI_CFG_W8(dev, args...) pci_cfg_w8(dev, args) +#define PCI_CFG_W16(dev, args...) pci_cfg_w16(dev, args) +#define PCI_CFG_W32(dev, args...) pci_cfg_w32(dev, args) + +/* Number of PCI buses */ +extern int pci_bus_cnt; + +int pci_config_auto_initialized = 0; + +/* Configuration setup */ +struct pci_auto_setup pci_auto_cfg; + +/* Insert BAR into the sorted resources list. The BARs are sorted on the + * BAR size/alignment need. + */ +static void pci_res_insert(struct pci_res **root, struct pci_res *res) +{ + struct pci_res *curr, *last; + unsigned long curr_size_resulting_boundary, size_resulting_boundary; + unsigned long boundary, size; + + res->start = 0; + res->end = 0; + boundary = res->boundary; + size = res->size; + + /* Insert the resources depending on the boundary needs + * Normally the boundary=size of the BAR, however when + * PCI bridges are involved the bridge's boundary may be + * smaller that the size due to the fact that a bridge + * may have different-sized BARs behind, the largest BAR + * (also the BAR with the largest boundary) will decide + * the alignment need. + */ + last = NULL; + curr = *root; + + /* Order List after boundary, the boundary is maintained + * when the size is on an equal boundary, normally it is + * but may not be with bridges. So in second hand it is + * sorted after resulting boundary - the boundary after + * the resource. + */ + while (curr && (curr->boundary >= boundary)) { + if (curr->boundary == boundary) { + /* Find Resulting boundary of size */ + size_resulting_boundary = 1; + while ((size & size_resulting_boundary) == 0) + size_resulting_boundary = + size_resulting_boundary << 1; + + /* Find Resulting boundary of curr->size */ + curr_size_resulting_boundary = 1; + while ((curr->size & curr_size_resulting_boundary) == 0) + curr_size_resulting_boundary = + curr_size_resulting_boundary << 1; + + if (size_resulting_boundary >= + curr_size_resulting_boundary) + break; + } + last = curr; + curr = curr->next; + } + + if (last == NULL) { + /* Insert first in list */ + res->next = *root; + *root = res; + } else { + last->next = res; + res->next = curr; + } +} + +#ifdef DEBUG +void pci_res_list_print(struct pci_res *root) +{ + if (root == NULL) + return; + + printf("RESOURCE LIST:\n"); + while (root) { + printf(" SIZE: 0x%08x, BOUNDARY: 0x%08x\n", root->size, + root->boundary); + root = root->next; + } +} +#endif + +/* Reorder a size/alignment ordered resources list. The idea is to + * avoid unused due to alignment/size restriction. + * + * NOTE: The first element is always untouched. + * NOTE: If less than three elements in list, nothing will be done + * + * Normally a BAR has the same alignment requirements as the size of the + * BAR. However, when bridges are invloved the alignment need may be smaller + * that the size, because a bridge resource consist or multiple BARs. + * For example, say that a bridge with a 256Mb and a 16Mb BAR is found, then + * the alignment is required to be 256Mb but the size 256+16Mb. + * + * In order to minimize dead space on the bus, the bounadry ordered list + * is reordered, example: + * BUS0 + * | BUS1 + * |------------| + * | |-- BAR0: SIZE=256Mb, ALIGNMENT=256MB + * | |-- BAR1: SIZE=16Mb, ALIGNMENT=16MB + * | | + * | | + * | | + * | | BUS2 (BAR_BRIDGE1: SIZE=256+16, ALIGNEMENT=256) + * | |----------| + * | | |-- BAR2: SIZE=256Mb, ALIGNMENT=256Mb + * | | |-- BAR3: SIZE=16Mb, ALIGNMENT=16MB + * + * A alignement/boundary ordered list of BUS1 will look like: + * - BAR_BRIDGE1 + * - BAR0 (ALIGMENT NEED 256Mb) + * - BAR1 + * + * However, Between BAR_BRIDGE1 and BAR0 will be a unused hole of 256-16Mb. + * We can put BAR1 before BAR0 to avoid the problem. + */ +static void pci_res_reorder(struct pci_res *root) +{ + struct pci_res *curr, *last, *curr2, *last2; + unsigned int start, start_next, hole_size, hole_boundary; + + if (root == NULL) + return; + + /* Make up a start address with the boundary of the + * First element. + */ + start = root->boundary + root->size; + last = root; + curr = root->next; + while (curr) { + + /* Find start address of resource */ + start_next = (start + (curr->boundary - 1)) & + ~(curr->boundary - 1); + + /* Find hole size, the unsed space inbetween last resource + *and next */ + hole_size = start_next - start; + + /* Find Boundary of START */ + hole_boundary = 1; + while ((start & hole_boundary) == 0) + hole_boundary = hole_boundary<<1; + + /* Detect dead hole */ + if (hole_size > 0) { + /* Step through list and try to find a resource that + * can fit into hole. Take into account hole start + * boundary and hole size. + */ + last2 = curr; + curr2 = curr->next; + while (curr2) { + if ((curr2->boundary <= hole_boundary) && + (curr2->size <= hole_size)) { + /* Found matching resource. Move it + * first in the hole. Then rescan, now + * that the hole has changed in + * size/boundary. + */ + last2->next = curr2->next; + curr2->next = curr; + last->next = curr2; + + /* New Start address */ + start_next = (start + + (curr2->boundary - 1)) & + ~(curr2->boundary - 1); + /* Since we inserted the resource before + * curr we need to re-evaluate curr one + * more, more resources may fit into the + * shrunken hole. + */ + curr = curr2; + break; + } + last2 = curr2; + curr2 = curr2->next; + } + } + + /* No hole or nothing fitted into hole. */ + start = start_next; + + last = curr; + curr = curr->next; + } +} + +/* Find the total size required in PCI address space needed by a resource list*/ +static unsigned int pci_res_size(struct pci_res *root) +{ + struct pci_res *curr; + unsigned int size; + + /* Get total size of all resources */ + size = 0; + curr = root; + while (curr) { + size = (size + (curr->boundary - 1)) & ~(curr->boundary - 1); + size += curr->size; + curr = curr->next; + } + + return size; +} + +#if 0 /* not used for now */ +/* Free a device and secondary bus if device is a bridge */ +static void pci_dev_free(struct pci_dev *dev) +{ + struct pci_dev *subdev; + struct pci_bus *bus; + + if (dev->flags & PCI_DEV_BRIDGE) { + bus = (struct pci_bus *)dev; + for (subdev = bus->devs; subdev ; subdev = subdev->next) + pci_dev_free(dev); + } + + free(dev); +} +#endif + +static struct pci_dev *pci_dev_create(int isbus) +{ + void *ptr; + int size; + + if (isbus) + size = sizeof(struct pci_bus); + else + size = sizeof(struct pci_dev); + + ptr = malloc(size); + if (!ptr) + rtems_fatal_error_occurred(RTEMS_NO_MEMORY); + memset(ptr, 0, size); + return ptr; +} + +static void pci_find_devs(struct pci_bus *bus) +{ + uint32_t id, tmp; + uint8_t header; + int slot, func, fail; + struct pci_dev *dev, **listptr; + struct pci_bus *bridge; + pci_dev_t pcidev; + + DBG("Scanning bus %d\n", bus->num); + + listptr = &bus->devs; + for (slot = 0; slot < PCI_MAX_DEVICES; slot++) { + + /* Slot address */ + pcidev = PCI_DEV(bus->num, slot, 0); + + for (func = 0; func < PCI_MAX_FUNCTIONS; func++, pcidev++) { + + fail = PCI_CFG_R32(pcidev, PCI_VENDOR_ID, &id); + if (fail || id == 0xffffffff || id == 0) { + /* + * This slot is empty + */ + if (func == 0) + break; + else + continue; + } + + DBG("Found PCIDEV 0x%x at (bus %x, slot %x, func %x)\n", + id, bus, slot, func); + + /* Set command to reset values, it disables bus + * mastering and address responses. + */ + PCI_CFG_W16(pcidev, PCI_COMMAND, 0); + + /* Clear any already set status bits */ + PCI_CFG_W16(pcidev, PCI_STATUS, 0xf900); + + /* Set latency timer to 64 */ + PCI_CFG_W8(pcidev, PCI_LATENCY_TIMER, 64); + + PCI_CFG_R32(pcidev, PCI_CLASS_REVISION, &tmp); + tmp >>= 16; + dev = pci_dev_create(tmp == PCI_CLASS_BRIDGE_PCI); + *listptr = dev; + listptr = &dev->next; + + dev->busdevfun = pcidev; + dev->bus = bus; + PCI_CFG_R16(pcidev, PCI_VENDOR_ID, &dev->vendor); + PCI_CFG_R16(pcidev, PCI_DEVICE_ID, &dev->device); + PCI_CFG_R32(pcidev, PCI_CLASS_REVISION, &dev->classrev); + + if (tmp == PCI_CLASS_BRIDGE_PCI) { + DBG("Found PCI-PCI Bridge 0x%x at " + "(bus %x, slot %x, func %x)\n", + id, bus, slot, func); + dev->flags = PCI_DEV_BRIDGE; + dev->subvendor = 0; + dev->subdevice = 0; + bridge = (struct pci_bus *)dev; + bridge->num = bus->sord + 1; + bridge->pri = bus->num; + bridge->sord = bus->sord + 1; + + /* Configure bridge (no support for 64-bit) */ + PCI_CFG_W32(pcidev, 0x28, 0); + PCI_CFG_W32(pcidev, 0x2C, 0); + tmp = (64 << 24) | (0xff << 16) | + (bridge->num << 8) | bridge->pri; + PCI_CFG_W32(pcidev, PCI_PRIMARY_BUS, tmp); + + /* Scan Secondary Bus */ + pci_find_devs(bridge); + + /* sord might have been updated */ + PCI_CFG_W8(pcidev, 0x1a, bridge->sord); + bus->sord = bridge->sord; + + DBG("PCI-PCI BRIDGE: Primary %x, Secondary %x, " + "Subordinate %x\n", + bridge->pri, bridge->num, bridge->sord); + } else { + /* Disable Cardbus CIS Pointer */ + PCI_CFG_W32(pcidev, PCI_CARDBUS_CIS, 0); + + /* Devices have subsytem device and vendor ID */ + PCI_CFG_R16(pcidev, PCI_SUBSYSTEM_VENDOR_ID, + &dev->subvendor); + PCI_CFG_R16(pcidev, PCI_SUBSYSTEM_ID, + &dev->subdevice); + } + + /* Stop if not a multi-function device */ + if (func == 0) { + pci_cfg_r8(pcidev, PCI_HEADER_TYPE, &header); + if ((header & PCI_MULTI_FUNCTION) == 0) + break; + } + } + } +} + +static void pci_find_bar(struct pci_dev *dev, int bar) +{ + uint32_t size, disable, mask; + struct pci_res *res = &dev->resources[bar]; + pci_dev_t pcidev = dev->busdevfun; + int ofs; +#ifdef DEBUG + char *str; +#define DBG_SET_STR(str, val) str = (val) +#else +#define DBG_SET_STR(str, val) +#endif + + DBG("Bus: %x, Slot: %x, function: %x, bar%d\n", + PCI_DEV_EXPAND(pcidev), bar); + + res->bar = bar; + if (bar == DEV_RES_ROM) { + if (dev->flags & PCI_DEV_BRIDGE) + ofs = PCI_ROM_ADDRESS1; + else + ofs = PCI_ROM_ADDRESS; + disable = 0; /* ROM BARs have a unique enable bit per BAR */ + } else { + ofs = PCI_BASE_ADDRESS_0 + (bar << 2); + disable = pci_invalid_address; + } + + PCI_CFG_W32(pcidev, ofs, 0xffffffff); + PCI_CFG_R32(pcidev, ofs, &size); + PCI_CFG_W32(pcidev, ofs, disable); + + if (size == 0 || size == 0xffffffff) + return; + if (bar == DEV_RES_ROM) { + mask = PCI_ROM_ADDRESS_MASK; + DBG_SET_STR(str, "ROM"); + if (dev->bus->flags & PCI_BUS_MEM) + res->flags = PCI_RES_MEM; + else + res->flags = PCI_RES_MEMIO; + } else if (((size & 0x1) == 0) && (size & 0x6)) { + /* unsupported Memory type */ + PCI_CFG_W32(pcidev, ofs, 0); + return; + } else { + mask = ~0xf; + if (size & 0x1) { + /* I/O */ + mask = ~0x3; + res->flags = PCI_RES_IO; + DBG_SET_STR(str, "I/O"); + if (size & 0xffff0000) + res->flags |= PCI_RES_IO32; + /* Limit size of I/O space to 256 byte */ + size |= 0xffffff00; + if ((dev->bus->flags & PCI_BUS_IO) == 0) { + res->flags |= PCI_RES_FAIL; + dev->flags |= PCI_DEV_RES_FAIL; + } + } else { + /* Memory. We convert Prefetchable Memory BARs to Memory + * BARs in case the Bridge does not support prefetchable + * memory. + */ + if ((size & 0x8) && (dev->bus->flags & PCI_BUS_MEM)) { + /* Prefetchable and Bus supports it */ + res->flags = PCI_RES_MEM; + DBG_SET_STR(str, "MEM"); + } else { + res->flags = PCI_RES_MEMIO; + DBG_SET_STR(str, "MEMIO"); + } + } + } + size &= mask; + res->size = ~size + 1; + res->boundary = ~size + 1; + + DBG("Bus: %x, Slot: %x, function: %x, %s bar%d size: %x\n", + PCI_DEV_EXPAND(pcidev), str, bar, res->size); +} + +static int pci_find_res_dev(struct pci_dev *dev, void *unused) +{ + struct pci_bus *bridge; + uint32_t tmp; + uint16_t tmp16; + pci_dev_t pcidev = dev->busdevfun; + int i, maxbars; + + if (dev->flags & PCI_DEV_BRIDGE) { + /* PCI-PCI Bridge */ + bridge = (struct pci_bus *)dev; + + /* Only 2 Bridge BARs */ + maxbars = 2; + + /* Probe Bridge Spaces (MEMIO space always implemented), the + * probe disables all space-decoding at the same time + */ + PCI_CFG_W32(pcidev, 0x30, 0); + PCI_CFG_W16(pcidev, 0x1c, 0x00f0); + PCI_CFG_R16(pcidev, 0x1c, &tmp16); + if (tmp16 != 0) { + bridge->flags |= PCI_BUS_IO; + if (tmp16 & 0x1) + bridge->flags |= PCI_BUS_IO32; + } + + PCI_CFG_W32(pcidev, 0x24, 0x0000ffff); + PCI_CFG_R32(pcidev, 0x24, &tmp); + if (tmp != 0) + bridge->flags |= PCI_BUS_MEM; + + PCI_CFG_W32(pcidev, 0x20, 0x0000ffff); + bridge->flags |= PCI_BUS_MEMIO; + } else { + /* Normal PCI Device as max 6 BARs */ + maxbars = 6; + } + + /* Probe BARs */ + for (i = 0; i < maxbars; i++) + pci_find_bar(dev, i); + pci_find_bar(dev, DEV_RES_ROM); + + return 0; +} + +static int pci_add_res_dev(struct pci_dev *dev, void *arg); + +static void pci_add_res_bus(struct pci_bus *bus, int type) +{ + int tindex = type - 1; + + /* Clear old resources */ + bus->busres[tindex] = NULL; + + /* Add resources of devices behind bridge if bridge supports + * resource type. If MEM space not supported by bridge, they are + * converted to MEMIO in the process. + */ + if (!((type == PCI_BUS_IO) && ((bus->flags & PCI_BUS_IO) == 0))) { + pci_for_each_child(bus, pci_add_res_dev, (void *)type, 0); + + /* Reorder Bus resources to fit more optimally (avoid dead + * PCI space). Currently they are sorted by boundary and size. + * + * This is especially important when multiple buses (bridges) + * are present. + */ + pci_res_reorder(bus->busres[tindex]); + } +} + +static int pci_add_res_dev(struct pci_dev *dev, void *arg) +{ + int tindex, type = (int)arg; + struct pci_bus *bridge; + struct pci_res *res, *first_busres; + int i; + uint32_t bbound; + + /* Type index in Bus resource */ + tindex = type - 1; + + if (dev->flags & PCI_DEV_BRIDGE) { + /* PCI-PCI Bridge. Add all sub-bus resources first */ + bridge = (struct pci_bus *)dev; + + /* Add all child device's resources to this type */ + pci_add_res_bus(bridge, type); + + /* Propagate the resources from child bus to BAR on + * this bus, by adding a "fake" BAR per type. + */ + res = &bridge->dev.resources[BUS_RES_START + tindex]; + res->bar = BUS_RES_START + tindex; + res->start = 0; + res->end = 0; + res->flags = 0; /* mark BAR resource not available */ + first_busres = bridge->busres[tindex]; + if (first_busres) { + res->flags = type; + res->size = pci_res_size(first_busres); + res->boundary = first_busres->boundary; + if (type == PCI_RES_IO) { + bbound = 0x1000; /* Bridge I/O min 4KB */ + } else { + bbound = 0x100000; /* Bridge MEM min 1MB */ + + /* Convert MEM to MEMIO if not supported by + * this bridge + */ + if ((bridge->flags & PCI_BUS_MEM) == 0) + res->flags = PCI_RES_MEMIO; + } + /* Fulfil minimum bridge boundary */ + if (res->boundary < bbound) + res->boundary = bbound; + /* Make sure that size is atleast bridge boundary */ + if (res->size > bbound && (res->size & (bbound-1))) + res->size = (res->size | (bbound-1)) + 1; + } + } + + /* Normal PCI Device as max 6 BARs and a ROM Bar. + * Insert BARs into the sorted resource list. + */ + for (i = 0; i < DEV_RES_CNT; i++) { + res = &dev->resources[i]; + if ((res->flags & PCI_RES_TYPE_MASK) != type) + continue; + pci_res_insert(&dev->bus->busres[tindex], res); + } + + return 0; +} + +/* Function assumes that base is properly aligned to the requirement of the + * largest BAR in the system. + */ +static uint32_t pci_alloc_res(struct pci_bus *bus, int type, + uint32_t start, uint32_t end) +{ + struct pci_dev *dev; + struct pci_res *res, **prev_next; + unsigned long starttmp; + struct pci_bus *bridge; + int removed, sec_type; + + /* The resources are sorted on their size (size and alignment is the + * same) + */ + prev_next = &bus->busres[type - 1]; + while ((res = *prev_next) != NULL) { + + dev = RES2DEV(res); + removed = 0; + + /* Align start to this reource's need, only needed after + * a bridge resource has been allocated. + */ + starttmp = (start + (res->boundary-1)) & ~(res->boundary-1); + + if ((starttmp + res->size - 1) > end) { + /* Not enough memory available for this resource */ + printk("PCI[%x:%x:%x]: DEV BAR%d (%d): no resource " + "assigned\n", + PCI_DEV_EXPAND(dev->busdevfun), + res->bar, res->flags & PCI_RES_TYPE_MASK); + res->start = res->end = 0; + + /* If this resources is a bridge window to the + * secondary bus, the secondary resources are not + * changed which has the following effect: + * I/O : Will never be assigned + * MEMIO : Will never be assigned + * MEM : Will stay marked as MEM, but bridge window + * is changed into MEMIO, when the window is + * assigned a MEMIO address the secondary + * resources will also be assigned. + */ + + if (type == PCI_RES_MEM) { + /* Try prefetchable as non-prefetchable mem */ + res->flags &= ~PCI_RES_MEM_PREFETCH; + /* Remove resource from MEM list, ideally we + * should regenerate this list in order to fit + * the comming BARs more optimially... + */ + *prev_next = res->next; + /* We should not update prev_next here since + * we just removed the resource from the list + */ + removed = 1; + } else { + res->flags |= PCI_RES_FAIL; + dev->flags |= PCI_DEV_RES_FAIL; + } + } else { + start = starttmp; + + res->start = start; + res->end = start + res->size; + + /* "Virtual BAR" on a bridge? A bridge resource need all + * its child devices resources allocated + */ + if ((res->bar != DEV_RES_ROM) && + (dev->flags & PCI_DEV_BRIDGE) && + (res->bar >= BUS_RES_START)) { + bridge = (struct pci_bus *)dev; + /* If MEM bar was changed into a MEMIO the + * secondary MEM resources are still set to MEM, + */ + if (type == PCI_BUS_MEMIO && + res->bar == BRIDGE_RES_MEM) + sec_type = PCI_RES_MEM; + else + sec_type = type; + + pci_alloc_res(bridge, sec_type, res->start, + res->end); + } + + start += res->size; + } + if (removed == 0) + prev_next = &res->next; + } + + return start; +} + +static void pci_set_bar(struct pci_dev *dev, int residx) +{ + uint32_t tmp; + uint16_t tmp16; + pci_dev_t pcidev; + struct pci_res *res; + int is_bridge, ofs; + + res = &dev->resources[residx]; + pcidev = dev->busdevfun; + + if ((res->flags == 0) || (res->flags & PCI_RES_FAIL)) + return; + + is_bridge = dev->flags & PCI_DEV_BRIDGE; + + if (res->bar == DEV_RES_ROM) { + /* ROM: 32-bit prefetchable memory BAR */ + if (is_bridge) + ofs = PCI_ROM_ADDRESS1; + else + ofs = PCI_ROM_ADDRESS; + PCI_CFG_W32(pcidev, ofs, res->start | PCI_ROM_ADDRESS_ENABLE); + DBG("PCI[%x:%x:%x]: ROM BAR: 0x%x-0x%x\n", + PCI_DEV_EXPAND(pcidev), res->start, res->end); + } else if (is_bridge && (res->bar == BRIDGE_RES_IO)) { + /* PCI Bridge I/O BAR */ + DBG("PCI[%x:%x:%x]: BAR 1C: 0x%x-0x%x\n", + PCI_DEV_EXPAND(pcidev), res->start, res->end); + + /* Limit and Base */ + tmp16 = ((res->end-1) & 0x0000f000) | + ((res->start & 0x0000f000) >> 8); + tmp = ((res->end-1) & 0xffff0000) | (res->start >> 16); + + DBG("PCI[%x:%x:%x]: BRIDGE BAR 0x%x: 0x%08x [0x30: 0x%x]\n", + PCI_DEV_EXPAND(pcidev), 0x1C, tmp, tmp2); + PCI_CFG_W16(pcidev, 0x1C, tmp16); + PCI_CFG_W32(pcidev, 0x30, tmp); + } else if (is_bridge && (res->bar >= BRIDGE_RES_MEMIO)) { + /* PCI Bridge MEM and MEMIO Space */ + + /* Limit and Base */ + tmp = ((res->end-1) & 0xfff00000) | (res->start >> 16); + + DBG("PCI[%x:%x:%x]: BRIDGE BAR 0x%x: 0x%08x\n", + PCI_DEV_EXPAND(pcidev), + 0x20 + (res->bar-BRIDGE_RES_MEMIO)*4, tmp); + PCI_CFG_W32(pcidev, 0x20+(res->bar-BRIDGE_RES_MEMIO)*4, tmp); + } else { + /* PCI Device */ + DBG("PCI[%x:%x:%x]: DEV BAR%d: 0x%08x\n", + PCI_DEV_EXPAND(pcidev), res->bar, res->start); + ofs = PCI_BASE_ADDRESS_0 + res->bar*4; + PCI_CFG_W32(pcidev, ofs, res->start); + } + + /* Enable Memory or I/O responses */ + if ((res->flags & PCI_RES_TYPE_MASK) == PCI_RES_IO) + pci_io_enable(pcidev); + else + pci_mem_enable(pcidev); + + /* Enable Master if bridge */ + if (is_bridge) + pci_master_enable(pcidev); +} + +static int pci_set_res_dev(struct pci_dev *dev, void *unused) +{ + int i, maxbars; + + if (dev->flags & PCI_DEV_BRIDGE) + maxbars = 2 + 3; /* 2 BARs + 3 Bridge-Windows "Virtual BARs" */ + else + maxbars = 6; /* Normal PCI Device as max 6 BARs. */ + + /* Set BAR resources with previous allocated values */ + for (i = 0; i < maxbars; i++) + pci_set_bar(dev, i); + pci_set_bar(dev, DEV_RES_ROM); + + return 0; +} + +/* Route IRQ through PCI-PCI Bridges */ +static int pci_route_irq(pci_dev_t dev, int irq_pin) +{ + int slot_grp; + + if (PCI_DEV_BUS(dev) == 0) + return irq_pin; + + slot_grp = PCI_DEV_SLOT(dev) & 0x3; + + return (((irq_pin - 1) + slot_grp) & 0x3) + 1; +} + +/* Put assigned system IRQ into PCI interrupt line information field. + * This is to make it possible for drivers to read system IRQ / Vector from + * configuration space later on. + * + * 1. Get Interrupt PIN + * 2. Route PIN to host bridge + * 3. Get System interrupt number assignment for PIN + * 4. Set Interrupt LINE + */ +static int pci_set_irq_dev(struct pci_dev *dev, void *cfg) +{ + struct pci_auto_setup *autocfg = cfg; + uint8_t irq_pin, irq_line, *psysirq; + pci_dev_t pcidev; + + psysirq = &dev->sysirq; + pcidev = dev->busdevfun; + PCI_CFG_R8(pcidev, PCI_INTERRUPT_PIN, &irq_pin); + + /* perform IRQ routing until we reach host bridge */ + while (dev->bus && irq_pin != 0) { + irq_pin = autocfg->irq_route(dev->busdevfun, irq_pin); + dev = &dev->bus->dev; + } + + /* Get IRQ from PIN on PCI bus0 */ + if (irq_pin != 0 && autocfg->irq_map) + irq_line = autocfg->irq_map(dev->busdevfun, irq_pin); + else + irq_line = 0; + + *psysirq = irq_line; + + /* Set System Interrupt/Vector for device. 0 means no-IRQ */ + PCI_CFG_W8(pcidev, PCI_INTERRUPT_LINE, irq_line); + + return 0; +} + +/* This routine assumes that PCI access library has been successfully + * initialized. All information about the PCI bus needed is found in + * the argument. + * + * The PCI buses are enumerated as bridges are found, PCI devices are + * setup with BARs and IRQs, etc. + */ +int pci_config_auto(void) +{ + uint32_t end; + uint32_t startmemio, startmem, startio; + struct pci_auto_setup *autocfg = &pci_auto_cfg; +#ifdef DEBUG + uint32_t endmemio, endmem, endio; + uint32_t start; +#endif + + if (pci_config_auto_initialized == 0) + return -1; /* no config given to library */ + +#ifdef DEBUG + DBG("\n--- PCI MEMORY AVAILABLE ---\n"); + if (autocfg->mem_size) { + start = autocfg->mem_start; + end = autocfg->mem_start + autocfg->mem_size - 1; + DBG(" MEM AVAIL [0x%08x-0x%08x]\n", start, end); + } else { + /* One big memory space */ + DBG(" MEM share the space with MEMIO\n"); + } + /* no-prefetchable memory space need separate memory space. + * For example PCI controller maps this region non-cachable. + */ + start = autocfg->memio_start; + end = autocfg->memio_start + autocfg->memio_size - 1; + DBG(" MEMIO AVAIL [0x%08x-0x%08x]\n", start, end); + if (autocfg->io_size) { + start = autocfg->io_start; + end = autocfg->io_start + autocfg->io_size - 1; + DBG(" I/O AVAIL [0x%08x-0x%08x]\n", start, end); + } else { + DBG(" I/O Space not available\n"); + } +#endif + + /* Init Host-Bridge */ + memset(&pci_hb, 0, sizeof(pci_hb)); + pci_hb.dev.flags = PCI_DEV_BRIDGE; + if (autocfg->memio_size <= 0) + return -1; + pci_hb.flags = PCI_BUS_MEMIO; + if (autocfg->mem_size) + pci_hb.flags |= PCI_BUS_MEM; + if (autocfg->io_size) + pci_hb.flags |= PCI_BUS_IO; + + /* Find all PCI devices/functions on all buses. The buses will be + * enumrated (assigned a unique PCI Bus ID 0..255). + */ + DBG("\n--- PCI SCANNING ---\n"); + pci_find_devs(&pci_hb); + pci_bus_cnt = pci_hb.sord + 1; + if (pci_hb.devs == NULL) + return 0; + + pci_system_type = PCI_SYSTEM_HOST; + + /* Find all resources (MEM/MEMIO/IO BARs) of all devices/functions + * on all buses. + * + * Device resources behind bridges which does not support prefetchable + * memory are already marked as non-prefetchable memory. + * Devices which as I/O resources behind a bridge that do not support + * I/O space are marked DISABLED. + * + * All BARs and Bridge Spaces are disabled after this. Only the ones + * that are allocated an address are initilized later on. + */ + DBG("\n\n--- PCI RESOURCES ---\n"); + pci_for_each_dev(pci_find_res_dev, 0); + + /* Add all device's resources to bus and sort them to fit in the PCI + * Window. The device resources are propagated upwards through bridges + * by adding a "virtual" BAR (boundary != BAR size). + * + * We wait with MEMIO (non-prefetchable memory) resources to after MEM + * resources have been allocated, so that MEM resources can be changed + * into MEMIO resources if not enough space. + */ + pci_add_res_bus(&pci_hb, PCI_RES_IO); + pci_add_res_bus(&pci_hb, PCI_RES_MEM); + + /* Start assigning found resource according to the sorted order. */ + + /* Allocate resources to I/O areas */ + if (pci_hb.busres[BUS_RES_IO]) { + startio = autocfg->io_start; + end = startio + autocfg->io_size; +#ifdef DEBUG + endio = +#endif + pci_alloc_res(&pci_hb, PCI_RES_IO, startio, end); + } + + /* Allocate resources to prefetchable memory */ + if (pci_hb.busres[BUS_RES_MEM]) { + startmem = autocfg->mem_start; + end = startmem + autocfg->mem_size; +#ifdef DEBUG + endmem = +#endif + pci_alloc_res(&pci_hb, PCI_RES_MEM, startmem, end); + } + + /* Add non-prefetchable memory resources and not fitting prefetchable + * memory resources. + * + * Some prefetchable memory resources may not have fitted into PCI + * window. Prefetchable memory can be mapped into non-prefetchable + * memory window. The failing BARs have been marked as MEMIO instead. + */ + pci_add_res_bus(&pci_hb, PCI_RES_MEMIO); + + /* Allocate resources to non-prefetchable memory */ + if (pci_hb.busres[BUS_RES_MEMIO]) { + startmemio = autocfg->memio_start; + end = startmemio + autocfg->memio_size; +#ifdef DEBUG + endmemio = +#endif + pci_alloc_res(&pci_hb, PCI_RES_MEMIO, startmemio, end); + } + + DBG("\n--- PCI ALLOCATED SPACE RANGES ---\n"); + DBG(" MEM NON-PREFETCHABLE: [0x%08x-0x%08x]\n", startmemio, endmemio); + DBG(" MEM PREFETCHABLE: [0x%08x-0x%08x]\n", startmem, endmem); + DBG(" I/O: [0x%08x-0x%08x]\n", startio, endio); + + /* Set all allocated BARs and Bridge Windows */ + pci_for_each_dev(pci_set_res_dev, NULL); + + /* Initialize IRQs of all devices. According to the PCI-PCI bridge + * specification the IRQs are routed differently depending on slot + * number. Drivers can override the default routing if a motherboard + * requires it. + */ + if ((autocfg->options & CFGOPT_NOSETUP_IRQ) == 0) { + if (autocfg->irq_route == NULL) /* use standard irq routing */ + autocfg->irq_route = pci_route_irq; + pci_for_each_dev(pci_set_irq_dev, autocfg); + } + + DBG("PCI resource allocation done\n"); + + return 0; +} + +void pci_config_auto_register(void *config) +{ + pci_config_auto_initialized = 1; + memcpy(&pci_auto_cfg, config, sizeof(struct pci_auto_setup)); +} |