2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-23 04:34:11 +08:00
linux-next/drivers/pci/setup-res.c

341 lines
8.3 KiB
C
Raw Normal View History

/*
* drivers/pci/setup-res.c
*
* Extruded from code written by
* Dave Rusling (david.rusling@reo.mts.dec.com)
* David Mosberger (davidm@cs.arizona.edu)
* David Miller (davem@redhat.com)
*
* Support routines for initializing a PCI subsystem.
*/
/* fixed for multiple pci buses, 1999 Andrea Arcangeli <andrea@suse.de> */
/*
* Nov 2000, Ivan Kokshaysky <ink@jurassic.park.msu.ru>
* Resource sorting
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/cache.h>
#include <linux/slab.h>
#include "pci.h"
void pci_update_resource(struct pci_dev *dev, int resno)
{
struct pci_bus_region region;
u32 new, check, mask;
int reg;
enum pci_bar_type type;
struct resource *res = dev->resource + resno;
/*
* Ignore resources for unimplemented BARs and unused resource slots
* for 64 bit BARs.
*/
if (!res->flags)
return;
/*
* Ignore non-moveable resources. This might be legacy resources for
* which no functional BAR register exists or another important
* system resource we shouldn't move around.
*/
if (res->flags & IORESOURCE_PCI_FIXED)
return;
pcibios_resource_to_bus(dev, &region, res);
new = region.start | (res->flags & PCI_REGION_FLAG_MASK);
if (res->flags & IORESOURCE_IO)
mask = (u32)PCI_BASE_ADDRESS_IO_MASK;
else
mask = (u32)PCI_BASE_ADDRESS_MEM_MASK;
reg = pci_resource_bar(dev, resno, &type);
if (!reg)
return;
if (type != pci_bar_unknown) {
if (!(res->flags & IORESOURCE_ROM_ENABLE))
return;
new |= PCI_ROM_ADDRESS_ENABLE;
}
pci_write_config_dword(dev, reg, new);
pci_read_config_dword(dev, reg, &check);
if ((new ^ check) & mask) {
dev_err(&dev->dev, "BAR %d: error updating (%#08x != %#08x)\n",
resno, new, check);
}
if (res->flags & IORESOURCE_MEM_64) {
new = region.start >> 16 >> 16;
pci_write_config_dword(dev, reg + 4, new);
pci_read_config_dword(dev, reg + 4, &check);
if (check != new) {
dev_err(&dev->dev, "BAR %d: error updating "
"(high %#08x != %#08x)\n", resno, new, check);
}
}
res->flags &= ~IORESOURCE_UNSET;
dev_dbg(&dev->dev, "BAR %d: set to %pR (PCI address [%#llx-%#llx])\n",
resno, res, (unsigned long long)region.start,
(unsigned long long)region.end);
}
pci: do not mark exported functions as __devinit Functions marked __devinit will be removed after kernel init. But being exported they are potentially called by a module much later. So the safer choice seems to be to keep the function even in the non CONFIG_HOTPLUG case. This silence the follwoing section mismatch warnings: WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_bus_add_device from __ksymtab_gpl between '__ksymtab_pci_bus_add_device' (at offset 0x20) and '__ksymtab_pci_walk_bus' WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_create_bus from __ksymtab_gpl between '__ksymtab_pci_create_bus' (at offset 0x40) and '__ksymtab_pci_stop_bus_device' WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_bus_max_busnr from __ksymtab_gpl between '__ksymtab_pci_bus_max_busnr' (at offset 0xc0) and '__ksymtab_pci_assign_resource_fixed' WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_claim_resource from __ksymtab_gpl between '__ksymtab_pci_claim_resource' (at offset 0xe0) and '__ksymtab_pcie_port_bus_type' WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_bus_add_devices from __ksymtab between '__ksymtab_pci_bus_add_devices' (at offset 0x70) and '__ksymtab_pci_bus_alloc_resource' WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_scan_bus_parented from __ksymtab between '__ksymtab_pci_scan_bus_parented' (at offset 0x90) and '__ksymtab_pci_root_buses' WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_bus_assign_resources from __ksymtab between '__ksymtab_pci_bus_assign_resources' (at offset 0x4d0) and '__ksymtab_pci_bus_size_bridges' WARNING: drivers/built-in.o - Section mismatch: reference to .init.text:pci_bus_size_bridges from __ksymtab between '__ksymtab_pci_bus_size_bridges' (at offset 0x4e0) and '__ksymtab_pci_setup_cardbus' Signed-off-by: Sam Ravnborg <sam@ravnborg.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2007-03-27 13:53:30 +08:00
int pci_claim_resource(struct pci_dev *dev, int resource)
{
struct resource *res = &dev->resource[resource];
struct resource *root, *conflict;
root = pci_find_parent_resource(dev, res);
if (!root) {
dev_info(&dev->dev, "no compatible bridge window for %pR\n",
res);
return -EINVAL;
}
conflict = request_resource_conflict(root, res);
if (conflict) {
dev_info(&dev->dev,
"address space collision: %pR conflicts with %s %pR\n",
res, conflict->name, conflict);
return -EBUSY;
}
return 0;
}
EXPORT_SYMBOL(pci_claim_resource);
2009-03-16 16:13:39 +08:00
void pci_disable_bridge_window(struct pci_dev *dev)
{
dev_info(&dev->dev, "disabling bridge mem windows\n");
2009-03-16 16:13:39 +08:00
/* MMIO Base/Limit */
pci_write_config_dword(dev, PCI_MEMORY_BASE, 0x0000fff0);
/* Prefetchable MMIO Base/Limit */
pci_write_config_dword(dev, PCI_PREF_LIMIT_UPPER32, 0);
pci_write_config_dword(dev, PCI_PREF_MEMORY_BASE, 0x0000fff0);
pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, 0xffffffff);
}
static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev,
int resno, resource_size_t size, resource_size_t align)
{
struct resource *res = dev->resource + resno;
resource_size_t min;
int ret;
min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM;
/* First, try exact prefetching match.. */
ret = pci_bus_alloc_resource(bus, res, size, align, min,
IORESOURCE_PREFETCH,
pcibios_align_resource, dev);
if (ret < 0 && (res->flags & IORESOURCE_PREFETCH)) {
/*
* That failed.
*
* But a prefetching area can handle a non-prefetching
* window (it will just not perform as well).
*/
ret = pci_bus_alloc_resource(bus, res, size, align, min, 0,
pcibios_align_resource, dev);
}
return ret;
}
/*
* Generic function that returns a value indicating that the device's
* original BIOS BAR address was not saved and so is not available for
* reinstatement.
*
* Can be over-ridden by architecture specific code that implements
* reinstatement functionality rather than leaving it disabled when
* normal allocation attempts fail.
*/
resource_size_t __weak pcibios_retrieve_fw_addr(struct pci_dev *dev, int idx)
{
return 0;
}
static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev,
int resno, resource_size_t size)
{
struct resource *root, *conflict;
resource_size_t fw_addr, start, end;
int ret = 0;
fw_addr = pcibios_retrieve_fw_addr(dev, resno);
if (!fw_addr)
return 1;
start = res->start;
end = res->end;
res->start = fw_addr;
res->end = res->start + size - 1;
root = pci_find_parent_resource(dev, res);
if (!root) {
if (res->flags & IORESOURCE_IO)
root = &ioport_resource;
else
root = &iomem_resource;
}
dev_info(&dev->dev, "BAR %d: trying firmware assignment %pR\n",
resno, res);
conflict = request_resource_conflict(root, res);
if (conflict) {
dev_info(&dev->dev,
"BAR %d: %pR conflicts with %s %pR\n", resno,
res, conflict->name, conflict);
res->start = start;
res->end = end;
ret = 1;
}
return ret;
}
static int _pci_assign_resource(struct pci_dev *dev, int resno, int size, resource_size_t min_align)
{
struct resource *res = dev->resource + resno;
struct pci_bus *bus;
int ret;
char *type;
bus = dev->bus;
while ((ret = __pci_assign_resource(bus, dev, resno, size, min_align))) {
if (!bus->parent || !bus->self->transparent)
break;
bus = bus->parent;
}
if (ret) {
if (res->flags & IORESOURCE_MEM)
if (res->flags & IORESOURCE_PREFETCH)
type = "mem pref";
else
type = "mem";
else if (res->flags & IORESOURCE_IO)
type = "io";
else
type = "unknown";
dev_info(&dev->dev,
"BAR %d: can't assign %s (size %#llx)\n",
resno, type, (unsigned long long) resource_size(res));
}
return ret;
}
int pci_reassign_resource(struct pci_dev *dev, int resno, resource_size_t addsize,
resource_size_t min_align)
{
struct resource *res = dev->resource + resno;
resource_size_t new_size;
int ret;
if (!res->parent) {
dev_info(&dev->dev, "BAR %d: can't reassign an unassigned resource %pR "
"\n", resno, res);
return -EINVAL;
}
PCI : Calculate right add_size During debug of one SRIOV enabled hotplug device, we found found that add_size is not passed properly. The device has devices under two level bridges: +-[0000:80]-+-00.0-[81-8f]-- | +-01.0-[90-9f]-- | +-02.0-[a0-af]----00.0-[a1-a3]--+-02.0-[a2]--+-00.0 Oracle Corporation Device | | \-03.0-[a3]--+-00.0 Oracle Corporation Device Which means later the parent bridge will not try to add a big enough range: [ 557.455077] pci 0000:a0:00.0: BAR 14: assigned [mem 0xf9000000-0xf93fffff] [ 557.461974] pci 0000:a0:00.0: BAR 15: assigned [mem 0xf6000000-0xf61fffff pref] [ 557.469340] pci 0000:a1:02.0: BAR 14: assigned [mem 0xf9000000-0xf91fffff] [ 557.476231] pci 0000:a1:02.0: BAR 15: assigned [mem 0xf6000000-0xf60fffff pref] [ 557.483582] pci 0000:a1:03.0: BAR 14: assigned [mem 0xf9200000-0xf93fffff] [ 557.490468] pci 0000:a1:03.0: BAR 15: assigned [mem 0xf6100000-0xf61fffff pref] [ 557.497833] pci 0000:a1:03.0: BAR 14: can't assign mem (size 0x200000) [ 557.504378] pci 0000:a1:03.0: failed to add optional resources res=[mem 0xf9200000-0xf93fffff] [ 557.513026] pci 0000:a1:02.0: BAR 14: can't assign mem (size 0x200000) [ 557.519578] pci 0000:a1:02.0: failed to add optional resources res=[mem 0xf9000000-0xf91fffff] It turns out we did not calculate size1 properly. static resource_size_t calculate_memsize(resource_size_t size, resource_size_t min_size, resource_size_t size1, resource_size_t old_size, resource_size_t align) { if (size < min_size) size = min_size; if (old_size == 1 ) old_size = 0; if (size < old_size) size = old_size; size = ALIGN(size + size1, align); return size; } We should not pass add_size with min_size in calculate_memsize since that will make add_size not contribute final add_size. So just pass add_size with size1 to calculate_memsize(). With this change, we should have chance to remove extra addon in pci_reassign_resource. Signed-off-by: Yinghai Lu <yinghai@kernel.org> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
2012-01-21 18:08:17 +08:00
/* already aligned with min_align */
new_size = resource_size(res) + addsize;
ret = _pci_assign_resource(dev, resno, new_size, min_align);
if (!ret) {
PCI: clean up resource alignment management Done per Linus' request and suggestions. Linus has explained that better than I'll be able to explain: On Thu, Mar 27, 2008 at 10:12:10AM -0700, Linus Torvalds wrote: > Actually, before we go any further, there might be a less intrusive > alternative: add just a couple of flags to the resource flags field (we > still have something like 8 unused bits on 32-bit), and use those to > implement a generic "resource_alignment()" routine. > > Two flags would do it: > > - IORESOURCE_SIZEALIGN: size indicates alignment (regular PCI device > resources) > > - IORESOURCE_STARTALIGN: start field is alignment (PCI bus resources > during probing) > > and then the case of both flags zero (or both bits set) would actually be > "invalid", and we would also clear the IORESOURCE_STARTALIGN flag when we > actually allocate the resource (so that we don't use the "start" field as > alignment incorrectly when it no longer indicates alignment). > > That wouldn't be totally generic, but it would have the nice property of > automatically at least add sanity checking for that whole "res->start has > the odd meaning of 'alignment' during probing" and remove the need for a > new field, and it would allow us to have a generic "resource_alignment()" > routine that just gets a resource pointer. Besides, I removed IORESOURCE_BUS_HAS_VGA flag which was unused for ages. Signed-off-by: Ivan Kokshaysky <ink@jurassic.park.msu.ru> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Gary Hade <garyhade@us.ibm.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2008-03-30 23:50:14 +08:00
res->flags &= ~IORESOURCE_STARTALIGN;
PCI : Calculate right add_size During debug of one SRIOV enabled hotplug device, we found found that add_size is not passed properly. The device has devices under two level bridges: +-[0000:80]-+-00.0-[81-8f]-- | +-01.0-[90-9f]-- | +-02.0-[a0-af]----00.0-[a1-a3]--+-02.0-[a2]--+-00.0 Oracle Corporation Device | | \-03.0-[a3]--+-00.0 Oracle Corporation Device Which means later the parent bridge will not try to add a big enough range: [ 557.455077] pci 0000:a0:00.0: BAR 14: assigned [mem 0xf9000000-0xf93fffff] [ 557.461974] pci 0000:a0:00.0: BAR 15: assigned [mem 0xf6000000-0xf61fffff pref] [ 557.469340] pci 0000:a1:02.0: BAR 14: assigned [mem 0xf9000000-0xf91fffff] [ 557.476231] pci 0000:a1:02.0: BAR 15: assigned [mem 0xf6000000-0xf60fffff pref] [ 557.483582] pci 0000:a1:03.0: BAR 14: assigned [mem 0xf9200000-0xf93fffff] [ 557.490468] pci 0000:a1:03.0: BAR 15: assigned [mem 0xf6100000-0xf61fffff pref] [ 557.497833] pci 0000:a1:03.0: BAR 14: can't assign mem (size 0x200000) [ 557.504378] pci 0000:a1:03.0: failed to add optional resources res=[mem 0xf9200000-0xf93fffff] [ 557.513026] pci 0000:a1:02.0: BAR 14: can't assign mem (size 0x200000) [ 557.519578] pci 0000:a1:02.0: failed to add optional resources res=[mem 0xf9000000-0xf91fffff] It turns out we did not calculate size1 properly. static resource_size_t calculate_memsize(resource_size_t size, resource_size_t min_size, resource_size_t size1, resource_size_t old_size, resource_size_t align) { if (size < min_size) size = min_size; if (old_size == 1 ) old_size = 0; if (size < old_size) size = old_size; size = ALIGN(size + size1, align); return size; } We should not pass add_size with min_size in calculate_memsize since that will make add_size not contribute final add_size. So just pass add_size with size1 to calculate_memsize(). With this change, we should have chance to remove extra addon in pci_reassign_resource. Signed-off-by: Yinghai Lu <yinghai@kernel.org> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
2012-01-21 18:08:17 +08:00
dev_info(&dev->dev, "BAR %d: reassigned %pR\n", resno, res);
PCI: clean up resource alignment management Done per Linus' request and suggestions. Linus has explained that better than I'll be able to explain: On Thu, Mar 27, 2008 at 10:12:10AM -0700, Linus Torvalds wrote: > Actually, before we go any further, there might be a less intrusive > alternative: add just a couple of flags to the resource flags field (we > still have something like 8 unused bits on 32-bit), and use those to > implement a generic "resource_alignment()" routine. > > Two flags would do it: > > - IORESOURCE_SIZEALIGN: size indicates alignment (regular PCI device > resources) > > - IORESOURCE_STARTALIGN: start field is alignment (PCI bus resources > during probing) > > and then the case of both flags zero (or both bits set) would actually be > "invalid", and we would also clear the IORESOURCE_STARTALIGN flag when we > actually allocate the resource (so that we don't use the "start" field as > alignment incorrectly when it no longer indicates alignment). > > That wouldn't be totally generic, but it would have the nice property of > automatically at least add sanity checking for that whole "res->start has > the odd meaning of 'alignment' during probing" and remove the need for a > new field, and it would allow us to have a generic "resource_alignment()" > routine that just gets a resource pointer. Besides, I removed IORESOURCE_BUS_HAS_VGA flag which was unused for ages. Signed-off-by: Ivan Kokshaysky <ink@jurassic.park.msu.ru> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Gary Hade <garyhade@us.ibm.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2008-03-30 23:50:14 +08:00
if (resno < PCI_BRIDGE_RESOURCES)
pci_update_resource(dev, resno);
}
return ret;
}
int pci_assign_resource(struct pci_dev *dev, int resno)
{
struct resource *res = dev->resource + resno;
resource_size_t align, size;
struct pci_bus *bus;
int ret;
PCI SR-IOV: correct broken resource alignment calculations An SR-IOV capable device includes an SR-IOV PCIe capability which describes the Virtual Function (VF) BAR requirements. A typical SR-IOV device can support multiple VFs whose BARs must be in a contiguous region, effectively an array of VF BARs. The BAR reports the size requirement for a single VF. We calculate the full range needed by simply multiplying the VF BAR size with the number of possible VFs and create a resource spanning the full range. This all seems sane enough except it artificially inflates the alignment requirement for the VF BAR. The VF BAR need only be aligned to the size of a single BAR not the contiguous range of VF BARs. This can cause us to fail to allocate resources for the BAR despite the fact that we actually have enough space. This patch adds a thin PCI specific layer over the generic resource_alignment() function which is aware of the special nature of VF BARs and does sorting and allocation based on the smaller alignment requirement. I recognize that while resource_alignment is generic, it's basically a PCI helper. An alternative to this patch is to add PCI VF BAR specific information to struct resource. I opted for the extra layer rather than adding such PCI specific information to struct resource. This does have the slight downside that we don't cache the BAR size and re-read for each alignment query (happens a small handful of times during boot for each VF BAR). Signed-off-by: Chris Wright <chrisw@sous-sol.org> Cc: Ivan Kokshaysky <ink@jurassic.park.msu.ru> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Matthew Wilcox <matthew@wil.cx> Cc: Yu Zhao <yu.zhao@intel.com> Cc: stable@kernel.org Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
2009-08-29 04:00:06 +08:00
align = pci_resource_alignment(dev, res);
if (!align) {
dev_info(&dev->dev, "BAR %d: can't assign %pR "
"(bogus alignment)\n", resno, res);
return -EINVAL;
}
bus = dev->bus;
size = resource_size(res);
ret = _pci_assign_resource(dev, resno, size, align);
/*
* If we failed to assign anything, let's try the address
* where firmware left it. That at least has a chance of
* working, which is better than just leaving it disabled.
*/
if (ret < 0)
ret = pci_revert_fw_address(res, dev, resno, size);
if (!ret) {
res->flags &= ~IORESOURCE_STARTALIGN;
dev_info(&dev->dev, "BAR %d: assigned %pR\n", resno, res);
if (resno < PCI_BRIDGE_RESOURCES)
pci_update_resource(dev, resno);
}
return ret;
}
int pci_enable_resources(struct pci_dev *dev, int mask)
{
u16 cmd, old_cmd;
int i;
struct resource *r;
pci_read_config_word(dev, PCI_COMMAND, &cmd);
old_cmd = cmd;
for (i = 0; i < PCI_NUM_RESOURCES; i++) {
if (!(mask & (1 << i)))
continue;
r = &dev->resource[i];
if (!(r->flags & (IORESOURCE_IO | IORESOURCE_MEM)))
continue;
if ((i == PCI_ROM_RESOURCE) &&
(!(r->flags & IORESOURCE_ROM_ENABLE)))
continue;
if (!r->parent) {
dev_err(&dev->dev, "device not available "
"(can't reserve %pR)\n", r);
return -EINVAL;
}
if (r->flags & IORESOURCE_IO)
cmd |= PCI_COMMAND_IO;
if (r->flags & IORESOURCE_MEM)
cmd |= PCI_COMMAND_MEMORY;
}
if (cmd != old_cmd) {
dev_info(&dev->dev, "enabling device (%04x -> %04x)\n",
old_cmd, cmd);
pci_write_config_word(dev, PCI_COMMAND, cmd);
}
return 0;
}