mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 02:09:42 +00:00
![Andrew Donnellan](/assets/img/avatar_default.png)
When cxl removes a vPHB, it's possible that the pci_controller may be freed before all references to the devices on the vPHB have been released. This in turn causes an invalid memory access when the devices are eventually released, as pcibios_release_device() attempts to call the phb's release_device hook. In cxl_pci_vphb_remove(), remove the existing call to pcibios_free_controller(). Instead, use pcibios_free_controller_deferred() to free the pci_controller after all devices have been released. Export pci_set_host_bridge_release() so we can do this. Cc: stable@vger.kernel.org Signed-off-by: Andrew Donnellan <andrew.donnellan@au1.ibm.com> Reviewed-by: Matthew R. Ochs <mrochs@linux.vnet.ibm.com> Acked-by: Ian Munsie <imunsie@au1.ibm.com> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
99 lines
2.3 KiB
C
99 lines
2.3 KiB
C
/*
|
|
* host bridge related code
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/module.h>
|
|
|
|
#include "pci.h"
|
|
|
|
static struct pci_bus *find_pci_root_bus(struct pci_bus *bus)
|
|
{
|
|
while (bus->parent)
|
|
bus = bus->parent;
|
|
|
|
return bus;
|
|
}
|
|
|
|
struct pci_host_bridge *pci_find_host_bridge(struct pci_bus *bus)
|
|
{
|
|
struct pci_bus *root_bus = find_pci_root_bus(bus);
|
|
|
|
return to_pci_host_bridge(root_bus->bridge);
|
|
}
|
|
|
|
struct device *pci_get_host_bridge_device(struct pci_dev *dev)
|
|
{
|
|
struct pci_bus *root_bus = find_pci_root_bus(dev->bus);
|
|
struct device *bridge = root_bus->bridge;
|
|
|
|
kobject_get(&bridge->kobj);
|
|
return bridge;
|
|
}
|
|
|
|
void pci_put_host_bridge_device(struct device *dev)
|
|
{
|
|
kobject_put(&dev->kobj);
|
|
}
|
|
|
|
void pci_set_host_bridge_release(struct pci_host_bridge *bridge,
|
|
void (*release_fn)(struct pci_host_bridge *),
|
|
void *release_data)
|
|
{
|
|
bridge->release_fn = release_fn;
|
|
bridge->release_data = release_data;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_set_host_bridge_release);
|
|
|
|
void pcibios_resource_to_bus(struct pci_bus *bus, struct pci_bus_region *region,
|
|
struct resource *res)
|
|
{
|
|
struct pci_host_bridge *bridge = pci_find_host_bridge(bus);
|
|
struct resource_entry *window;
|
|
resource_size_t offset = 0;
|
|
|
|
resource_list_for_each_entry(window, &bridge->windows) {
|
|
if (resource_contains(window->res, res)) {
|
|
offset = window->offset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
region->start = res->start - offset;
|
|
region->end = res->end - offset;
|
|
}
|
|
EXPORT_SYMBOL(pcibios_resource_to_bus);
|
|
|
|
static bool region_contains(struct pci_bus_region *region1,
|
|
struct pci_bus_region *region2)
|
|
{
|
|
return region1->start <= region2->start && region1->end >= region2->end;
|
|
}
|
|
|
|
void pcibios_bus_to_resource(struct pci_bus *bus, struct resource *res,
|
|
struct pci_bus_region *region)
|
|
{
|
|
struct pci_host_bridge *bridge = pci_find_host_bridge(bus);
|
|
struct resource_entry *window;
|
|
resource_size_t offset = 0;
|
|
|
|
resource_list_for_each_entry(window, &bridge->windows) {
|
|
struct pci_bus_region bus_region;
|
|
|
|
if (resource_type(res) != resource_type(window->res))
|
|
continue;
|
|
|
|
bus_region.start = window->res->start - window->offset;
|
|
bus_region.end = window->res->end - window->offset;
|
|
|
|
if (region_contains(&bus_region, region)) {
|
|
offset = window->offset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
res->start = region->start + offset;
|
|
res->end = region->end + offset;
|
|
}
|
|
EXPORT_SYMBOL(pcibios_bus_to_resource);
|