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