git: a87884e34ec1 - stable/14 - LinuxKPI: Add pcie_capability_clear_and_set_word() function

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
Date: Sat, 17 Feb 2024 21:33:16 UTC
The branch stable/14 has been updated by wulf:

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

commit a87884e34ec10a498f8857f6337d65620a68476e
Author:     Vladimir Kondratyev <wulf@FreeBSD.org>
AuthorDate: 2023-12-24 08:20:00 +0000
Commit:     Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2024-02-17 20:58:39 +0000

    LinuxKPI: Add pcie_capability_clear_and_set_word() function
    
    It does a Read-Modify-Write operation using clear and set bitmasks on
    PCI Express Capability Register at pos. As certain PCI Express
    Capability Registers are accessed concurrently in RMW fashion, hence
    require locking which is handled transparently to the caller.
    
    Sponsored by:   Serenity CyberSecurity, LLC
    Reviewed by:    manu, bz
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D42821
    
    (cherry picked from commit 808ae4e29b6b9c9acc7eab013c5045370df8182a)
---
 sys/compat/linuxkpi/common/include/linux/pci.h | 36 ++++++++++++++------------
 sys/compat/linuxkpi/common/src/linux_pci.c     |  3 +++
 2 files changed, 23 insertions(+), 16 deletions(-)

diff --git a/sys/compat/linuxkpi/common/include/linux/pci.h b/sys/compat/linuxkpi/common/include/linux/pci.h
index aa99b050ffd9..174599d06141 100644
--- a/sys/compat/linuxkpi/common/include/linux/pci.h
+++ b/sys/compat/linuxkpi/common/include/linux/pci.h
@@ -349,6 +349,7 @@ struct pci_dev {
 	char			*path_name;
 
 	TAILQ_HEAD(, pci_mmio_region)	mmio;
+	spinlock_t		pcie_cap_lock;
 };
 
 int pci_request_region(struct pci_dev *pdev, int bar, const char *res_name);
@@ -1012,35 +1013,38 @@ pcie_capability_write_word(struct pci_dev *dev, int pos, u16 val)
 }
 
 static inline int
-pcie_capability_set_word(struct pci_dev *dev, int pos, uint16_t val)
+pcie_capability_clear_and_set_word(struct pci_dev *dev, int pos,
+    uint16_t clear, uint16_t set)
 {
 	int error;
 	uint16_t v;
 
+	if (pos == PCI_EXP_LNKCTL || pos == PCI_EXP_RTCTL)
+		spin_lock(&dev->pcie_cap_lock);
+
 	error = pcie_capability_read_word(dev, pos, &v);
-	if (error != 0)
-		return (error);
+	if (error == 0) {
+		v &= ~clear;
+		v |= set;
+		error = pcie_capability_write_word(dev, pos, v);
+	}
 
-	v |= val;
+	if (pos == PCI_EXP_LNKCTL || pos == PCI_EXP_RTCTL)
+		spin_unlock(&dev->pcie_cap_lock);
 
-	error = pcie_capability_write_word(dev, pos, v);
 	return (error);
 }
 
 static inline int
-pcie_capability_clear_word(struct pci_dev *dev, int pos, uint16_t val)
+pcie_capability_set_word(struct pci_dev *dev, int pos, uint16_t val)
 {
-	int error;
-	uint16_t v;
-
-	error = pcie_capability_read_word(dev, pos, &v);
-	if (error != 0)
-		return (error);
-
-	v &= ~val;
+	return (pcie_capability_clear_and_set_word(dev, pos, 0, val));
+}
 
-	error = pcie_capability_write_word(dev, pos, v);
-	return (error);
+static inline int
+pcie_capability_clear_word(struct pci_dev *dev, int pos, uint16_t val)
+{
+	return (pcie_capability_clear_and_set_word(dev, pos, val, 0));
 }
 
 static inline int pcie_get_minimum_link(struct pci_dev *dev,
diff --git a/sys/compat/linuxkpi/common/src/linux_pci.c b/sys/compat/linuxkpi/common/src/linux_pci.c
index 4e6cc92f3615..551b924d6f5e 100644
--- a/sys/compat/linuxkpi/common/src/linux_pci.c
+++ b/sys/compat/linuxkpi/common/src/linux_pci.c
@@ -526,6 +526,7 @@ linux_pci_attach_device(device_t dev, struct pci_driver *pdrv,
 		goto out_dma_init;
 
 	TAILQ_INIT(&pdev->mmio);
+	spin_lock_init(&pdev->pcie_cap_lock);
 
 	spin_lock(&pci_lock);
 	list_add(&pdev->links, &pci_devices);
@@ -540,6 +541,7 @@ linux_pci_attach_device(device_t dev, struct pci_driver *pdrv,
 
 out_probe:
 	free(pdev->bus, M_DEVBUF);
+	spin_lock_destroy(&pdev->pcie_cap_lock);
 	linux_pdev_dma_uninit(pdev);
 out_dma_init:
 	spin_lock(&pci_lock);
@@ -580,6 +582,7 @@ linux_pci_detach_device(struct pci_dev *pdev)
 	spin_lock(&pci_lock);
 	list_del(&pdev->links);
 	spin_unlock(&pci_lock);
+	spin_lock_destroy(&pdev->pcie_cap_lock);
 	put_device(&pdev->dev);
 
 	return (0);