[PATCH 01/17] PCI: Add concurrency safe clear_and_set variants for LNKCTL{,2}

From: Ilpo Järvinen
Date: Thu May 11 2023 - 09:15:21 EST


A few places write LNKCTL and LNKCTL2 registers without proper
concurrency control and this could result in losing the changes
one of the writers intended to make.

Add pcie_capability_clear_and_set_word_locked() and helpers to use it
with LNKCTL and LNKCTL2. The concurrency control is provided using a
spinlock in the struct pci_dev.

Suggested-by: Lukas Wunner <lukas@xxxxxxxxx>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@xxxxxxxxxxxxxxx>
---
drivers/pci/access.c | 14 ++++++++++++++
drivers/pci/probe.c | 1 +
include/linux/pci.h | 17 +++++++++++++++++
3 files changed, 32 insertions(+)

diff --git a/drivers/pci/access.c b/drivers/pci/access.c
index 3c230ca3de58..d92a3daadd0c 100644
--- a/drivers/pci/access.c
+++ b/drivers/pci/access.c
@@ -531,6 +531,20 @@ int pcie_capability_clear_and_set_dword(struct pci_dev *dev, int pos,
}
EXPORT_SYMBOL(pcie_capability_clear_and_set_dword);

+int pcie_capability_clear_and_set_word_locked(struct pci_dev *dev, int pos,
+ u16 clear, u16 set)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&dev->cap_lock, flags);
+ ret = pcie_capability_clear_and_set_word(dev, pos, clear, set);
+ spin_unlock_irqrestore(&dev->cap_lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(pcie_capability_clear_and_set_word_locked);
+
int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val)
{
if (pci_dev_is_disconnected(dev)) {
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 0b2826c4a832..0c14a283f1c7 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2318,6 +2318,7 @@ struct pci_dev *pci_alloc_dev(struct pci_bus *bus)
.end = -1,
};

+ spin_lock_init(&dev->cap_lock);
#ifdef CONFIG_PCI_MSI
raw_spin_lock_init(&dev->msi_lock);
#endif
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 60b8772b5bd4..82faea085d95 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -467,6 +467,7 @@ struct pci_dev {
pci_dev_flags_t dev_flags;
atomic_t enable_cnt; /* pci_enable_device has been called */

+ spinlock_t cap_lock; /* Protects RMW ops done with locked RMW capability accessors */
u32 saved_config_space[16]; /* Config space saved at suspend time */
struct hlist_head saved_cap_space;
int rom_attr_enabled; /* Display of ROM attribute enabled? */
@@ -1221,6 +1222,8 @@ int pcie_capability_clear_and_set_word(struct pci_dev *dev, int pos,
u16 clear, u16 set);
int pcie_capability_clear_and_set_dword(struct pci_dev *dev, int pos,
u32 clear, u32 set);
+int pcie_capability_clear_and_set_word_locked(struct pci_dev *dev, int pos,
+ u16 clear, u16 set);

static inline int pcie_capability_set_word(struct pci_dev *dev, int pos,
u16 set)
@@ -1246,6 +1249,20 @@ static inline int pcie_capability_clear_dword(struct pci_dev *dev, int pos,
return pcie_capability_clear_and_set_dword(dev, pos, clear, 0);
}

+static inline int pcie_lnkctl_clear_and_set(struct pci_dev *dev, u16 clear,
+ u16 set)
+{
+ return pcie_capability_clear_and_set_word_locked(dev, PCI_EXP_LNKCTL,
+ clear, set);
+}
+
+static inline int pcie_lnkctl2_clear_and_set(struct pci_dev *dev, u16 clear,
+ u16 set)
+{
+ return pcie_capability_clear_and_set_word_locked(dev, PCI_EXP_LNKCTL2,
+ clear, set);
+}
+
/* User-space driven config access */
int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val);
--
2.30.2