[ACPI] suspend/resume ACPI PCI Interrupt Links
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>
This commit is contained in:
parent
68ac767686
commit
87bec66b96
8 changed files with 157 additions and 47 deletions
|
@ -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) {
|
||||
/*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue