forked from Imagelibrary/rtems
The refactoring of pci_dev_create() was incorrect since the code relied on different defines before including pci/cfg.h. This reverts back to the original code having two pci_dev_create() one in auto and one in read library. confdefs.h selectes between the two libraries so both there is no link conflict. Updates #3029
1007 lines
27 KiB
C
1007 lines
27 KiB
C
/* 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.org/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>
|
|
|
|
#include "pci_internal.h"
|
|
|
|
/* #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)
|
|
|
|
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 than 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 involved the alignment need may be smaller
|
|
* than 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 boundary 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 alignment/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 in between 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 fit 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 = calloc(1, size);
|
|
if (!ptr)
|
|
rtems_fatal_error_occurred(RTEMS_NO_MEMORY);
|
|
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_SLOTMAX; slot++) {
|
|
|
|
/* Slot address */
|
|
pcidev = PCI_DEV(bus->num, slot, 0);
|
|
|
|
for (func = 0; func <= PCI_FUNCMAX; func++, pcidev++) {
|
|
|
|
fail = PCI_CFG_R32(pcidev, PCIR_VENDOR, &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, PCIR_COMMAND, 0);
|
|
|
|
/* Clear any already set status bits */
|
|
PCI_CFG_W16(pcidev, PCIR_STATUS, 0xf900);
|
|
|
|
/* Set latency timer to 64 */
|
|
PCI_CFG_W8(pcidev, PCIR_LATTIMER, 64);
|
|
|
|
PCI_CFG_R32(pcidev, PCIR_REVID, &tmp);
|
|
tmp >>= 16;
|
|
dev = pci_dev_create(tmp == PCID_PCI2PCI_BRIDGE);
|
|
*listptr = dev;
|
|
listptr = &dev->next;
|
|
|
|
dev->busdevfun = pcidev;
|
|
dev->bus = bus;
|
|
PCI_CFG_R16(pcidev, PCIR_VENDOR, &dev->vendor);
|
|
PCI_CFG_R16(pcidev, PCIR_DEVICE, &dev->device);
|
|
PCI_CFG_R32(pcidev, PCIR_REVID, &dev->classrev);
|
|
|
|
if (tmp == PCID_PCI2PCI_BRIDGE) {
|
|
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, PCIR_PRIBUS_1, 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, PCIR_CIS, 0);
|
|
|
|
/* Devices have subsytem device and vendor ID */
|
|
PCI_CFG_R16(pcidev, PCIR_SUBVEND_0,
|
|
&dev->subvendor);
|
|
PCI_CFG_R16(pcidev, PCIR_SUBDEV_0,
|
|
&dev->subdevice);
|
|
}
|
|
|
|
/* Stop if not a multi-function device */
|
|
if (func == 0) {
|
|
pci_cfg_r8(pcidev, PCIR_HDRTYPE, &header);
|
|
if ((header & PCIM_MFDEV) == 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 = PCIR_BIOS_1;
|
|
else
|
|
ofs = PCIR_BIOS;
|
|
disable = 0; /* ROM BARs have a unique enable bit per BAR */
|
|
} else {
|
|
ofs = PCIR_BAR(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 = PCIM_BIOS_ADDR_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 = PCIR_BIOS_1;
|
|
else
|
|
ofs = PCIR_BIOS;
|
|
PCI_CFG_W32(pcidev, ofs, res->start | PCIM_BIOS_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 = PCIR_BAR(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, PCIR_INTPIN, &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, PCIR_INTLINE, 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 pci_auto_cfg structure passed on by pci_config_register().
|
|
*
|
|
* 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));
|
|
}
|