forked from Minki/linux
7d40c0f70d
Currently at root bridge preparation, the corresponding ACPI device will be set as the companion, however for a Hyper-V virtual PCI root bridge, there is no corresponding ACPI device, because a Hyper-V virtual PCI root bridge is discovered via VMBus rather than ACPI table. In order to support this, we need to make pcibios_root_bridge_prepare() work with cfg->parent being NULL. Use a NULL pointer as the ACPI device if there is no corresponding ACPI device, and this is fine because: 1) ACPI_COMPANION_SET() can work with the second parameter being NULL, 2) semantically, if a NULL pointer is set via ACPI_COMPANION_SET(), ACPI_COMPANION() (the read API for this field) will return NULL, and since ACPI_COMPANION() may return NULL, so users must have handled the cases where it returns NULL, and 3) since there is no corresponding ACPI device, it would be wrong to use any other value here. Link: https://lore.kernel.org/r/20210726180657.142727-5-boqun.feng@gmail.com Signed-off-by: Boqun Feng <boqun.feng@gmail.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Acked-by: Catalin Marinas <catalin.marinas@arm.com>
236 lines
5.4 KiB
C
236 lines
5.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Code borrowed from powerpc/kernel/pci-common.c
|
|
*
|
|
* Copyright (C) 2003 Anton Blanchard <anton@au.ibm.com>, IBM
|
|
* Copyright (C) 2014 ARM Ltd.
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/of_pci.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/pci-acpi.h>
|
|
#include <linux/pci-ecam.h>
|
|
#include <linux/slab.h>
|
|
|
|
#ifdef CONFIG_ACPI
|
|
/*
|
|
* Try to assign the IRQ number when probing a new device
|
|
*/
|
|
int pcibios_alloc_irq(struct pci_dev *dev)
|
|
{
|
|
if (!acpi_disabled)
|
|
acpi_pci_irq_enable(dev);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* raw_pci_read/write - Platform-specific PCI config space access.
|
|
*/
|
|
int raw_pci_read(unsigned int domain, unsigned int bus,
|
|
unsigned int devfn, int reg, int len, u32 *val)
|
|
{
|
|
struct pci_bus *b = pci_find_bus(domain, bus);
|
|
|
|
if (!b)
|
|
return PCIBIOS_DEVICE_NOT_FOUND;
|
|
return b->ops->read(b, devfn, reg, len, val);
|
|
}
|
|
|
|
int raw_pci_write(unsigned int domain, unsigned int bus,
|
|
unsigned int devfn, int reg, int len, u32 val)
|
|
{
|
|
struct pci_bus *b = pci_find_bus(domain, bus);
|
|
|
|
if (!b)
|
|
return PCIBIOS_DEVICE_NOT_FOUND;
|
|
return b->ops->write(b, devfn, reg, len, val);
|
|
}
|
|
|
|
#ifdef CONFIG_NUMA
|
|
|
|
int pcibus_to_node(struct pci_bus *bus)
|
|
{
|
|
return dev_to_node(&bus->dev);
|
|
}
|
|
EXPORT_SYMBOL(pcibus_to_node);
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_ACPI
|
|
|
|
struct acpi_pci_generic_root_info {
|
|
struct acpi_pci_root_info common;
|
|
struct pci_config_window *cfg; /* config space mapping */
|
|
};
|
|
|
|
int acpi_pci_bus_find_domain_nr(struct pci_bus *bus)
|
|
{
|
|
struct pci_config_window *cfg = bus->sysdata;
|
|
struct acpi_device *adev = to_acpi_device(cfg->parent);
|
|
struct acpi_pci_root *root = acpi_driver_data(adev);
|
|
|
|
return root->segment;
|
|
}
|
|
|
|
int pcibios_root_bridge_prepare(struct pci_host_bridge *bridge)
|
|
{
|
|
struct pci_config_window *cfg;
|
|
struct acpi_device *adev;
|
|
struct device *bus_dev;
|
|
|
|
if (acpi_disabled)
|
|
return 0;
|
|
|
|
cfg = bridge->bus->sysdata;
|
|
|
|
/*
|
|
* On Hyper-V there is no corresponding ACPI device for a root bridge,
|
|
* therefore ->parent is set as NULL by the driver. And set 'adev' as
|
|
* NULL in this case because there is no proper ACPI device.
|
|
*/
|
|
if (!cfg->parent)
|
|
adev = NULL;
|
|
else
|
|
adev = to_acpi_device(cfg->parent);
|
|
|
|
bus_dev = &bridge->bus->dev;
|
|
|
|
ACPI_COMPANION_SET(&bridge->dev, adev);
|
|
set_dev_node(bus_dev, acpi_get_node(acpi_device_handle(adev)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pci_acpi_root_prepare_resources(struct acpi_pci_root_info *ci)
|
|
{
|
|
struct resource_entry *entry, *tmp;
|
|
int status;
|
|
|
|
status = acpi_pci_probe_root_resources(ci);
|
|
resource_list_for_each_entry_safe(entry, tmp, &ci->resources) {
|
|
if (!(entry->res->flags & IORESOURCE_WINDOW))
|
|
resource_list_destroy_entry(entry);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Lookup the bus range for the domain in MCFG, and set up config space
|
|
* mapping.
|
|
*/
|
|
static struct pci_config_window *
|
|
pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root)
|
|
{
|
|
struct device *dev = &root->device->dev;
|
|
struct resource *bus_res = &root->secondary;
|
|
u16 seg = root->segment;
|
|
const struct pci_ecam_ops *ecam_ops;
|
|
struct resource cfgres;
|
|
struct acpi_device *adev;
|
|
struct pci_config_window *cfg;
|
|
int ret;
|
|
|
|
ret = pci_mcfg_lookup(root, &cfgres, &ecam_ops);
|
|
if (ret) {
|
|
dev_err(dev, "%04x:%pR ECAM region not found\n", seg, bus_res);
|
|
return NULL;
|
|
}
|
|
|
|
adev = acpi_resource_consumer(&cfgres);
|
|
if (adev)
|
|
dev_info(dev, "ECAM area %pR reserved by %s\n", &cfgres,
|
|
dev_name(&adev->dev));
|
|
else
|
|
dev_warn(dev, FW_BUG "ECAM area %pR not reserved in ACPI namespace\n",
|
|
&cfgres);
|
|
|
|
cfg = pci_ecam_create(dev, &cfgres, bus_res, ecam_ops);
|
|
if (IS_ERR(cfg)) {
|
|
dev_err(dev, "%04x:%pR error %ld mapping ECAM\n", seg, bus_res,
|
|
PTR_ERR(cfg));
|
|
return NULL;
|
|
}
|
|
|
|
return cfg;
|
|
}
|
|
|
|
/* release_info: free resources allocated by init_info */
|
|
static void pci_acpi_generic_release_info(struct acpi_pci_root_info *ci)
|
|
{
|
|
struct acpi_pci_generic_root_info *ri;
|
|
|
|
ri = container_of(ci, struct acpi_pci_generic_root_info, common);
|
|
pci_ecam_free(ri->cfg);
|
|
kfree(ci->ops);
|
|
kfree(ri);
|
|
}
|
|
|
|
/* Interface called from ACPI code to setup PCI host controller */
|
|
struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
|
|
{
|
|
struct acpi_pci_generic_root_info *ri;
|
|
struct pci_bus *bus, *child;
|
|
struct acpi_pci_root_ops *root_ops;
|
|
struct pci_host_bridge *host;
|
|
|
|
ri = kzalloc(sizeof(*ri), GFP_KERNEL);
|
|
if (!ri)
|
|
return NULL;
|
|
|
|
root_ops = kzalloc(sizeof(*root_ops), GFP_KERNEL);
|
|
if (!root_ops) {
|
|
kfree(ri);
|
|
return NULL;
|
|
}
|
|
|
|
ri->cfg = pci_acpi_setup_ecam_mapping(root);
|
|
if (!ri->cfg) {
|
|
kfree(ri);
|
|
kfree(root_ops);
|
|
return NULL;
|
|
}
|
|
|
|
root_ops->release_info = pci_acpi_generic_release_info;
|
|
root_ops->prepare_resources = pci_acpi_root_prepare_resources;
|
|
root_ops->pci_ops = (struct pci_ops *)&ri->cfg->ops->pci_ops;
|
|
bus = acpi_pci_root_create(root, root_ops, &ri->common, ri->cfg);
|
|
if (!bus)
|
|
return NULL;
|
|
|
|
/* If we must preserve the resource configuration, claim now */
|
|
host = pci_find_host_bridge(bus);
|
|
if (host->preserve_config)
|
|
pci_bus_claim_resources(bus);
|
|
|
|
/*
|
|
* Assign whatever was left unassigned. If we didn't claim above,
|
|
* this will reassign everything.
|
|
*/
|
|
pci_assign_unassigned_root_bus_resources(bus);
|
|
|
|
list_for_each_entry(child, &bus->children, node)
|
|
pcie_bus_configure_settings(child);
|
|
|
|
return bus;
|
|
}
|
|
|
|
void pcibios_add_bus(struct pci_bus *bus)
|
|
{
|
|
acpi_pci_add_bus(bus);
|
|
}
|
|
|
|
void pcibios_remove_bus(struct pci_bus *bus)
|
|
{
|
|
acpi_pci_remove_bus(bus);
|
|
}
|
|
|
|
#endif
|