mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

The PCI core has historically not allowed built-in drivers to opt in to async initial probing: Drivers may set "PROBE_PREFER_ASYNCHRONOUS", but initial probing always happens synchronously. That's because the PCI core uses device_attach() instead of device_initial_probe(). Should a driver return -EPROBE_DEFER on initial probe, reprobing later on does honor the PROBE_PREFER_ASYNCHRONOUS setting. Modular drivers are also allowed to probe asynchronously, which is inconsistent. The choice of device_attach() is likely not deliberate: It was introduced in 2013 with commit58d9a38f6f
("PCI: Skip attaching driver in device_add()"), but asynchronous probing was added two years later with commit765230b5f0
("driver-core: add asynchronous probing support for drivers"). According to the kernel-doc of "enum probe_type", "the end goal is to switch the kernel to use asynchronous probing by default". To this end, use device_initial_probe() to allow asynchronous initial probing. The function returns void, making the return value check unnecessary. Initial PCI probing often takes on the order of seconds even on laptops, so this may speed up booting significantly. A small number of PCI drivers already opt in to asynchronous probing. Their maintainers (who are all cc'ed) should watch out for issues, now that asynchronous probing is not just allowed for deferred and modular probing, but also initial probing: hl_pci_driver drivers/accel/habanalabs/common/habanalabs_drv.c cxl_pci_driver drivers/cxl/pci.c quicki2c_driver drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c quickspi_driver drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c i801_driver drivers/i2c/busses/i2c-i801.c mei_me_driver drivers/misc/mei/pci-me.c mei_vsc_drv drivers/misc/mei/platform-vsc.c sdhci_driver drivers/mmc/host/sdhci-pci-core.c nvme_driver drivers/nvme/host/pci.c ehci_pci_driver drivers/usb/host/ehci-pci.c hvfb_pci_stub_driver drivers/video/fbdev/hyperv_fb.c All other driver maintainers may test asynchronous probing by specifying the command line parameter "driver_async_probe=drv_name1,drv_name2,...", and on success setting "probe_type = PROBE_PREFER_ASYNCHRONOUS" in the pci_driver struct. Signed-off-by: Lukas Wunner <lukas@wunner.de> [bhelgaas: updated commit log per https://lore.kernel.org/r/aHYUh7WoDlhHckxd@wunner.de] Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Link: https://patch.msgid.link/53abe6f5ac7c631f95f5d061aa748b192eda0379.1751614426.git.lukas@wunner.de
467 lines
11 KiB
C
467 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* From setup-res.c, by:
|
|
* Dave Rusling (david.rusling@reo.mts.dec.com)
|
|
* David Mosberger (davidm@cs.arizona.edu)
|
|
* David Miller (davem@redhat.com)
|
|
* Ivan Kokshaysky (ink@jurassic.park.msu.ru)
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "pci.h"
|
|
|
|
/*
|
|
* The first PCI_BRIDGE_RESOURCE_NUM PCI bus resources (those that correspond
|
|
* to P2P or CardBus bridge windows) go in a table. Additional ones (for
|
|
* buses below host bridges or subtractive decode bridges) go in the list.
|
|
* Use pci_bus_for_each_resource() to iterate through all the resources.
|
|
*/
|
|
|
|
struct pci_bus_resource {
|
|
struct list_head list;
|
|
struct resource *res;
|
|
};
|
|
|
|
void pci_add_resource_offset(struct list_head *resources, struct resource *res,
|
|
resource_size_t offset)
|
|
{
|
|
struct resource_entry *entry;
|
|
|
|
entry = resource_list_create_entry(res, 0);
|
|
if (!entry) {
|
|
pr_err("PCI: can't add host bridge window %pR\n", res);
|
|
return;
|
|
}
|
|
|
|
entry->offset = offset;
|
|
resource_list_add_tail(entry, resources);
|
|
}
|
|
EXPORT_SYMBOL(pci_add_resource_offset);
|
|
|
|
void pci_add_resource(struct list_head *resources, struct resource *res)
|
|
{
|
|
pci_add_resource_offset(resources, res, 0);
|
|
}
|
|
EXPORT_SYMBOL(pci_add_resource);
|
|
|
|
void pci_free_resource_list(struct list_head *resources)
|
|
{
|
|
resource_list_free(resources);
|
|
}
|
|
EXPORT_SYMBOL(pci_free_resource_list);
|
|
|
|
void pci_bus_add_resource(struct pci_bus *bus, struct resource *res)
|
|
{
|
|
struct pci_bus_resource *bus_res;
|
|
|
|
bus_res = kzalloc(sizeof(struct pci_bus_resource), GFP_KERNEL);
|
|
if (!bus_res) {
|
|
dev_err(&bus->dev, "can't add %pR resource\n", res);
|
|
return;
|
|
}
|
|
|
|
bus_res->res = res;
|
|
list_add_tail(&bus_res->list, &bus->resources);
|
|
}
|
|
|
|
struct resource *pci_bus_resource_n(const struct pci_bus *bus, int n)
|
|
{
|
|
struct pci_bus_resource *bus_res;
|
|
|
|
if (n < PCI_BRIDGE_RESOURCE_NUM)
|
|
return bus->resource[n];
|
|
|
|
n -= PCI_BRIDGE_RESOURCE_NUM;
|
|
list_for_each_entry(bus_res, &bus->resources, list) {
|
|
if (n-- == 0)
|
|
return bus_res->res;
|
|
}
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_bus_resource_n);
|
|
|
|
void pci_bus_remove_resource(struct pci_bus *bus, struct resource *res)
|
|
{
|
|
struct pci_bus_resource *bus_res, *tmp;
|
|
int i;
|
|
|
|
for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) {
|
|
if (bus->resource[i] == res) {
|
|
bus->resource[i] = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
list_for_each_entry_safe(bus_res, tmp, &bus->resources, list) {
|
|
if (bus_res->res == res) {
|
|
list_del(&bus_res->list);
|
|
kfree(bus_res);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pci_bus_remove_resources(struct pci_bus *bus)
|
|
{
|
|
int i;
|
|
struct pci_bus_resource *bus_res, *tmp;
|
|
|
|
for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++)
|
|
bus->resource[i] = NULL;
|
|
|
|
list_for_each_entry_safe(bus_res, tmp, &bus->resources, list) {
|
|
list_del(&bus_res->list);
|
|
kfree(bus_res);
|
|
}
|
|
}
|
|
|
|
int devm_request_pci_bus_resources(struct device *dev,
|
|
struct list_head *resources)
|
|
{
|
|
struct resource_entry *win;
|
|
struct resource *parent, *res;
|
|
int err;
|
|
|
|
resource_list_for_each_entry(win, resources) {
|
|
res = win->res;
|
|
switch (resource_type(res)) {
|
|
case IORESOURCE_IO:
|
|
parent = &ioport_resource;
|
|
break;
|
|
case IORESOURCE_MEM:
|
|
parent = &iomem_resource;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
err = devm_request_resource(dev, parent, res);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_request_pci_bus_resources);
|
|
|
|
static struct pci_bus_region pci_32_bit = {0, 0xffffffffULL};
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
static struct pci_bus_region pci_64_bit = {0,
|
|
(pci_bus_addr_t) 0xffffffffffffffffULL};
|
|
static struct pci_bus_region pci_high = {(pci_bus_addr_t) 0x100000000ULL,
|
|
(pci_bus_addr_t) 0xffffffffffffffffULL};
|
|
#endif
|
|
|
|
/*
|
|
* @res contains CPU addresses. Clip it so the corresponding bus addresses
|
|
* on @bus are entirely within @region. This is used to control the bus
|
|
* addresses of resources we allocate, e.g., we may need a resource that
|
|
* can be mapped by a 32-bit BAR.
|
|
*/
|
|
static void pci_clip_resource_to_region(struct pci_bus *bus,
|
|
struct resource *res,
|
|
struct pci_bus_region *region)
|
|
{
|
|
struct pci_bus_region r;
|
|
|
|
pcibios_resource_to_bus(bus, &r, res);
|
|
if (r.start < region->start)
|
|
r.start = region->start;
|
|
if (r.end > region->end)
|
|
r.end = region->end;
|
|
|
|
if (r.end < r.start)
|
|
res->end = res->start - 1;
|
|
else
|
|
pcibios_bus_to_resource(bus, res, &r);
|
|
}
|
|
|
|
static int pci_bus_alloc_from_region(struct pci_bus *bus, struct resource *res,
|
|
resource_size_t size, resource_size_t align,
|
|
resource_size_t min, unsigned long type_mask,
|
|
resource_alignf alignf,
|
|
void *alignf_data,
|
|
struct pci_bus_region *region)
|
|
{
|
|
struct resource *r, avail;
|
|
resource_size_t max;
|
|
int ret;
|
|
|
|
type_mask |= IORESOURCE_TYPE_BITS;
|
|
|
|
pci_bus_for_each_resource(bus, r) {
|
|
resource_size_t min_used = min;
|
|
|
|
if (!r)
|
|
continue;
|
|
|
|
/* type_mask must match */
|
|
if ((res->flags ^ r->flags) & type_mask)
|
|
continue;
|
|
|
|
/* We cannot allocate a non-prefetching resource
|
|
from a pre-fetching area */
|
|
if ((r->flags & IORESOURCE_PREFETCH) &&
|
|
!(res->flags & IORESOURCE_PREFETCH))
|
|
continue;
|
|
|
|
avail = *r;
|
|
pci_clip_resource_to_region(bus, &avail, region);
|
|
|
|
/*
|
|
* "min" is typically PCIBIOS_MIN_IO or PCIBIOS_MIN_MEM to
|
|
* protect badly documented motherboard resources, but if
|
|
* this is an already-configured bridge window, its start
|
|
* overrides "min".
|
|
*/
|
|
if (avail.start)
|
|
min_used = avail.start;
|
|
|
|
max = avail.end;
|
|
|
|
/* Don't bother if available space isn't large enough */
|
|
if (size > max - min_used + 1)
|
|
continue;
|
|
|
|
/* Ok, try it out.. */
|
|
ret = allocate_resource(r, res, size, min_used, max,
|
|
align, alignf, alignf_data);
|
|
if (ret == 0)
|
|
return 0;
|
|
}
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/**
|
|
* pci_bus_alloc_resource - allocate a resource from a parent bus
|
|
* @bus: PCI bus
|
|
* @res: resource to allocate
|
|
* @size: size of resource to allocate
|
|
* @align: alignment of resource to allocate
|
|
* @min: minimum /proc/iomem address to allocate
|
|
* @type_mask: IORESOURCE_* type flags
|
|
* @alignf: resource alignment function
|
|
* @alignf_data: data argument for resource alignment function
|
|
*
|
|
* Given the PCI bus a device resides on, the size, minimum address,
|
|
* alignment and type, try to find an acceptable resource allocation
|
|
* for a specific device resource.
|
|
*/
|
|
int pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res,
|
|
resource_size_t size, resource_size_t align,
|
|
resource_size_t min, unsigned long type_mask,
|
|
resource_alignf alignf,
|
|
void *alignf_data)
|
|
{
|
|
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
|
|
int rc;
|
|
|
|
if (res->flags & IORESOURCE_MEM_64) {
|
|
rc = pci_bus_alloc_from_region(bus, res, size, align, min,
|
|
type_mask, alignf, alignf_data,
|
|
&pci_high);
|
|
if (rc == 0)
|
|
return 0;
|
|
|
|
return pci_bus_alloc_from_region(bus, res, size, align, min,
|
|
type_mask, alignf, alignf_data,
|
|
&pci_64_bit);
|
|
}
|
|
#endif
|
|
|
|
return pci_bus_alloc_from_region(bus, res, size, align, min,
|
|
type_mask, alignf, alignf_data,
|
|
&pci_32_bit);
|
|
}
|
|
EXPORT_SYMBOL(pci_bus_alloc_resource);
|
|
|
|
/*
|
|
* The @idx resource of @dev should be a PCI-PCI bridge window. If this
|
|
* resource fits inside a window of an upstream bridge, do nothing. If it
|
|
* overlaps an upstream window but extends outside it, clip the resource so
|
|
* it fits completely inside.
|
|
*/
|
|
bool pci_bus_clip_resource(struct pci_dev *dev, int idx)
|
|
{
|
|
struct pci_bus *bus = dev->bus;
|
|
struct resource *res = &dev->resource[idx];
|
|
struct resource orig_res = *res;
|
|
struct resource *r;
|
|
|
|
pci_bus_for_each_resource(bus, r) {
|
|
resource_size_t start, end;
|
|
|
|
if (!r)
|
|
continue;
|
|
|
|
if (resource_type(res) != resource_type(r))
|
|
continue;
|
|
|
|
start = max(r->start, res->start);
|
|
end = min(r->end, res->end);
|
|
|
|
if (start > end)
|
|
continue; /* no overlap */
|
|
|
|
if (res->start == start && res->end == end)
|
|
return false; /* no change */
|
|
|
|
res->start = start;
|
|
res->end = end;
|
|
res->flags &= ~IORESOURCE_UNSET;
|
|
orig_res.flags &= ~IORESOURCE_UNSET;
|
|
pci_info(dev, "%pR clipped to %pR\n", &orig_res, res);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void __weak pcibios_resource_survey_bus(struct pci_bus *bus) { }
|
|
|
|
void __weak pcibios_bus_add_device(struct pci_dev *pdev) { }
|
|
|
|
/**
|
|
* pci_bus_add_device - start driver for a single device
|
|
* @dev: device to add
|
|
*
|
|
* This adds add sysfs entries and start device drivers
|
|
*/
|
|
void pci_bus_add_device(struct pci_dev *dev)
|
|
{
|
|
struct device_node *dn = dev->dev.of_node;
|
|
struct platform_device *pdev;
|
|
|
|
/*
|
|
* Can not put in pci_device_add yet because resources
|
|
* are not assigned yet for some devices.
|
|
*/
|
|
pcibios_bus_add_device(dev);
|
|
pci_fixup_device(pci_fixup_final, dev);
|
|
if (pci_is_bridge(dev))
|
|
of_pci_make_dev_node(dev);
|
|
pci_create_sysfs_dev_files(dev);
|
|
pci_proc_attach_device(dev);
|
|
pci_bridge_d3_update(dev);
|
|
|
|
/*
|
|
* If the PCI device is associated with a pwrctrl device with a
|
|
* power supply, create a device link between the PCI device and
|
|
* pwrctrl device. This ensures that pwrctrl drivers are probed
|
|
* before PCI client drivers.
|
|
*/
|
|
pdev = of_find_device_by_node(dn);
|
|
if (pdev && of_pci_supply_present(dn)) {
|
|
if (!device_link_add(&dev->dev, &pdev->dev,
|
|
DL_FLAG_AUTOREMOVE_CONSUMER))
|
|
pci_err(dev, "failed to add device link to power control device %s\n",
|
|
pdev->name);
|
|
}
|
|
|
|
if (!dn || of_device_is_available(dn))
|
|
pci_dev_allow_binding(dev);
|
|
|
|
device_initial_probe(&dev->dev);
|
|
|
|
pci_dev_assign_added(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_bus_add_device);
|
|
|
|
/**
|
|
* pci_bus_add_devices - start driver for PCI devices
|
|
* @bus: bus to check for new devices
|
|
*
|
|
* Start driver for PCI devices and add some sysfs entries.
|
|
*/
|
|
void pci_bus_add_devices(const struct pci_bus *bus)
|
|
{
|
|
struct pci_dev *dev;
|
|
struct pci_bus *child;
|
|
|
|
list_for_each_entry(dev, &bus->devices, bus_list) {
|
|
/* Skip already-added devices */
|
|
if (pci_dev_is_added(dev))
|
|
continue;
|
|
pci_bus_add_device(dev);
|
|
}
|
|
|
|
list_for_each_entry(dev, &bus->devices, bus_list) {
|
|
/* Skip if device attach failed */
|
|
if (!pci_dev_is_added(dev))
|
|
continue;
|
|
child = dev->subordinate;
|
|
if (child)
|
|
pci_bus_add_devices(child);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(pci_bus_add_devices);
|
|
|
|
static int __pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
|
|
void *userdata)
|
|
{
|
|
struct pci_dev *dev;
|
|
int ret = 0;
|
|
|
|
list_for_each_entry(dev, &top->devices, bus_list) {
|
|
ret = cb(dev, userdata);
|
|
if (ret)
|
|
break;
|
|
if (dev->subordinate) {
|
|
ret = __pci_walk_bus(dev->subordinate, cb, userdata);
|
|
if (ret)
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* pci_walk_bus - walk devices on/under bus, calling callback.
|
|
* @top: bus whose devices should be walked
|
|
* @cb: callback to be called for each device found
|
|
* @userdata: arbitrary pointer to be passed to callback
|
|
*
|
|
* Walk the given bus, including any bridged devices
|
|
* on buses under this bus. Call the provided callback
|
|
* on each device found.
|
|
*
|
|
* We check the return of @cb each time. If it returns anything
|
|
* other than 0, we break out.
|
|
*/
|
|
void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *), void *userdata)
|
|
{
|
|
down_read(&pci_bus_sem);
|
|
__pci_walk_bus(top, cb, userdata);
|
|
up_read(&pci_bus_sem);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_walk_bus);
|
|
|
|
void pci_walk_bus_locked(struct pci_bus *top, int (*cb)(struct pci_dev *, void *), void *userdata)
|
|
{
|
|
lockdep_assert_held(&pci_bus_sem);
|
|
|
|
__pci_walk_bus(top, cb, userdata);
|
|
}
|
|
|
|
struct pci_bus *pci_bus_get(struct pci_bus *bus)
|
|
{
|
|
if (bus)
|
|
get_device(&bus->dev);
|
|
return bus;
|
|
}
|
|
|
|
void pci_bus_put(struct pci_bus *bus)
|
|
{
|
|
if (bus)
|
|
put_device(&bus->dev);
|
|
}
|