mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-23 00:20:52 -05:00
199f968f14
Arul, Mateusz, Imcarneiro91, and Aman reported a regression caused by07eab0901e
("efi/x86: Remove EfiMemoryMappedIO from E820 map"). On the Lenovo Legion 9i laptop, that commit removes the ECAM area from E820, which means the early E820 validation fails, which means we don't enable ECAM in the "early MCFG" path. The static MCFG table describes ECAM without depending on the ACPI interpreter. Many Legion 9i ACPI methods rely on that, so they fail when PCI config access isn't available, resulting in the embedded controller, PS/2, audio, trackpad, and battery devices not being detected. The _OSC method also fails, so Linux can't take control of the PCIe hotplug, PME, and AER features: # pci_mmcfg_early_init() PCI: ECAM [mem 0xc0000000-0xce0fffff] (base 0xc0000000) for domain 0000 [bus 00-e0] PCI: not using ECAM ([mem 0xc0000000-0xce0fffff] not reserved) ACPI Error: AE_ERROR, Returned by Handler for [PCI_Config] (20230628/evregion-300) ACPI: Interpreter enabled ACPI: Ignoring error and continuing table load ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PC00.RP01._SB.PC00], AE_NOT_FOUND (20230628/dswload2-162) ACPI Error: AE_NOT_FOUND, During name lookup/catalog (20230628/psobject-220) ACPI: Skipping parse of AML opcode: OpcodeName unavailable (0x0010) ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PC00.RP01._SB.PC00], AE_NOT_FOUND (20230628/dswload2-162) ACPI Error: AE_NOT_FOUND, During name lookup/catalog (20230628/psobject-220) ... ACPI Error: Aborting method \_SB.PC00._OSC due to previous error (AE_NOT_FOUND) (20230628/psparse-529) acpi PNP0A08:00: _OSC: platform retains control of PCIe features (AE_NOT_FOUND) # pci_mmcfg_late_init() PCI: ECAM [mem 0xc0000000-0xce0fffff] (base 0xc0000000) for domain 0000 [bus 00-e0] PCI: [Firmware Info]: ECAM [mem 0xc0000000-0xce0fffff] not reserved in ACPI motherboard resources PCI: ECAM [mem 0xc0000000-0xce0fffff] is EfiMemoryMappedIO; assuming valid PCI: ECAM [mem 0xc0000000-0xce0fffff] reserved to work around lack of ACPI motherboard _CRS Per PCI Firmware r3.3, sec 4.1.2, ECAM space must be reserved by a PNP0C02 resource, but there's no requirement to mention it in E820, so we shouldn't look at E820 to validate the ECAM space described by MCFG. In 2006,946f2ee5c7
("[PATCH] i386/x86-64: Check that MCFG points to an e820 reserved area") added a sanity check of E820 to work around buggy MCFG tables, but that over-aggressive validation causes failures like this one. Keep the E820 validation check for machines older than 2016, an arbitrary ten years after946f2ee5c7
, so machines that depend on it don't break. Skip the early E820 check for 2016 and newer BIOSes since there's no requirement to describe ECAM in E820. Link: https://lore.kernel.org/r/20240417204012.215030-2-helgaas@kernel.org Fixes:07eab0901e
("efi/x86: Remove EfiMemoryMappedIO from E820 map") Reported-by: Mateusz Kaduk <mateusz.kaduk@gmail.com> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=218444 Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Tested-by: Mateusz Kaduk <mateusz.kaduk@gmail.com> Reviewed-by: Andy Shevchenko <andy@kernel.org> Reviewed-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com> Cc: stable@vger.kernel.org
877 lines
21 KiB
C
877 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Low-level direct PCI config space access via ECAM - common code between
|
|
* i386 and x86-64.
|
|
*
|
|
* This code does:
|
|
* - known chipset handling
|
|
* - ACPI decoding and validation
|
|
*
|
|
* Per-architecture code takes care of the mappings and accesses
|
|
* themselves.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "PCI: " fmt
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/efi.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/init.h>
|
|
#include <linux/bitmap.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/rculist.h>
|
|
#include <asm/e820/api.h>
|
|
#include <asm/pci_x86.h>
|
|
#include <asm/acpi.h>
|
|
|
|
/* Indicate if the ECAM resources have been placed into the resource table */
|
|
static bool pci_mmcfg_running_state;
|
|
static bool pci_mmcfg_arch_init_failed;
|
|
static DEFINE_MUTEX(pci_mmcfg_lock);
|
|
#define pci_mmcfg_lock_held() lock_is_held(&(pci_mmcfg_lock).dep_map)
|
|
|
|
LIST_HEAD(pci_mmcfg_list);
|
|
|
|
static void __init pci_mmconfig_remove(struct pci_mmcfg_region *cfg)
|
|
{
|
|
if (cfg->res.parent)
|
|
release_resource(&cfg->res);
|
|
list_del(&cfg->list);
|
|
kfree(cfg);
|
|
}
|
|
|
|
static void __init free_all_mmcfg(void)
|
|
{
|
|
struct pci_mmcfg_region *cfg, *tmp;
|
|
|
|
pci_mmcfg_arch_free();
|
|
list_for_each_entry_safe(cfg, tmp, &pci_mmcfg_list, list)
|
|
pci_mmconfig_remove(cfg);
|
|
}
|
|
|
|
static 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_rcu(cfg, &pci_mmcfg_list, list, pci_mmcfg_lock_held()) {
|
|
if (cfg->segment > new->segment ||
|
|
(cfg->segment == new->segment &&
|
|
cfg->start_bus >= new->start_bus)) {
|
|
list_add_tail_rcu(&new->list, &cfg->list);
|
|
return;
|
|
}
|
|
}
|
|
list_add_tail_rcu(&new->list, &pci_mmcfg_list);
|
|
}
|
|
|
|
static struct pci_mmcfg_region *pci_mmconfig_alloc(int segment, int start,
|
|
int end, u64 addr)
|
|
{
|
|
struct pci_mmcfg_region *new;
|
|
struct resource *res;
|
|
|
|
if (addr == 0)
|
|
return NULL;
|
|
|
|
new = kzalloc(sizeof(*new), GFP_KERNEL);
|
|
if (!new)
|
|
return NULL;
|
|
|
|
new->address = addr;
|
|
new->segment = segment;
|
|
new->start_bus = start;
|
|
new->end_bus = end;
|
|
|
|
res = &new->res;
|
|
res->start = addr + PCI_MMCFG_BUS_OFFSET(start);
|
|
res->end = addr + PCI_MMCFG_BUS_OFFSET(end + 1) - 1;
|
|
res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
|
|
snprintf(new->name, PCI_MMCFG_RESOURCE_NAME_LEN,
|
|
"PCI ECAM %04x [bus %02x-%02x]", segment, start, end);
|
|
res->name = new->name;
|
|
|
|
return new;
|
|
}
|
|
|
|
struct pci_mmcfg_region *__init pci_mmconfig_add(int segment, int start,
|
|
int end, u64 addr)
|
|
{
|
|
struct pci_mmcfg_region *new;
|
|
|
|
new = pci_mmconfig_alloc(segment, start, end, addr);
|
|
if (!new)
|
|
return NULL;
|
|
|
|
mutex_lock(&pci_mmcfg_lock);
|
|
list_add_sorted(new);
|
|
mutex_unlock(&pci_mmcfg_lock);
|
|
|
|
pr_info("ECAM %pR (base %#lx) for domain %04x [bus %02x-%02x]\n",
|
|
&new->res, (unsigned long)addr, segment, start, end);
|
|
|
|
return new;
|
|
}
|
|
|
|
struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus)
|
|
{
|
|
struct pci_mmcfg_region *cfg;
|
|
|
|
list_for_each_entry_rcu(cfg, &pci_mmcfg_list, list, pci_mmcfg_lock_held())
|
|
if (cfg->segment == segment &&
|
|
cfg->start_bus <= bus && bus <= cfg->end_bus)
|
|
return cfg;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *__init pci_mmcfg_e7520(void)
|
|
{
|
|
u32 win;
|
|
raw_pci_ops->read(0, 0, PCI_DEVFN(0, 0), 0xce, 2, &win);
|
|
|
|
win = win & 0xf000;
|
|
if (win == 0x0000 || win == 0xf000)
|
|
return NULL;
|
|
|
|
if (pci_mmconfig_add(0, 0, 255, win << 16) == NULL)
|
|
return NULL;
|
|
|
|
return "Intel Corporation E7520 Memory Controller Hub";
|
|
}
|
|
|
|
static const char *__init pci_mmcfg_intel_945(void)
|
|
{
|
|
u32 pciexbar, mask = 0, len = 0;
|
|
|
|
raw_pci_ops->read(0, 0, PCI_DEVFN(0, 0), 0x48, 4, &pciexbar);
|
|
|
|
/* Enable bit */
|
|
if (!(pciexbar & 1))
|
|
return NULL;
|
|
|
|
/* 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:
|
|
return NULL;
|
|
}
|
|
|
|
/* Errata #2, things break when not aligned on a 256Mb boundary */
|
|
/* Can only happen in 64M/128M mode */
|
|
|
|
if ((pciexbar & mask) & 0x0fffffffU)
|
|
return NULL;
|
|
|
|
/* Don't hit the APIC registers and their friends */
|
|
if ((pciexbar & mask) >= 0xf0000000U)
|
|
return NULL;
|
|
|
|
if (pci_mmconfig_add(0, 0, (len >> 20) - 1, pciexbar & mask) == NULL)
|
|
return NULL;
|
|
|
|
return "Intel Corporation 945G/GZ/P/PL Express Memory Controller Hub";
|
|
}
|
|
|
|
static const char *__init pci_mmcfg_amd_fam10h(void)
|
|
{
|
|
u32 low, high, address;
|
|
u64 base, msr;
|
|
int i;
|
|
unsigned segnbits = 0, busnbits, end_bus;
|
|
|
|
if (!(pci_probe & PCI_CHECK_ENABLE_AMD_MMCONF))
|
|
return NULL;
|
|
|
|
address = MSR_FAM10H_MMIO_CONF_BASE;
|
|
if (rdmsr_safe(address, &low, &high))
|
|
return NULL;
|
|
|
|
msr = high;
|
|
msr <<= 32;
|
|
msr |= low;
|
|
|
|
/* ECAM is not enabled */
|
|
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;
|
|
}
|
|
|
|
end_bus = (1 << busnbits) - 1;
|
|
for (i = 0; i < (1 << segnbits); i++)
|
|
if (pci_mmconfig_add(i, 0, end_bus,
|
|
base + (1<<28) * i) == NULL) {
|
|
free_all_mmcfg();
|
|
return NULL;
|
|
}
|
|
|
|
return "AMD Family 10h NB";
|
|
}
|
|
|
|
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 __initconst = 0x90;
|
|
static const u32 extcfg_regsize __initconst = 4;
|
|
static const u32 extcfg_enable_mask __initconst = 1 << 31;
|
|
static const u32 extcfg_start_mask __initconst = 0xff << 16;
|
|
static const int extcfg_start_shift __initconst = 16;
|
|
static const u32 extcfg_size_mask __initconst = 0x3 << 28;
|
|
static const int extcfg_size_shift __initconst = 28;
|
|
static const int extcfg_sizebus[] __initconst = {
|
|
0x100, 0x80, 0x40, 0x20
|
|
};
|
|
static const u32 extcfg_base_mask[] __initconst = {
|
|
0x7ff8, 0x7ffc, 0x7ffe, 0x7fff
|
|
};
|
|
static const int extcfg_base_lshift __initconst = 25;
|
|
|
|
/*
|
|
* do check if amd fam10h already took over
|
|
*/
|
|
if (!acpi_disabled || !list_empty(&pci_mmcfg_list) || mcp55_checked)
|
|
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;
|
|
if (pci_mmconfig_add(0, start, end, base) == NULL)
|
|
continue;
|
|
mcp55_mmconf_found++;
|
|
}
|
|
|
|
if (!mcp55_mmconf_found)
|
|
return NULL;
|
|
|
|
return "nVidia MCP55";
|
|
}
|
|
|
|
struct pci_mmcfg_hostbridge_probe {
|
|
u32 bus;
|
|
u32 devfn;
|
|
u32 vendor;
|
|
u32 device;
|
|
const char *(*probe)(void);
|
|
};
|
|
|
|
static const struct pci_mmcfg_hostbridge_probe pci_mmcfg_probes[] __initconst = {
|
|
{ 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 },
|
|
{ 0, PCI_DEVFN(0, 0), PCI_VENDOR_ID_NVIDIA,
|
|
0x0369, pci_mmcfg_nvidia_mcp55 },
|
|
};
|
|
|
|
static void __init pci_mmcfg_check_end_bus_number(void)
|
|
{
|
|
struct pci_mmcfg_region *cfg, *cfgx;
|
|
|
|
/* Fixup overlaps */
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
|
if (cfg->end_bus < cfg->start_bus)
|
|
cfg->end_bus = 255;
|
|
|
|
/* Don't access the list head ! */
|
|
if (cfg->list.next == &pci_mmcfg_list)
|
|
break;
|
|
|
|
cfgx = list_entry(cfg->list.next, typeof(*cfg), list);
|
|
if (cfg->end_bus >= cfgx->start_bus)
|
|
cfg->end_bus = cfgx->start_bus - 1;
|
|
}
|
|
}
|
|
|
|
static int __init pci_mmcfg_check_hostbridge(void)
|
|
{
|
|
u32 l;
|
|
u32 bus, devfn;
|
|
u16 vendor, device;
|
|
int i;
|
|
const char *name;
|
|
|
|
if (!raw_pci_ops)
|
|
return 0;
|
|
|
|
free_all_mmcfg();
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pci_mmcfg_probes); i++) {
|
|
bus = pci_mmcfg_probes[i].bus;
|
|
devfn = pci_mmcfg_probes[i].devfn;
|
|
raw_pci_ops->read(0, bus, devfn, 0, 4, &l);
|
|
vendor = l & 0xffff;
|
|
device = (l >> 16) & 0xffff;
|
|
|
|
name = NULL;
|
|
if (pci_mmcfg_probes[i].vendor == vendor &&
|
|
pci_mmcfg_probes[i].device == device)
|
|
name = pci_mmcfg_probes[i].probe();
|
|
|
|
if (name)
|
|
pr_info("%s with ECAM support\n", name);
|
|
}
|
|
|
|
/* some end_bus_number is crazy, fix it */
|
|
pci_mmcfg_check_end_bus_number();
|
|
|
|
return !list_empty(&pci_mmcfg_list);
|
|
}
|
|
|
|
static acpi_status 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) &&
|
|
(mcfg_res->end < (fixmem32->address +
|
|
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.address_length <= 0) ||
|
|
(address.resource_type != ACPI_MEMORY_RANGE))
|
|
return AE_OK;
|
|
|
|
if ((mcfg_res->start >= address.address.minimum) &&
|
|
(mcfg_res->end < (address.address.minimum + address.address.address_length))) {
|
|
mcfg_res->flags = 1;
|
|
return AE_CTRL_TERMINATE;
|
|
}
|
|
return AE_OK;
|
|
}
|
|
|
|
static acpi_status 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;
|
|
}
|
|
|
|
static bool is_acpi_reserved(u64 start, u64 end, enum e820_type not_used)
|
|
{
|
|
struct resource mcfg_res;
|
|
|
|
mcfg_res.start = start;
|
|
mcfg_res.end = end - 1;
|
|
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;
|
|
}
|
|
|
|
static bool is_efi_mmio(struct resource *res)
|
|
{
|
|
#ifdef CONFIG_EFI
|
|
u64 start = res->start;
|
|
u64 end = res->start + resource_size(res);
|
|
efi_memory_desc_t *md;
|
|
u64 size, mmio_start, mmio_end;
|
|
|
|
for_each_efi_memory_desc(md) {
|
|
if (md->type == EFI_MEMORY_MAPPED_IO) {
|
|
size = md->num_pages << EFI_PAGE_SHIFT;
|
|
mmio_start = md->phys_addr;
|
|
mmio_end = mmio_start + size;
|
|
|
|
if (mmio_start <= start && end <= mmio_end)
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
typedef bool (*check_reserved_t)(u64 start, u64 end, enum e820_type type);
|
|
|
|
static bool __ref is_mmconf_reserved(check_reserved_t is_reserved,
|
|
struct pci_mmcfg_region *cfg,
|
|
struct device *dev, const char *method)
|
|
{
|
|
u64 addr = cfg->res.start;
|
|
u64 size = resource_size(&cfg->res);
|
|
u64 old_size = size;
|
|
int num_buses;
|
|
|
|
while (!is_reserved(addr, addr + size, E820_TYPE_RESERVED)) {
|
|
size >>= 1;
|
|
if (size < (16UL<<20))
|
|
break;
|
|
}
|
|
|
|
if (size < (16UL<<20) && size != old_size)
|
|
return false;
|
|
|
|
if (dev)
|
|
dev_info(dev, "ECAM %pR reserved as %s\n",
|
|
&cfg->res, method);
|
|
else
|
|
pr_info("ECAM %pR reserved as %s\n", &cfg->res, method);
|
|
|
|
if (old_size != size) {
|
|
/* update end_bus */
|
|
cfg->end_bus = cfg->start_bus + ((size>>20) - 1);
|
|
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 ECAM %04x [bus %02x-%02x]",
|
|
cfg->segment, cfg->start_bus, cfg->end_bus);
|
|
|
|
if (dev)
|
|
dev_info(dev, "ECAM %pR (base %#lx) (size reduced!)\n",
|
|
&cfg->res, (unsigned long) cfg->address);
|
|
else
|
|
pr_info("ECAM %pR (base %#lx) for %04x [bus%02x-%02x] (size reduced!)\n",
|
|
&cfg->res, (unsigned long) cfg->address,
|
|
cfg->segment, cfg->start_bus, cfg->end_bus);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool __ref pci_mmcfg_reserved(struct device *dev,
|
|
struct pci_mmcfg_region *cfg, int early)
|
|
{
|
|
struct resource *conflict;
|
|
|
|
if (early) {
|
|
|
|
/*
|
|
* Don't try to do this check unless configuration type 1
|
|
* is available. How about type 2?
|
|
*/
|
|
|
|
/*
|
|
* 946f2ee5c731 ("Check that MCFG points to an e820
|
|
* reserved area") added this E820 check in 2006 to work
|
|
* around BIOS defects.
|
|
*
|
|
* Per PCI Firmware r3.3, sec 4.1.2, ECAM space must be
|
|
* reserved by a PNP0C02 resource, but it need not be
|
|
* mentioned in E820. Before the ACPI interpreter is
|
|
* available, we can't check for PNP0C02 resources, so
|
|
* there's no reliable way to verify the region in this
|
|
* early check. Keep it only for the old machines that
|
|
* motivated 946f2ee5c731.
|
|
*/
|
|
if (dmi_get_bios_year() < 2016 && raw_pci_ops)
|
|
return is_mmconf_reserved(e820__mapped_all, cfg, dev,
|
|
"E820 entry");
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!acpi_disabled) {
|
|
if (is_mmconf_reserved(is_acpi_reserved, cfg, dev,
|
|
"ACPI motherboard resource"))
|
|
return true;
|
|
|
|
if (dev)
|
|
dev_info(dev, FW_INFO "ECAM %pR not reserved in ACPI motherboard resources\n",
|
|
&cfg->res);
|
|
else
|
|
pr_info(FW_INFO "ECAM %pR not reserved in ACPI motherboard resources\n",
|
|
&cfg->res);
|
|
|
|
if (is_efi_mmio(&cfg->res)) {
|
|
pr_info("ECAM %pR is EfiMemoryMappedIO; assuming valid\n",
|
|
&cfg->res);
|
|
conflict = insert_resource_conflict(&iomem_resource,
|
|
&cfg->res);
|
|
if (conflict)
|
|
pr_warn("ECAM %pR conflicts with %s %pR\n",
|
|
&cfg->res, conflict->name, conflict);
|
|
else
|
|
pr_info("ECAM %pR reserved to work around lack of ACPI motherboard _CRS\n",
|
|
&cfg->res);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* e820__mapped_all() is marked as __init.
|
|
* All entries from ACPI MCFG table have been checked at boot time.
|
|
* For MCFG information constructed from hotpluggable host bridge's
|
|
* _CBA method, just assume it's reserved.
|
|
*/
|
|
return pci_mmcfg_running_state;
|
|
}
|
|
|
|
static void __init pci_mmcfg_reject_broken(int early)
|
|
{
|
|
struct pci_mmcfg_region *cfg;
|
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
|
if (!pci_mmcfg_reserved(NULL, cfg, early)) {
|
|
pr_info("not using ECAM (%pR not reserved)\n",
|
|
&cfg->res);
|
|
free_all_mmcfg();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool __init acpi_mcfg_valid_entry(struct acpi_table_mcfg *mcfg,
|
|
struct acpi_mcfg_allocation *cfg)
|
|
{
|
|
if (cfg->address < 0xFFFFFFFF)
|
|
return true;
|
|
|
|
if (!strncmp(mcfg->header.oem_id, "SGI", 3))
|
|
return true;
|
|
|
|
if ((mcfg->header.revision >= 1) && (dmi_get_bios_year() >= 2010))
|
|
return true;
|
|
|
|
pr_err("ECAM at %#llx for %04x [bus %02x-%02x] is above 4GB, ignored\n",
|
|
cfg->address, cfg->pci_segment, cfg->start_bus_number,
|
|
cfg->end_bus_number);
|
|
return false;
|
|
}
|
|
|
|
static int __init pci_parse_mcfg(struct acpi_table_header *header)
|
|
{
|
|
struct acpi_table_mcfg *mcfg;
|
|
struct acpi_mcfg_allocation *cfg_table, *cfg;
|
|
unsigned long i;
|
|
int entries;
|
|
|
|
if (!header)
|
|
return -EINVAL;
|
|
|
|
mcfg = (struct acpi_table_mcfg *)header;
|
|
|
|
/* how many config structures do we have */
|
|
free_all_mmcfg();
|
|
entries = 0;
|
|
i = header->length - sizeof(struct acpi_table_mcfg);
|
|
while (i >= sizeof(struct acpi_mcfg_allocation)) {
|
|
entries++;
|
|
i -= sizeof(struct acpi_mcfg_allocation);
|
|
}
|
|
if (entries == 0) {
|
|
pr_err("MCFG has no entries\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
cfg_table = (struct acpi_mcfg_allocation *) &mcfg[1];
|
|
for (i = 0; i < entries; i++) {
|
|
cfg = &cfg_table[i];
|
|
if (!acpi_mcfg_valid_entry(mcfg, cfg)) {
|
|
free_all_mmcfg();
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number,
|
|
cfg->end_bus_number, cfg->address) == NULL) {
|
|
pr_warn("no memory for MCFG entries\n");
|
|
free_all_mmcfg();
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_ACPI_APEI
|
|
extern int (*arch_apei_filter_addr)(int (*func)(__u64 start, __u64 size,
|
|
void *data), void *data);
|
|
|
|
static int pci_mmcfg_for_each_region(int (*func)(__u64 start, __u64 size,
|
|
void *data), void *data)
|
|
{
|
|
struct pci_mmcfg_region *cfg;
|
|
int rc;
|
|
|
|
if (list_empty(&pci_mmcfg_list))
|
|
return 0;
|
|
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
|
rc = func(cfg->res.start, resource_size(&cfg->res), data);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#define set_apei_filter() (arch_apei_filter_addr = pci_mmcfg_for_each_region)
|
|
#else
|
|
#define set_apei_filter()
|
|
#endif
|
|
|
|
static void __init __pci_mmcfg_init(int early)
|
|
{
|
|
pr_debug("%s(%s)\n", __func__, early ? "early" : "late");
|
|
|
|
pci_mmcfg_reject_broken(early);
|
|
if (list_empty(&pci_mmcfg_list))
|
|
return;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (pci_mmcfg_arch_init())
|
|
pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF;
|
|
else {
|
|
free_all_mmcfg();
|
|
pci_mmcfg_arch_init_failed = true;
|
|
}
|
|
}
|
|
|
|
static int __initdata known_bridge;
|
|
|
|
void __init pci_mmcfg_early_init(void)
|
|
{
|
|
pr_debug("%s() pci_probe %#x\n", __func__, pci_probe);
|
|
|
|
if (pci_probe & PCI_PROBE_MMCONF) {
|
|
if (pci_mmcfg_check_hostbridge())
|
|
known_bridge = 1;
|
|
else
|
|
acpi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg);
|
|
__pci_mmcfg_init(1);
|
|
|
|
set_apei_filter();
|
|
}
|
|
}
|
|
|
|
void __init pci_mmcfg_late_init(void)
|
|
{
|
|
pr_debug("%s() pci_probe %#x\n", __func__, pci_probe);
|
|
|
|
/* ECAM disabled */
|
|
if ((pci_probe & PCI_PROBE_MMCONF) == 0)
|
|
return;
|
|
|
|
if (known_bridge)
|
|
return;
|
|
|
|
/* ECAM hasn't been enabled yet, try again */
|
|
if (pci_probe & PCI_PROBE_MASK & ~PCI_PROBE_MMCONF) {
|
|
acpi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg);
|
|
__pci_mmcfg_init(0);
|
|
}
|
|
}
|
|
|
|
static int __init pci_mmcfg_late_insert_resources(void)
|
|
{
|
|
struct pci_mmcfg_region *cfg;
|
|
|
|
pci_mmcfg_running_state = true;
|
|
|
|
pr_debug("%s() pci_probe %#x\n", __func__, pci_probe);
|
|
|
|
/* If we are not using ECAM, don't insert the resources. */
|
|
if ((pci_probe & PCI_PROBE_MMCONF) == 0)
|
|
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.
|
|
*/
|
|
list_for_each_entry(cfg, &pci_mmcfg_list, list) {
|
|
if (!cfg->res.parent) {
|
|
pr_debug("%s() insert %pR\n", __func__, &cfg->res);
|
|
insert_resource(&iomem_resource, &cfg->res);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Perform ECAM 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);
|
|
|
|
/* Add ECAM information for host bridges */
|
|
int pci_mmconfig_insert(struct device *dev, u16 seg, u8 start, u8 end,
|
|
phys_addr_t addr)
|
|
{
|
|
int rc;
|
|
struct resource *tmp = NULL;
|
|
struct pci_mmcfg_region *cfg;
|
|
|
|
dev_dbg(dev, "%s(%04x [bus %02x-%02x])\n", __func__, seg, start, end);
|
|
|
|
if (!(pci_probe & PCI_PROBE_MMCONF) || pci_mmcfg_arch_init_failed)
|
|
return -ENODEV;
|
|
|
|
if (start > end)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&pci_mmcfg_lock);
|
|
cfg = pci_mmconfig_lookup(seg, start);
|
|
if (cfg) {
|
|
if (cfg->end_bus < end)
|
|
dev_info(dev, FW_INFO "ECAM %pR for domain %04x [bus %02x-%02x] only partially covers this bridge\n",
|
|
&cfg->res, cfg->segment, cfg->start_bus,
|
|
cfg->end_bus);
|
|
mutex_unlock(&pci_mmcfg_lock);
|
|
return -EEXIST;
|
|
}
|
|
|
|
/*
|
|
* Don't move earlier; we must return -EEXIST, not -EINVAL, if
|
|
* pci_mmconfig_lookup() finds something
|
|
*/
|
|
if (!addr) {
|
|
mutex_unlock(&pci_mmcfg_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = -EBUSY;
|
|
cfg = pci_mmconfig_alloc(seg, start, end, addr);
|
|
if (cfg == NULL) {
|
|
dev_warn(dev, "fail to add ECAM (out of memory)\n");
|
|
rc = -ENOMEM;
|
|
} else if (!pci_mmcfg_reserved(dev, cfg, 0)) {
|
|
dev_warn(dev, FW_BUG "ECAM %pR isn't reserved\n",
|
|
&cfg->res);
|
|
} else {
|
|
/* Insert resource if it's not in boot stage */
|
|
if (pci_mmcfg_running_state)
|
|
tmp = insert_resource_conflict(&iomem_resource,
|
|
&cfg->res);
|
|
|
|
if (tmp) {
|
|
dev_warn(dev, "ECAM %pR conflicts with %s %pR\n",
|
|
&cfg->res, tmp->name, tmp);
|
|
} else if (pci_mmcfg_arch_map(cfg)) {
|
|
dev_warn(dev, "fail to map ECAM %pR\n", &cfg->res);
|
|
} else {
|
|
list_add_sorted(cfg);
|
|
dev_info(dev, "ECAM %pR (base %#lx)\n",
|
|
&cfg->res, (unsigned long)addr);
|
|
cfg = NULL;
|
|
rc = 0;
|
|
}
|
|
}
|
|
|
|
if (cfg) {
|
|
if (cfg->res.parent)
|
|
release_resource(&cfg->res);
|
|
kfree(cfg);
|
|
}
|
|
|
|
mutex_unlock(&pci_mmcfg_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Delete ECAM information for host bridges */
|
|
int pci_mmconfig_delete(u16 seg, u8 start, u8 end)
|
|
{
|
|
struct pci_mmcfg_region *cfg;
|
|
|
|
mutex_lock(&pci_mmcfg_lock);
|
|
list_for_each_entry_rcu(cfg, &pci_mmcfg_list, list)
|
|
if (cfg->segment == seg && cfg->start_bus == start &&
|
|
cfg->end_bus == end) {
|
|
list_del_rcu(&cfg->list);
|
|
synchronize_rcu();
|
|
pci_mmcfg_arch_unmap(cfg);
|
|
if (cfg->res.parent)
|
|
release_resource(&cfg->res);
|
|
mutex_unlock(&pci_mmcfg_lock);
|
|
kfree(cfg);
|
|
return 0;
|
|
}
|
|
mutex_unlock(&pci_mmcfg_lock);
|
|
|
|
return -ENOENT;
|
|
}
|