PCI: Fix is_added/is_busmaster race condition
When a PCI device is detected, pdev->is_added is set to 1 and proc and sysfs entries are created. When the device is removed, pdev->is_added is checked for one and then device is detached with clearing of proc and sys entries and at end, pdev->is_added is set to 0. is_added and is_busmaster are bit fields in pci_dev structure sharing same memory location. A strange issue was observed with multiple removal and rescan of a PCIe NVMe device using sysfs commands where is_added flag was observed as zero instead of one while removing device and proc,sys entries are not cleared. This causes issue in later device addition with warning message "proc_dir_entry" already registered. Debugging revealed a race condition between the PCI core setting the is_added bit in pci_bus_add_device() and the NVMe driver reset work-queue setting the is_busmaster bit in pci_set_master(). As these fields are not handled atomically, that clears the is_added bit. Move the is_added bit to a separate private flag variable and use atomic functions to set and retrieve the device addition state. This avoids the race because is_added no longer shares a memory location with is_busmaster. Link: https://bugzilla.kernel.org/show_bug.cgi?id=200283 Signed-off-by: Hari Vyas <hari.vyas@broadcom.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Reviewed-by: Lukas Wunner <lukas@wunner.de> Acked-by: Michael Ellerman <mpe@ellerman.id.au>
This commit is contained in:
parent
a54e43f993
commit
44bda4b7d2
9 changed files with 27 additions and 12 deletions
|
@ -42,6 +42,8 @@
|
||||||
#include <asm/ppc-pci.h>
|
#include <asm/ppc-pci.h>
|
||||||
#include <asm/eeh.h>
|
#include <asm/eeh.h>
|
||||||
|
|
||||||
|
#include "../../../drivers/pci/pci.h"
|
||||||
|
|
||||||
/* hose_spinlock protects accesses to the the phb_bitmap. */
|
/* hose_spinlock protects accesses to the the phb_bitmap. */
|
||||||
static DEFINE_SPINLOCK(hose_spinlock);
|
static DEFINE_SPINLOCK(hose_spinlock);
|
||||||
LIST_HEAD(hose_list);
|
LIST_HEAD(hose_list);
|
||||||
|
@ -1014,7 +1016,7 @@ void pcibios_setup_bus_devices(struct pci_bus *bus)
|
||||||
/* Cardbus can call us to add new devices to a bus, so ignore
|
/* Cardbus can call us to add new devices to a bus, so ignore
|
||||||
* those who are already fully discovered
|
* those who are already fully discovered
|
||||||
*/
|
*/
|
||||||
if (dev->is_added)
|
if (pci_dev_is_added(dev))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
pcibios_setup_device(dev);
|
pcibios_setup_device(dev);
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
|
|
||||||
#include "powernv.h"
|
#include "powernv.h"
|
||||||
#include "pci.h"
|
#include "pci.h"
|
||||||
|
#include "../../../../drivers/pci/pci.h"
|
||||||
|
|
||||||
#define PNV_IODA1_M64_NUM 16 /* Number of M64 BARs */
|
#define PNV_IODA1_M64_NUM 16 /* Number of M64 BARs */
|
||||||
#define PNV_IODA1_M64_SEGS 8 /* Segments per M64 BAR */
|
#define PNV_IODA1_M64_SEGS 8 /* Segments per M64 BAR */
|
||||||
|
@ -3138,7 +3139,7 @@ static void pnv_pci_ioda_fixup_iov_resources(struct pci_dev *pdev)
|
||||||
struct pci_dn *pdn;
|
struct pci_dn *pdn;
|
||||||
int mul, total_vfs;
|
int mul, total_vfs;
|
||||||
|
|
||||||
if (!pdev->is_physfn || pdev->is_added)
|
if (!pdev->is_physfn || pci_dev_is_added(pdev))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
pdn = pci_get_pdn(pdev);
|
pdn = pci_get_pdn(pdev);
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
#include <asm/security_features.h>
|
#include <asm/security_features.h>
|
||||||
|
|
||||||
#include "pseries.h"
|
#include "pseries.h"
|
||||||
|
#include "../../../../drivers/pci/pci.h"
|
||||||
|
|
||||||
int CMO_PrPSP = -1;
|
int CMO_PrPSP = -1;
|
||||||
int CMO_SecPSP = -1;
|
int CMO_SecPSP = -1;
|
||||||
|
@ -664,7 +665,7 @@ static void pseries_pci_fixup_iov_resources(struct pci_dev *pdev)
|
||||||
const int *indexes;
|
const int *indexes;
|
||||||
struct device_node *dn = pci_device_to_OF_node(pdev);
|
struct device_node *dn = pci_device_to_OF_node(pdev);
|
||||||
|
|
||||||
if (!pdev->is_physfn || pdev->is_added)
|
if (!pdev->is_physfn || pci_dev_is_added(pdev))
|
||||||
return;
|
return;
|
||||||
/*Firmware must support open sriov otherwise dont configure*/
|
/*Firmware must support open sriov otherwise dont configure*/
|
||||||
indexes = of_get_property(dn, "ibm,open-sriov-vf-bar-info", NULL);
|
indexes = of_get_property(dn, "ibm,open-sriov-vf-bar-info", NULL);
|
||||||
|
|
|
@ -330,7 +330,7 @@ void pci_bus_add_device(struct pci_dev *dev)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev->is_added = 1;
|
pci_dev_assign_added(dev, true);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(pci_bus_add_device);
|
EXPORT_SYMBOL_GPL(pci_bus_add_device);
|
||||||
|
|
||||||
|
@ -347,14 +347,14 @@ void pci_bus_add_devices(const struct pci_bus *bus)
|
||||||
|
|
||||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||||
/* Skip already-added devices */
|
/* Skip already-added devices */
|
||||||
if (dev->is_added)
|
if (pci_dev_is_added(dev))
|
||||||
continue;
|
continue;
|
||||||
pci_bus_add_device(dev);
|
pci_bus_add_device(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||||
/* Skip if device attach failed */
|
/* Skip if device attach failed */
|
||||||
if (!dev->is_added)
|
if (!pci_dev_is_added(dev))
|
||||||
continue;
|
continue;
|
||||||
child = dev->subordinate;
|
child = dev->subordinate;
|
||||||
if (child)
|
if (child)
|
||||||
|
|
|
@ -509,7 +509,7 @@ static void enable_slot(struct acpiphp_slot *slot)
|
||||||
|
|
||||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||||
/* Assume that newly added devices are powered on already. */
|
/* Assume that newly added devices are powered on already. */
|
||||||
if (!dev->is_added)
|
if (!pci_dev_is_added(dev))
|
||||||
dev->current_state = PCI_D0;
|
dev->current_state = PCI_D0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -288,6 +288,7 @@ struct pci_sriov {
|
||||||
|
|
||||||
/* pci_dev priv_flags */
|
/* pci_dev priv_flags */
|
||||||
#define PCI_DEV_DISCONNECTED 0
|
#define PCI_DEV_DISCONNECTED 0
|
||||||
|
#define PCI_DEV_ADDED 1
|
||||||
|
|
||||||
static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)
|
static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)
|
||||||
{
|
{
|
||||||
|
@ -300,6 +301,16 @@ static inline bool pci_dev_is_disconnected(const struct pci_dev *dev)
|
||||||
return test_bit(PCI_DEV_DISCONNECTED, &dev->priv_flags);
|
return test_bit(PCI_DEV_DISCONNECTED, &dev->priv_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void pci_dev_assign_added(struct pci_dev *dev, bool added)
|
||||||
|
{
|
||||||
|
assign_bit(PCI_DEV_ADDED, &dev->priv_flags, added);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool pci_dev_is_added(const struct pci_dev *dev)
|
||||||
|
{
|
||||||
|
return test_bit(PCI_DEV_ADDED, &dev->priv_flags);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PCI_ATS
|
#ifdef CONFIG_PCI_ATS
|
||||||
void pci_restore_ats_state(struct pci_dev *dev);
|
void pci_restore_ats_state(struct pci_dev *dev);
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -2433,13 +2433,13 @@ int pci_scan_slot(struct pci_bus *bus, int devfn)
|
||||||
dev = pci_scan_single_device(bus, devfn);
|
dev = pci_scan_single_device(bus, devfn);
|
||||||
if (!dev)
|
if (!dev)
|
||||||
return 0;
|
return 0;
|
||||||
if (!dev->is_added)
|
if (!pci_dev_is_added(dev))
|
||||||
nr++;
|
nr++;
|
||||||
|
|
||||||
for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) {
|
for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) {
|
||||||
dev = pci_scan_single_device(bus, devfn + fn);
|
dev = pci_scan_single_device(bus, devfn + fn);
|
||||||
if (dev) {
|
if (dev) {
|
||||||
if (!dev->is_added)
|
if (!pci_dev_is_added(dev))
|
||||||
nr++;
|
nr++;
|
||||||
dev->multifunction = 1;
|
dev->multifunction = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,12 @@ static void pci_stop_dev(struct pci_dev *dev)
|
||||||
{
|
{
|
||||||
pci_pme_active(dev, false);
|
pci_pme_active(dev, false);
|
||||||
|
|
||||||
if (dev->is_added) {
|
if (pci_dev_is_added(dev)) {
|
||||||
device_release_driver(&dev->dev);
|
device_release_driver(&dev->dev);
|
||||||
pci_proc_detach_device(dev);
|
pci_proc_detach_device(dev);
|
||||||
pci_remove_sysfs_dev_files(dev);
|
pci_remove_sysfs_dev_files(dev);
|
||||||
dev->is_added = 0;
|
|
||||||
|
pci_dev_assign_added(dev, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev->bus->self)
|
if (dev->bus->self)
|
||||||
|
|
|
@ -368,7 +368,6 @@ struct pci_dev {
|
||||||
unsigned int transparent:1; /* Subtractive decode bridge */
|
unsigned int transparent:1; /* Subtractive decode bridge */
|
||||||
unsigned int multifunction:1; /* Multi-function device */
|
unsigned int multifunction:1; /* Multi-function device */
|
||||||
|
|
||||||
unsigned int is_added:1;
|
|
||||||
unsigned int is_busmaster:1; /* Is busmaster */
|
unsigned int is_busmaster:1; /* Is busmaster */
|
||||||
unsigned int no_msi:1; /* May not use MSI */
|
unsigned int no_msi:1; /* May not use MSI */
|
||||||
unsigned int no_64bit_msi:1; /* May only use 32-bit MSIs */
|
unsigned int no_64bit_msi:1; /* May only use 32-bit MSIs */
|
||||||
|
|
Loading…
Add table
Reference in a new issue