mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-15 09:56:58 +07:00
01fcb7f777
Add support for unbinding the generic PCI host controller. This is particularly useful when working in virtual environments where the controller may come and go, but possibly not only there. Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> CC: Will Deacon <will.deacon@arm.com> CC: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
119 lines
2.6 KiB
C
119 lines
2.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Generic PCI host driver common code
|
|
*
|
|
* Copyright (C) 2014 ARM Limited
|
|
*
|
|
* Author: Will Deacon <will.deacon@arm.com>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_pci.h>
|
|
#include <linux/pci-ecam.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
static void gen_pci_unmap_cfg(void *ptr)
|
|
{
|
|
pci_ecam_free((struct pci_config_window *)ptr);
|
|
}
|
|
|
|
static struct pci_config_window *gen_pci_init(struct device *dev,
|
|
struct list_head *resources, struct pci_ecam_ops *ops)
|
|
{
|
|
int err;
|
|
struct resource cfgres;
|
|
struct resource *bus_range = NULL;
|
|
struct pci_config_window *cfg;
|
|
|
|
/* Parse our PCI ranges and request their resources */
|
|
err = pci_parse_request_of_pci_ranges(dev, resources, &bus_range);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
err = of_address_to_resource(dev->of_node, 0, &cfgres);
|
|
if (err) {
|
|
dev_err(dev, "missing \"reg\" property\n");
|
|
goto err_out;
|
|
}
|
|
|
|
cfg = pci_ecam_create(dev, &cfgres, bus_range, ops);
|
|
if (IS_ERR(cfg)) {
|
|
err = PTR_ERR(cfg);
|
|
goto err_out;
|
|
}
|
|
|
|
err = devm_add_action(dev, gen_pci_unmap_cfg, cfg);
|
|
if (err) {
|
|
gen_pci_unmap_cfg(cfg);
|
|
goto err_out;
|
|
}
|
|
return cfg;
|
|
|
|
err_out:
|
|
pci_free_resource_list(resources);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
int pci_host_common_probe(struct platform_device *pdev,
|
|
struct pci_ecam_ops *ops)
|
|
{
|
|
const char *type;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct pci_host_bridge *bridge;
|
|
struct pci_config_window *cfg;
|
|
struct list_head resources;
|
|
int ret;
|
|
|
|
bridge = devm_pci_alloc_host_bridge(dev, 0);
|
|
if (!bridge)
|
|
return -ENOMEM;
|
|
|
|
type = of_get_property(np, "device_type", NULL);
|
|
if (!type || strcmp(type, "pci")) {
|
|
dev_err(dev, "invalid \"device_type\" %s\n", type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
of_pci_check_probe_only();
|
|
|
|
/* Parse and map our Configuration Space windows */
|
|
cfg = gen_pci_init(dev, &resources, ops);
|
|
if (IS_ERR(cfg))
|
|
return PTR_ERR(cfg);
|
|
|
|
/* Do not reassign resources if probe only */
|
|
if (!pci_has_flag(PCI_PROBE_ONLY))
|
|
pci_add_flags(PCI_REASSIGN_ALL_BUS);
|
|
|
|
list_splice_init(&resources, &bridge->windows);
|
|
bridge->dev.parent = dev;
|
|
bridge->sysdata = cfg;
|
|
bridge->busnr = cfg->busr.start;
|
|
bridge->ops = &ops->pci_ops;
|
|
bridge->map_irq = of_irq_parse_and_map_pci;
|
|
bridge->swizzle_irq = pci_common_swizzle;
|
|
|
|
ret = pci_host_probe(bridge);
|
|
if (ret < 0) {
|
|
pci_free_resource_list(&resources);
|
|
return ret;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, bridge->bus);
|
|
return 0;
|
|
}
|
|
|
|
int pci_host_common_remove(struct platform_device *pdev)
|
|
{
|
|
struct pci_bus *bus = platform_get_drvdata(pdev);
|
|
|
|
pci_lock_rescan_remove();
|
|
pci_stop_root_bus(bus);
|
|
pci_remove_root_bus(bus);
|
|
pci_unlock_rescan_remove();
|
|
|
|
return 0;
|
|
}
|