summaryrefslogtreecommitdiffstats
path: root/cpukit/libpci/pci_cfg_auto.c
diff options
context:
space:
mode:
authorDaniel Hellstrom <daniel@gaisler.com>2011-11-28 10:11:10 +0100
committerDaniel Hellstrom <daniel@gaisler.com>2015-04-17 01:10:15 +0200
commita31845f7f9b4770cf9ddd8b6820641d2f4f4c1da (patch)
tree0d7f215ec45d7c4cf6f1293af72ece2fbde1ddc3 /cpukit/libpci/pci_cfg_auto.c
parentleon3,ngmp: simplify cpucounter initialization (diff)
downloadrtems-a31845f7f9b4770cf9ddd8b6820641d2f4f4c1da.tar.bz2
LIBPCI: added PCI layer to cpukit/libpci
Diffstat (limited to 'cpukit/libpci/pci_cfg_auto.c')
-rw-r--r--cpukit/libpci/pci_cfg_auto.c1014
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));
+}