[ACPI] suspend/resume ACPI PCI Interrupt Links
David Shaohua Li [Thu, 28 Jul 2005 03:02:00 +0000 (23:02 -0400)]
Add reference count and disable ACPI PCI Interrupt Link
when no device still uses it.

Warn when drivers have not released Link at suspend time.

http://bugzilla.kernel.org/show_bug.cgi?id=3469

Signed-off-by: David Shaohua Li <shaohua.li@intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>

arch/i386/pci/acpi.c
arch/i386/pci/common.c
arch/i386/pci/irq.c
arch/i386/pci/pci.h
drivers/acpi/pci_irq.c
drivers/acpi/pci_link.c
include/acpi/acpi_drivers.h
include/linux/acpi.h

index 2db65ec..42913f4 100644 (file)
@@ -30,6 +30,7 @@ static int __init pci_acpi_init(void)
        acpi_irq_penalty_init();
        pcibios_scanned++;
        pcibios_enable_irq = acpi_pci_irq_enable;
+       pcibios_disable_irq = acpi_pci_irq_disable;
 
        if (pci_routeirq) {
                /*
index 720975e..751e49b 100644 (file)
@@ -249,3 +249,9 @@ int pcibios_enable_device(struct pci_dev *dev, int mask)
 
        return pcibios_enable_irq(dev);
 }
+
+void pcibios_disable_device (struct pci_dev *dev)
+{
+       if (pcibios_disable_irq)
+               pcibios_disable_irq(dev);
+}
index d21b3a2..66e4149 100644 (file)
@@ -56,6 +56,7 @@ struct irq_router_handler {
 };
 
 int (*pcibios_enable_irq)(struct pci_dev *dev) = NULL;
+void (*pcibios_disable_irq)(struct pci_dev *dev) = NULL;
 
 /*
  *  Search 0xf0000 -- 0xfffff for the PCI IRQ Routing Table.
index a8fc80c..dc442df 100644 (file)
@@ -72,3 +72,4 @@ extern int pcibios_scanned;
 extern spinlock_t pci_config_lock;
 
 extern int (*pcibios_enable_irq)(struct pci_dev *dev);
+extern void (*pcibios_disable_irq)(struct pci_dev *dev);
index 8093f2e..c536ccf 100644 (file)
@@ -269,7 +269,51 @@ acpi_pci_irq_del_prt (int segment, int bus)
 /* --------------------------------------------------------------------------
                           PCI Interrupt Routing Support
    -------------------------------------------------------------------------- */
+typedef int (*irq_lookup_func)(struct acpi_prt_entry *, int *, int *, char **);
 
+static int
+acpi_pci_allocate_irq(struct acpi_prt_entry *entry,
+       int     *edge_level,
+       int     *active_high_low,
+       char    **link)
+{
+       int     irq;
+
+       ACPI_FUNCTION_TRACE("acpi_pci_allocate_irq");
+
+       if (entry->link.handle) {
+               irq = acpi_pci_link_allocate_irq(entry->link.handle,
+                       entry->link.index, edge_level, active_high_low, link);
+               if (irq < 0) {
+                       ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid IRQ link routing entry\n"));
+                       return_VALUE(-1);
+               }
+       } else {
+               irq = entry->link.index;
+               *edge_level = ACPI_LEVEL_SENSITIVE;
+               *active_high_low = ACPI_ACTIVE_LOW;
+       }
+
+       ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found IRQ %d\n", irq));
+       return_VALUE(irq);
+}
+
+static int
+acpi_pci_free_irq(struct acpi_prt_entry *entry,
+       int     *edge_level,
+       int     *active_high_low,
+       char    **link)
+{
+       int     irq;
+
+       ACPI_FUNCTION_TRACE("acpi_pci_free_irq");
+       if (entry->link.handle) {
+               irq = acpi_pci_link_free_irq(entry->link.handle);
+       } else {
+               irq = entry->link.index;
+       }
+       return_VALUE(irq);
+}
 /*
  * acpi_pci_irq_lookup
  * success: return IRQ >= 0
@@ -282,12 +326,13 @@ acpi_pci_irq_lookup (
        int                     pin,
        int                     *edge_level,
        int                     *active_high_low,
-       char                    **link)
+       char                    **link,
+       irq_lookup_func         func)
 {
        struct acpi_prt_entry   *entry = NULL;
        int segment = pci_domain_nr(bus);
        int bus_nr = bus->number;
-       int irq;
+       int ret;
 
        ACPI_FUNCTION_TRACE("acpi_pci_irq_lookup");
 
@@ -301,22 +346,8 @@ acpi_pci_irq_lookup (
                return_VALUE(-1);
        }
        
-       if (entry->link.handle) {
-               irq = acpi_pci_link_get_irq(entry->link.handle,
-                       entry->link.index, edge_level, active_high_low, link);
-               if (irq < 0) {
-                       ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid IRQ link routing entry\n"));
-                       return_VALUE(-1);
-               }
-       } else {
-               irq = entry->link.index;
-               *edge_level = ACPI_LEVEL_SENSITIVE;
-               *active_high_low = ACPI_ACTIVE_LOW;
-       }
-
-       ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found IRQ %d\n", irq));
-
-       return_VALUE(irq);
+       ret = func(entry, edge_level, active_high_low, link);
+       return_VALUE(ret);
 }
 
 /*
@@ -330,7 +361,8 @@ acpi_pci_irq_derive (
        int                     pin,
        int                     *edge_level,
        int                     *active_high_low,
-       char                    **link)
+       char                    **link,
+       irq_lookup_func         func)
 {
        struct pci_dev          *bridge = dev;
        int                     irq = -1;
@@ -363,7 +395,7 @@ acpi_pci_irq_derive (
                }
 
                irq = acpi_pci_irq_lookup(bridge->bus, PCI_SLOT(bridge->devfn),
-                       pin, edge_level, active_high_low, link);
+                       pin, edge_level, active_high_low, link, func);
        }
 
        if (irq < 0) {
@@ -415,7 +447,7 @@ acpi_pci_irq_enable (
         * values override any BIOS-assigned IRQs set during boot.
         */
        irq = acpi_pci_irq_lookup(dev->bus, PCI_SLOT(dev->devfn), pin,
-               &edge_level, &active_high_low, &link);
+               &edge_level, &active_high_low, &link, acpi_pci_allocate_irq);
 
        /*
         * If no PRT entry was found, we'll try to derive an IRQ from the
@@ -423,7 +455,7 @@ acpi_pci_irq_enable (
         */
        if (irq < 0)
                irq = acpi_pci_irq_derive(dev, pin, &edge_level,
-                       &active_high_low, &link);
+                       &active_high_low, &link, acpi_pci_allocate_irq);
  
        /*
         * No IRQ known to the ACPI subsystem - maybe the BIOS / 
@@ -461,7 +493,9 @@ acpi_pci_irq_enable (
 EXPORT_SYMBOL(acpi_pci_irq_enable);
 
 
-#ifdef CONFIG_ACPI_DEALLOCATE_IRQ
+/* FIXME: implement x86/x86_64 version */
+void __attribute__((weak)) acpi_unregister_gsi(u32 i) {}
+
 void
 acpi_pci_irq_disable (
        struct pci_dev          *dev)
@@ -488,14 +522,14 @@ acpi_pci_irq_disable (
         * First we check the PCI IRQ routing table (PRT) for an IRQ.
         */
        gsi = acpi_pci_irq_lookup(dev->bus, PCI_SLOT(dev->devfn), pin,
-                                 &edge_level, &active_high_low, NULL);
+                       &edge_level, &active_high_low, NULL, acpi_pci_free_irq);
        /*
         * If no PRT entry was found, we'll try to derive an IRQ from the
         * device's parent bridge.
         */
        if (gsi < 0)
                gsi = acpi_pci_irq_derive(dev, pin,
-                                         &edge_level, &active_high_low, NULL);
+                       &edge_level, &active_high_low, NULL, acpi_pci_free_irq);
        if (gsi < 0)
                return_VOID;
 
@@ -511,4 +545,3 @@ acpi_pci_irq_disable (
 
        return_VOID;
 }
-#endif /* CONFIG_ACPI_DEALLOCATE_IRQ */
index 6ad0e77..6a29610 100644 (file)
@@ -68,6 +68,10 @@ static struct acpi_driver acpi_pci_link_driver = {
                        },
 };
 
+/*
+ * If a link is initialized, we never change its active and initialized
+ * later even the link is disable. Instead, we just repick the active irq
+ */
 struct acpi_pci_link_irq {
        u8                      active;                 /* Current IRQ */
        u8                      edge_level;             /* All IRQs */
@@ -76,8 +80,7 @@ struct acpi_pci_link_irq {
        u8                      possible_count;
        u8                      possible[ACPI_PCI_LINK_MAX_POSSIBLE];
        u8                      initialized:1;
-       u8                      suspend_resume:1;
-       u8                      reserved:6;
+       u8                      reserved:7;
 };
 
 struct acpi_pci_link {
@@ -85,12 +88,14 @@ struct acpi_pci_link {
        struct acpi_device      *device;
        acpi_handle             handle;
        struct acpi_pci_link_irq irq;
+       int                     refcnt;
 };
 
 static struct {
        int                     count;
        struct list_head        entries;
 }                              acpi_link;
+DECLARE_MUTEX(acpi_link_lock);
 
 
 /* --------------------------------------------------------------------------
@@ -532,12 +537,12 @@ static int acpi_pci_link_allocate(
 
        ACPI_FUNCTION_TRACE("acpi_pci_link_allocate");
 
-       if (link->irq.suspend_resume) {
-               acpi_pci_link_set(link, link->irq.active);
-               link->irq.suspend_resume = 0;
-       }
-       if (link->irq.initialized)
+       if (link->irq.initialized) {
+               if (link->refcnt == 0)
+                       /* This means the link is disabled but initialized */
+                       acpi_pci_link_set(link, link->irq.active);
                return_VALUE(0);
+       }
 
        /*
         * search for active IRQ in list of possible IRQs.
@@ -596,13 +601,13 @@ static int acpi_pci_link_allocate(
 }
 
 /*
- * acpi_pci_link_get_irq
+ * acpi_pci_link_allocate_irq
  * success: return IRQ >= 0
  * failure: return -1
  */
 
 int
-acpi_pci_link_get_irq (
+acpi_pci_link_allocate_irq (
        acpi_handle             handle,
        int                     index,
        int                     *edge_level,
@@ -613,7 +618,7 @@ acpi_pci_link_get_irq (
        struct acpi_device      *device = NULL;
        struct acpi_pci_link    *link = NULL;
 
-       ACPI_FUNCTION_TRACE("acpi_pci_link_get_irq");
+       ACPI_FUNCTION_TRACE("acpi_pci_link_allocate_irq");
 
        result = acpi_bus_get_device(handle, &device);
        if (result) {
@@ -633,21 +638,70 @@ acpi_pci_link_get_irq (
                return_VALUE(-1);
        }
 
-       if (acpi_pci_link_allocate(link))
+       down(&acpi_link_lock);
+       if (acpi_pci_link_allocate(link)) {
+               up(&acpi_link_lock);
                return_VALUE(-1);
+       }
           
        if (!link->irq.active) {
+               up(&acpi_link_lock);
                ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Link active IRQ is 0!\n"));
                return_VALUE(-1);
        }
+       link->refcnt ++;
+       up(&acpi_link_lock);
 
        if (edge_level) *edge_level = link->irq.edge_level;
        if (active_high_low) *active_high_low = link->irq.active_high_low;
        if (name) *name = acpi_device_bid(link->device);
+       ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+               "Link %s is referenced\n", acpi_device_bid(link->device)));
        return_VALUE(link->irq.active);
 }
 
+/*
+ * We don't change link's irq information here.  After it is reenabled, we
+ * continue use the info
+ */
+int
+acpi_pci_link_free_irq(acpi_handle handle)
+{
+       struct acpi_device      *device = NULL;
+       struct acpi_pci_link    *link = NULL;
+       acpi_status             result;
+
+       ACPI_FUNCTION_TRACE("acpi_pci_link_free_irq");
+
+       result = acpi_bus_get_device(handle, &device);
+       if (result) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid link device\n"));
+               return_VALUE(-1);
+       }
 
+       link = (struct acpi_pci_link *) acpi_driver_data(device);
+       if (!link) {
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid link context\n"));
+               return_VALUE(-1);
+       }
+
+       down(&acpi_link_lock);
+       if (!link->irq.initialized) {
+               up(&acpi_link_lock);
+               ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Link isn't initialized\n"));
+               return_VALUE(-1);
+       }
+
+       link->refcnt --;
+       ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+               "Link %s is dereferenced\n", acpi_device_bid(link->device)));
+
+       if (link->refcnt == 0) {
+               acpi_ut_evaluate_object(link->handle, "_DIS", 0, NULL);
+       }
+       up(&acpi_link_lock);
+       return_VALUE(link->irq.active);
+}
 /* --------------------------------------------------------------------------
                                  Driver Interface
    -------------------------------------------------------------------------- */
@@ -677,6 +731,7 @@ acpi_pci_link_add (
        strcpy(acpi_device_class(device), ACPI_PCI_LINK_CLASS);
        acpi_driver_data(device) = link;
 
+       down(&acpi_link_lock);
        result = acpi_pci_link_get_possible(link);
        if (result)
                goto end;
@@ -712,6 +767,7 @@ acpi_pci_link_add (
 end:
        /* disable all links -- to be activated on use */
        acpi_ut_evaluate_object(link->handle, "_DIS", 0, NULL);
+       up(&acpi_link_lock);
 
        if (result)
                kfree(link);
@@ -726,19 +782,32 @@ irqrouter_suspend(
 {
        struct list_head        *node = NULL;
        struct acpi_pci_link    *link = NULL;
+       int                     ret = 0;
 
        ACPI_FUNCTION_TRACE("irqrouter_suspend");
 
        list_for_each(node, &acpi_link.entries) {
                link = list_entry(node, struct acpi_pci_link, node);
                if (!link) {
-                       ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid link context\n"));
+                       ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+                               "Invalid link context\n"));
                        continue;
                }
-               if (link->irq.active && link->irq.initialized)
-                       link->irq.suspend_resume = 1;
+               if (link->irq.initialized && link->refcnt != 0
+                       /* We ignore legacy IDE device irq */
+                       && link->irq.active != 14 && link->irq.active !=15) {
+                       printk(KERN_WARNING PREFIX
+                               "%d drivers with interrupt %d neglected to call"
+                               " pci_disable_device at .suspend\n",
+                               link->refcnt,
+                               link->irq.active);
+                       printk(KERN_WARNING PREFIX
+                               "Fix the driver, or rmmod before suspend\n");
+                       link->refcnt = 0;
+                       ret = -EINVAL;
+               }
        }
-       return_VALUE(0);
+       return_VALUE(ret);
 }
 
 
@@ -756,8 +825,9 @@ acpi_pci_link_remove (
 
        link = (struct acpi_pci_link *) acpi_driver_data(device);
 
-       /* TBD: Acquire/release lock */
+       down(&acpi_link_lock);
        list_del(&link->node);
+       up(&acpi_link_lock);
 
        kfree(link);
 
@@ -849,6 +919,7 @@ int __init acpi_irq_balance_set(char *str)
 __setup("acpi_irq_balance", acpi_irq_balance_set);
 
 
+/* FIXME: we will remove this interface after all drivers call pci_disable_device */
 static struct sysdev_class irqrouter_sysdev_class = {
         set_kset_name("irqrouter"),
         .suspend = irqrouter_suspend,
index e00d928..13f0929 100644 (file)
@@ -56,8 +56,9 @@
 /* ACPI PCI Interrupt Link (pci_link.c) */
 
 int acpi_irq_penalty_init (void);
-int acpi_pci_link_get_irq (acpi_handle handle, int index, int *edge_level,
+int acpi_pci_link_allocate_irq (acpi_handle handle, int index, int *edge_level,
        int *active_high_low, char **name);
+int acpi_pci_link_free_irq(acpi_handle handle);
 
 /* ACPI PCI Interrupt Routing (pci_irq.c) */
 
index 9c14959..ca0cd24 100644 (file)
@@ -440,9 +440,7 @@ int acpi_gsi_to_irq (u32 gsi, unsigned int *irq);
  * If this matches the last registration, any IRQ resources for gsi
  * are freed.
  */
-#ifdef CONFIG_ACPI_DEALLOCATE_IRQ
 void acpi_unregister_gsi (u32 gsi);
-#endif
 
 #ifdef CONFIG_ACPI_PCI
 
@@ -467,9 +465,7 @@ struct pci_dev;
 int acpi_pci_irq_enable (struct pci_dev *dev);
 void acpi_penalize_isa_irq(int irq, int active);
 
-#ifdef CONFIG_ACPI_DEALLOCATE_IRQ
 void acpi_pci_irq_disable (struct pci_dev *dev);
-#endif
 
 struct acpi_pci_driver {
        struct acpi_pci_driver *next;