From 073b4964b3b75fd9e19bf3933b26d9c23591c9db Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:37:31 +0100 Subject: [PATCH 01/16] ACPI: Do not export functions that are only used in osl.c The functions acpi_os_map_generic_address() and acpi_os_unmap_generic_address() are only used in drivers/acpi/osl.c, so make them static and remove the extern definitions of them from include/linux/acpi_io.h. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 6 ++---- include/linux/acpi_io.h | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index c90c76aa7f8b..ff2189d3fa06 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -397,7 +397,7 @@ void __init early_acpi_os_unmap_memory(void __iomem *virt, acpi_size size) __acpi_unmap_table(virt, size); } -int acpi_os_map_generic_address(struct acpi_generic_address *addr) +static int acpi_os_map_generic_address(struct acpi_generic_address *addr) { void __iomem *virt; @@ -413,9 +413,8 @@ int acpi_os_map_generic_address(struct acpi_generic_address *addr) return 0; } -EXPORT_SYMBOL_GPL(acpi_os_map_generic_address); -void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) +static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) { void __iomem *virt; unsigned long flags; @@ -433,7 +432,6 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) acpi_os_unmap_memory(virt, size); } -EXPORT_SYMBOL_GPL(acpi_os_unmap_generic_address); #ifdef ACPI_FUTURE_USAGE acpi_status diff --git a/include/linux/acpi_io.h b/include/linux/acpi_io.h index 7180013a4a3a..28a3ae279d7a 100644 --- a/include/linux/acpi_io.h +++ b/include/linux/acpi_io.h @@ -10,7 +10,4 @@ static inline void __iomem *acpi_os_ioremap(acpi_physical_address phys, return ioremap_cache(phys, size); } -int acpi_os_map_generic_address(struct acpi_generic_address *addr); -void acpi_os_unmap_generic_address(struct acpi_generic_address *addr); - #endif From 7bbb890358b96cb6f77adc6815f2072bdf813d5d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:37:42 +0100 Subject: [PATCH 02/16] ACPI: Change acpi_ioremap_lock into a mutex There's no reason why acpi_ioremap_lock has to be a spinlock, because all of the functions it is used in may sleep anyway and there's no reason why it should be locked with interrupts off. Use a mutex instead (that's going to allow us to put some more operations under the lock later). Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index ff2189d3fa06..97061928249a 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -109,7 +109,7 @@ struct acpi_ioremap { }; static LIST_HEAD(acpi_ioremaps); -static DEFINE_SPINLOCK(acpi_ioremap_lock); +static DEFINE_MUTEX(acpi_ioremap_lock); static void __init acpi_osi_setup_late(void); @@ -303,7 +303,6 @@ void __iomem *__init_refok acpi_os_map_memory(acpi_physical_address phys, acpi_size size) { struct acpi_ioremap *map, *tmp_map; - unsigned long flags; void __iomem *virt; acpi_physical_address pg_off; acpi_size pg_sz; @@ -334,18 +333,18 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) map->size = pg_sz; kref_init(&map->ref); - spin_lock_irqsave(&acpi_ioremap_lock, flags); + mutex_lock(&acpi_ioremap_lock); /* Check if page has already been mapped. */ tmp_map = acpi_map_lookup(phys, size); if (tmp_map) { kref_get(&tmp_map->ref); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + mutex_unlock(&acpi_ioremap_lock); iounmap(map->virt); kfree(map); return tmp_map->virt + (phys - tmp_map->phys); } list_add_tail_rcu(&map->list, &acpi_ioremaps); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + mutex_unlock(&acpi_ioremap_lock); return map->virt + (phys - map->phys); } @@ -362,7 +361,6 @@ static void acpi_kref_del_iomap(struct kref *ref) void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) { struct acpi_ioremap *map; - unsigned long flags; int del; if (!acpi_gbl_permanent_mmap) { @@ -370,17 +368,17 @@ void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) return; } - spin_lock_irqsave(&acpi_ioremap_lock, flags); + mutex_lock(&acpi_ioremap_lock); map = acpi_map_lookup_virt(virt, size); if (!map) { - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + mutex_unlock(&acpi_ioremap_lock); printk(KERN_ERR PREFIX "%s: bad address %p\n", __func__, virt); dump_stack(); return; } del = kref_put(&map->ref, acpi_kref_del_iomap); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + mutex_unlock(&acpi_ioremap_lock); if (!del) return; @@ -417,7 +415,6 @@ static int acpi_os_map_generic_address(struct acpi_generic_address *addr) static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) { void __iomem *virt; - unsigned long flags; acpi_size size = addr->bit_width / 8; if (addr->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) @@ -426,9 +423,9 @@ static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) if (!addr->address || !addr->bit_width) return; - spin_lock_irqsave(&acpi_ioremap_lock, flags); + mutex_lock(&acpi_ioremap_lock); virt = acpi_map_vaddr_lookup(addr->address, size); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + mutex_unlock(&acpi_ioremap_lock); acpi_os_unmap_memory(virt, size); } From 7fe135dc058faea0ce319a03e3b6f98c5049955c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:37:53 +0100 Subject: [PATCH 03/16] ACPI: Avoid walking the list of memory mappings in osl.c twice in a row Make acpi_os_unmap_generic_address() use acpi_map_lookup() to find the desired iomap and drop the reference to it directly (and eventually remove it if necessary) instead of calling acpi_os_unmap_memory(), which requires us to walk the list of ACPI iomaps twice in a row (first, to get the virtual address associated with the iomap and second, to get the iomap itself). Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 97061928249a..f6785bced215 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -358,6 +358,13 @@ static void acpi_kref_del_iomap(struct kref *ref) list_del_rcu(&map->list); } +static void acpi_os_remove_map(struct acpi_ioremap *map) +{ + synchronize_rcu(); + iounmap(map->virt); + kfree(map); +} + void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) { struct acpi_ioremap *map; @@ -372,20 +379,14 @@ void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) map = acpi_map_lookup_virt(virt, size); if (!map) { mutex_unlock(&acpi_ioremap_lock); - printk(KERN_ERR PREFIX "%s: bad address %p\n", __func__, virt); - dump_stack(); + WARN(true, PREFIX "%s: bad address %p\n", __func__, virt); return; } - del = kref_put(&map->ref, acpi_kref_del_iomap); mutex_unlock(&acpi_ioremap_lock); - if (!del) - return; - - synchronize_rcu(); - iounmap(map->virt); - kfree(map); + if (del) + acpi_os_remove_map(map); } EXPORT_SYMBOL_GPL(acpi_os_unmap_memory); @@ -414,8 +415,8 @@ static int acpi_os_map_generic_address(struct acpi_generic_address *addr) static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) { - void __iomem *virt; - acpi_size size = addr->bit_width / 8; + struct acpi_ioremap *map; + int del; if (addr->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) return; @@ -424,10 +425,16 @@ static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) return; mutex_lock(&acpi_ioremap_lock); - virt = acpi_map_vaddr_lookup(addr->address, size); + map = acpi_map_lookup(addr->address, addr->bit_width / 8); + if (!map) { + mutex_unlock(&acpi_ioremap_lock); + return; + } + del = kref_put(&map->ref, acpi_kref_del_iomap); mutex_unlock(&acpi_ioremap_lock); - acpi_os_unmap_memory(virt, size); + if (del) + acpi_os_remove_map(map); } #ifdef ACPI_FUTURE_USAGE From 7ffd0443f2502478545e23e194b7eb8e16376072 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:38:05 +0100 Subject: [PATCH 04/16] ACPI: Make acpi_os_map_memory() avoid creating unnecessary mappings Modify acpi_os_map_memory() so that it doesn't call acpi_os_ioremap() unconditionally every time it is executed (except when acpi_gbl_permanent_mmap is unset), which pretty much defeats the purpose of maintaining the list of ACPI iomaps in osl.c. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index f6785bced215..445f205752a2 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -302,7 +302,7 @@ acpi_map_lookup_virt(void __iomem *virt, acpi_size size) void __iomem *__init_refok acpi_os_map_memory(acpi_physical_address phys, acpi_size size) { - struct acpi_ioremap *map, *tmp_map; + struct acpi_ioremap *map; void __iomem *virt; acpi_physical_address pg_off; acpi_size pg_sz; @@ -315,14 +315,25 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) if (!acpi_gbl_permanent_mmap) return __acpi_map_table((unsigned long)phys, size); + mutex_lock(&acpi_ioremap_lock); + /* Check if there's a suitable mapping already. */ + map = acpi_map_lookup(phys, size); + if (map) { + kref_get(&map->ref); + goto out; + } + map = kzalloc(sizeof(*map), GFP_KERNEL); - if (!map) + if (!map) { + mutex_unlock(&acpi_ioremap_lock); return NULL; + } pg_off = round_down(phys, PAGE_SIZE); pg_sz = round_up(phys + size, PAGE_SIZE) - pg_off; virt = acpi_os_ioremap(pg_off, pg_sz); if (!virt) { + mutex_unlock(&acpi_ioremap_lock); kfree(map); return NULL; } @@ -333,19 +344,10 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) map->size = pg_sz; kref_init(&map->ref); - mutex_lock(&acpi_ioremap_lock); - /* Check if page has already been mapped. */ - tmp_map = acpi_map_lookup(phys, size); - if (tmp_map) { - kref_get(&tmp_map->ref); - mutex_unlock(&acpi_ioremap_lock); - iounmap(map->virt); - kfree(map); - return tmp_map->virt + (phys - tmp_map->phys); - } list_add_tail_rcu(&map->list, &acpi_ioremaps); - mutex_unlock(&acpi_ioremap_lock); + out: + mutex_unlock(&acpi_ioremap_lock); return map->virt + (phys - map->phys); } EXPORT_SYMBOL_GPL(acpi_os_map_memory); From b7c1fadd6c2eead56d0664a3a921980120de0c11 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:38:15 +0100 Subject: [PATCH 05/16] ACPI: Do not use krefs under a mutex in osl.c The reference counting of ACPI iomaps is carried out entirely under acpi_ioremap_lock, so it is sufficient to use simple counters instead of krefs for this purpose. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 445f205752a2..5389f9f2e2ff 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -105,7 +105,7 @@ struct acpi_ioremap { void __iomem *virt; acpi_physical_address phys; acpi_size size; - struct kref ref; + unsigned long refcount; }; static LIST_HEAD(acpi_ioremaps); @@ -319,7 +319,7 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) /* Check if there's a suitable mapping already. */ map = acpi_map_lookup(phys, size); if (map) { - kref_get(&map->ref); + map->refcount++; goto out; } @@ -342,7 +342,7 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) map->virt = virt; map->phys = pg_off; map->size = pg_sz; - kref_init(&map->ref); + map->refcount = 1; list_add_tail_rcu(&map->list, &acpi_ioremaps); @@ -352,25 +352,24 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) } EXPORT_SYMBOL_GPL(acpi_os_map_memory); -static void acpi_kref_del_iomap(struct kref *ref) +static void acpi_os_drop_map_ref(struct acpi_ioremap *map) { - struct acpi_ioremap *map; - - map = container_of(ref, struct acpi_ioremap, ref); - list_del_rcu(&map->list); + if (!--map->refcount) + list_del_rcu(&map->list); } -static void acpi_os_remove_map(struct acpi_ioremap *map) +static void acpi_os_map_cleanup(struct acpi_ioremap *map) { - synchronize_rcu(); - iounmap(map->virt); - kfree(map); + if (!map->refcount) { + synchronize_rcu(); + iounmap(map->virt); + kfree(map); + } } void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) { struct acpi_ioremap *map; - int del; if (!acpi_gbl_permanent_mmap) { __acpi_unmap_table(virt, size); @@ -384,11 +383,10 @@ void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) WARN(true, PREFIX "%s: bad address %p\n", __func__, virt); return; } - del = kref_put(&map->ref, acpi_kref_del_iomap); + acpi_os_drop_map_ref(map); mutex_unlock(&acpi_ioremap_lock); - if (del) - acpi_os_remove_map(map); + acpi_os_map_cleanup(map); } EXPORT_SYMBOL_GPL(acpi_os_unmap_memory); @@ -418,7 +416,6 @@ static int acpi_os_map_generic_address(struct acpi_generic_address *addr) static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) { struct acpi_ioremap *map; - int del; if (addr->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) return; @@ -432,11 +429,10 @@ static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) mutex_unlock(&acpi_ioremap_lock); return; } - del = kref_put(&map->ref, acpi_kref_del_iomap); + acpi_os_drop_map_ref(map); mutex_unlock(&acpi_ioremap_lock); - if (del) - acpi_os_remove_map(map); + acpi_os_map_cleanup(map); } #ifdef ACPI_FUTURE_USAGE From 13606a2de1996f8d83a9ce296f74022bdbadf712 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:38:25 +0100 Subject: [PATCH 06/16] ACPI: Introduce acpi_os_get_iomem() Introduce function acpi_os_get_iomem() that may be used by its callers to get a reference to an ACPI iomap. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 16 ++++++++++++++++ include/linux/acpi_io.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 5389f9f2e2ff..52ca8721bc9c 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -285,6 +285,22 @@ acpi_map_vaddr_lookup(acpi_physical_address phys, unsigned int size) return NULL; } +void __iomem *acpi_os_get_iomem(acpi_physical_address phys, unsigned int size) +{ + struct acpi_ioremap *map; + void __iomem *virt = NULL; + + mutex_lock(&acpi_ioremap_lock); + map = acpi_map_lookup(phys, size); + if (map) { + virt = map->virt + (phys - map->phys); + map->refcount++; + } + mutex_unlock(&acpi_ioremap_lock); + return virt; +} +EXPORT_SYMBOL_GPL(acpi_os_get_iomem); + /* Must be called with 'acpi_ioremap_lock' or RCU read lock held. */ static struct acpi_ioremap * acpi_map_lookup_virt(void __iomem *virt, acpi_size size) diff --git a/include/linux/acpi_io.h b/include/linux/acpi_io.h index 28a3ae279d7a..4afd7102459d 100644 --- a/include/linux/acpi_io.h +++ b/include/linux/acpi_io.h @@ -10,4 +10,6 @@ static inline void __iomem *acpi_os_ioremap(acpi_physical_address phys, return ioremap_cache(phys, size); } +void __iomem *acpi_os_get_iomem(acpi_physical_address phys, unsigned int size); + #endif From bb45e394e21eb2abc710ad43d98ebac1069bf355 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:38:38 +0100 Subject: [PATCH 07/16] ACPI / PM: Use existing ACPI iomaps for NVS save/restore (v2) Modify the NVS save/restore code to use acpi_os_get_iomem() and acpi_os_unmap_memory() to acquire and release references to ACPI iomaps, respectively. If there's no ACPI iomap corresponding to the given NVS page, acpi_os_ioremap() is used to map that page and iounmap() is used to unmap it during resume. [If the page is not present in the ACPI iomaps already, it doesn't make sense to add its mapping to the list of ACPI iomaps, because it's going to be thrown away during the subsequent resume anyway.] Testing on my HP nx6325 shows that approx. 90% of the NVS pages have already been mapped by ACPI before suspend and are present in the ACPI iomaps, so this change appears to be the right thing to do in general. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/nvs.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/nvs.c b/drivers/acpi/nvs.c index fa5a1df42b79..096787b43c96 100644 --- a/drivers/acpi/nvs.c +++ b/drivers/acpi/nvs.c @@ -26,6 +26,7 @@ struct nvs_page { unsigned int size; void *kaddr; void *data; + bool unmap; struct list_head node; }; @@ -44,6 +45,9 @@ int suspend_nvs_register(unsigned long start, unsigned long size) { struct nvs_page *entry, *next; + pr_info("PM: Registering ACPI NVS region at %lx (%ld bytes)\n", + start, size); + while (size > 0) { unsigned int nr_bytes; @@ -81,7 +85,13 @@ void suspend_nvs_free(void) free_page((unsigned long)entry->data); entry->data = NULL; if (entry->kaddr) { - iounmap(entry->kaddr); + if (entry->unmap) { + iounmap(entry->kaddr); + entry->unmap = false; + } else { + acpi_os_unmap_memory(entry->kaddr, + entry->size); + } entry->kaddr = NULL; } } @@ -115,8 +125,14 @@ int suspend_nvs_save(void) list_for_each_entry(entry, &nvs_list, node) if (entry->data) { - entry->kaddr = acpi_os_ioremap(entry->phys_start, - entry->size); + unsigned long phys = entry->phys_start; + unsigned int size = entry->size; + + entry->kaddr = acpi_os_get_iomem(phys, size); + if (!entry->kaddr) { + entry->kaddr = acpi_os_ioremap(phys, size); + entry->unmap = !!entry->kaddr; + } if (!entry->kaddr) { suspend_nvs_free(); return -ENOMEM; From 5190726765b40774c069e187a958e10ccd970e65 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:40:37 +0100 Subject: [PATCH 08/16] ACPI: Remove the wakeup.run_wake_count device field The wakeup.run_wake_count ACPI device field is only used by the PCI runtime PM code to "protect" devices from being prepared for generating wakeup signals more than once in a row. However, it really doesn't provide any protection, because (1) all of the functions it is supposed to protect use their own reference counters effectively ensuring that the device will be set up for generating wakeup signals just once and (2) the PCI runtime PM code uses wakeup.run_wake_count in a racy way, since nothing prevents acpi_dev_run_wake() from being called concurrently from two different threads for the same device. Remove the wakeup.run_wake_count ACPI device field which is unnecessary, confusing and used in a wrong way. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/button.c | 2 -- drivers/acpi/scan.c | 1 - drivers/pci/pci-acpi.c | 16 ++++------------ include/acpi/acpi_bus.h | 1 - 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 76bbb78a5ad9..e643a0936dc4 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -430,7 +430,6 @@ static int acpi_button_add(struct acpi_device *device) /* Button's GPE is run-wake GPE */ acpi_enable_gpe(device->wakeup.gpe_device, device->wakeup.gpe_number); - device->wakeup.run_wake_count++; device_set_wakeup_enable(&device->dev, true); } @@ -453,7 +452,6 @@ static int acpi_button_remove(struct acpi_device *device, int type) if (device->wakeup.flags.valid) { acpi_disable_gpe(device->wakeup.gpe_device, device->wakeup.gpe_number); - device->wakeup.run_wake_count--; device_set_wakeup_enable(&device->dev, false); } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index b99e62494607..b136c9c1e531 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -797,7 +797,6 @@ static void acpi_bus_set_run_wake_flags(struct acpi_device *device) acpi_status status; acpi_event_status event_status; - device->wakeup.run_wake_count = 0; device->wakeup.flags.notifier_present = 0; /* Power button, Lid switch always enable wakeup */ diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 6fe0772e0e7d..7c3b18e78cee 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -293,19 +293,11 @@ static int acpi_dev_run_wake(struct device *phys_dev, bool enable) } if (enable) { - if (!dev->wakeup.run_wake_count++) { - acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0); - acpi_enable_gpe(dev->wakeup.gpe_device, - dev->wakeup.gpe_number); - } - } else if (dev->wakeup.run_wake_count > 0) { - if (!--dev->wakeup.run_wake_count) { - acpi_disable_gpe(dev->wakeup.gpe_device, - dev->wakeup.gpe_number); - acpi_disable_wakeup_device_power(dev); - } + acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0); + acpi_enable_gpe(dev->wakeup.gpe_device, dev->wakeup.gpe_number); } else { - error = -EALREADY; + acpi_disable_gpe(dev->wakeup.gpe_device, dev->wakeup.gpe_number); + acpi_disable_wakeup_device_power(dev); } return error; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 78ca429929f7..f50ebb9bc53b 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -250,7 +250,6 @@ struct acpi_device_wakeup { struct acpi_handle_list resources; struct acpi_device_wakeup_flags flags; int prepare_count; - int run_wake_count; }; /* Device */ From c19f9a84ec807da57fd75bbd9a3f2b8269611f79 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:41:13 +0100 Subject: [PATCH 09/16] ACPI / Button: Avoid disabling wakeup unnecessarily on remove If a button device had already been enabled to wake up the system from sleep states before the button driver saw it, the driver shouldn't disable the device's wakeup capability when being detached from the device. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/button.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index e643a0936dc4..12c28f4adb67 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -98,6 +98,7 @@ struct acpi_button { struct input_dev *input; char phys[32]; /* for input device */ unsigned long pushed; + bool wakeup_enabled; }; static const struct file_operations acpi_button_info_fops = { @@ -430,7 +431,10 @@ static int acpi_button_add(struct acpi_device *device) /* Button's GPE is run-wake GPE */ acpi_enable_gpe(device->wakeup.gpe_device, device->wakeup.gpe_number); - device_set_wakeup_enable(&device->dev, true); + if (!device_may_wakeup(&device->dev)) { + device_set_wakeup_enable(&device->dev, true); + button->wakeup_enabled = true; + } } printk(KERN_INFO PREFIX "%s [%s]\n", name, acpi_device_bid(device)); @@ -452,7 +456,8 @@ static int acpi_button_remove(struct acpi_device *device, int type) if (device->wakeup.flags.valid) { acpi_disable_gpe(device->wakeup.gpe_device, device->wakeup.gpe_number); - device_set_wakeup_enable(&device->dev, false); + if (button->wakeup_enabled) + device_set_wakeup_enable(&device->dev, false); } acpi_button_remove_fs(device); From c41b93fb8551148a93d3bba870365e8489317f02 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:41:35 +0100 Subject: [PATCH 10/16] ACPI / PM: Drop acpi_restore_state_mem() The function acpi_restore_state_mem() has never been and most likely never will be used, so remove it. Signed-off-by: Rafael J. Wysocki --- arch/ia64/include/asm/acpi.h | 4 ++-- arch/ia64/kernel/acpi.c | 5 ----- arch/x86/include/asm/acpi.h | 3 +-- arch/x86/kernel/acpi/sleep.c | 8 -------- drivers/acpi/sleep.c | 4 ---- 5 files changed, 3 insertions(+), 21 deletions(-) diff --git a/arch/ia64/include/asm/acpi.h b/arch/ia64/include/asm/acpi.h index 837dc82a013e..db3986a71af8 100644 --- a/arch/ia64/include/asm/acpi.h +++ b/arch/ia64/include/asm/acpi.h @@ -128,9 +128,9 @@ static inline const char *acpi_get_sysname (void) int acpi_request_vector (u32 int_type); int acpi_gsi_to_irq (u32 gsi, unsigned int *irq); -/* routines for saving/restoring kernel state */ +/* Routine for saving kernel state during suspend. */ extern int acpi_save_state_mem(void); -extern void acpi_restore_state_mem(void); + extern unsigned long acpi_wakeup_address; /* diff --git a/arch/ia64/kernel/acpi.c b/arch/ia64/kernel/acpi.c index 90ebceb899a0..7cf2d10c0677 100644 --- a/arch/ia64/kernel/acpi.c +++ b/arch/ia64/kernel/acpi.c @@ -1040,11 +1040,6 @@ EXPORT_SYMBOL(acpi_unregister_ioapic); */ int acpi_save_state_mem(void) { return 0; } -/* - * acpi_restore_state() - */ -void acpi_restore_state_mem(void) {} - /* * do_suspend_lowlevel() */ diff --git a/arch/x86/include/asm/acpi.h b/arch/x86/include/asm/acpi.h index 211ca3f7fd16..47981f0d121b 100644 --- a/arch/x86/include/asm/acpi.h +++ b/arch/x86/include/asm/acpi.h @@ -112,9 +112,8 @@ static inline void acpi_disable_pci(void) acpi_noirq_set(); } -/* routines for saving/restoring kernel state */ +/* Routine for saving kernel state during suspend. */ extern int acpi_save_state_mem(void); -extern void acpi_restore_state_mem(void); extern unsigned long acpi_wakeup_address; diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c index 68d1537b8c81..c27a483094b1 100644 --- a/arch/x86/kernel/acpi/sleep.c +++ b/arch/x86/kernel/acpi/sleep.c @@ -110,14 +110,6 @@ int acpi_save_state_mem(void) return 0; } -/* - * acpi_restore_state - undo effects of acpi_save_state_mem - */ -void acpi_restore_state_mem(void) -{ -} - - /** * acpi_reserve_wakeup_memory - do _very_ early ACPI initialisation * diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index d6a8cd14de2e..67dcd3886563 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -293,10 +293,6 @@ static int acpi_suspend_enter(suspend_state_t pm_state) local_irq_restore(flags); printk(KERN_DEBUG "Back to C!\n"); - /* restore processor state */ - if (acpi_state == ACPI_STATE_S3) - acpi_restore_state_mem(); - suspend_nvs_restore(); return ACPI_SUCCESS(status) ? 0 : -EFAULT; From 82911fe1988fadfc9c01673202cbc411aa803244 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:41:47 +0100 Subject: [PATCH 11/16] ACPI / PM: acpi_suspend_enter() need not switch interrupts off The function acpi_suspend_enter() is always called with interrupts off, so it doesn't need to switch them off and on. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 67dcd3886563..3b549379801a 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -243,7 +243,6 @@ static int acpi_suspend_begin(suspend_state_t pm_state) static int acpi_suspend_enter(suspend_state_t pm_state) { acpi_status status = AE_OK; - unsigned long flags = 0; u32 acpi_state = acpi_target_sleep_state; ACPI_FLUSH_CPU_CACHE(); @@ -256,7 +255,6 @@ static int acpi_suspend_enter(suspend_state_t pm_state) return error; } - local_irq_save(flags); switch (acpi_state) { case ACPI_STATE_S1: barrier(); @@ -290,7 +288,6 @@ static int acpi_suspend_enter(suspend_state_t pm_state) /* Allow EC transactions to happen. */ acpi_ec_unblock_transactions_early(); - local_irq_restore(flags); printk(KERN_DEBUG "Back to C!\n"); suspend_nvs_restore(); From 7a63f08b2b904d25e966dd0e15c9af4a13c80b90 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:41:57 +0100 Subject: [PATCH 12/16] ACPI / PM: Modify the "low-level resume finished" message Move the low-level resume completion message to the point where control goes back to acpi_suspend_enter() during resume and change it so that it's more informative. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 3b549379801a..6bea2fe8434b 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -263,6 +263,7 @@ static int acpi_suspend_enter(suspend_state_t pm_state) case ACPI_STATE_S3: do_suspend_lowlevel(); + pr_info(PREFIX "Low-level resume complete\n"); break; } @@ -288,8 +289,6 @@ static int acpi_suspend_enter(suspend_state_t pm_state) /* Allow EC transactions to happen. */ acpi_ec_unblock_transactions_early(); - printk(KERN_DEBUG "Back to C!\n"); - suspend_nvs_restore(); return ACPI_SUCCESS(status) ? 0 : -EFAULT; From 979f11b060c0b35b03b86ae854d6f21a710305d0 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:42:09 +0100 Subject: [PATCH 13/16] ACPI / PM: Call acpi_save_state_mem() right before low-level suspend Since acpi_save_state_mem() is only called by acpi_suspend_enter() if the target sleep state is S3, it's better to call it under the switch (acpi_state), right before do_suspend_lowlevel(). Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 6bea2fe8434b..2307604064b3 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -244,17 +244,10 @@ static int acpi_suspend_enter(suspend_state_t pm_state) { acpi_status status = AE_OK; u32 acpi_state = acpi_target_sleep_state; + int error; ACPI_FLUSH_CPU_CACHE(); - /* Do arch specific saving of state. */ - if (acpi_state == ACPI_STATE_S3) { - int error = acpi_save_state_mem(); - - if (error) - return error; - } - switch (acpi_state) { case ACPI_STATE_S1: barrier(); @@ -262,6 +255,9 @@ static int acpi_suspend_enter(suspend_state_t pm_state) break; case ACPI_STATE_S3: + error = acpi_save_state_mem(); + if (error) + return error; do_suspend_lowlevel(); pr_info(PREFIX "Low-level resume complete\n"); break; From f1a2003e22f6b50ea21f7f4b38b38c5ebc9c8017 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:42:22 +0100 Subject: [PATCH 14/16] ACPI / PM: Merge do_suspend_lowlevel() into acpi_save_state_mem() The function do_suspend_lowlevel() is specific to x86 and defined in assembly code, so it should be called from the x86 low-level suspend code rather than from acpi_suspend_enter(). Merge do_suspend_lowlevel() into the x86's acpi_save_state_mem() and change the name of the latter to acpi_suspend_lowlevel(), so that the function's purpose is better reflected by its name. Signed-off-by: Rafael J. Wysocki --- arch/ia64/include/asm/acpi.h | 4 ++-- arch/ia64/kernel/acpi.c | 9 ++------- arch/x86/include/asm/acpi.h | 4 ++-- arch/x86/kernel/acpi/sleep.c | 5 +++-- arch/x86/kernel/acpi/sleep.h | 2 ++ drivers/acpi/sleep.c | 5 +---- 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/arch/ia64/include/asm/acpi.h b/arch/ia64/include/asm/acpi.h index db3986a71af8..a06dfb13d518 100644 --- a/arch/ia64/include/asm/acpi.h +++ b/arch/ia64/include/asm/acpi.h @@ -128,8 +128,8 @@ static inline const char *acpi_get_sysname (void) int acpi_request_vector (u32 int_type); int acpi_gsi_to_irq (u32 gsi, unsigned int *irq); -/* Routine for saving kernel state during suspend. */ -extern int acpi_save_state_mem(void); +/* Low-level suspend routine. */ +extern int acpi_suspend_lowlevel(void); extern unsigned long acpi_wakeup_address; diff --git a/arch/ia64/kernel/acpi.c b/arch/ia64/kernel/acpi.c index 7cf2d10c0677..a54d054ed4b0 100644 --- a/arch/ia64/kernel/acpi.c +++ b/arch/ia64/kernel/acpi.c @@ -1034,13 +1034,8 @@ int acpi_unregister_ioapic(acpi_handle handle, u32 gsi_base) EXPORT_SYMBOL(acpi_unregister_ioapic); /* - * acpi_save_state_mem() - save kernel state + * acpi_suspend_lowlevel() - save kernel state and suspend. * * TBD when when IA64 starts to support suspend... */ -int acpi_save_state_mem(void) { return 0; } - -/* - * do_suspend_lowlevel() - */ -void do_suspend_lowlevel(void) {} +int acpi_suspend_lowlevel(void) { return 0; } diff --git a/arch/x86/include/asm/acpi.h b/arch/x86/include/asm/acpi.h index 47981f0d121b..aa92684aa674 100644 --- a/arch/x86/include/asm/acpi.h +++ b/arch/x86/include/asm/acpi.h @@ -112,8 +112,8 @@ static inline void acpi_disable_pci(void) acpi_noirq_set(); } -/* Routine for saving kernel state during suspend. */ -extern int acpi_save_state_mem(void); +/* Low-level suspend routine. */ +extern int acpi_suspend_lowlevel(void); extern unsigned long acpi_wakeup_address; diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c index c27a483094b1..5f1b747f6ef1 100644 --- a/arch/x86/kernel/acpi/sleep.c +++ b/arch/x86/kernel/acpi/sleep.c @@ -29,14 +29,14 @@ static char temp_stack[4096]; #endif /** - * acpi_save_state_mem - save kernel state + * acpi_suspend_lowlevel - save kernel state * * Create an identity mapped page table and copy the wakeup routine to * low memory. * * Note that this is too late to change acpi_wakeup_address. */ -int acpi_save_state_mem(void) +int acpi_suspend_lowlevel(void) { struct wakeup_header *header; @@ -107,6 +107,7 @@ int acpi_save_state_mem(void) saved_magic = 0x123456789abcdef0L; #endif /* CONFIG_64BIT */ + do_suspend_lowlevel(); return 0; } diff --git a/arch/x86/kernel/acpi/sleep.h b/arch/x86/kernel/acpi/sleep.h index adbcbaa6f1df..31ce13f20297 100644 --- a/arch/x86/kernel/acpi/sleep.h +++ b/arch/x86/kernel/acpi/sleep.h @@ -14,3 +14,5 @@ extern char swsusp_pg_dir[PAGE_SIZE]; extern unsigned long acpi_copy_wakeup_routine(unsigned long); extern void wakeup_long64(void); + +extern void do_suspend_lowlevel(void); diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 2307604064b3..0a81bf11f473 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -199,8 +199,6 @@ static void acpi_pm_end(void) #endif /* CONFIG_ACPI_SLEEP */ #ifdef CONFIG_SUSPEND -extern void do_suspend_lowlevel(void); - static u32 acpi_suspend_states[] = { [PM_SUSPEND_ON] = ACPI_STATE_S0, [PM_SUSPEND_STANDBY] = ACPI_STATE_S1, @@ -255,10 +253,9 @@ static int acpi_suspend_enter(suspend_state_t pm_state) break; case ACPI_STATE_S3: - error = acpi_save_state_mem(); + error = acpi_suspend_lowlevel(); if (error) return error; - do_suspend_lowlevel(); pr_info(PREFIX "Low-level resume complete\n"); break; } From aad83b143008e1d406248803550bfbfc600b6398 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:42:35 +0100 Subject: [PATCH 15/16] ACPI / PM: acpi_hibernation_enter() need not switch interrupts off The function acpi_hibernation_enter() is always called with interrupts off, so it doesn't need to switch them off and on. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 0a81bf11f473..84f57143ad7c 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -457,16 +457,13 @@ static int acpi_hibernation_begin(void) static int acpi_hibernation_enter(void) { acpi_status status = AE_OK; - unsigned long flags = 0; ACPI_FLUSH_CPU_CACHE(); - local_irq_save(flags); /* This shouldn't return. If it returns, we have a problem */ status = acpi_enter_sleep_state(ACPI_STATE_S4); /* Reprogram control registers and execute _BFS */ acpi_leave_sleep_state_prep(ACPI_STATE_S4); - local_irq_restore(flags); return ACPI_SUCCESS(status) ? 0 : -EFAULT; } From 23fe36306ea489eef7dd88506bdcefdc8da39c91 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 8 Feb 2011 23:48:16 +0100 Subject: [PATCH 16/16] ACPI: Avoid calling request_irq() many times for the same interrupt In principle acpi_os_install_interrupt_handler() may be called multiple times for different interrupts, either from acpi_ev_get_gpe_xrupt_block(), or from acpi_ev_install_sci_handler(). However, it always attempts to request the same interrupt, acpi_gbl_FADT.sci_interrupt and it doesn't check whether or not this interrupt has already been requested. Modify this function so that it refuses to request interrupts other than acpi_gbl_FADT.sci_interrupt and change acpi_os_remove_interrupt_handler() so that it refuses to free such interrupts. Use the observation that the only supported ACPI interrupt must be equal to acpi_gbl_FADT.sci_interrupt and drop an unnecessary variable acpi_irq_irq. This change has been tested on Toshiba Portege R500 and HP nx6325 without introducing any visible problems. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index c90c76aa7f8b..187dff96356b 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -76,7 +76,6 @@ EXPORT_SYMBOL(acpi_in_debugger); extern char line_buf[80]; #endif /*ENABLE_DEBUGGER */ -static unsigned int acpi_irq_irq; static acpi_osd_handler acpi_irq_handler; static void *acpi_irq_context; static struct workqueue_struct *kacpid_wq; @@ -516,11 +515,15 @@ acpi_os_install_interrupt_handler(u32 gsi, acpi_osd_handler handler, acpi_irq_stats_init(); /* - * Ignore the GSI from the core, and use the value in our copy of the - * FADT. It may not be the same if an interrupt source override exists - * for the SCI. + * ACPI interrupts different from the SCI in our copy of the FADT are + * not supported. */ - gsi = acpi_gbl_FADT.sci_interrupt; + if (gsi != acpi_gbl_FADT.sci_interrupt) + return AE_BAD_PARAMETER; + + if (acpi_irq_handler) + return AE_ALREADY_ACQUIRED; + if (acpi_gsi_to_irq(gsi, &irq) < 0) { printk(KERN_ERR PREFIX "SCI (ACPI GSI %d) not registered\n", gsi); @@ -531,20 +534,20 @@ acpi_os_install_interrupt_handler(u32 gsi, acpi_osd_handler handler, acpi_irq_context = context; if (request_irq(irq, acpi_irq, IRQF_SHARED, "acpi", acpi_irq)) { printk(KERN_ERR PREFIX "SCI (IRQ%d) allocation failed\n", irq); + acpi_irq_handler = NULL; return AE_NOT_ACQUIRED; } - acpi_irq_irq = irq; return AE_OK; } acpi_status acpi_os_remove_interrupt_handler(u32 irq, acpi_osd_handler handler) { - if (irq) { - free_irq(irq, acpi_irq); - acpi_irq_handler = NULL; - acpi_irq_irq = 0; - } + if (irq != acpi_gbl_FADT.sci_interrupt) + return AE_BAD_PARAMETER; + + free_irq(irq, acpi_irq); + acpi_irq_handler = NULL; return AE_OK; } @@ -1603,7 +1606,7 @@ acpi_status __init acpi_os_initialize1(void) acpi_status acpi_os_terminate(void) { if (acpi_irq_handler) { - acpi_os_remove_interrupt_handler(acpi_irq_irq, + acpi_os_remove_interrupt_handler(acpi_gbl_FADT.sci_interrupt, acpi_irq_handler); }