From 26fcaf60fe3861409eb4c455c5c0d0f00f599b08 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Fri, 7 Jan 2011 01:42:31 +0100 Subject: [PATCH 1/5] PM: Fix oops in suspend/hibernate code related to failing ioremap() When ioremap() fails (which might happen for some reason), we nicely oops in suspend_nvs_save() due to NULL dereference by memcpy() in there. Fail gracefully instead. Signed-off-by: Jiri Slaby Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown --- drivers/acpi/sleep.c | 5 ++--- include/linux/suspend.h | 4 ++-- kernel/power/nvs.c | 8 +++++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index febb153b5a68..d8bca6c90719 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -124,8 +124,7 @@ static int acpi_pm_freeze(void) static int acpi_pm_pre_suspend(void) { acpi_pm_freeze(); - suspend_nvs_save(); - return 0; + return suspend_nvs_save(); } /** @@ -151,7 +150,7 @@ static int acpi_pm_prepare(void) { int error = __acpi_pm_prepare(); if (!error) - acpi_pm_pre_suspend(); + error = acpi_pm_pre_suspend(); return error; } diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 26697514c5ec..acb7d911bb0c 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -262,7 +262,7 @@ static inline bool system_entering_hibernation(void) { return false; } extern int suspend_nvs_register(unsigned long start, unsigned long size); extern int suspend_nvs_alloc(void); extern void suspend_nvs_free(void); -extern void suspend_nvs_save(void); +extern int suspend_nvs_save(void); extern void suspend_nvs_restore(void); #else /* CONFIG_SUSPEND_NVS */ static inline int suspend_nvs_register(unsigned long a, unsigned long b) @@ -271,7 +271,7 @@ static inline int suspend_nvs_register(unsigned long a, unsigned long b) } static inline int suspend_nvs_alloc(void) { return 0; } static inline void suspend_nvs_free(void) {} -static inline void suspend_nvs_save(void) {} +static inline int suspend_nvs_save(void) {} static inline void suspend_nvs_restore(void) {} #endif /* CONFIG_SUSPEND_NVS */ diff --git a/kernel/power/nvs.c b/kernel/power/nvs.c index 1836db60bbb6..57c6fabbb6b6 100644 --- a/kernel/power/nvs.c +++ b/kernel/power/nvs.c @@ -105,7 +105,7 @@ int suspend_nvs_alloc(void) /** * suspend_nvs_save - save NVS memory regions */ -void suspend_nvs_save(void) +int suspend_nvs_save(void) { struct nvs_page *entry; @@ -114,8 +114,14 @@ void suspend_nvs_save(void) list_for_each_entry(entry, &nvs_list, node) if (entry->data) { entry->kaddr = ioremap(entry->phys_start, entry->size); + if (!entry->kaddr) { + suspend_nvs_free(); + return -ENOMEM; + } memcpy(entry->data, entry->kaddr, entry->size); } + + return 0; } /** From 976513dbfc1547c7b1822566923058655f0c32fd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 7 Jan 2011 01:43:44 +0100 Subject: [PATCH 2/5] PM / ACPI: Move NVS saving and restoring code to drivers/acpi The saving of the ACPI NVS area during hibernation and suspend and restoring it during the subsequent resume is entirely specific to ACPI, so move it to drivers/acpi and drop the CONFIG_SUSPEND_NVS configuration option which is redundant. Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown --- arch/x86/kernel/e820.c | 1 + drivers/acpi/Makefile | 2 +- drivers/acpi/internal.h | 8 ++++++++ {kernel/power => drivers/acpi}/nvs.c | 0 include/linux/acpi.h | 9 +++++++++ include/linux/suspend.h | 17 ----------------- kernel/power/Kconfig | 5 ----- kernel/power/Makefile | 1 - 8 files changed, 19 insertions(+), 24 deletions(-) rename {kernel/power => drivers/acpi}/nvs.c (100%) diff --git a/arch/x86/kernel/e820.c b/arch/x86/kernel/e820.c index 0c2b7ef7a34d..294f26da0c0c 100644 --- a/arch/x86/kernel/e820.c +++ b/arch/x86/kernel/e820.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 3d031d02e54b..9cc9f2c4da79 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -24,7 +24,7 @@ acpi-y += atomicio.o # sleep related files acpi-y += wakeup.o acpi-y += sleep.o -acpi-$(CONFIG_ACPI_SLEEP) += proc.o +acpi-$(CONFIG_ACPI_SLEEP) += proc.o nvs.o # diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index a212bfeddf8c..7c23b76e8eca 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -82,8 +82,16 @@ extern int acpi_sleep_init(void); #ifdef CONFIG_ACPI_SLEEP int acpi_sleep_proc_init(void); +int suspend_nvs_alloc(void); +void suspend_nvs_free(void); +int suspend_nvs_save(void); +void suspend_nvs_restore(void); #else static inline int acpi_sleep_proc_init(void) { return 0; } +static inline int suspend_nvs_alloc(void) { return 0; } +static inline void suspend_nvs_free(void) {} +static inline int suspend_nvs_save(void) {} +static inline void suspend_nvs_restore(void) {} #endif #endif /* _ACPI_INTERNAL_H_ */ diff --git a/kernel/power/nvs.c b/drivers/acpi/nvs.c similarity index 100% rename from kernel/power/nvs.c rename to drivers/acpi/nvs.c diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 67c91b4418b0..fa7ed6a983d0 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -254,6 +254,15 @@ void __init acpi_old_suspend_ordering(void); void __init acpi_nvs_nosave(void); #endif /* CONFIG_PM_SLEEP */ +#ifdef CONFIG_ACPI_SLEEP +int suspend_nvs_register(unsigned long start, unsigned long size); +#else +static inline int suspend_nvs_register(unsigned long a, unsigned long b) +{ + return 0; +} +#endif + struct acpi_osc_context { char *uuid_str; /* uuid string */ int rev; diff --git a/include/linux/suspend.h b/include/linux/suspend.h index acb7d911bb0c..0e288e3c37be 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -258,23 +258,6 @@ static inline int hibernate(void) { return -ENOSYS; } static inline bool system_entering_hibernation(void) { return false; } #endif /* CONFIG_HIBERNATION */ -#ifdef CONFIG_SUSPEND_NVS -extern int suspend_nvs_register(unsigned long start, unsigned long size); -extern int suspend_nvs_alloc(void); -extern void suspend_nvs_free(void); -extern int suspend_nvs_save(void); -extern void suspend_nvs_restore(void); -#else /* CONFIG_SUSPEND_NVS */ -static inline int suspend_nvs_register(unsigned long a, unsigned long b) -{ - return 0; -} -static inline int suspend_nvs_alloc(void) { return 0; } -static inline void suspend_nvs_free(void) {} -static inline int suspend_nvs_save(void) {} -static inline void suspend_nvs_restore(void) {} -#endif /* CONFIG_SUSPEND_NVS */ - #ifdef CONFIG_PM_SLEEP void save_processor_state(void); void restore_processor_state(void); diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index a5aff3ebad38..265729966ece 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -100,13 +100,9 @@ config PM_SLEEP_ADVANCED_DEBUG depends on PM_ADVANCED_DEBUG default n -config SUSPEND_NVS - bool - config SUSPEND bool "Suspend to RAM and standby" depends on PM && ARCH_SUSPEND_POSSIBLE - select SUSPEND_NVS if HAS_IOMEM default y ---help--- Allow the system to enter sleep states in which main memory is @@ -140,7 +136,6 @@ config HIBERNATION depends on PM && SWAP && ARCH_HIBERNATION_POSSIBLE select LZO_COMPRESS select LZO_DECOMPRESS - select SUSPEND_NVS if HAS_IOMEM ---help--- Enable the suspend to disk (STD) functionality, which is usually called "hibernation" in user interfaces. STD checkpoints the diff --git a/kernel/power/Makefile b/kernel/power/Makefile index f9063c6b185d..120a15823325 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -10,6 +10,5 @@ obj-$(CONFIG_SUSPEND) += suspend.o obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \ block_io.o -obj-$(CONFIG_SUSPEND_NVS) += nvs.o obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o From d146df18c13d16e321efa8ef9b57c95c3bec1722 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 7 Jan 2011 01:44:28 +0100 Subject: [PATCH 3/5] ACPI / PM: Update file information and the list of includes in nvs.c The file information and the list of include in drivers/acpi/nvs.c are outdated, so update them. Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown --- drivers/acpi/nvs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/nvs.c b/drivers/acpi/nvs.c index 57c6fabbb6b6..7d648092427e 100644 --- a/drivers/acpi/nvs.c +++ b/drivers/acpi/nvs.c @@ -1,7 +1,7 @@ /* - * linux/kernel/power/hibernate_nvs.c - Routines for handling NVS memory + * nvs.c - Routines for saving and restoring ACPI NVS memory region * - * Copyright (C) 2008,2009 Rafael J. Wysocki , Novell Inc. + * Copyright (C) 2008-2011 Rafael J. Wysocki , Novell Inc. * * This file is released under the GPLv2. */ @@ -11,7 +11,7 @@ #include #include #include -#include +#include /* * Platforms, like ACPI, may want us to save some memory used by them during From ca9b600be38c73b7d25acfb8b7e4e9a9e941d881 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 7 Jan 2011 01:45:58 +0100 Subject: [PATCH 4/5] ACPI / PM: Make suspend_nvs_save() use acpi_os_map_memory() It turns out that the NVS memory region that suspend_nvs_save() attempts to map has been already mapped by acpi_os_map_memory(), so suspend_nvs_save() should better use acpi_os_map_memory() for mapping memory to avoid conflicts. Signed-off-by: Rafael J. Wysocki Signed-off-by: Len Brown --- drivers/acpi/nvs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/nvs.c b/drivers/acpi/nvs.c index 7d648092427e..54b6ab8040a6 100644 --- a/drivers/acpi/nvs.c +++ b/drivers/acpi/nvs.c @@ -12,6 +12,7 @@ #include #include #include +#include /* * Platforms, like ACPI, may want us to save some memory used by them during @@ -79,7 +80,7 @@ void suspend_nvs_free(void) free_page((unsigned long)entry->data); entry->data = NULL; if (entry->kaddr) { - iounmap(entry->kaddr); + acpi_os_unmap_memory(entry->kaddr, entry->size); entry->kaddr = NULL; } } @@ -113,7 +114,8 @@ int suspend_nvs_save(void) list_for_each_entry(entry, &nvs_list, node) if (entry->data) { - entry->kaddr = ioremap(entry->phys_start, entry->size); + entry->kaddr = acpi_os_map_memory(entry->phys_start, + entry->size); if (!entry->kaddr) { suspend_nvs_free(); return -ENOMEM; From 6d5bbf00d251cc73223a71422d69e069dc2e0b8d Mon Sep 17 00:00:00 2001 From: Len Brown Date: Fri, 7 Jan 2011 01:46:40 +0100 Subject: [PATCH 5/5] ACPI: Use ioremap_cache() Although the temporary boot-time ACPI table mappings were set up with CPU caching enabled, the permanent table mappings and AML run-time region memory accesses were set up with ioremap(), which on x86 is a synonym for ioremap_nocache(). Changing this to ioremap_cache() improves performance as seen when accessing the tables via acpidump, or /sys/firmware/acpi/tables. It should also improve AML run-time performance. No change on ia64. Reported-by: Jack Steiner Signed-off-by: Len Brown Signed-off-by: Rafael J. Wysocki --- arch/ia64/include/asm/io.h | 5 +++++ drivers/acpi/osl.c | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/arch/ia64/include/asm/io.h b/arch/ia64/include/asm/io.h index cc8335eb3110..e5a6c3530c6c 100644 --- a/arch/ia64/include/asm/io.h +++ b/arch/ia64/include/asm/io.h @@ -426,6 +426,11 @@ extern void __iomem * ioremap_nocache (unsigned long offset, unsigned long size) extern void iounmap (volatile void __iomem *addr); extern void __iomem * early_ioremap (unsigned long phys_addr, unsigned long size); extern void early_iounmap (volatile void __iomem *addr, unsigned long size); +static inline void __iomem * ioremap_cache (unsigned long phys_addr, unsigned long size) +{ + return ioremap(phys_addr, size); +} + /* * String version of IO memory access ops: diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 055d7b701fff..3a7b4879fd99 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -320,7 +320,7 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) pg_off = round_down(phys, PAGE_SIZE); pg_sz = round_up(phys + size, PAGE_SIZE) - pg_off; - virt = ioremap(pg_off, pg_sz); + virt = ioremap_cache(pg_off, pg_sz); if (!virt) { kfree(map); return NULL; @@ -642,7 +642,7 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u32 * value, u32 width) virt_addr = acpi_map_vaddr_lookup(phys_addr, size); rcu_read_unlock(); if (!virt_addr) { - virt_addr = ioremap(phys_addr, size); + virt_addr = ioremap_cache(phys_addr, size); unmap = 1; } if (!value) @@ -678,7 +678,7 @@ acpi_os_write_memory(acpi_physical_address phys_addr, u32 value, u32 width) virt_addr = acpi_map_vaddr_lookup(phys_addr, size); rcu_read_unlock(); if (!virt_addr) { - virt_addr = ioremap(phys_addr, size); + virt_addr = ioremap_cache(phys_addr, size); unmap = 1; }