diff options
author | Rafael J. Wysocki <rjw@sisk.pl> | 2007-10-18 06:04:55 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-10-18 17:37:20 -0400 |
commit | c7e0831d385d620a58d95b25e4afa9b643f9a411 (patch) | |
tree | d3dde7c47276aeb6fad0a1f25fa6b74cc5dacfc1 | |
parent | efa4d2fb047b25a6be67fe92178a2a78da6b3f6a (diff) |
Hibernation: Check if ACPI is enabled during restore in the right place
The following scenario leads to total confusion of the platform firmware on
some boxes (eg. HPC nx6325):
* Hibernate with ACPI enabled
* Resume passing "acpi=off" to the boot kernel
To prevent this from happening it's necessary to check if ACPI is enabled (and
enable it if that's not the case) _right_ _after_ control has been transfered
from the boot kernel to the image kernel, before device_power_up() is called
(ie. with interrupts disabled). Enabling ACPI after calling
device_power_up() turns out to be insufficient.
For this reason, introduce new hibernation callback ->leave() that will be
executed before device_power_up() by the restored image kernel. To make it
work, it also is necessary to move swsusp_suspend() from swsusp.c to disk.c
(it's name is changed to "create_image", which is more up to the point).
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/acpi/sleep/main.c | 10 | ||||
-rw-r--r-- | include/linux/suspend.h | 7 | ||||
-rw-r--r-- | kernel/power/disk.c | 58 | ||||
-rw-r--r-- | kernel/power/power.h | 1 | ||||
-rw-r--r-- | kernel/power/swsusp.c | 33 |
5 files changed, 74 insertions, 35 deletions
diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c index acfb01b8ec82..048295ec3707 100644 --- a/drivers/acpi/sleep/main.c +++ b/drivers/acpi/sleep/main.c | |||
@@ -257,6 +257,15 @@ static int acpi_hibernation_enter(void) | |||
257 | return ACPI_SUCCESS(status) ? 0 : -EFAULT; | 257 | return ACPI_SUCCESS(status) ? 0 : -EFAULT; |
258 | } | 258 | } |
259 | 259 | ||
260 | static void acpi_hibernation_leave(void) | ||
261 | { | ||
262 | /* | ||
263 | * If ACPI is not enabled by the BIOS and the boot kernel, we need to | ||
264 | * enable it here. | ||
265 | */ | ||
266 | acpi_enable(); | ||
267 | } | ||
268 | |||
260 | static void acpi_hibernation_finish(void) | 269 | static void acpi_hibernation_finish(void) |
261 | { | 270 | { |
262 | acpi_leave_sleep_state(ACPI_STATE_S4); | 271 | acpi_leave_sleep_state(ACPI_STATE_S4); |
@@ -288,6 +297,7 @@ static struct platform_hibernation_ops acpi_hibernation_ops = { | |||
288 | .finish = acpi_hibernation_finish, | 297 | .finish = acpi_hibernation_finish, |
289 | .prepare = acpi_hibernation_prepare, | 298 | .prepare = acpi_hibernation_prepare, |
290 | .enter = acpi_hibernation_enter, | 299 | .enter = acpi_hibernation_enter, |
300 | .leave = acpi_hibernation_leave, | ||
291 | .pre_restore = acpi_hibernation_pre_restore, | 301 | .pre_restore = acpi_hibernation_pre_restore, |
292 | .restore_cleanup = acpi_hibernation_restore_cleanup, | 302 | .restore_cleanup = acpi_hibernation_restore_cleanup, |
293 | }; | 303 | }; |
diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 9fd2b9acf7d9..4360e0816956 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h | |||
@@ -156,6 +156,12 @@ extern void mark_free_pages(struct zone *zone); | |||
156 | * Called after the nonboot CPUs have been disabled and all of the low | 156 | * Called after the nonboot CPUs have been disabled and all of the low |
157 | * level devices have been shut down (runs with IRQs off). | 157 | * level devices have been shut down (runs with IRQs off). |
158 | * | 158 | * |
159 | * @leave: Perform the first stage of the cleanup after the system sleep state | ||
160 | * indicated by @set_target() has been left. | ||
161 | * Called right after the control has been passed from the boot kernel to | ||
162 | * the image kernel, before the nonboot CPUs are enabled and before devices | ||
163 | * are resumed. Executed with interrupts disabled. | ||
164 | * | ||
159 | * @pre_restore: Prepare system for the restoration from a hibernation image. | 165 | * @pre_restore: Prepare system for the restoration from a hibernation image. |
160 | * Called right after devices have been frozen and before the nonboot | 166 | * Called right after devices have been frozen and before the nonboot |
161 | * CPUs are disabled (runs with IRQs on). | 167 | * CPUs are disabled (runs with IRQs on). |
@@ -170,6 +176,7 @@ struct platform_hibernation_ops { | |||
170 | void (*finish)(void); | 176 | void (*finish)(void); |
171 | int (*prepare)(void); | 177 | int (*prepare)(void); |
172 | int (*enter)(void); | 178 | int (*enter)(void); |
179 | void (*leave)(void); | ||
173 | int (*pre_restore)(void); | 180 | int (*pre_restore)(void); |
174 | void (*restore_cleanup)(void); | 181 | void (*restore_cleanup)(void); |
175 | }; | 182 | }; |
diff --git a/kernel/power/disk.c b/kernel/power/disk.c index 555c0f0b2f7e..e50f4da18fd5 100644 --- a/kernel/power/disk.c +++ b/kernel/power/disk.c | |||
@@ -93,6 +93,17 @@ static int platform_pre_snapshot(int platform_mode) | |||
93 | } | 93 | } |
94 | 94 | ||
95 | /** | 95 | /** |
96 | * platform_leave - prepare the machine for switching to the normal mode | ||
97 | * of operation using the platform driver (called with interrupts disabled) | ||
98 | */ | ||
99 | |||
100 | static void platform_leave(int platform_mode) | ||
101 | { | ||
102 | if (platform_mode && hibernation_ops) | ||
103 | hibernation_ops->leave(); | ||
104 | } | ||
105 | |||
106 | /** | ||
96 | * platform_finish - switch the machine to the normal mode of operation | 107 | * platform_finish - switch the machine to the normal mode of operation |
97 | * using the platform driver (must be called after platform_prepare()) | 108 | * using the platform driver (must be called after platform_prepare()) |
98 | */ | 109 | */ |
@@ -129,6 +140,51 @@ static void platform_restore_cleanup(int platform_mode) | |||
129 | } | 140 | } |
130 | 141 | ||
131 | /** | 142 | /** |
143 | * create_image - freeze devices that need to be frozen with interrupts | ||
144 | * off, create the hibernation image and thaw those devices. Control | ||
145 | * reappears in this routine after a restore. | ||
146 | */ | ||
147 | |||
148 | int create_image(int platform_mode) | ||
149 | { | ||
150 | int error; | ||
151 | |||
152 | error = arch_prepare_suspend(); | ||
153 | if (error) | ||
154 | return error; | ||
155 | |||
156 | local_irq_disable(); | ||
157 | /* At this point, device_suspend() has been called, but *not* | ||
158 | * device_power_down(). We *must* call device_power_down() now. | ||
159 | * Otherwise, drivers for some devices (e.g. interrupt controllers) | ||
160 | * become desynchronized with the actual state of the hardware | ||
161 | * at resume time, and evil weirdness ensues. | ||
162 | */ | ||
163 | error = device_power_down(PMSG_FREEZE); | ||
164 | if (error) { | ||
165 | printk(KERN_ERR "Some devices failed to power down, " | ||
166 | KERN_ERR "aborting suspend\n"); | ||
167 | goto Enable_irqs; | ||
168 | } | ||
169 | |||
170 | save_processor_state(); | ||
171 | error = swsusp_arch_suspend(); | ||
172 | if (error) | ||
173 | printk(KERN_ERR "Error %d while creating the image\n", error); | ||
174 | /* Restore control flow magically appears here */ | ||
175 | restore_processor_state(); | ||
176 | if (!in_suspend) | ||
177 | platform_leave(platform_mode); | ||
178 | /* NOTE: device_power_up() is just a resume() for devices | ||
179 | * that suspended with irqs off ... no overall powerup. | ||
180 | */ | ||
181 | device_power_up(); | ||
182 | Enable_irqs: | ||
183 | local_irq_enable(); | ||
184 | return error; | ||
185 | } | ||
186 | |||
187 | /** | ||
132 | * hibernation_snapshot - quiesce devices and create the hibernation | 188 | * hibernation_snapshot - quiesce devices and create the hibernation |
133 | * snapshot image. | 189 | * snapshot image. |
134 | * @platform_mode - if set, use the platform driver, if available, to | 190 | * @platform_mode - if set, use the platform driver, if available, to |
@@ -163,7 +219,7 @@ int hibernation_snapshot(int platform_mode) | |||
163 | if (!error) { | 219 | if (!error) { |
164 | if (hibernation_mode != HIBERNATION_TEST) { | 220 | if (hibernation_mode != HIBERNATION_TEST) { |
165 | in_suspend = 1; | 221 | in_suspend = 1; |
166 | error = swsusp_suspend(); | 222 | error = create_image(platform_mode); |
167 | /* Control returns here after successful restore */ | 223 | /* Control returns here after successful restore */ |
168 | } else { | 224 | } else { |
169 | printk("swsusp debug: Waiting for 5 seconds.\n"); | 225 | printk("swsusp debug: Waiting for 5 seconds.\n"); |
diff --git a/kernel/power/power.h b/kernel/power/power.h index a0204dfc6c4c..195dc4611764 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h | |||
@@ -183,7 +183,6 @@ extern int swsusp_swap_in_use(void); | |||
183 | extern int swsusp_check(void); | 183 | extern int swsusp_check(void); |
184 | extern int swsusp_shrink_memory(void); | 184 | extern int swsusp_shrink_memory(void); |
185 | extern void swsusp_free(void); | 185 | extern void swsusp_free(void); |
186 | extern int swsusp_suspend(void); | ||
187 | extern int swsusp_resume(void); | 186 | extern int swsusp_resume(void); |
188 | extern int swsusp_read(unsigned int *flags_p); | 187 | extern int swsusp_read(unsigned int *flags_p); |
189 | extern int swsusp_write(unsigned int flags); | 188 | extern int swsusp_write(unsigned int flags); |
diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c index 5da304c8f1f6..e1722d3155f1 100644 --- a/kernel/power/swsusp.c +++ b/kernel/power/swsusp.c | |||
@@ -270,39 +270,6 @@ int swsusp_shrink_memory(void) | |||
270 | return 0; | 270 | return 0; |
271 | } | 271 | } |
272 | 272 | ||
273 | int swsusp_suspend(void) | ||
274 | { | ||
275 | int error; | ||
276 | |||
277 | if ((error = arch_prepare_suspend())) | ||
278 | return error; | ||
279 | |||
280 | local_irq_disable(); | ||
281 | /* At this point, device_suspend() has been called, but *not* | ||
282 | * device_power_down(). We *must* device_power_down() now. | ||
283 | * Otherwise, drivers for some devices (e.g. interrupt controllers) | ||
284 | * become desynchronized with the actual state of the hardware | ||
285 | * at resume time, and evil weirdness ensues. | ||
286 | */ | ||
287 | if ((error = device_power_down(PMSG_FREEZE))) { | ||
288 | printk(KERN_ERR "Some devices failed to power down, aborting suspend\n"); | ||
289 | goto Enable_irqs; | ||
290 | } | ||
291 | |||
292 | save_processor_state(); | ||
293 | if ((error = swsusp_arch_suspend())) | ||
294 | printk(KERN_ERR "Error %d suspending\n", error); | ||
295 | /* Restore control flow magically appears here */ | ||
296 | restore_processor_state(); | ||
297 | /* NOTE: device_power_up() is just a resume() for devices | ||
298 | * that suspended with irqs off ... no overall powerup. | ||
299 | */ | ||
300 | device_power_up(); | ||
301 | Enable_irqs: | ||
302 | local_irq_enable(); | ||
303 | return error; | ||
304 | } | ||
305 | |||
306 | int swsusp_resume(void) | 273 | int swsusp_resume(void) |
307 | { | 274 | { |
308 | int error; | 275 | int error; |