2007-02-13 13:26:20 +01:00
|
|
|
/*
|
|
|
|
* mmconfig-shared.c - Low-level direct PCI config space access via
|
|
|
|
* MMCONFIG - common code between i386 and x86-64.
|
|
|
|
*
|
|
|
|
* This code does:
|
2007-02-13 13:26:20 +01:00
|
|
|
* - known chipset handling
|
2007-02-13 13:26:20 +01:00
|
|
|
* - ACPI decoding and validation
|
|
|
|
*
|
|
|
|
* Per-architecture code takes care of the mappings and accesses
|
|
|
|
* themselves.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/acpi.h>
|
2009-08-14 21:37:50 +02:00
|
|
|
#include <linux/sfi_acpi.h>
|
2007-02-13 13:26:20 +01:00
|
|
|
#include <linux/bitmap.h>
|
2009-10-23 23:20:33 +02:00
|
|
|
#include <linux/dmi.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 09:04:11 +01:00
|
|
|
#include <linux/slab.h>
|
2007-02-13 13:26:20 +01:00
|
|
|
#include <asm/e820.h>
|
2008-12-27 14:02:28 +01:00
|
|
|
#include <asm/pci_x86.h>
|
2009-08-14 21:37:50 +02:00
|
|
|
#include <asm/acpi.h>
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2009-07-28 22:48:02 +02:00
|
|
|
#define PREFIX "PCI: "
|
2009-07-28 22:45:54 +02:00
|
|
|
|
2007-07-21 17:10:34 +02:00
|
|
|
/* Indicate if the mmcfg resources have been placed into the resource table. */
|
|
|
|
static int __initdata pci_mmcfg_resources_inserted;
|
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
LIST_HEAD(pci_mmcfg_list);
|
|
|
|
|
2009-11-14 01:34:54 +01:00
|
|
|
static __init void pci_mmconfig_remove(struct pci_mmcfg_region *cfg)
|
|
|
|
{
|
|
|
|
if (cfg->res.parent)
|
|
|
|
release_resource(&cfg->res);
|
|
|
|
list_del(&cfg->list);
|
|
|
|
kfree(cfg);
|
|
|
|
}
|
|
|
|
|
2009-11-14 01:33:53 +01:00
|
|
|
static __init void free_all_mmcfg(void)
|
|
|
|
{
|
2009-11-14 01:34:49 +01:00
|
|
|
struct pci_mmcfg_region *cfg, *tmp;
|
2009-11-14 01:34:29 +01:00
|
|
|
|
2009-11-14 01:33:53 +01:00
|
|
|
pci_mmcfg_arch_free();
|
2009-11-14 01:34:54 +01:00
|
|
|
list_for_each_entry_safe(cfg, tmp, &pci_mmcfg_list, list)
|
|
|
|
pci_mmconfig_remove(cfg);
|
2009-11-14 01:34:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static __init void list_add_sorted(struct pci_mmcfg_region *new)
|
|
|
|
{
|
|
|
|
struct pci_mmcfg_region *cfg;
|
|
|
|
|
|
|
|
/* keep list sorted by segment and starting bus number */
|
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
|
|
|
if (cfg->segment > new->segment ||
|
|
|
|
(cfg->segment == new->segment &&
|
|
|
|
cfg->start_bus >= new->start_bus)) {
|
|
|
|
list_add_tail(&new->list, &cfg->list);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
list_add_tail(&new->list, &pci_mmcfg_list);
|
2009-11-14 01:33:53 +01:00
|
|
|
}
|
|
|
|
|
2009-11-14 01:34:13 +01:00
|
|
|
static __init struct pci_mmcfg_region *pci_mmconfig_add(int segment, int start,
|
|
|
|
int end, u64 addr)
|
2009-03-20 04:55:35 +01:00
|
|
|
{
|
2009-11-14 01:34:13 +01:00
|
|
|
struct pci_mmcfg_region *new;
|
2009-11-14 01:34:29 +01:00
|
|
|
struct resource *res;
|
2009-03-20 04:55:35 +01:00
|
|
|
|
2009-11-14 01:34:03 +01:00
|
|
|
if (addr == 0)
|
|
|
|
return NULL;
|
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
2009-03-20 04:55:35 +01:00
|
|
|
if (!new)
|
2009-11-14 01:33:53 +01:00
|
|
|
return NULL;
|
2009-03-20 04:55:35 +01:00
|
|
|
|
2009-11-14 01:34:24 +01:00
|
|
|
new->address = addr;
|
|
|
|
new->segment = segment;
|
|
|
|
new->start_bus = start;
|
|
|
|
new->end_bus = end;
|
2009-11-14 01:33:53 +01:00
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
list_add_sorted(new);
|
|
|
|
|
2009-11-14 01:34:29 +01:00
|
|
|
res = &new->res;
|
|
|
|
res->start = addr + PCI_MMCFG_BUS_OFFSET(start);
|
2010-10-04 20:49:24 +02:00
|
|
|
res->end = addr + PCI_MMCFG_BUS_OFFSET(end + 1) - 1;
|
2009-11-14 01:34:29 +01:00
|
|
|
res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
|
|
|
|
snprintf(new->name, PCI_MMCFG_RESOURCE_NAME_LEN,
|
|
|
|
"PCI MMCONFIG %04x [bus %02x-%02x]", segment, start, end);
|
|
|
|
res->name = new->name;
|
|
|
|
|
2009-11-14 01:34:59 +01:00
|
|
|
printk(KERN_INFO PREFIX "MMCONFIG for domain %04x [bus %02x-%02x] at "
|
|
|
|
"%pR (base %#lx)\n", segment, start, end, &new->res,
|
|
|
|
(unsigned long) addr);
|
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
return new;
|
2009-03-20 04:55:35 +01:00
|
|
|
}
|
|
|
|
|
2009-11-14 01:35:04 +01:00
|
|
|
struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus)
|
|
|
|
{
|
|
|
|
struct pci_mmcfg_region *cfg;
|
|
|
|
|
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list)
|
|
|
|
if (cfg->segment == segment &&
|
|
|
|
cfg->start_bus <= bus && bus <= cfg->end_bus)
|
|
|
|
return cfg;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
static const char __init *pci_mmcfg_e7520(void)
|
2007-02-13 13:26:20 +01:00
|
|
|
{
|
|
|
|
u32 win;
|
2008-02-29 08:56:50 +01:00
|
|
|
raw_pci_ops->read(0, 0, PCI_DEVFN(0, 0), 0xce, 2, &win);
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2007-05-02 19:27:22 +02:00
|
|
|
win = win & 0xf000;
|
2009-03-20 04:55:35 +01:00
|
|
|
if (win == 0x0000 || win == 0xf000)
|
|
|
|
return NULL;
|
|
|
|
|
2009-11-14 01:33:53 +01:00
|
|
|
if (pci_mmconfig_add(0, 0, 255, win << 16) == NULL)
|
2009-03-20 04:55:35 +01:00
|
|
|
return NULL;
|
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
return "Intel Corporation E7520 Memory Controller Hub";
|
|
|
|
}
|
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
static const char __init *pci_mmcfg_intel_945(void)
|
2007-02-13 13:26:20 +01:00
|
|
|
{
|
|
|
|
u32 pciexbar, mask = 0, len = 0;
|
|
|
|
|
2008-02-29 08:56:50 +01:00
|
|
|
raw_pci_ops->read(0, 0, PCI_DEVFN(0, 0), 0x48, 4, &pciexbar);
|
2007-02-13 13:26:20 +01:00
|
|
|
|
|
|
|
/* Enable bit */
|
|
|
|
if (!(pciexbar & 1))
|
2009-03-20 04:55:35 +01:00
|
|
|
return NULL;
|
2007-02-13 13:26:20 +01:00
|
|
|
|
|
|
|
/* Size bits */
|
|
|
|
switch ((pciexbar >> 1) & 3) {
|
|
|
|
case 0:
|
|
|
|
mask = 0xf0000000U;
|
|
|
|
len = 0x10000000U;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
mask = 0xf8000000U;
|
|
|
|
len = 0x08000000U;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
mask = 0xfc000000U;
|
|
|
|
len = 0x04000000U;
|
|
|
|
break;
|
|
|
|
default:
|
2009-03-20 04:55:35 +01:00
|
|
|
return NULL;
|
2007-02-13 13:26:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Errata #2, things break when not aligned on a 256Mb boundary */
|
|
|
|
/* Can only happen in 64M/128M mode */
|
|
|
|
|
|
|
|
if ((pciexbar & mask) & 0x0fffffffU)
|
2009-03-20 04:55:35 +01:00
|
|
|
return NULL;
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2007-05-02 19:27:22 +02:00
|
|
|
/* Don't hit the APIC registers and their friends */
|
|
|
|
if ((pciexbar & mask) >= 0xf0000000U)
|
2009-03-20 04:55:35 +01:00
|
|
|
return NULL;
|
|
|
|
|
2009-11-14 01:33:53 +01:00
|
|
|
if (pci_mmconfig_add(0, 0, (len >> 20) - 1, pciexbar & mask) == NULL)
|
2009-03-20 04:55:35 +01:00
|
|
|
return NULL;
|
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
return "Intel Corporation 945G/GZ/P/PL Express Memory Controller Hub";
|
|
|
|
}
|
|
|
|
|
2008-02-19 12:13:02 +01:00
|
|
|
static const char __init *pci_mmcfg_amd_fam10h(void)
|
|
|
|
{
|
|
|
|
u32 low, high, address;
|
|
|
|
u64 base, msr;
|
|
|
|
int i;
|
2009-11-14 01:33:53 +01:00
|
|
|
unsigned segnbits = 0, busnbits, end_bus;
|
2008-02-19 12:13:02 +01:00
|
|
|
|
2008-04-15 01:08:25 +02:00
|
|
|
if (!(pci_probe & PCI_CHECK_ENABLE_AMD_MMCONF))
|
|
|
|
return NULL;
|
|
|
|
|
2008-02-19 12:13:02 +01:00
|
|
|
address = MSR_FAM10H_MMIO_CONF_BASE;
|
|
|
|
if (rdmsr_safe(address, &low, &high))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
msr = high;
|
|
|
|
msr <<= 32;
|
|
|
|
msr |= low;
|
|
|
|
|
|
|
|
/* mmconfig is not enable */
|
|
|
|
if (!(msr & FAM10H_MMIO_CONF_ENABLE))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
base = msr & (FAM10H_MMIO_CONF_BASE_MASK<<FAM10H_MMIO_CONF_BASE_SHIFT);
|
|
|
|
|
|
|
|
busnbits = (msr >> FAM10H_MMIO_CONF_BUSRANGE_SHIFT) &
|
|
|
|
FAM10H_MMIO_CONF_BUSRANGE_MASK;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* only handle bus 0 ?
|
|
|
|
* need to skip it
|
|
|
|
*/
|
|
|
|
if (!busnbits)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (busnbits > 8) {
|
|
|
|
segnbits = busnbits - 8;
|
|
|
|
busnbits = 8;
|
|
|
|
}
|
|
|
|
|
2009-11-14 01:33:53 +01:00
|
|
|
end_bus = (1 << busnbits) - 1;
|
2009-03-20 04:55:35 +01:00
|
|
|
for (i = 0; i < (1 << segnbits); i++)
|
2009-11-14 01:33:53 +01:00
|
|
|
if (pci_mmconfig_add(i, 0, end_bus,
|
|
|
|
base + (1<<28) * i) == NULL) {
|
|
|
|
free_all_mmcfg();
|
|
|
|
return NULL;
|
|
|
|
}
|
2008-02-19 12:13:02 +01:00
|
|
|
|
|
|
|
return "AMD Family 10h NB";
|
|
|
|
}
|
|
|
|
|
2009-03-20 04:57:56 +01:00
|
|
|
static bool __initdata mcp55_checked;
|
|
|
|
static const char __init *pci_mmcfg_nvidia_mcp55(void)
|
|
|
|
{
|
|
|
|
int bus;
|
|
|
|
int mcp55_mmconf_found = 0;
|
|
|
|
|
|
|
|
static const u32 extcfg_regnum = 0x90;
|
|
|
|
static const u32 extcfg_regsize = 4;
|
|
|
|
static const u32 extcfg_enable_mask = 1<<31;
|
|
|
|
static const u32 extcfg_start_mask = 0xff<<16;
|
|
|
|
static const int extcfg_start_shift = 16;
|
|
|
|
static const u32 extcfg_size_mask = 0x3<<28;
|
|
|
|
static const int extcfg_size_shift = 28;
|
|
|
|
static const int extcfg_sizebus[] = {0x100, 0x80, 0x40, 0x20};
|
|
|
|
static const u32 extcfg_base_mask[] = {0x7ff8, 0x7ffc, 0x7ffe, 0x7fff};
|
|
|
|
static const int extcfg_base_lshift = 25;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* do check if amd fam10h already took over
|
|
|
|
*/
|
2009-11-14 01:34:49 +01:00
|
|
|
if (!acpi_disabled || !list_empty(&pci_mmcfg_list) || mcp55_checked)
|
2009-03-20 04:57:56 +01:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
mcp55_checked = true;
|
|
|
|
for (bus = 0; bus < 256; bus++) {
|
|
|
|
u64 base;
|
|
|
|
u32 l, extcfg;
|
|
|
|
u16 vendor, device;
|
|
|
|
int start, size_index, end;
|
|
|
|
|
|
|
|
raw_pci_ops->read(0, bus, PCI_DEVFN(0, 0), 0, 4, &l);
|
|
|
|
vendor = l & 0xffff;
|
|
|
|
device = (l >> 16) & 0xffff;
|
|
|
|
|
|
|
|
if (PCI_VENDOR_ID_NVIDIA != vendor || 0x0369 != device)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
raw_pci_ops->read(0, bus, PCI_DEVFN(0, 0), extcfg_regnum,
|
|
|
|
extcfg_regsize, &extcfg);
|
|
|
|
|
|
|
|
if (!(extcfg & extcfg_enable_mask))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
size_index = (extcfg & extcfg_size_mask) >> extcfg_size_shift;
|
|
|
|
base = extcfg & extcfg_base_mask[size_index];
|
|
|
|
/* base could > 4G */
|
|
|
|
base <<= extcfg_base_lshift;
|
|
|
|
start = (extcfg & extcfg_start_mask) >> extcfg_start_shift;
|
|
|
|
end = start + extcfg_sizebus[size_index] - 1;
|
2009-11-14 01:33:53 +01:00
|
|
|
if (pci_mmconfig_add(0, start, end, base) == NULL)
|
|
|
|
continue;
|
2009-03-20 04:57:56 +01:00
|
|
|
mcp55_mmconf_found++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mcp55_mmconf_found)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return "nVidia MCP55";
|
|
|
|
}
|
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
struct pci_mmcfg_hostbridge_probe {
|
2008-02-19 12:13:02 +01:00
|
|
|
u32 bus;
|
|
|
|
u32 devfn;
|
2007-02-13 13:26:20 +01:00
|
|
|
u32 vendor;
|
|
|
|
u32 device;
|
|
|
|
const char *(*probe)(void);
|
|
|
|
};
|
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
static struct pci_mmcfg_hostbridge_probe pci_mmcfg_probes[] __initdata = {
|
2008-02-19 12:13:02 +01:00
|
|
|
{ 0, PCI_DEVFN(0, 0), PCI_VENDOR_ID_INTEL,
|
|
|
|
PCI_DEVICE_ID_INTEL_E7520_MCH, pci_mmcfg_e7520 },
|
|
|
|
{ 0, PCI_DEVFN(0, 0), PCI_VENDOR_ID_INTEL,
|
|
|
|
PCI_DEVICE_ID_INTEL_82945G_HB, pci_mmcfg_intel_945 },
|
|
|
|
{ 0, PCI_DEVFN(0x18, 0), PCI_VENDOR_ID_AMD,
|
|
|
|
0x1200, pci_mmcfg_amd_fam10h },
|
|
|
|
{ 0xff, PCI_DEVFN(0, 0), PCI_VENDOR_ID_AMD,
|
|
|
|
0x1200, pci_mmcfg_amd_fam10h },
|
2009-03-20 04:57:56 +01:00
|
|
|
{ 0, PCI_DEVFN(0, 0), PCI_VENDOR_ID_NVIDIA,
|
|
|
|
0x0369, pci_mmcfg_nvidia_mcp55 },
|
2007-02-13 13:26:20 +01:00
|
|
|
};
|
|
|
|
|
2009-03-20 04:55:35 +01:00
|
|
|
static void __init pci_mmcfg_check_end_bus_number(void)
|
|
|
|
{
|
2009-11-14 01:34:44 +01:00
|
|
|
struct pci_mmcfg_region *cfg, *cfgx;
|
2009-03-20 04:55:35 +01:00
|
|
|
|
2010-02-25 16:42:11 +01:00
|
|
|
/* Fixup overlaps */
|
2009-11-14 01:34:49 +01:00
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
2009-11-14 01:34:18 +01:00
|
|
|
if (cfg->end_bus < cfg->start_bus)
|
|
|
|
cfg->end_bus = 255;
|
2009-03-20 04:55:35 +01:00
|
|
|
|
2010-02-25 16:42:11 +01:00
|
|
|
/* Don't access the list head ! */
|
|
|
|
if (cfg->list.next == &pci_mmcfg_list)
|
|
|
|
break;
|
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
cfgx = list_entry(cfg->list.next, typeof(*cfg), list);
|
2010-02-25 16:42:11 +01:00
|
|
|
if (cfg->end_bus >= cfgx->start_bus)
|
2009-11-14 01:34:18 +01:00
|
|
|
cfg->end_bus = cfgx->start_bus - 1;
|
2009-03-20 04:55:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
static int __init pci_mmcfg_check_hostbridge(void)
|
|
|
|
{
|
|
|
|
u32 l;
|
2008-02-19 12:13:02 +01:00
|
|
|
u32 bus, devfn;
|
2007-02-13 13:26:20 +01:00
|
|
|
u16 vendor, device;
|
|
|
|
int i;
|
|
|
|
const char *name;
|
|
|
|
|
2008-02-29 08:56:50 +01:00
|
|
|
if (!raw_pci_ops)
|
|
|
|
return 0;
|
|
|
|
|
2009-11-14 01:33:53 +01:00
|
|
|
free_all_mmcfg();
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2009-03-20 04:55:35 +01:00
|
|
|
for (i = 0; i < ARRAY_SIZE(pci_mmcfg_probes); i++) {
|
2008-02-19 12:13:02 +01:00
|
|
|
bus = pci_mmcfg_probes[i].bus;
|
|
|
|
devfn = pci_mmcfg_probes[i].devfn;
|
2008-02-29 08:56:50 +01:00
|
|
|
raw_pci_ops->read(0, bus, devfn, 0, 4, &l);
|
2008-02-19 12:13:02 +01:00
|
|
|
vendor = l & 0xffff;
|
|
|
|
device = (l >> 16) & 0xffff;
|
|
|
|
|
2009-03-20 04:55:35 +01:00
|
|
|
name = NULL;
|
2007-02-13 13:26:20 +01:00
|
|
|
if (pci_mmcfg_probes[i].vendor == vendor &&
|
|
|
|
pci_mmcfg_probes[i].device == device)
|
2007-02-13 13:26:20 +01:00
|
|
|
name = pci_mmcfg_probes[i].probe();
|
|
|
|
|
2009-03-20 04:55:35 +01:00
|
|
|
if (name)
|
2009-11-14 01:34:59 +01:00
|
|
|
printk(KERN_INFO PREFIX "%s with MMCONFIG support\n",
|
2009-03-20 04:55:35 +01:00
|
|
|
name);
|
2007-02-13 13:26:20 +01:00
|
|
|
}
|
|
|
|
|
2009-03-20 04:55:35 +01:00
|
|
|
/* some end_bus_number is crazy, fix it */
|
|
|
|
pci_mmcfg_check_end_bus_number();
|
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
return !list_empty(&pci_mmcfg_list);
|
2007-02-13 13:26:20 +01:00
|
|
|
}
|
|
|
|
|
2008-09-04 21:04:32 +02:00
|
|
|
static void __init pci_mmcfg_insert_resources(void)
|
2007-02-13 13:26:20 +01:00
|
|
|
{
|
2009-11-14 01:34:29 +01:00
|
|
|
struct pci_mmcfg_region *cfg;
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list)
|
2009-11-14 01:34:29 +01:00
|
|
|
insert_resource(&iomem_resource, &cfg->res);
|
2007-07-21 17:10:34 +02:00
|
|
|
|
|
|
|
/* Mark that the resources have been inserted. */
|
|
|
|
pci_mmcfg_resources_inserted = 1;
|
2007-02-13 13:26:20 +01:00
|
|
|
}
|
|
|
|
|
2008-02-15 10:27:20 +01:00
|
|
|
static acpi_status __init check_mcfg_resource(struct acpi_resource *res,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct resource *mcfg_res = data;
|
|
|
|
struct acpi_resource_address64 address;
|
|
|
|
acpi_status status;
|
|
|
|
|
|
|
|
if (res->type == ACPI_RESOURCE_TYPE_FIXED_MEMORY32) {
|
|
|
|
struct acpi_resource_fixed_memory32 *fixmem32 =
|
|
|
|
&res->data.fixed_memory32;
|
|
|
|
if (!fixmem32)
|
|
|
|
return AE_OK;
|
|
|
|
if ((mcfg_res->start >= fixmem32->address) &&
|
2009-06-03 09:13:13 +02:00
|
|
|
(mcfg_res->end < (fixmem32->address +
|
2008-02-15 10:27:20 +01:00
|
|
|
fixmem32->address_length))) {
|
|
|
|
mcfg_res->flags = 1;
|
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((res->type != ACPI_RESOURCE_TYPE_ADDRESS32) &&
|
|
|
|
(res->type != ACPI_RESOURCE_TYPE_ADDRESS64))
|
|
|
|
return AE_OK;
|
|
|
|
|
|
|
|
status = acpi_resource_to_address64(res, &address);
|
|
|
|
if (ACPI_FAILURE(status) ||
|
|
|
|
(address.address_length <= 0) ||
|
|
|
|
(address.resource_type != ACPI_MEMORY_RANGE))
|
|
|
|
return AE_OK;
|
|
|
|
|
|
|
|
if ((mcfg_res->start >= address.minimum) &&
|
2009-06-03 09:13:13 +02:00
|
|
|
(mcfg_res->end < (address.minimum + address.address_length))) {
|
2008-02-15 10:27:20 +01:00
|
|
|
mcfg_res->flags = 1;
|
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
|
}
|
|
|
|
return AE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static acpi_status __init find_mboard_resource(acpi_handle handle, u32 lvl,
|
|
|
|
void *context, void **rv)
|
|
|
|
{
|
|
|
|
struct resource *mcfg_res = context;
|
|
|
|
|
|
|
|
acpi_walk_resources(handle, METHOD_NAME__CRS,
|
|
|
|
check_mcfg_resource, context);
|
|
|
|
|
|
|
|
if (mcfg_res->flags)
|
|
|
|
return AE_CTRL_TERMINATE;
|
|
|
|
|
|
|
|
return AE_OK;
|
|
|
|
}
|
|
|
|
|
2008-07-18 22:22:36 +02:00
|
|
|
static int __init is_acpi_reserved(u64 start, u64 end, unsigned not_used)
|
2008-02-15 10:27:20 +01:00
|
|
|
{
|
|
|
|
struct resource mcfg_res;
|
|
|
|
|
|
|
|
mcfg_res.start = start;
|
2009-06-03 09:13:13 +02:00
|
|
|
mcfg_res.end = end - 1;
|
2008-02-15 10:27:20 +01:00
|
|
|
mcfg_res.flags = 0;
|
|
|
|
|
|
|
|
acpi_get_devices("PNP0C01", find_mboard_resource, &mcfg_res, NULL);
|
|
|
|
|
|
|
|
if (!mcfg_res.flags)
|
|
|
|
acpi_get_devices("PNP0C02", find_mboard_resource, &mcfg_res,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
return mcfg_res.flags;
|
|
|
|
}
|
|
|
|
|
2008-07-18 22:22:36 +02:00
|
|
|
typedef int (*check_reserved_t)(u64 start, u64 end, unsigned type);
|
|
|
|
|
|
|
|
static int __init is_mmconf_reserved(check_reserved_t is_reserved,
|
2009-11-14 01:34:59 +01:00
|
|
|
struct pci_mmcfg_region *cfg, int with_e820)
|
2008-07-18 22:22:36 +02:00
|
|
|
{
|
2009-11-14 01:34:34 +01:00
|
|
|
u64 addr = cfg->res.start;
|
|
|
|
u64 size = resource_size(&cfg->res);
|
2008-07-18 22:22:36 +02:00
|
|
|
u64 old_size = size;
|
2009-11-14 01:34:29 +01:00
|
|
|
int valid = 0, num_buses;
|
2008-07-18 22:22:36 +02:00
|
|
|
|
2009-04-18 10:43:46 +02:00
|
|
|
while (!is_reserved(addr, addr + size, E820_RESERVED)) {
|
2008-07-18 22:22:36 +02:00
|
|
|
size >>= 1;
|
|
|
|
if (size < (16UL<<20))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size >= (16UL<<20) || size == old_size) {
|
2009-11-14 01:34:59 +01:00
|
|
|
printk(KERN_INFO PREFIX "MMCONFIG at %pR reserved in %s\n",
|
|
|
|
&cfg->res,
|
|
|
|
with_e820 ? "E820" : "ACPI motherboard resources");
|
2008-07-18 22:22:36 +02:00
|
|
|
valid = 1;
|
|
|
|
|
|
|
|
if (old_size != size) {
|
2009-11-14 01:34:18 +01:00
|
|
|
/* update end_bus */
|
|
|
|
cfg->end_bus = cfg->start_bus + ((size>>20) - 1);
|
2009-11-14 01:34:29 +01:00
|
|
|
num_buses = cfg->end_bus - cfg->start_bus + 1;
|
|
|
|
cfg->res.end = cfg->res.start +
|
|
|
|
PCI_MMCFG_BUS_OFFSET(num_buses) - 1;
|
|
|
|
snprintf(cfg->name, PCI_MMCFG_RESOURCE_NAME_LEN,
|
|
|
|
"PCI MMCONFIG %04x [bus %02x-%02x]",
|
|
|
|
cfg->segment, cfg->start_bus, cfg->end_bus);
|
2009-11-14 01:34:59 +01:00
|
|
|
printk(KERN_INFO PREFIX
|
|
|
|
"MMCONFIG for %04x [bus%02x-%02x] "
|
|
|
|
"at %pR (base %#lx) (size reduced!)\n",
|
|
|
|
cfg->segment, cfg->start_bus, cfg->end_bus,
|
|
|
|
&cfg->res, (unsigned long) cfg->address);
|
2008-07-18 22:22:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
2008-02-29 08:56:50 +01:00
|
|
|
static void __init pci_mmcfg_reject_broken(int early)
|
2007-02-13 13:26:20 +01:00
|
|
|
{
|
2009-11-14 01:34:44 +01:00
|
|
|
struct pci_mmcfg_region *cfg;
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
2009-11-14 01:34:29 +01:00
|
|
|
int valid = 0;
|
2008-07-18 22:22:36 +02:00
|
|
|
|
2010-05-05 11:08:49 +02:00
|
|
|
if (!early && !acpi_disabled) {
|
2009-11-14 01:34:59 +01:00
|
|
|
valid = is_mmconf_reserved(is_acpi_reserved, cfg, 0);
|
2008-02-15 10:30:14 +01:00
|
|
|
|
2010-05-05 11:08:49 +02:00
|
|
|
if (valid)
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
printk(KERN_ERR FW_BUG PREFIX
|
|
|
|
"MMCONFIG at %pR not reserved in "
|
|
|
|
"ACPI motherboard resources\n",
|
|
|
|
&cfg->res);
|
|
|
|
}
|
2008-07-18 22:22:36 +02:00
|
|
|
|
2008-02-15 10:30:14 +01:00
|
|
|
/* Don't try to do this check unless configuration
|
2008-02-29 08:56:50 +01:00
|
|
|
type 1 is available. how about type 2 ?*/
|
2008-07-18 22:22:36 +02:00
|
|
|
if (raw_pci_ops)
|
2009-11-14 01:34:59 +01:00
|
|
|
valid = is_mmconf_reserved(e820_all_mapped, cfg, 1);
|
2008-02-15 10:30:14 +01:00
|
|
|
|
|
|
|
if (!valid)
|
|
|
|
goto reject;
|
2007-02-13 13:26:20 +01:00
|
|
|
}
|
2008-02-15 10:27:20 +01:00
|
|
|
|
2007-02-13 13:26:20 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
reject:
|
2009-11-14 01:34:59 +01:00
|
|
|
printk(KERN_INFO PREFIX "not using MMCONFIG\n");
|
2009-11-14 01:33:53 +01:00
|
|
|
free_all_mmcfg();
|
2007-02-13 13:26:20 +01:00
|
|
|
}
|
|
|
|
|
2008-02-15 10:30:14 +01:00
|
|
|
static int __initdata known_bridge;
|
2008-02-15 10:27:20 +01:00
|
|
|
|
2009-10-23 23:20:33 +02:00
|
|
|
static int __init acpi_mcfg_check_entry(struct acpi_table_mcfg *mcfg,
|
|
|
|
struct acpi_mcfg_allocation *cfg)
|
2009-06-12 05:53:55 +02:00
|
|
|
{
|
2009-10-23 23:20:33 +02:00
|
|
|
int year;
|
|
|
|
|
|
|
|
if (cfg->address < 0xFFFFFFFF)
|
|
|
|
return 0;
|
|
|
|
|
2011-06-02 21:59:43 +02:00
|
|
|
if (!strcmp(mcfg->header.oem_id, "SGI") ||
|
|
|
|
!strcmp(mcfg->header.oem_id, "SGI2"))
|
2009-10-23 23:20:33 +02:00
|
|
|
return 0;
|
2009-06-12 05:53:55 +02:00
|
|
|
|
2009-10-23 23:20:33 +02:00
|
|
|
if (mcfg->header.revision >= 1) {
|
|
|
|
if (dmi_get_date(DMI_BIOS_DATE, &year, NULL, NULL) &&
|
|
|
|
year >= 2010)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-11-14 01:34:59 +01:00
|
|
|
printk(KERN_ERR PREFIX "MCFG region for %04x [bus %02x-%02x] at %#llx "
|
2009-10-23 23:20:33 +02:00
|
|
|
"is above 4GB, ignored\n", cfg->pci_segment,
|
|
|
|
cfg->start_bus_number, cfg->end_bus_number, cfg->address);
|
|
|
|
return -EINVAL;
|
2009-06-12 05:53:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __init pci_parse_mcfg(struct acpi_table_header *header)
|
|
|
|
{
|
|
|
|
struct acpi_table_mcfg *mcfg;
|
2009-11-14 01:33:47 +01:00
|
|
|
struct acpi_mcfg_allocation *cfg_table, *cfg;
|
2009-06-12 05:53:55 +02:00
|
|
|
unsigned long i;
|
2009-11-14 01:33:53 +01:00
|
|
|
int entries;
|
2009-06-12 05:53:55 +02:00
|
|
|
|
|
|
|
if (!header)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
mcfg = (struct acpi_table_mcfg *)header;
|
|
|
|
|
|
|
|
/* how many config structures do we have */
|
2009-11-14 01:33:53 +01:00
|
|
|
free_all_mmcfg();
|
2009-11-14 01:33:42 +01:00
|
|
|
entries = 0;
|
2009-06-12 05:53:55 +02:00
|
|
|
i = header->length - sizeof(struct acpi_table_mcfg);
|
|
|
|
while (i >= sizeof(struct acpi_mcfg_allocation)) {
|
2009-11-14 01:33:42 +01:00
|
|
|
entries++;
|
2009-06-12 05:53:55 +02:00
|
|
|
i -= sizeof(struct acpi_mcfg_allocation);
|
|
|
|
};
|
2009-11-14 01:33:42 +01:00
|
|
|
if (entries == 0) {
|
2009-06-12 05:53:55 +02:00
|
|
|
printk(KERN_ERR PREFIX "MMCONFIG has no entries\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2009-11-14 01:33:47 +01:00
|
|
|
cfg_table = (struct acpi_mcfg_allocation *) &mcfg[1];
|
2009-11-14 01:33:42 +01:00
|
|
|
for (i = 0; i < entries; i++) {
|
2009-11-14 01:33:47 +01:00
|
|
|
cfg = &cfg_table[i];
|
|
|
|
if (acpi_mcfg_check_entry(mcfg, cfg)) {
|
2009-11-14 01:33:53 +01:00
|
|
|
free_all_mmcfg();
|
2009-06-12 05:53:55 +02:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
2009-11-14 01:33:53 +01:00
|
|
|
|
|
|
|
if (pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number,
|
|
|
|
cfg->end_bus_number, cfg->address) == NULL) {
|
|
|
|
printk(KERN_WARNING PREFIX
|
|
|
|
"no memory for MCFG entries\n");
|
|
|
|
free_all_mmcfg();
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2009-06-12 05:53:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-05-12 15:43:37 +02:00
|
|
|
static void __init __pci_mmcfg_init(int early)
|
2007-02-13 13:26:20 +01:00
|
|
|
{
|
2008-02-15 10:27:20 +01:00
|
|
|
/* MMCONFIG disabled */
|
2007-02-13 13:26:20 +01:00
|
|
|
if ((pci_probe & PCI_PROBE_MMCONF) == 0)
|
|
|
|
return;
|
|
|
|
|
2008-02-15 10:27:20 +01:00
|
|
|
/* MMCONFIG already enabled */
|
2008-02-15 10:30:14 +01:00
|
|
|
if (!early && !(pci_probe & PCI_PROBE_MASK & ~PCI_PROBE_MMCONF))
|
2008-02-15 10:27:20 +01:00
|
|
|
return;
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2008-02-15 10:30:14 +01:00
|
|
|
/* for late to exit */
|
|
|
|
if (known_bridge)
|
|
|
|
return;
|
2008-02-15 10:27:20 +01:00
|
|
|
|
2008-02-29 08:56:50 +01:00
|
|
|
if (early) {
|
2008-02-15 10:30:14 +01:00
|
|
|
if (pci_mmcfg_check_hostbridge())
|
|
|
|
known_bridge = 1;
|
|
|
|
}
|
|
|
|
|
2009-03-20 04:55:35 +01:00
|
|
|
if (!known_bridge)
|
2009-08-14 21:37:50 +02:00
|
|
|
acpi_sfi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg);
|
2009-03-20 04:55:35 +01:00
|
|
|
|
|
|
|
pci_mmcfg_reject_broken(early);
|
2007-02-13 13:26:20 +01:00
|
|
|
|
2009-11-14 01:34:49 +01:00
|
|
|
if (list_empty(&pci_mmcfg_list))
|
2007-02-13 13:26:20 +01:00
|
|
|
return;
|
|
|
|
|
2011-02-23 11:08:10 +01:00
|
|
|
if (pcibios_last_bus < 0) {
|
|
|
|
const struct pci_mmcfg_region *cfg;
|
|
|
|
|
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
|
|
|
if (cfg->segment)
|
|
|
|
break;
|
|
|
|
pcibios_last_bus = cfg->end_bus;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-09-04 21:04:32 +02:00
|
|
|
if (pci_mmcfg_arch_init())
|
2007-02-13 13:26:20 +01:00
|
|
|
pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF;
|
2008-09-04 21:04:32 +02:00
|
|
|
else {
|
2007-07-21 17:10:34 +02:00
|
|
|
/*
|
|
|
|
* Signal not to attempt to insert mmcfg resources because
|
|
|
|
* the architecture mmcfg setup could not initialize.
|
|
|
|
*/
|
|
|
|
pci_mmcfg_resources_inserted = 1;
|
2007-02-13 13:26:20 +01:00
|
|
|
}
|
|
|
|
}
|
2007-07-21 17:10:34 +02:00
|
|
|
|
2008-02-29 08:56:50 +01:00
|
|
|
void __init pci_mmcfg_early_init(void)
|
2008-02-15 10:30:14 +01:00
|
|
|
{
|
2008-02-29 08:56:50 +01:00
|
|
|
__pci_mmcfg_init(1);
|
2008-02-15 10:30:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void __init pci_mmcfg_late_init(void)
|
|
|
|
{
|
2008-02-29 08:56:50 +01:00
|
|
|
__pci_mmcfg_init(0);
|
2008-02-15 10:30:14 +01:00
|
|
|
}
|
|
|
|
|
2007-07-21 17:10:34 +02:00
|
|
|
static int __init pci_mmcfg_late_insert_resources(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If resources are already inserted or we are not using MMCONFIG,
|
|
|
|
* don't insert the resources.
|
|
|
|
*/
|
|
|
|
if ((pci_mmcfg_resources_inserted == 1) ||
|
|
|
|
(pci_probe & PCI_PROBE_MMCONF) == 0 ||
|
2009-11-14 01:34:49 +01:00
|
|
|
list_empty(&pci_mmcfg_list))
|
2007-07-21 17:10:34 +02:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Attempt to insert the mmcfg resources but not with the busy flag
|
|
|
|
* marked so it won't cause request errors when __request_region is
|
|
|
|
* called.
|
|
|
|
*/
|
2008-09-04 21:04:32 +02:00
|
|
|
pci_mmcfg_insert_resources();
|
2007-07-21 17:10:34 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Perform MMCONFIG resource insertion after PCI initialization to allow for
|
|
|
|
* misprogrammed MCFG tables that state larger sizes but actually conflict
|
|
|
|
* with other system resources.
|
|
|
|
*/
|
|
|
|
late_initcall(pci_mmcfg_late_insert_resources);
|