git: b3b836251f9f - main - LinuxKPI: pci: implement pci_upstream_bridge()

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Mon, 21 Mar 2022 16:20:25 UTC
The branch main has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=b3b836251f9fefa817d158784189f6d336917f7a

commit b3b836251f9fefa817d158784189f6d336917f7a
Author:     Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2022-02-16 18:04:08 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2022-03-21 14:35:23 +0000

    LinuxKPI: pci: implement pci_upstream_bridge()
    
    Allow drivers to query the "upstream PCI bridge".
    Currently we point back to ourselves on pdev->bus->self rather than
    to the parent PCI bridge.
    We keep this as status-quo with an extra comment and only on-demand
    allocate a pci_dev for the parent bridge if we are asked for in
    pci_upstream_bridge().
    When releasing the pci_dev we check if pdev->bus->self has changed
    and call pci_dev_put() to release the reference count on the parent
    bridge as well.
    
    This code moves pci_is_root_bus() higher up in pci.h but no functional
    change there.
    
    Sponsored by:   The FreeBSD Foundation
    MFC after:      3 days
    Reviewed by:    hselasky, (jhb some earlier)
    Thanks to:      wulf for handling drm-kmod
    Differential Revision: https://reviews.freebsd.org/D34305
---
 sys/compat/linuxkpi/common/include/linux/pci.h | 49 ++++++++++++++++++++++----
 sys/compat/linuxkpi/common/src/linux_pci.c     |  7 ++++
 2 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/sys/compat/linuxkpi/common/include/linux/pci.h b/sys/compat/linuxkpi/common/include/linux/pci.h
index 7bf785fb209a..0b4cee0f8dc2 100644
--- a/sys/compat/linuxkpi/common/include/linux/pci.h
+++ b/sys/compat/linuxkpi/common/include/linux/pci.h
@@ -478,6 +478,48 @@ pci_clear_master(struct pci_dev *pdev)
 	return (0);
 }
 
+static inline bool
+pci_is_root_bus(struct pci_bus *pbus)
+{
+
+	return (pbus->self == NULL);
+}
+
+static inline struct pci_dev *
+pci_upstream_bridge(struct pci_dev *pdev)
+{
+
+	if (pci_is_root_bus(pdev->bus))
+		return (NULL);
+
+	/*
+	 * If we do not have a (proper) "upstream bridge" set, e.g., we point
+	 * to ourselves, try to handle this case on the fly like we do
+	 * for pcie_find_root_port().
+	 */
+	if (pdev == pdev->bus->self) {
+		device_t bridge;
+
+		bridge = device_get_parent(pdev->dev.bsddev);
+		if (bridge == NULL)
+			goto done;
+		bridge = device_get_parent(bridge);
+		if (bridge == NULL)
+			goto done;
+		if (device_get_devclass(device_get_parent(bridge)) !=
+		    devclass_find("pci"))
+			goto done;
+
+		/*
+		 * "bridge" is a PCI-to-PCI bridge.  Create a Linux pci_dev
+		 * for it so it can be returned.
+		 */
+		pdev->bus->self = lkpinew_pci_dev(bridge);
+	}
+done:
+	return (pdev->bus->self);
+}
+
 static inline struct pci_devres *
 lkpi_pci_devres_get_alloc(struct pci_dev *pdev)
 {
@@ -1399,13 +1441,6 @@ pci_dev_present(const struct pci_device_id *cur)
 	return (0);
 }
 
-static inline bool
-pci_is_root_bus(struct pci_bus *pbus)
-{
-
-	return (pbus->self == NULL);
-}
-
 struct pci_dev *lkpi_pci_get_domain_bus_and_slot(int domain,
     unsigned int bus, unsigned int devfn);
 #define	pci_get_domain_bus_and_slot(domain, bus, devfn)	\
diff --git a/sys/compat/linuxkpi/common/src/linux_pci.c b/sys/compat/linuxkpi/common/src/linux_pci.c
index 5f0ec07d3841..b4f4a2219bb4 100644
--- a/sys/compat/linuxkpi/common/src/linux_pci.c
+++ b/sys/compat/linuxkpi/common/src/linux_pci.c
@@ -278,6 +278,11 @@ lkpifill_pci_dev(device_t dev, struct pci_dev *pdev)
 	pdev->class = pci_get_class(dev);
 	pdev->revision = pci_get_revid(dev);
 	pdev->bus = malloc(sizeof(*pdev->bus), M_DEVBUF, M_WAITOK | M_ZERO);
+	/*
+	 * This should be the upstream bridge; pci_upstream_bridge()
+	 * handles that case on demand as otherwise we'll shadow the
+	 * entire PCI hierarchy.
+	 */
 	pdev->bus->self = pdev;
 	pdev->bus->number = pci_get_bus(dev);
 	pdev->bus->domain = pci_get_domain(dev);
@@ -301,6 +306,8 @@ lkpinew_pci_dev_release(struct device *dev)
 	pdev = to_pci_dev(dev);
 	if (pdev->root != NULL)
 		pci_dev_put(pdev->root);
+	if (pdev->bus->self != pdev)
+		pci_dev_put(pdev->bus->self);
 	free(pdev->bus, M_DEVBUF);
 	free(pdev, M_DEVBUF);
 }