forked from Minki/linux
10a9574756
On ACPI-based platforms, the pci_slot driver creates PCI slot devices according to information from ACPI tables by registering an ACPI PCI subdriver. The ACPI PCI subdriver will only be called when creating/ destroying PCI root buses, and it won't be called when hot-plugging P2P bridges. It may cause stale PCI slot devices after hot-removing a P2P bridge if that bridge has associated PCI slots. And the acpiphp driver has the same issue too. This patch introduces two hook points into the PCI core, which will be invoked when creating/destroying PCI buses for PCI host and P2P bridges. They could be used to setup/destroy platform dependent stuff in a unified way, both at boot time and for PCI hotplug operations. Signed-off-by: Jiang Liu <jiang.liu@huawei.com> Signed-off-by: Yijing Wang <wangyijing@huawei.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Reviewed-by: Yinghai Lu <yinghai@kernel.org> Cc: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com> Cc: Toshi Kani <toshi.kani@hp.com> Cc: Tony Luck <tony.luck@intel.com> Cc: Fenghua Yu <fenghua.yu@intel.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Ingo Molnar <mingo@redhat.com> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Myron Stowe <myron.stowe@redhat.com>
150 lines
3.3 KiB
C
150 lines
3.3 KiB
C
#include <linux/pci.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci-aspm.h>
|
|
#include "pci.h"
|
|
|
|
static void pci_free_resources(struct pci_dev *dev)
|
|
{
|
|
int i;
|
|
|
|
msi_remove_pci_irq_vectors(dev);
|
|
|
|
pci_cleanup_rom(dev);
|
|
for (i = 0; i < PCI_NUM_RESOURCES; i++) {
|
|
struct resource *res = dev->resource + i;
|
|
if (res->parent)
|
|
release_resource(res);
|
|
}
|
|
}
|
|
|
|
static void pci_stop_dev(struct pci_dev *dev)
|
|
{
|
|
pci_pme_active(dev, false);
|
|
|
|
if (dev->is_added) {
|
|
pci_proc_detach_device(dev);
|
|
pci_remove_sysfs_dev_files(dev);
|
|
device_del(&dev->dev);
|
|
dev->is_added = 0;
|
|
}
|
|
|
|
if (dev->bus->self)
|
|
pcie_aspm_exit_link_state(dev);
|
|
}
|
|
|
|
static void pci_destroy_dev(struct pci_dev *dev)
|
|
{
|
|
down_write(&pci_bus_sem);
|
|
list_del(&dev->bus_list);
|
|
up_write(&pci_bus_sem);
|
|
|
|
pci_free_resources(dev);
|
|
put_device(&dev->dev);
|
|
}
|
|
|
|
void pci_remove_bus(struct pci_bus *bus)
|
|
{
|
|
pci_proc_detach_bus(bus);
|
|
|
|
down_write(&pci_bus_sem);
|
|
list_del(&bus->node);
|
|
pci_bus_release_busn_res(bus);
|
|
up_write(&pci_bus_sem);
|
|
pci_remove_legacy_files(bus);
|
|
pcibios_remove_bus(bus);
|
|
device_unregister(&bus->dev);
|
|
}
|
|
EXPORT_SYMBOL(pci_remove_bus);
|
|
|
|
static void pci_stop_bus_device(struct pci_dev *dev)
|
|
{
|
|
struct pci_bus *bus = dev->subordinate;
|
|
struct pci_dev *child, *tmp;
|
|
|
|
/*
|
|
* Stopping an SR-IOV PF device removes all the associated VFs,
|
|
* which will update the bus->devices list and confuse the
|
|
* iterator. Therefore, iterate in reverse so we remove the VFs
|
|
* first, then the PF.
|
|
*/
|
|
if (bus) {
|
|
list_for_each_entry_safe_reverse(child, tmp,
|
|
&bus->devices, bus_list)
|
|
pci_stop_bus_device(child);
|
|
}
|
|
|
|
pci_stop_dev(dev);
|
|
}
|
|
|
|
static void pci_remove_bus_device(struct pci_dev *dev)
|
|
{
|
|
struct pci_bus *bus = dev->subordinate;
|
|
struct pci_dev *child, *tmp;
|
|
|
|
if (bus) {
|
|
list_for_each_entry_safe(child, tmp,
|
|
&bus->devices, bus_list)
|
|
pci_remove_bus_device(child);
|
|
|
|
pci_remove_bus(bus);
|
|
dev->subordinate = NULL;
|
|
}
|
|
|
|
pci_destroy_dev(dev);
|
|
}
|
|
|
|
/**
|
|
* pci_stop_and_remove_bus_device - remove a PCI device and any children
|
|
* @dev: the device to remove
|
|
*
|
|
* Remove a PCI device from the device lists, informing the drivers
|
|
* that the device has been removed. We also remove any subordinate
|
|
* buses and children in a depth-first manner.
|
|
*
|
|
* For each device we remove, delete the device structure from the
|
|
* device lists, remove the /proc entry, and notify userspace
|
|
* (/sbin/hotplug).
|
|
*/
|
|
void pci_stop_and_remove_bus_device(struct pci_dev *dev)
|
|
{
|
|
pci_stop_bus_device(dev);
|
|
pci_remove_bus_device(dev);
|
|
}
|
|
EXPORT_SYMBOL(pci_stop_and_remove_bus_device);
|
|
|
|
void pci_stop_root_bus(struct pci_bus *bus)
|
|
{
|
|
struct pci_dev *child, *tmp;
|
|
struct pci_host_bridge *host_bridge;
|
|
|
|
if (!pci_is_root_bus(bus))
|
|
return;
|
|
|
|
host_bridge = to_pci_host_bridge(bus->bridge);
|
|
list_for_each_entry_safe_reverse(child, tmp,
|
|
&bus->devices, bus_list)
|
|
pci_stop_bus_device(child);
|
|
|
|
/* stop the host bridge */
|
|
device_del(&host_bridge->dev);
|
|
}
|
|
|
|
void pci_remove_root_bus(struct pci_bus *bus)
|
|
{
|
|
struct pci_dev *child, *tmp;
|
|
struct pci_host_bridge *host_bridge;
|
|
|
|
if (!pci_is_root_bus(bus))
|
|
return;
|
|
|
|
host_bridge = to_pci_host_bridge(bus->bridge);
|
|
list_for_each_entry_safe(child, tmp,
|
|
&bus->devices, bus_list)
|
|
pci_remove_bus_device(child);
|
|
pci_remove_bus(bus);
|
|
host_bridge->bus = NULL;
|
|
|
|
/* remove the host bridge */
|
|
put_device(&host_bridge->dev);
|
|
}
|