git: 03a85378b81f - stable/13 - LinuxKPI: PCI: implement support for more than 1 MSI vector

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Fri, 17 Feb 2023 23:45:02 UTC
The branch stable/13 has been updated by bz:

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

commit 03a85378b81fcaffae5f8efdd75e2b8e10094e6a
Author:     Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2023-01-27 15:34:42 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2023-02-17 23:42:16 +0000

    LinuxKPI: PCI: implement support for more than 1 MSI vector
    
    Following e9715b1c4474333ff119aba3a9a74bff91f72372 and
    4b56afaf7bf4fa37bae5b26fd93ee1ff5969c1bb, implement support
    for up-to 32 MSI vectors.  This is used by wireless drivers.
    This also switches msi_desc to an array in order to store
    per-vector information.
    
    Sponsored by:   The FreeBSD Foundation
    Discussed with: grehan (in Dec)
    MFC after:      3 days
    Reviewed by:    jhb
    Differential Revision: https://reviews.freebsd.org/D38222
    
    (cherry picked from commit b15491b4773af99ce2470144ef6bcd9146cc9a98)
---
 sys/compat/linuxkpi/common/include/linux/pci.h | 36 +++++++++++++++++++-------
 sys/compat/linuxkpi/common/src/linux_pci.c     | 32 ++++++++++++++++++-----
 2 files changed, 51 insertions(+), 17 deletions(-)

diff --git a/sys/compat/linuxkpi/common/include/linux/pci.h b/sys/compat/linuxkpi/common/include/linux/pci.h
index 6a7dd1eaaa83..5fdfc3a46d8b 100644
--- a/sys/compat/linuxkpi/common/include/linux/pci.h
+++ b/sys/compat/linuxkpi/common/include/linux/pci.h
@@ -354,7 +354,7 @@ struct pci_dev {
 	bool			want_iomap_res;
 	bool			msix_enabled;
 	uint8_t			msi_cap;
-	struct msi_desc		*msi_desc;
+	struct msi_desc		**msi_desc;
 };
 
 /* XXX add kassert here on the mmio offset */
@@ -906,28 +906,44 @@ pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
   linux_pci_enable_msi(pdev)
 
 static inline int
-pci_enable_msi(struct pci_dev *pdev)
+_lkpi_pci_enable_msi_range(struct pci_dev *pdev, int minvec, int maxvec)
 {
 	struct resource_list_entry *rle;
 	int error;
-	int avail;
+	int nvec;
 
-	avail = pci_msi_count(pdev->dev.bsddev);
-	if (avail < 1)
-		return -EINVAL;
+	if (maxvec < minvec)
+		return (-EINVAL);
+
+	nvec = pci_msi_count(pdev->dev.bsddev);
+	if (nvec < 1 || nvec < minvec)
+		return (-ENOSPC);
 
-	avail = 1;	/* this function only enable one MSI IRQ */
-	if ((error = -pci_alloc_msi(pdev->dev.bsddev, &avail)) != 0)
+	nvec = min(nvec, maxvec);
+	if ((error = -pci_alloc_msi(pdev->dev.bsddev, &nvec)) != 0)
 		return error;
 
+	/* Native PCI might only ever ask for 32 vectors. */
+	if (nvec < minvec) {
+		pci_release_msi(pdev->dev.bsddev);
+		return (-ENOSPC);
+	}
+
 	rle = linux_pci_get_rle(pdev, SYS_RES_IRQ, 1, false);
 	pdev->dev.irq_start = rle->start;
-	pdev->dev.irq_end = rle->start + avail;
+	pdev->dev.irq_end = rle->start + nvec;
 	pdev->irq = rle->start;
 	pdev->msi_enabled = true;
 	return (0);
 }
 
+static inline int
+pci_enable_msi(struct pci_dev *pdev)
+{
+
+	return (_lkpi_pci_enable_msi_range(pdev, 1, 1));
+}
+
 static inline int
 pci_channel_offline(struct pci_dev *pdev)
 {
@@ -1628,7 +1644,7 @@ err:
 /*
  * We cannot simply re-define pci_get_device() as we would normally do
  * and then hide it in linux_pci.c as too many semi-native drivers still
- * inlucde linux/pci.h and run into the conflict with native PCI. Linux drivers
+ * include linux/pci.h and run into the conflict with native PCI. Linux drivers
  * using pci_get_device() need to be changed to call linuxkpi_pci_get_device().
  */
 static inline struct pci_dev *
diff --git a/sys/compat/linuxkpi/common/src/linux_pci.c b/sys/compat/linuxkpi/common/src/linux_pci.c
index ba40ded9cddf..5f3a24210c03 100644
--- a/sys/compat/linuxkpi/common/src/linux_pci.c
+++ b/sys/compat/linuxkpi/common/src/linux_pci.c
@@ -322,6 +322,11 @@ lkpifill_pci_dev(device_t dev, struct pci_dev *pdev)
 	pdev->dev.parent = &linux_root_device;
 	pdev->dev.release = lkpi_pci_dev_release;
 	INIT_LIST_HEAD(&pdev->dev.irqents);
+
+	if (pci_msi_count(dev) > 0)
+		pdev->msi_desc = malloc(pci_msi_count(dev) *
+		    sizeof(*pdev->msi_desc), M_DEVBUF, M_WAITOK | M_ZERO);
+
 	kobject_init(&pdev->dev.kobj, &linux_dev_ktype);
 	kobject_set_name(&pdev->dev.kobj, device_get_nameunit(dev));
 	kobject_add(&pdev->dev.kobj, &linux_root_device.kobj,
@@ -334,6 +339,7 @@ static void
 lkpinew_pci_dev_release(struct device *dev)
 {
 	struct pci_dev *pdev;
+	int i;
 
 	pdev = to_pci_dev(dev);
 	if (pdev->root != NULL)
@@ -341,8 +347,11 @@ lkpinew_pci_dev_release(struct device *dev)
 	if (pdev->bus->self != pdev)
 		pci_dev_put(pdev->bus->self);
 	free(pdev->bus, M_DEVBUF);
-	if (pdev->msi_desc != NULL)
+	if (pdev->msi_desc != NULL) {
+		for (i = pci_msi_count(pdev->dev.bsddev) - 1; i >= 0; i--)
+			free(pdev->msi_desc[i], M_DEVBUF);
 		free(pdev->msi_desc, M_DEVBUF);
+	}
 	free(pdev, M_DEVBUF);
 }
 
@@ -949,10 +958,7 @@ out:
 	if (flags & PCI_IRQ_MSI) {
 		if (pci_msi_count(pdev->dev.bsddev) < minv)
 			return (-ENOSPC);
-		/* We only support 1 vector in pci_enable_msi() */
-		if (minv != 1)
-			return (-ENOSPC);
-		error = pci_enable_msi(pdev);
+		error = _lkpi_pci_enable_msi_range(pdev, minv, maxv);
 		if (error == 0 && pdev->msi_enabled)
 			return (pdev->dev.irq_end - pdev->dev.irq_start);
 	}
@@ -982,14 +988,24 @@ lkpi_pci_msi_desc_alloc(int irq)
 	struct msi_desc *desc;
 	struct pci_devinfo *dinfo;
 	struct pcicfg_msi *msi;
+	int vec;
 
 	dev = linux_pci_find_irq_dev(irq);
 	if (dev == NULL)
 		return (NULL);
 
 	pdev = to_pci_dev(dev);
-	if (pdev->msi_desc != NULL)
-		return (pdev->msi_desc);
+
+	if (pdev->msi_desc == NULL)
+		return (NULL);
+
+	if (irq < pdev->dev.irq_start || irq >= pdev->dev.irq_end)
+		return (NULL);
+
+	vec = pdev->dev.irq_start - irq;
+
+	if (pdev->msi_desc[vec] != NULL)
+		return (pdev->msi_desc[vec]);
 
 	dinfo = device_get_ivars(dev->bsddev);
 	msi = &dinfo->cfg.msi;
@@ -1000,6 +1016,8 @@ lkpi_pci_msi_desc_alloc(int irq)
 	   (msi->msi_ctrl & PCIM_MSICTRL_64BIT) ? true : false;
 	desc->msg.data = msi->msi_data;
 
+	pdev->msi_desc[vec] = desc;
+
 	return (desc);
 }