| From e9f5174db35d4ead68f86302065b50b61a227fa6 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 10 Jun 2021 10:06:09 -0600 |
| Subject: PCI/P2PDMA: Avoid pci_get_slot(), which may sleep |
| |
| From: Logan Gunthorpe <logang@deltatee.com> |
| |
| [ Upstream commit 3ec0c3ec2d92c09465534a1ff9c6f9d9506ffef6 ] |
| |
| In order to use upstream_bridge_distance_warn() from a dma_map function, it |
| must not sleep. However, pci_get_slot() takes the pci_bus_sem so it might |
| sleep. |
| |
| In order to avoid this, try to get the host bridge's device from the first |
| element in the device list. It should be impossible for the host bridge's |
| device to go away while references are held on child devices, so the first |
| element should not be able to change and, thus, this should be safe. |
| |
| Introduce a static function called pci_host_bridge_dev() to obtain the host |
| bridge's root device. |
| |
| Link: https://lore.kernel.org/r/20210610160609.28447-7-logang@deltatee.com |
| Signed-off-by: Logan Gunthorpe <logang@deltatee.com> |
| Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/pci/p2pdma.c | 34 ++++++++++++++++++++++++++++++++-- |
| 1 file changed, 32 insertions(+), 2 deletions(-) |
| |
| diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c |
| index 196382630363..c49c13a5fedc 100644 |
| --- a/drivers/pci/p2pdma.c |
| +++ b/drivers/pci/p2pdma.c |
| @@ -308,10 +308,41 @@ static const struct pci_p2pdma_whitelist_entry { |
| {} |
| }; |
| |
| +/* |
| + * This lookup function tries to find the PCI device corresponding to a given |
| + * host bridge. |
| + * |
| + * It assumes the host bridge device is the first PCI device in the |
| + * bus->devices list and that the devfn is 00.0. These assumptions should hold |
| + * for all the devices in the whitelist above. |
| + * |
| + * This function is equivalent to pci_get_slot(host->bus, 0), however it does |
| + * not take the pci_bus_sem lock seeing __host_bridge_whitelist() must not |
| + * sleep. |
| + * |
| + * For this to be safe, the caller should hold a reference to a device on the |
| + * bridge, which should ensure the host_bridge device will not be freed |
| + * or removed from the head of the devices list. |
| + */ |
| +static struct pci_dev *pci_host_bridge_dev(struct pci_host_bridge *host) |
| +{ |
| + struct pci_dev *root; |
| + |
| + root = list_first_entry_or_null(&host->bus->devices, |
| + struct pci_dev, bus_list); |
| + |
| + if (!root) |
| + return NULL; |
| + if (root->devfn != PCI_DEVFN(0, 0)) |
| + return NULL; |
| + |
| + return root; |
| +} |
| + |
| static bool __host_bridge_whitelist(struct pci_host_bridge *host, |
| bool same_host_bridge) |
| { |
| - struct pci_dev *root = pci_get_slot(host->bus, PCI_DEVFN(0, 0)); |
| + struct pci_dev *root = pci_host_bridge_dev(host); |
| const struct pci_p2pdma_whitelist_entry *entry; |
| unsigned short vendor, device; |
| |
| @@ -320,7 +351,6 @@ static bool __host_bridge_whitelist(struct pci_host_bridge *host, |
| |
| vendor = root->vendor; |
| device = root->device; |
| - pci_dev_put(root); |
| |
| for (entry = pci_p2pdma_whitelist; entry->vendor; entry++) { |
| if (vendor != entry->vendor || device != entry->device) |
| -- |
| 2.30.2 |
| |