mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 19:00:25 +00:00
![Rafael J. Wysocki](/assets/img/avatar_default.png)
On some systems, in order to get to the deepest low-power state of the platform (which may be necessary to save significant enough amounts of energy while suspended to idle. for example), devices on the PCI bus exposed by the VMD driver need to be power-managed via ACPI. However, the layout of the ACPI namespace below the VMD controller device object does not reflect the layout of the PCI bus under the VMD host bridge, so in order to identify the ACPI companion objects for the devices on that bus, it is necessary to use a special _ADR encoding on the ACPI side. In other words, acpi_pci_find_companion() does not work for these devices, so it needs to be amended with a special lookup logic specific to the VMD bus. Address this issue by allowing the VMD driver to temporarily install an ACPI companion lookup hook containing the code matching the devices on the VMD PCI bus with the corresponding objects in the ACPI namespace. Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Acked-by: Jon Derrick <jonathan.derrick@intel.com>
101 lines
2.4 KiB
C
101 lines
2.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* 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);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_find_host_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);
|