x86/PCI: Refine the way to release PCI IRQ resources
authorJiang Liu <jiang.liu@linux.intel.com>
Thu, 5 Feb 2015 05:44:47 +0000 (13:44 +0800)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Thu, 5 Feb 2015 14:09:26 +0000 (15:09 +0100)
Some PCI device drivers assume that pci_dev->irq won't change after
calling pci_disable_device() and pci_enable_device() during suspend and
resume.

Commit c03b3b0738a5 ("x86, irq, mpparse: Release IOAPIC pin when
PCI device is disabled") frees PCI IRQ resources when pci_disable_device()
is called and reallocate IRQ resources when pci_enable_device() is
called again. This breaks above assumption. So commit 3eec595235c1
("x86, irq, PCI: Keep IRQ assignment for PCI devices during
suspend/hibernation") and 9eabc99a635a ("x86, irq, PCI: Keep IRQ
assignment for runtime power management") fix the issue by avoiding
freeing/reallocating IRQ resources during PCI device suspend/resume.
They achieve this by checking dev.power.is_prepared and
dev.power.runtime_status.  PM maintainer, Rafael, then pointed out that
it's really an ugly fix which leaking PM internal state information to
IRQ subsystem.

Recently David Vrabel <david.vrabel@citrix.com> also reports an
regression in pciback driver caused by commit cffe0a2b5a34 ("x86, irq:
Keep balance of IOAPIC pin reference count"). Please refer to:
http://lkml.org/lkml/2015/1/14/546

So this patch refine the way to release PCI IRQ resources. Instead of
releasing PCI IRQ resources in pci_disable_device()/
pcibios_disable_device(), we now release it at driver unbinding
notification BUS_NOTIFY_UNBOUND_DRIVER. In other word, we only release
PCI IRQ resources when there's no driver bound to the PCI device, and
it keeps the assumption that pci_dev->irq won't through multiple
invocation of pci_enable_device()/pci_disable_device().

Signed-off-by: Jiang Liu <jiang.liu@linux.intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
arch/x86/include/asm/pci_x86.h
arch/x86/pci/common.c
arch/x86/pci/intel_mid_pci.c
arch/x86/pci/irq.c
drivers/acpi/pci_irq.c

index 164e3f8d3c3dbb6eb4fc0ea60e01cae15fe5116e..fa1195dae42541aaa1d836782a3a65aa25640e74 100644 (file)
@@ -93,8 +93,6 @@ extern raw_spinlock_t pci_config_lock;
 extern int (*pcibios_enable_irq)(struct pci_dev *dev);
 extern void (*pcibios_disable_irq)(struct pci_dev *dev);
 
-extern bool mp_should_keep_irq(struct device *dev);
-
 struct pci_raw_ops {
        int (*read)(unsigned int domain, unsigned int bus, unsigned int devfn,
                                                int reg, int len, u32 *val);
index 7b20bccf3648dfb0fcb534f0290c023e12a44f9a..ff1f0afa5ed15b47a8597ec238cf6272b79a8d3f 100644 (file)
@@ -497,6 +497,31 @@ void __init pcibios_set_cache_line_size(void)
        }
 }
 
+/*
+ * Some device drivers assume dev->irq won't change after calling
+ * pci_disable_device(). So delay releasing of IRQ resource to driver
+ * unbinding time. Otherwise it will break PM subsystem and drivers
+ * like xen-pciback etc.
+ */
+static int pci_irq_notifier(struct notifier_block *nb, unsigned long action,
+                           void *data)
+{
+       struct pci_dev *dev = to_pci_dev(data);
+
+       if (action != BUS_NOTIFY_UNBOUND_DRIVER)
+               return NOTIFY_DONE;
+
+       if (pcibios_disable_irq)
+               pcibios_disable_irq(dev);
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block pci_irq_nb = {
+       .notifier_call = pci_irq_notifier,
+       .priority = INT_MIN,
+};
+
 int __init pcibios_init(void)
 {
        if (!raw_pci_ops) {
@@ -509,6 +534,9 @@ int __init pcibios_init(void)
 
        if (pci_bf_sort >= pci_force_bf)
                pci_sort_breadthfirst();
+
+       bus_register_notifier(&pci_bus_type, &pci_irq_nb);
+
        return 0;
 }
 
@@ -667,12 +695,6 @@ int pcibios_enable_device(struct pci_dev *dev, int mask)
        return 0;
 }
 
-void pcibios_disable_device (struct pci_dev *dev)
-{
-       if (!pci_dev_msi_enabled(dev) && pcibios_disable_irq)
-               pcibios_disable_irq(dev);
-}
-
 int pci_ext_cfg_avail(void)
 {
        if (raw_pci_ext_ops)
index 44b9271580b5b0532bddf121af554cc0ec951779..95c2471f6819d9759a8c73ad2ea1d166581c6fb7 100644 (file)
@@ -234,10 +234,10 @@ static int intel_mid_pci_irq_enable(struct pci_dev *dev)
 
 static void intel_mid_pci_irq_disable(struct pci_dev *dev)
 {
-       if (!mp_should_keep_irq(&dev->dev) && dev->irq_managed &&
-           dev->irq > 0) {
+       if (dev->irq_managed && dev->irq > 0) {
                mp_unmap_irq(dev->irq);
                dev->irq_managed = 0;
+               dev->irq = 0;
        }
 }
 
index 5dc6ca5e174131d2c7208ea1ed86739ef4532d22..e71b3dbd87b8f688d3f2cbfa995421bd516ba581 100644 (file)
@@ -1256,22 +1256,9 @@ static int pirq_enable_irq(struct pci_dev *dev)
        return 0;
 }
 
-bool mp_should_keep_irq(struct device *dev)
-{
-       if (dev->power.is_prepared)
-               return true;
-#ifdef CONFIG_PM
-       if (dev->power.runtime_status == RPM_SUSPENDING)
-               return true;
-#endif
-
-       return false;
-}
-
 static void pirq_disable_irq(struct pci_dev *dev)
 {
-       if (io_apic_assign_pci_irqs && !mp_should_keep_irq(&dev->dev) &&
-           dev->irq_managed && dev->irq) {
+       if (io_apic_assign_pci_irqs && dev->irq_managed && dev->irq) {
                mp_unmap_irq(dev->irq);
                dev->irq = 0;
                dev->irq_managed = 0;
index b1def411c0b89cbf7847b767063c5c2ab528e8a8..e7f718d6918a6a29b775aa97f7a40dc654d47136 100644 (file)
@@ -485,14 +485,6 @@ void acpi_pci_irq_disable(struct pci_dev *dev)
        if (!pin || !dev->irq_managed || dev->irq <= 0)
                return;
 
-       /* Keep IOAPIC pin configuration when suspending */
-       if (dev->dev.power.is_prepared)
-               return;
-#ifdef CONFIG_PM
-       if (dev->dev.power.runtime_status == RPM_SUSPENDING)
-               return;
-#endif
-
        entry = acpi_pci_irq_lookup(dev, pin);
        if (!entry)
                return;
@@ -513,5 +505,6 @@ void acpi_pci_irq_disable(struct pci_dev *dev)
        if (gsi >= 0) {
                acpi_unregister_gsi(gsi);
                dev->irq_managed = 0;
+               dev->irq = 0;
        }
 }