Suspend 2 merge: 42/51: Suspend.c

Nigel Cunningham
2004-11-24T13:49:44Z

Here's the heart of the core :> (No, that's not a typo).

- Device suspend/resume calls
- Power down
- Highest level routine
- all_settings proc entry handling

diff -ruN 832-suspend-old/kernel/power/suspend.c 832-suspend-new/kernel/power/suspend.c
--- 832-suspend-old/kernel/power/suspend.c 1970-01-01 10:00:00.000000000 +1000
+++ 832-suspend-new/kernel/power/suspend.c 2004-11-21 20:00:10.000000000 +1100
@@ -0,0 +1,2019 @@
+/*
+ * kernel/power/suspend2.c
+ *
+ * Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu>
+ * Copyright (C) 1998,2001,2002 Pavel Machek <pavel@suse.cz>
+ * Copyright (C) 2002-2003 Florent Chabaud <fchabaud@free.fr>
+ * Copyright (C) 2002-2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * This file is to realize architecture-independent
+ * machine suspend feature using pretty near only high-level routines
+ *
+ * We'd like to thank the following people for their work:
+ *
+ * Pavel Machek <pavel@ucw.cz>:
+ * Modifications, defectiveness pointing, being with Gabor at the very beginning,
+ * suspend to swap space, stop all tasks. Port to 2.4.18-ac and 2.5.17.
+ *
+ * Steve Doddi <dirk@loth.demon.co.uk>:
+ * Support the possibility of hardware state restoring.
+ *
+ * Raph <grey.havens@earthling.net>:
+ * Support for preserving states of network devices and virtual console
+ * (including X and svgatextmode)
+ *
+ * Kurt Garloff <garloff@suse.de>:
+ * Straightened the critical function in order to prevent compilers from
+ * playing tricks with local variables.
+ *
+ * Andreas Mohr <a.mohr@mailto.de>
+ *
+ * Alex Badea <vampire@go.ro>:
+ * Fixed runaway init
+ *
+ * Jeff Snyder <je4d@pobox.com>
+ * ACPI patch
+ *
+ * Nathan Friess <natmanz@shaw.ca>
+ * Some patches.
+ *
+ * Michael Frank <mhf@linuxmail.org>
+ * Extensive testing and help with improving stability.
+ *
+ * Variable definitions which are needed if PM is enabled but
+ * SOFTWARE_SUSPEND is disabled are found near the top of process.c.
+ */
+
+#define SUSPEND_MAIN_C
+//#define DEBUG_DEVICE_TREE
+
+#include <linux/suspend.h>
+#include <linux/reboot.h>
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/version.h>
+#include <linux/device.h>
+#include <linux/highmem.h>
+#include <asm/uaccess.h>
+
+#include "suspend.h"
+#include "block_io.h"
+#include "plugins.h"
+#include "proc.h"
+#include "pageflags.h"
+
+#ifdef CONFIG_X86
+#include <asm/i387.h> /* for kernel_fpu_end */
+#endif
+
+static unsigned long suspend_powerdown_method = 5; /* S5 = off */
+static int suspend_acpi_state_used = 0;
+
+static u32 pm_disk_mode_save;
+struct partial_device_tree * suspend_device_tree;
+EXPORT_SYMBOL(suspend_device_tree);
+
+#ifdef CONFIG_SMP
+static void ensure_on_processor_zero(void)
+{
+ set_cpus_allowed(current, cpumask_of_cpu(0));
+ BUG_ON(smp_processor_id() != 0);
+}
+#else
+#define ensure_on_processor_zero() do { } while(0)
+#endif
+
+#ifdef CONFIG_ACPI
+extern u32 acpi_leave_sleep_state (u8 sleep_state);
+#endif
+
+#ifdef DEBUG_DEVICE_TREE
+#include "../../drivers/base/power/power.h"
+#include <linux/device.h>
+
+void display_device_tree(struct partial_device_tree * tree, char * header)
+{
+ struct device * dev;
+
+ if (header)
+ printk(header);
+ printk(" === Tree %p ===\n\n", tree);
+
+ printk(" -- Active\n");
+ list_for_each_entry(dev, &tree->dpm_active, power.entry) {
+ printk(" %p->%p", dev, dev->parent);
+ printk(" %s\n", dev->kobj.k_name ? dev->kobj.k_name : "<null>");
+ }
+ printk("\n -- DPM Off\n");
+ list_for_each_entry(dev, &tree->dpm_off, power.entry) {
+ printk(" %p->%p", dev, dev->parent);
+ printk(" %s\n", dev->kobj.k_name ? dev->kobj.k_name : "<null>");
+ }
+
+ printk("\n -- DPM Off IRQ\n");
+ list_for_each_entry(dev, &tree->dpm_off_irq, power.entry) {
+ printk(" %p->%p", dev, dev->parent);
+ printk(" %s\n", dev->kobj.k_name ? dev->kobj.k_name : "<null>");
+ }
+ printk("\n--- Done ---\n");
+}
+#else
+#define display_device_tree(tree, header) do { } while(0)
+#endif
+
+/* suspend_drivers_resume
+ * @stage - One of...
+ */
+
+enum {
+ SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED,
+ SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED,
+ SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED,
+ SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED,
+ SUSPEND_DRIVERS_PRE_POWERDOWN,
+};
+
+void suspend_drivers_resume(int stage)
+{
+ switch (stage) {
+ case SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED:
+ BUG_ON(!irqs_disabled());
+ if (!TEST_ACTION_STATE(SUSPEND_DISABLE_SYSDEV_SUPPORT))
+ sysdev_resume();
+ dpm_power_up_tree(suspend_device_tree);
+ break;
+
+ case SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED:
+ BUG_ON(irqs_disabled());
+ display_device_tree(suspend_device_tree,
+ "suspend_drivers_resume stage 1.");
+ device_resume_tree(suspend_device_tree);
+ display_device_tree(suspend_device_tree,
+ "Post resume tree call.");
+ break;
+
+ case SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED:
+ BUG_ON(!irqs_disabled());
+ display_device_tree(&default_device_tree,
+ "suspend_drivers_resume stage 2.\n");
+ dpm_power_up_tree(&default_device_tree);
+ break;
+
+ case SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED:
+ BUG_ON(irqs_disabled());
+ device_resume_tree(&default_device_tree);
+ display_device_tree(&default_device_tree,
+ "Post power up.\n");
+#ifdef CONFIG_ACPI
+ if (suspend_acpi_state_used)
+ acpi_leave_sleep_state(suspend_acpi_state_used);
+#endif
+ display_device_tree(&default_device_tree,
+ "suspend_drivers_resume stage 3.\n");
+ device_resume_tree(&default_device_tree);
+ display_device_tree(&default_device_tree,
+ "Post resume default device tree.\n");
+#ifdef CONFIG_ACPI
+ if (suspend_acpi_state_used &&
+ pm_ops && pm_ops->finish)
+ pm_ops->finish(suspend_acpi_state_used);
+#endif
+ break;
+ }
+}
+
+/* suspend_drivers_suspend
+ * @stage - one of:
+ * 1: Power down drivers not used in writing the image
+ * 2: Quiesce drivers used in writing the image
+ * (prior to making atomic copy)
+ * 3: Power down drivers used in writing the image and
+ * enter the suspend mode if configured to do so.
+ *
+ */
+static int suspend_drivers_suspend(int stage)
+{
+ int result = 0;
+
+ switch (stage) {
+ case SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED:
+ BUG_ON(!irqs_disabled());
+ if (!result)
+ result = device_power_down_tree(
+ PM_SUSPEND_DISK, &default_device_tree);
+ display_device_tree(&default_device_tree,
+ "Post suspend power down device tree.\n");
+ break;
+
+ case SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED:
+ BUG_ON(irqs_disabled());
+ display_device_tree(&default_device_tree,
+ "suspend_drivers_suspend stage 1.\n");
+ result = device_suspend_tree(
+ PM_SUSPEND_DISK, &default_device_tree);
+ break;
+
+ case SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED:
+ BUG_ON(!irqs_disabled());
+ result = device_power_down_tree(PM_SUSPEND_DISK, suspend_device_tree);
+ if (!TEST_ACTION_STATE(SUSPEND_DISABLE_SYSDEV_SUPPORT))
+ sysdev_suspend(PM_SUSPEND_DISK);
+ break;
+
+ case SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED:
+ BUG_ON(irqs_disabled());
+ display_device_tree(suspend_device_tree,
+ "suspend_drivers_suspend stage 2.\n");
+ result = device_suspend_tree(
+ PM_SUSPEND_DISK, suspend_device_tree);
+ display_device_tree(suspend_device_tree,
+ "Post suspend device tree.\n");
+ break;
+
+ case SUSPEND_DRIVERS_PRE_POWERDOWN: /* Power down system */
+ BUG_ON(irqs_disabled());
+ display_device_tree(suspend_device_tree,
+ "suspend_drivers_suspend stage 3.\n");
+
+ result = device_suspend_tree(
+ PM_SUSPEND_DISK, suspend_device_tree);
+ break;
+ }
+ return result;
+}
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+void show_pcp_lists(void)
+{
+ int cpu, temperature;
+ struct zone *zone;
+
+ for_each_zone(zone) {
+ printk("%s per-cpu:", zone->name);
+
+ if (!zone->present_pages) {
+ printk(" empty\n");
+ continue;
+ } else
+ printk("\n");
+
+ for (cpu = 0; cpu < NR_CPUS; ++cpu) {
+ struct per_cpu_pageset *pageset;
+
+ if (!cpu_possible(cpu))
+ continue;
+
+ pageset = zone->pageset + cpu;
+
+ for (temperature = 0; temperature < 2; temperature++)
+ printk("cpu %d %s: low %d, high %d, batch %d, count %d.\n",
+ cpu,
+ temperature ? "cold" : "hot",
+ pageset->pcp[temperature].low,
+ pageset->pcp[temperature].high,
+ pageset->pcp[temperature].batch,
+ pageset->pcp[temperature].count);
+ }
+ }
+}
+#else
+#define show_pcp_lists() do { } while(0)
+#endif
+
+/* -------------------------------------------------------------------------- */
+
+static int suspend_version_specific_initialise(void)
+{
+ struct class * class;
+ struct class_device * class_dev;
+
+ suspend_save_avenrun();
+
+ PRINTFREEMEM("after draining local pages");
+ suspend_store_free_mem(SUSPEND_FREE_DRAIN_PCP, 0);
+
+ if (TEST_DEBUG_STATE(SUSPEND_FREEZER))
+ show_pcp_lists();
+
+ if (pm_ops) {
+ pm_disk_mode_save = pm_ops->pm_disk_mode;
+ pm_ops->pm_disk_mode = PM_DISK_PLATFORM;
+ }
+
+ BUG_ON(suspend_device_tree);
+ suspend_device_tree = device_create_tree();
+ if (IS_ERR(suspend_device_tree)) {
+ suspend_device_tree = NULL;
+ return -ENOMEM;
+ }
+
+ /* Now check for graphics class devices, so we can keep the display on while suspending */
+ class = class_find("graphics");
+ if (class) {
+ list_for_each_entry(class_dev, &class->children, node)
+ device_switch_trees(class_dev->dev, suspend_device_tree);
+ class_put(class);
+ }
+ return 0;
+}
+
+static void suspend_version_specific_cleanup(void)
+{
+ suspend_restore_avenrun();
+
+ if (pm_ops) pm_ops->pm_disk_mode = pm_disk_mode_save;
+
+ if (suspend_device_tree) {
+ device_merge_tree(suspend_device_tree, &default_device_tree);
+ device_destroy_tree(suspend_device_tree);
+ display_device_tree(&default_device_tree,
+ " ==== POST DEVICE MERGE TREE ====\n");
+ suspend_device_tree = NULL;
+ }
+}
+/* Variables to be preserved over suspend */
+int pageset1_sizelow = 0, pageset2_sizelow = 0;
+
+unsigned long orig_mem_free = 0;
+
+extern void do_suspend2_lowlevel(int resume);
+extern unsigned long header_storage_for_plugins(void);
+extern int suspend_initialise_plugin_lists(void);
+extern void suspend_relinquish_console(void);
+extern volatile int suspend_io_time[2][2];
+void empty_suspend_memory_pool(void);
+int read_primary_suspend_image(void);
+extern void display_nosave_pages(void);
+extern int num_writers;
+
+extern void suspend_console_proc_init(void);
+extern void suspend_console_proc_exit(void);
+extern int suspend2_prepare_console(void);
+extern void suspend2_cleanup_console(void);
+
+unsigned long * in_use_map = NULL;
+unsigned long * pageset2_map = NULL;
+unsigned long * checksum_map = NULL;
+#ifdef CONFIG_DEBUG_PAGEALLOC
+unsigned long * unmap_map = NULL;
+#endif
+
+char * debug_info_buffer;
+
+int image_size_limit = 0;
+int max_async_ios = 128;
+
+/* Pagedir.c */
+extern void copy_pageset1(void);
+extern int allocate_local_pageflags(unsigned long ** pagemap, int setnosave);
+extern void free_pagedir(struct pagedir * p);
+extern int free_local_pageflags(unsigned long ** pagemap);
+
+/* Prepare_image.c */
+
+extern int prepare_image(void);
+
+unsigned long forced_ps1_size = 0, forced_ps2_size = 0;
+
+/* proc.c */
+extern int suspend_cleanup_proc(void);
+
+extern int suspend2_register_core(struct suspend2_core_ops * ops_pointer);
+extern void suspend2_unregister_core(void);
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+
+int suspend_free_mem_values[MAX_FREEMEM_SLOTS][2];
+/* These should match the enumerated type in suspend.h */
+static char * suspend_free_mem_descns[MAX_FREEMEM_SLOTS] = {
+ "Start/End ", /* 0 */
+ "Console Allocn ",
+ "Drain pcp ",
+ "InUse map ",
+ "PS2 map ",
+ "Checksum map ", /* 5 */
+ "Reload pages ",
+ "Init plugins ",
+ "Memory pool ",
+ "Freezer ",
+ "Eat Memory ", /* 10 */
+ "Syncing ",
+ "Grabbed Memory ",
+ "Range Pages ",
+ "Extra PD1 pages",
+ "Writer storage ", /* 15 */
+ "Header storage ",
+ "Checksum pages ",
+ "KStat data ",
+ "Debug Info ",
+ "Remove Image ", /* 20 */
+ "I/O ",
+ "I/O info ",
+ "Start one ",
+};
+
+/* store_free_mem
+ */
+
+
+void suspend_store_free_mem(int slot, int side)
+{
+ static int last_free_mem;
+ int this_free_mem = real_nr_free_pages() + suspend_amount_grabbed +
+ suspend_memory_pool_level(0);
+ int i;
+
+ BUG_ON(slot >= MAX_FREEMEM_SLOTS);
+
+ suspend_message(SUSPEND_MEMORY, SUSPEND_HIGH, 0,
+ "Last free mem was %d. Is now %d. ",
+ last_free_mem, this_free_mem);
+
+ if (slot == 0) {
+ if (!side)
+ for (i = 1; i < MAX_FREEMEM_SLOTS; i++) {
+ suspend_free_mem_values[i][0] = 0;
+ suspend_free_mem_values[i][1] = 0;
+ }
+ suspend_free_mem_values[slot][side] = this_free_mem;
+ } else
+ suspend_free_mem_values[slot][side] += this_free_mem - last_free_mem;
+ last_free_mem = this_free_mem;
+ suspend_message(SUSPEND_MEMORY, SUSPEND_HIGH, 0,
+ "%s value %d now %d.\n",
+ suspend_free_mem_descns[slot],
+ side,
+ suspend_free_mem_values[slot][side]);
+}
+
+/*
+ * display_free_mem
+ */
+static void display_free_mem(void)
+{
+ int i;
+
+
+ if (!TEST_DEBUG_STATE(SUSPEND_MEMORY))
+ return;
+
+ suspend_message(SUSPEND_MEMORY, SUSPEND_HIGH, 0,
+ "Start: %7d End: %7d.\n",
+ suspend_free_mem_values[0][0],
+ suspend_free_mem_values[0][1]);
+
+ for (i = 1; i < MAX_FREEMEM_SLOTS; i++)
+ if (suspend_free_mem_values[i][0] + suspend_free_mem_values[i][1])
+ suspend_message(SUSPEND_MEMORY, SUSPEND_HIGH, 0,
+ "%s %7d %7d.\n",
+ suspend_free_mem_descns[i],
+ suspend_free_mem_values[i][0],
+ suspend_free_mem_values[i][1]);
+}
+#endif
+
+/*
+ * save_image
+ * Result code (int): Zero on success, non zero on failure.
+ * Functionality : High level routine which performs the steps necessary
+ * to prepare and save the image after preparatory steps
+ * have been taken.
+ * Key Assumptions : Processes frozen, sufficient memory available, drivers
+ * suspended.
+ * Called from : do_suspend2_suspend_2
+ */
+extern struct pageset_sizes_result recalculate_stats(void);
+extern int write_pageset(struct pagedir * pagedir, int whichtowrite);
+extern int write_image_header(void);
+extern int read_secondary_pagedir(int overwrittenpagesonly);
+
+static int save_image(void)
+{
+ int temp_result;
+
+ if (RAM_TO_SUSPEND > max_mapnr) {
+ prepare_status(1, 1,
+ "Couldn't get enough free pages, on %ld pages short",
+ RAM_TO_SUSPEND - max_mapnr);
+ goto abort_saving;
+ }
+
+ suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+ " - Final values: %d and %d.\n",
+ pageset1_size,
+ pageset2_size);
+
+ /* Suspend devices we're not going to use in writing the image */
+ if (active_writer && active_writer->dpm_set_devices)
+ active_writer->dpm_set_devices();
+ suspend_drivers_suspend(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED);
+ local_irq_disable();
+ suspend_drivers_suspend(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED);
+ local_irq_enable();
+
+ check_shift_keys(1, "About to write pagedir2.");
+
+ temp_result = write_pageset(&pagedir2, 2);
+
+ check_shift_keys(1, "About to copy pageset 1.");
+
+ if (temp_result == -1 || TEST_RESULT_STATE(SUSPEND_ABORTED))
+ goto abort_saving;
+
+ prepare_status(1, 0, "Doing atomic copy...");
+
+ do_suspend2_lowlevel(0);
+
+ return 0;
+abort_saving:
+ suspend_drivers_resume(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED);
+ local_irq_disable();
+ suspend_drivers_resume(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED);
+ local_irq_enable();
+
+ return -1;
+}
+
+int save_image_part1(void)
+{
+ int temp_result;
+
+ suspend_map_atomic_copy_pages();
+
+ suspend_checksum_calculate_checksums();
+
+ BUG_ON(!irqs_disabled());
+
+ if (!TEST_ACTION_STATE(SUSPEND_TEST_FILTER_SPEED))
+ copy_pageset1();
+
+ /*
+ * ---- FROM HERE ON, NEED TO REREAD PAGESET2 IF ABORTING!!! -----
+ *
+ */
+
+ suspend_unmap_atomic_copy_pages();
+
+ /*
+ * Other processors have waited for me to make the atomic copy of the
+ * kernel
+ */
+
+ smp_continue();
+
+#ifdef CONFIG_X86
+ kernel_fpu_end();
+#endif
+
+#ifdef CONFIG_PREEMPT
+ preempt_enable_no_resched();
+#endif
+
+ suspend_drivers_resume(SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED);
+
+ local_irq_enable();
+ suspend_drivers_resume(SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED);
+
+ update_status(pageset2_size, pageset1_size + pageset2_size, NULL);
+
+ if (TEST_RESULT_STATE(SUSPEND_ABORTED))
+ goto abort_reloading_pagedir_two;
+
+ check_shift_keys(1, "About to write pageset1.");
+
+ /*
+ * End of critical section.
+ */
+
+ suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+ "-- Writing pageset1\n");
+
+ temp_result = write_pageset(&pagedir1, 1);
+
+ if (TEST_ACTION_STATE(SUSPEND_TEST_FILTER_SPEED)) {
+ /* We didn't overwrite any memory, so no reread needs to be done. */
+ return -1;
+ }
+
+ if (temp_result == -1 || TEST_RESULT_STATE(SUSPEND_ABORTED))
+ goto abort_reloading_pagedir_two;
+
+ check_shift_keys(1, "About to write header.");
+
+ if (TEST_RESULT_STATE(SUSPEND_ABORTED))
+ goto abort_reloading_pagedir_two;
+
+ temp_result = write_image_header();
+
+ if (temp_result || (TEST_RESULT_STATE(SUSPEND_ABORTED)))
+ goto abort_reloading_pagedir_two;
+
+ check_shift_keys(1, "About to power down or reboot.");
+
+ return 0;
+
+abort_reloading_pagedir_two:
+ temp_result = read_secondary_pagedir(1);
+
+ /* If that failed, we're sunk. Panic! */
+ if (temp_result)
+ panic("Attempt to reload pagedir 2 while aborting "
+ "a suspend failed.");
+
+ return -1;
+
+}
+
+static void suspend_power_off(void)
+{
+ sys_reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
+ LINUX_REBOOT_CMD_POWER_OFF, NULL);
+
+ prepare_status(1, 0, "Probably not capable for powerdown.");
+ while (1)
+ cpu_relax();
+ /* NOTREACHED */
+}
+
+static void suspend_enter_acpi_state(u32 state)
+{
+ suspend_acpi_state_used = state;
+
+ if (pm_ops && pm_ops->prepare) {
+ if (!pm_ops->prepare(state)) {
+ if (pm_ops && pm_ops->enter)
+ pm_ops->enter(state);
+ else
+ printk("Failed to enter state.\n");
+ } else
+ printk("Prepare ops failed.\n");
+ } else
+ printk("No prepare ops.\n");
+}
+
+/*
+ * suspend_power_down
+ * Functionality : Powers down or reboots the computer once the image
+ * has been written to disk.
+ * Key Assumptions : Able to reboot/power down via code called or that
+ * the warning emitted if the calls fail will be visible
+ * to the user (ie printk resumes devices).
+ * Called From : do_suspend2_suspend_2
+ */
+
+extern asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd,
+ void * arg);
+extern void apm_power_off(void);
+
+void suspend_power_down(void)
+{
+ if (TEST_ACTION_STATE(SUSPEND_REBOOT)) {
+ prepare_status(1, 0, "Ready to reboot.");
+ sys_reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
+ LINUX_REBOOT_CMD_RESTART, NULL);
+ }
+
+ suspend_drivers_suspend(SUSPEND_DRIVERS_PRE_POWERDOWN);
+
+ if (suspend_powerdown_method == 3) {
+ prepare_status(1, 0, "Seeking to suspend-to-ram.");
+ suspend_enter_acpi_state(PM_SUSPEND_MEM);
+ prepare_status(1, 0, "Entering S3 failed. Using normal powerdown.");
+ } else if (suspend_powerdown_method == 4) {
+ prepare_status(1, 0, "Seeking to enter ACPI suspend-to-disk state.");
+ suspend_enter_acpi_state(PM_SUSPEND_DISK);
+ prepare_status(1, 0, "Entering S4 failed. Using normal powerdown.");
+ } else
+ prepare_status(1, 0, "Powering down.");
+
+ /*
+ * FIXME: At resume, we'll still think we used S4 if we tried it.
+ * Does it matter?
+ */
+ suspend_acpi_state_used = 0;
+ suspend_power_off();
+}
+
+/*
+ * do_suspend2_resume_1
+ * Functionality : Preparatory steps for copying the original kernel back.
+ * Called From : do_suspend2_lowlevel
+ */
+
+static void do_suspend2_resume_1(void)
+{
+ suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+ name_suspend "About to copy pageset1 back...\n");
+
+ if (active_writer && active_writer->dpm_set_devices)
+ active_writer->dpm_set_devices();
+
+ suspend_drivers_suspend(SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED);
+ suspend_drivers_suspend(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED);
+ local_irq_disable(); /* irqs might have been re-enabled on us */
+ suspend_drivers_suspend(SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED);
+ local_irq_disable();
+ suspend_drivers_suspend(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED);
+ local_irq_enable();
+
+ suspend_map_atomic_copy_pages();
+
+ /* Get other cpus ready to restore their original contexts */
+ smp_suspend();
+
+ local_irq_disable();
+
+#ifdef CONFIG_PREEMPT
+ preempt_disable();
+#endif
+
+ barrier();
+ mb();
+
+ MDELAY(2000);
+}
+
+/*
+ * do_suspend2_resume_2
+ * Functionality : Steps taken after copying back the original kernel at
+ * resume.
+ * Key Assumptions : Will be able to read back secondary pagedir (if
+ * applicable).
+ * Called From : do_suspend2_lowlevel
+ */
+
+static void do_suspend2_resume_2(void)
+{
+ set_suspend_state(SUSPEND_NOW_RESUMING);
+ set_suspend_state(SUSPEND_PAGESET2_NOT_LOADED);
+
+ suspend_unmap_atomic_copy_pages();
+
+#ifdef CONFIG_PREEMPT
+ preempt_enable();
+#endif
+
+ local_irq_disable();
+ suspend_drivers_resume(SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED);
+ local_irq_enable();
+
+ suspend_drivers_resume(SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED);
+
+ suspend_post_restore_redraw();
+
+ check_shift_keys(1, "About to reload secondary pagedir.");
+
+ read_secondary_pagedir(0);
+ clear_suspend_state(SUSPEND_PAGESET2_NOT_LOADED);
+
+ suspend_checksum_print_differences();
+
+ prepare_status(0, 0, "Cleaning up...");
+
+ clear_suspend_state(SUSPEND_USE_MEMORY_POOL);
+
+ local_irq_disable();
+ suspend_drivers_resume(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED);
+ local_irq_enable();
+ suspend_drivers_resume(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED);
+}
+
+/*
+ * do_suspend2_suspend_1
+ * Functionality : Steps taken prior to saving CPU state and the image
+ * itself.
+ * Called From : do_suspend2_lowlevel
+ */
+
+static void do_suspend2_suspend_1(void)
+{
+ /* Save other cpu contexts */
+ smp_suspend();
+
+ suspend_drivers_suspend(SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED);
+
+ mb();
+ barrier();
+
+#ifdef CONFIG_PREEMPT
+ preempt_disable();
+#endif
+ local_irq_disable();
+ suspend_drivers_suspend(SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED);
+}
+
+/*
+ * do_suspend2_suspend_2
+ * Functionality : Steps taken after saving CPU state to save the
+ * image and powerdown/reboot or recover on failure.
+ * Key Assumptions : save_image returns zero on success; otherwise we need to
+ * clean up and exit. The state on exiting this routine
+ * should be essentially the same as if we have suspended,
+ * resumed and reached the end of do_suspend2_resume_2.
+ * Called From : do_suspend2_lowlevel
+ */
+static void do_suspend2_suspend_2(void)
+{
+ if (!save_image_part1())
+ suspend_power_down();
+
+ if (!TEST_RESULT_STATE(SUSPEND_ABORT_REQUESTED) &&
+ !TEST_ACTION_STATE(SUSPEND_TEST_FILTER_SPEED) &&
+ suspend_powerdown_method != 3)
+ printk(KERN_EMERG name_suspend
+ "Suspend failed, trying to recover...\n");
+ MDELAY(1000);
+
+ local_irq_disable();
+ suspend_drivers_resume(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED);
+ local_irq_enable();
+ suspend_drivers_resume(SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED);
+
+ barrier();
+ mb();
+}
+
+static inline void lru_check_page(struct page * page)
+{
+ if (!PageLRU(page))
+ printk("Page %p/%p in inactivelist but not marked LRU.\n",
+ page, page_address(page));
+}
+
+/* get_debug_info
+ * Functionality: Store debug info in a buffer.
+ * Called from: suspend_try_suspend.
+ */
+
+#define SNPRINTF(a...) len += suspend_snprintf(debug_info_buffer + len, \
+ PAGE_SIZE - len - 1, ## a)
+
+static int get_suspend_debug_info(void)
+{
+ int len = 0;
+ if (!debug_info_buffer) {
+ debug_info_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+ if (!debug_info_buffer) {
+ printk("Error! Unable to allocate buffer for"
+ "software suspend debug info.\n");
+ return 0;
+ }
+ }
+
+ SNPRINTF("Please include the following information in bug reports:\n");
+ SNPRINTF("- SUSPEND core : %s\n", SUSPEND_CORE_VERSION);
+ SNPRINTF("- Kernel Version : %s\n", UTS_RELEASE);
+ SNPRINTF("- Compiler vers. : %d.%d\n", __GNUC__, __GNUC_MINOR__);
+#ifdef CONFIG_MODULES
+ SNPRINTF("- Modules loaded : ");
+ {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ struct module *this_mod;
+ extern struct module *module_list;
+ this_mod = module_list;
+ while (this_mod) {
+ if (this_mod->name)
+ SNPRINTF("%s ", this_mod->name);
+ this_mod = this_mod->next;
+ }
+#else
+ extern int print_module_list_to_buffer(char * buffer, int size);
+ len+= print_module_list_to_buffer(debug_info_buffer + len,
+ PAGE_SIZE - len - 1);
+#endif
+ }
+ SNPRINTF("\n");
+#else
+ SNPRINTF("- No module support.\n");
+#endif
+ SNPRINTF("- Attempt number : %d\n", nr_suspends);
+ if (num_range_pages)
+ SNPRINTF("- Pageset sizes : %d (%d low) and %d (%d low).\n",
+ pagedir1.lastpageset_size,
+ pageset1_sizelow,
+ pagedir2.lastpageset_size,
+ pageset2_sizelow);
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+ SNPRINTF("- Parameters : %ld %ld %ld %d %d %d %ld\n",
+ suspend_result,
+ suspend_action,
+ suspend_debug_state,
+ suspend_default_console_level,
+ image_size_limit,
+ max_async_ios,
+ suspend_powerdown_method);
+#else
+ SNPRINTF("- Parameters : %ld %ld %d %d %ld\n",
+ suspend_result,
+ suspend_action,
+ image_size_limit,
+ max_async_ios,
+ suspend_powerdown_method);
+#endif
+ if (num_range_pages)
+ SNPRINTF("- Calculations : Image size: %lu. "
+ "Ram to suspend: %ld.\n",
+ STORAGE_NEEDED(1), RAM_TO_SUSPEND);
+ SNPRINTF("- Limits : %lu pages RAM. Initial boot: %lu.\n",
+ max_mapnr, orig_mem_free);
+ SNPRINTF("- Overall expected compression percentage: %d.\n",
+ 100 - expected_compression_ratio());
+ len+= print_plugin_debug_info(debug_info_buffer + len,
+ PAGE_SIZE - len - 1);
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+ SNPRINTF("- Debugging compiled in.\n");
+#endif
+#ifdef CONFIG_PREEMPT
+ SNPRINTF("- Preemptive kernel.\n");
+#endif
+#ifdef CONFIG_SMP
+ SNPRINTF("- SMP kernel.\n");
+#endif
+#ifdef CONFIG_HIGHMEM
+ SNPRINTF("- Highmem Support.\n");
+#endif
+ if (num_range_pages)
+ SNPRINTF("- Max ranges used: %d ranges in %d pages.\n",
+ max_ranges_used, num_range_pages);
+ if (suspend_io_time[0][1]) {
+ SNPRINTF("- I/O speed: Write %d MB/s",
+ (MB((unsigned long) suspend_io_time[0][0]) * HZ /
+ suspend_io_time[0][1]));
+ if (suspend_io_time[1][1])
+ SNPRINTF(", Read %d MB/s",
+ (MB((unsigned long) suspend_io_time[1][0]) * HZ /
+ suspend_io_time[1][1]));
+ SNPRINTF(".\n");
+ }
+ else if (num_range_pages)
+ SNPRINTF("- Suspend cancelled. No I/O speed stats.\n");
+
+ return len;
+}
+
+extern int PageInPagedir(struct pagedir * p, struct page * page);
+static unsigned long display_metadata_page;
+
+static char * state_string[3] = { "Source", "Destination", "Dest/Allocd" };
+
+void __display_metadata_state(int pagedir, int state)
+{
+ int i;
+
+ if (!state)
+ return;
+
+ printk("[ Pagedir %d:", pagedir);
+ for (i=0; i < 3; i++)
+ if (state & (1 << i))
+ printk("%s ", state_string[i]);
+ printk("]");
+}
+
+void display_metadata_state(struct page * page)
+{
+ __display_metadata_state(1, PageInPagedir(&pagedir1, page));
+ __display_metadata_state(2, PageInPagedir(&pagedir2, page));
+ if (PageNosave(page))
+ printk("[ NoSave ]");
+ if (PageReserved(page))
+ printk("[ Reserved ]");
+ if (PageHighMem(page))
+ printk("[ Highmem ]");
+}
+
+void proc_display_metadata_state(void)
+{
+ printk("Page number %lu. Struct page at %p, virt address %p:",
+ display_metadata_page,
+ mem_map + display_metadata_page,
+ page_address(mem_map + display_metadata_page));
+ display_metadata_state(mem_map + display_metadata_page);
+ printk("\n");
+}
+
+/*
+ * debuginfo_read_proc
+ * Functionality : Displays information that may be helpful in debugging
+ * software suspend.
+ */
+int debuginfo_read_proc(char * page, char ** start, off_t off, int count,
+ int *eof, void *data)
+{
+ int info_len, copy_len;
+
+ initialise_suspend_plugins();
+ info_len = get_suspend_debug_info();
+ cleanup_suspend_plugins();
+
+ copy_len = min(info_len - (int) off, count);
+ if (copy_len < 0)
+ copy_len = 0;
+
+ if (copy_len) {
+ memcpy(page, debug_info_buffer + off, copy_len);
+ *start = page;
+ }
+
+ if (copy_len + off == info_len)
+ *eof = 1;
+
+ free_pages((unsigned long) debug_info_buffer, 0);
+ debug_info_buffer = NULL;
+ return copy_len;
+}
+
+static int get_suspend_debug_info(void);
+
+static int allocate_bitmaps(void)
+{
+ suspend_message(SUSPEND_MEMORY, SUSPEND_VERBOSE, 1,
+ "Allocating in_use_map\n");
+ if (allocate_local_pageflags(&in_use_map, 1))
+ return 1;
+
+ suspend_store_free_mem(SUSPEND_FREE_IN_USE_MAP, 0);
+ PRINTFREEMEM("after allocating in_use_map");
+
+ if (allocate_local_pageflags(&pageset2_map, 1))
+ return 1;
+
+ suspend_store_free_mem(SUSPEND_FREE_PS2_MAP, 0);
+ PRINTFREEMEM("after allocating pageset2 map");
+
+#ifdef CONFIG_DEBUG_PAGEALLOC
+ if (allocate_local_pageflags(&unmap_map, 1))
+ return 1;
+
+ suspend_store_free_mem(SUSPEND_FREE_UNMAP_MAP, 0);
+ PRINTFREEMEM("after allocating unmap map");
+#endif
+
+ suspend_store_free_mem(4, 0);
+
+ return 0;
+}
+
+static void free_metadata(void)
+{
+ put_rangepages_list();
+
+ free_ranges();
+ suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 1);
+ PRINTFREEMEM("after freeing ranges");
+
+ free_local_pageflags(&pageset2_map);
+ PRINTFREEMEM("after freeing pageset2 map");
+ suspend_store_free_mem(SUSPEND_FREE_PS2_MAP, 1);
+
+ free_local_pageflags(&in_use_map);
+ PRINTFREEMEM("after freeing inuse map");
+ suspend_store_free_mem(SUSPEND_FREE_IN_USE_MAP, 1);
+}
+
+/*
+ * software_suspend_pending
+ * Functionality : First level of code for software suspend invocations.
+ * Stores and restores load averages (to avoid a spike),
+ * allocates bitmaps, freezes processes and eats memory
+ * as required before suspending drivers and invoking
+ * the 'low level' code to save the state to disk.
+ * By the time we return from do_suspend2_lowlevel, we
+ * have either failed to save the image or successfully
+ * suspended and reloaded the image. The difference can
+ * be discerned by checking SUSPEND_ABORTED.
+ * Called From :
+ */
+
+extern void free_pageset_size_bloat(void);
+
+void do_activate(void)
+{
+ int i;
+
+ /* Suspend always runs on processor 0 */
+ ensure_on_processor_zero();
+
+ display_nosave_pages();
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE
+ if (TEST_RESULT_STATE(SUSPEND_KEPT_IMAGE)) {
+ if (TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) {
+ printk("Image already stored:"
+ " powering down immediately.");
+ suspend_power_down();
+ return; /* It might now, but just in case we're using S3*/
+ } else {
+ printk("Invalidating previous image.\n");
+ active_writer->ops.writer.invalidate_image();
+ }
+ }
+#endif
+
+ printk(name_suspend "Initiating a software suspend cycle.\n");
+ set_suspend_state(SUSPEND_RUNNING);
+
+ max_ranges_used = 0;
+ nr_suspends++;
+ clear_suspend_state(SUSPEND_NOW_RESUMING);
+
+ suspend_io_time[0][0] = suspend_io_time[0][1] = suspend_io_time[1][0] =
+ suspend_io_time[1][1] = 0;
+
+ PRINTFREEMEM("at start of do_activate");
+ suspend_store_free_mem(SUSPEND_FREE_BASE, 0);
+
+ suspend2_prepare_console();
+
+ free_metadata(); /* We might have kept it */
+
+ if (suspend_version_specific_initialise())
+ goto out;
+
+ if (allocate_bitmaps())
+ goto out;
+
+ PRINTFREEMEM("after allocating bitmaps");
+
+ display_nosave_pages();
+
+ set_chain_names(&pagedir1);
+ set_chain_names(&pagedir2);
+
+ if (initialise_suspend_plugins())
+ goto out;
+
+ PRINTFREEMEM("after initialising plugins");
+ suspend_store_free_mem(SUSPEND_FREE_INIT_PLUGINS, 0);
+
+ /* Free up memory if necessary */
+ suspend_message(SUSPEND_ANY_SECTION, SUSPEND_VERBOSE, 1,
+ "Preparing image.\n");
+ if (prepare_image() || TEST_RESULT_STATE(SUSPEND_ABORTED))
+ goto out;
+
+ PRINTFREEMEM("after preparing image");
+
+ if (TEST_ACTION_STATE(SUSPEND_FREEZER_TEST))
+ goto out;
+
+ display_nosave_pages();
+
+ if (!TEST_RESULT_STATE(SUSPEND_ABORTED)) {
+ prepare_status(1, 0, "Starting to save the image..");
+ save_image();
+ }
+
+out:
+ free_pageset_size_bloat();
+
+ PRINTFREEMEM("at 'out'");
+
+ i = get_suspend_debug_info();
+
+ suspend_store_free_mem(SUSPEND_FREE_DEBUG_INFO, 0);
+
+ clear_suspend_state(SUSPEND_USE_MEMORY_POOL);
+
+ free_pagedir(&pagedir2);
+ PRINTFREEMEM("after freeing pagedir 1");
+ free_pagedir(&pagedir1);
+ PRINTFREEMEM("after freeing pagedir 2");
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE
+ if (TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE) &&
+ !TEST_ACTION_STATE(SUSPEND_ABORTED)) {
+ suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+ name_suspend "Not invalidating the image due "
+ "to Keep Image being enabled.\n");
+ SET_RESULT_STATE(SUSPEND_KEPT_IMAGE);
+ } else
+#endif
+ active_writer->ops.writer.invalidate_image();
+
+ empty_suspend_memory_pool();
+ PRINTFREEMEM("after freeing memory pool");
+ suspend_store_free_mem(SUSPEND_FREE_MEM_POOL, 1);
+
+ if (!TEST_ACTION_STATE(SUSPEND_KEEP_METADATA))
+ free_metadata();
+
+#ifdef CONFIG_DEBUG_PAGE_ALLOC
+ free_local_pageflags(&unmap_map);
+ PRINTFREEMEM("after freeing unmap map");
+ suspend_store_free_mem(SUSPEND_UNMAP_MAP, 1);
+#endif
+
+ if (debug_info_buffer) {
+ /* Printk can only handle 1023 bytes, including
+ * its level mangling. */
+ for (i = 0; i < 3; i++)
+ printk("%s", debug_info_buffer + (1023 * i));
+ free_pages((unsigned long) debug_info_buffer, 0);
+ debug_info_buffer = NULL;
+ }
+
+ PRINTFREEMEM("after freeing debug info buffer");
+ suspend_store_free_mem(SUSPEND_FREE_DEBUG_INFO, 1);
+
+ cleanup_suspend_plugins();
+
+ PRINTFREEMEM("after cleaning up suspend plugins");
+ suspend_store_free_mem(SUSPEND_FREE_INIT_PLUGINS, 1);
+
+ suspend_version_specific_cleanup();
+
+ display_nosave_pages();
+
+ thaw_processes(FREEZER_ALL_THREADS);
+
+ PRINTFREEMEM("after thawing processes");
+ suspend_store_free_mem(SUSPEND_FREE_FREEZER, 1);
+
+ suspend_store_free_mem(SUSPEND_FREE_BASE, 1);
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+ display_free_mem();
+#endif
+
+ clear_suspend_state(SUSPEND_RUNNING);
+ PRINTFREEMEM("at end of do_activate");
+#ifdef CONFIG_PREEMPT
+#endif
+ suspend2_cleanup_console();
+
+}
+
+int attempt_to_parse_resume_device(void)
+{
+ struct list_head *writer;
+ struct suspend_plugin_ops * this_writer;
+ int result = 0;
+ mm_segment_t oldfs;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+
+ active_writer = NULL;
+ clear_suspend_state(SUSPEND_RESUME_DEVICE_OK);
+ set_suspend_state(SUSPEND_DISABLED);
+
+ if (!num_writers) {
+ printk(name_suspend "No writers have been registered.\n");
+ goto out;
+ }
+
+ if (!resume2_file[0]) {
+ result = -EINVAL;
+ goto out;
+ }
+
+ list_for_each(writer, &suspend_writers) {
+ this_writer = list_entry(writer, struct suspend_plugin_ops,
+ ops.writer.writer_list);
+
+ /*
+ * Not sure why you'd want to disable a writer, but
+ * we should honour the flag if we're providing it
+ */
+ if (this_writer->disabled) {
+ printk(name_suspend
+ "Writer '%s' is disabled. Ignoring it.\n",
+ this_writer->name);
+ continue;
+ }
+
+ result = this_writer->ops.writer.parse_image_location(
+ resume2_file, (num_writers == 1));
+
+ switch (result) {
+ case -EINVAL:
+ /*
+ * For this writer, but not a valid
+ * configuration
+ */
+
+ printk(name_suspend
+ "Not able to successfully parse this "
+ "resume device. Suspending disabled.\n");
+ goto out;
+
+ case 0:
+ /*
+ * For this writer and valid.
+ */
+
+ active_writer = this_writer;
+
+ /* We may not have any filters compiled in */
+
+ set_suspend_state(SUSPEND_RESUME_DEVICE_OK);
+ clear_suspend_state(SUSPEND_DISABLED);
+ printk(name_suspend "Suspending enabled.\n");
+ goto out;
+
+ case 1:
+ /*
+ * Not for this writer. Try the next one.
+ */
+
+ break;
+ }
+ }
+ printk(name_suspend "No matching writer found. Suspending disabled.\n");
+ result = -EINVAL;
+out:
+ clear_suspend_state(SUSPEND_RUNNING);
+ set_fs(oldfs);
+ return result;
+}
+
+/*
+ *
+ */
+
+static void __suspend2_verify_checksums(void)
+{
+ if (checksum_plugin)
+ checksum_plugin->ops.checksum.check_checksums();
+}
+
+#define ALL_SETTINGS_VERSION 2
+
+/*
+ * suspend_write_compat_proc.
+ *
+ * This entry allows all of the settings to be set at once.
+ * It was originally for compatibility with pre- /proc/suspend
+ * versions, but has been retained because it makes saving and
+ * restoring the configuration simpler.
+ */
+static int suspend_write_compat_proc(struct file *file, const char * buffer,
+ unsigned long count, void * data)
+{
+ char * buf1 = (char *) get_zeroed_page(GFP_ATOMIC), *curbuf, *lastbuf;
+ char * buf2 = (char *) get_zeroed_page(GFP_ATOMIC);
+ int i, file_offset = 0, used_size = 0, reparse_resume_device = 0;
+ unsigned long nextval;
+ struct suspend_plugin_ops * plugin;
+ struct plugin_header * plugin_header = NULL;
+
+ if ((!buf1) || (!buf2))
+ return -ENOMEM;
+
+ while (file_offset < count) {
+ int length = count - file_offset;
+ if (length > (PAGE_SIZE - used_size))
+ length = PAGE_SIZE - used_size;
+
+ if (copy_from_user(buf1 + used_size, buffer + file_offset, length))
+ return -EFAULT;
+
+ curbuf = buf1;
+
+ if (!file_offset) {
+ /* Integers first */
+ for (i = 0; i < 8; i++) {
+ if (!*curbuf)
+ break;
+ lastbuf = curbuf;
+ nextval = simple_strtoul(curbuf, &curbuf, 0);
+ if (curbuf == lastbuf)
+ break;
+ switch (i) {
+ case 0:
+ if (nextval != ALL_SETTINGS_VERSION) {
+ printk("Error loading saved settings. This data is for version %ld, but kernel module uses format %d.\n",
+ nextval, ALL_SETTINGS_VERSION);
+ goto out;
+ }
+ case 1:
+ suspend_result = nextval;
+ break;
+ case 2:
+ suspend_action = nextval;
+ break;
+ case 3:
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+ suspend_debug_state = nextval;
+#endif
+ break;
+ case 4:
+ suspend_default_console_level = nextval;
+#ifndef CONFIG_SOFTWARE_SUSPEND_DEBUG
+ if (suspend_default_console_level > 1)
+ suspend_default_console_level = 1;
+#endif
+ break;
+ case 5:
+ image_size_limit = nextval;
+ break;
+ case 6:
+ max_async_ios = nextval;
+ if (max_async_ios > MAX_READAHEAD)
+ max_async_ios = MAX_READAHEAD;
+ if (max_async_ios < 1)
+ max_async_ios = 1;
+ break;
+ case 7:
+#ifdef CONFIG_ACPI
+ suspend_powerdown_method = nextval;
+ if (suspend_powerdown_method < 3)
+ suspend_powerdown_method = 3;
+ if (suspend_powerdown_method > 5)
+#endif
+ suspend_powerdown_method = 5;
+ break;
+ }
+
+ curbuf++;
+ while (*curbuf == ' ')
+ curbuf++;
+ }
+
+ if (count <= (curbuf - buf1))
+ goto out;
+ else {
+ list_for_each_entry(plugin, &suspend_plugins, plugin_list)
+ plugin->disabled = 1;
+ }
+ }
+
+ if (((unsigned long) curbuf & ~PAGE_MASK) + sizeof(plugin_header) > PAGE_SIZE)
+ goto shift_buffer;
+
+ /* Plugins */
+ plugin_header = (struct plugin_header *) curbuf;
+
+ if (((unsigned long) curbuf & ~PAGE_MASK) + sizeof(plugin_header) + plugin_header->data_length > PAGE_SIZE)
+ goto shift_buffer;
+
+ if (plugin_header->magic != 0xADEDC0DE) {
+ printk("Bad plugin data magic.\n");
+ break;
+ }
+
+ plugin = find_plugin_given_name(plugin_header->name);
+
+ if (plugin) { /* May validly have config saved for a plugin not now loaded */
+ if ((plugin->type == WRITER_PLUGIN) &&
+ ((!active_writer && plugin->disabled && !plugin_header->disabled) ||
+ (active_writer == plugin && plugin_header->disabled)))
+ reparse_resume_device = 1;
+ plugin->disabled = plugin_header->disabled;
+ if (plugin_header->data_length)
+ plugin->load_config_info(curbuf + sizeof(struct plugin_header),
+ plugin_header->data_length);
+ } else
+ printk("Data for plugin %s not used because not currently loaded.\n", plugin_header->name);
+
+ curbuf += sizeof(struct plugin_header) + plugin_header->data_length;
+
+shift_buffer:
+ if (!(curbuf - buf1))
+ break;
+
+ file_offset += curbuf - buf1;
+
+ used_size = PAGE_SIZE + buf1 - curbuf;
+ memcpy(buf2, curbuf, used_size);
+ memcpy(buf1, buf2, used_size);
+ }
+out:
+ free_pages((unsigned long) buf1, 0);
+ free_pages((unsigned long) buf2, 0);
+
+ if (reparse_resume_device) {
+ printk("Active writer disabled or no active writer and one or more just enabled. Reparsing resume device.\n");
+ attempt_to_parse_resume_device();
+ }
+
+ return count;
+}
+
+/*
+ * suspend_read_compat_proc.
+ *
+ * Like it's _write_ sibling, this entry allows all of the settings
+ * to be read at once.
+ * It too was originally for compatibility with pre- /proc/suspend
+ * versions, but has been retained because it makes saving and
+ * restoring the configuration simpler.
+ */
+static int suspend_read_compat_proc(char * page, char ** start, off_t off, int count,
+ int *eof, void *data)
+{
+ struct suspend_plugin_ops * this_plugin;
+ char * buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+ int index = 1, file_pos = 0, page_offset = 0, len;
+ int copy_len = 0;
+ struct plugin_header plugin_header;
+
+ if (!buffer) {
+ printk("Failed to allocate a buffer for getting "
+ "plugin configuration info.\n");
+ return -ENOMEM;
+ }
+
+ plugin_header.magic = 0xADEDC0DE;
+
+ len = sprintf(buffer, "%d %ld %ld %ld %d %d %d %ld\n",
+ ALL_SETTINGS_VERSION,
+ suspend_result,
+ suspend_action,
+ suspend_debug_state,
+ suspend_default_console_level,
+ image_size_limit,
+ max_async_ios,
+ suspend_powerdown_method);
+
+ if (len >= off) {
+ copy_len = (len < off + count) ? len - off : count - off;
+ memcpy(page, buffer + off, copy_len);
+ page_offset+= copy_len;
+ }
+
+ file_pos += len;
+
+ /*
+ * We have to know which data goes with which plugin, so we at
+ * least write a length of zero for a plugin. Note that we are
+ * also assuming every plugin's config data takes <= PAGE_SIZE.
+ */
+
+ /* For each plugin (in registration order) */
+ list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+
+ /* Get the data from the plugin */
+ if (this_plugin->save_config_info) {
+ plugin_header.data_length = this_plugin->save_config_info(buffer);
+ } else
+ plugin_header.data_length = 0;
+
+ if (file_pos > (off + count)) {
+ file_pos += sizeof(struct plugin_header) + plugin_header.data_length;
+ continue;
+ }
+
+ len = 0;
+ if ((file_pos + sizeof(struct plugin_header) >= off) &&
+ (file_pos < (off + count))) {
+
+ /* Save the details of the plugin */
+ memcpy(plugin_header.name, this_plugin->name,
+ SUSPEND_MAX_PLUGIN_NAME_LENGTH);
+ plugin_header.disabled = this_plugin->disabled;
+ plugin_header.type = this_plugin->type;
+ plugin_header.index = index++;
+
+ copy_len = sizeof(struct plugin_header);
+
+ if (copy_len + page_offset > count)
+ copy_len = count - page_offset;
+
+ memcpy(page + page_offset,
+ ((char *) &plugin_header) + off + page_offset - file_pos,
+ copy_len);
+
+ page_offset += copy_len;
+ }
+
+ file_pos += sizeof(struct plugin_header);
+
+ if (plugin_header.data_length && (file_pos >= off) && (file_pos < (off + count))) {
+ copy_len = plugin_header.data_length;
+
+ if (copy_len + page_offset > count + off)
+ copy_len = count - page_offset;
+
+ memcpy(page + page_offset,
+ buffer,
+ copy_len);
+
+ page_offset += copy_len;
+
+ }
+
+ file_pos += plugin_header.data_length;
+
+ }
+ free_pages((unsigned long) buffer, 0);
+ if (page_offset < count)
+ *eof = 1;
+ return page_offset;
+}
+
+extern int initialise_suspend_plugins(void);
+extern void cleanup_suspend_plugins(void);
+static char suspend_core_version[] = SUSPEND_CORE_VERSION;
+
+static int resume2_write_proc(void)
+{
+ mm_segment_t oldfs;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ initialise_suspend_plugins();
+ attempt_to_parse_resume_device();
+ cleanup_suspend_plugins();
+ set_fs(oldfs);
+ return 0;
+}
+
+/*
+ * Core proc entries that aren't built in.
+ *
+ * This array contains entries that are automatically registered at
+ * boot. Plugins and the console code register their own entries separately.
+ */
+
+static struct suspend_proc_data proc_params[] = {
+ { .filename = "all_settings",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_CUSTOM,
+ .data = {
+ .special = {
+ .read_proc = suspend_read_compat_proc,
+ .write_proc = suspend_write_compat_proc,
+ }
+ }
+ },
+
+ { .filename = "debug_info",
+ .permissions = PROC_READONLY,
+ .type = SUSPEND_PROC_DATA_CUSTOM,
+ .data = {
+ .special = {
+ .read_proc = debuginfo_read_proc,
+ }
+ }
+ },
+
+ { .filename = "disable_sysdev_suspend",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_DISABLE_SYSDEV_SUPPORT,
+ }
+ }
+ },
+
+ { .filename = "freeze_timers",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_FREEZE_TIMERS,
+ }
+ }
+ },
+
+ { .filename = "image_size_limit",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_INTEGER,
+ .data = {
+ .integer = {
+ .variable = &image_size_limit,
+ .minimum = -2,
+ .maximum = 32767,
+ }
+ }
+ },
+
+ { .filename = "last_result",
+ .permissions = PROC_READONLY,
+ .type = SUSPEND_PROC_DATA_UL,
+ .data = {
+ .ul = {
+ .variable = &suspend_result,
+ }
+ }
+ },
+
+ { .filename = "reboot",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_REBOOT,
+ }
+ }
+ },
+
+ { .filename = "resume2",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_STRING,
+ .data = {
+ .string = {
+ .variable = resume2_file,
+ .max_length = 255,
+ }
+ },
+ .write_proc = resume2_write_proc,
+ },
+
+
+ { .filename = "version",
+ .permissions = PROC_READONLY,
+ .type = SUSPEND_PROC_DATA_STRING,
+ .data = {
+ .string = {
+ .variable = suspend_core_version,
+ }
+ }
+ },
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+ { .filename = "freezer_test",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_FREEZER_TEST,
+ }
+ }
+ },
+
+ { .filename = "keep_metadata",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_KEEP_METADATA,
+ }
+ }
+ },
+
+ { .filename = "test_filter_speed",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_TEST_FILTER_SPEED,
+ }
+ }
+ },
+
+ { .filename = "slow",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_SLOW,
+ }
+ }
+ },
+
+ { .filename = "display_metadata_page",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_UL,
+ .data = {
+ .ul = {
+ .variable = &display_metadata_page,
+ }
+ }
+ },
+#endif
+
+ { .filename = "forced_pageset1_size",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_UL,
+ .data = {
+ .ul = {
+ .variable = &forced_ps1_size,
+ }
+ }
+ },
+
+ { .filename = "forced_pageset2_size",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_UL,
+ .data = {
+ .ul = {
+ .variable = &forced_ps2_size,
+ }
+ }
+ },
+
+#if defined(CONFIG_ACPI)
+ { .filename = "powerdown_method",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_UL,
+ .data = {
+ .ul = {
+ .variable = &suspend_powerdown_method,
+ .minimum = 3,
+ .maximum = 5,
+ }
+ }
+ },
+#endif
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE
+ { .filename = "keep_image",
+ .permissions = PROC_RW,
+ .type = SUSPEND_PROC_DATA_BIT,
+ .data = {
+ .bit = {
+ .bit_vector = &suspend_action,
+ .bit = SUSPEND_KEEP_IMAGE,
+ }
+ }
+ },
+#endif
+};
+
+extern int debuginfo_read_proc(char * page, char ** start, off_t off, int count,
+ int *eof, void *data);
+
+/*
+ * Called from init kernel_thread.
+ * We check if we have an image and if so we try to resume.
+ * We also start ksuspendd if configuration looks right.
+ */
+
+extern int freeze_processes(int no_progress);
+
+static int do_resume(void)
+{
+ int ret = 0;
+ int read_image_result = 0;
+
+ /* Suspend always runs on processor 0 */
+ ensure_on_processor_zero();
+
+ if (sizeof(swp_entry_t) != sizeof(long)) {
+ printk(KERN_WARNING name_suspend
+ "The size of swp_entry_t != size of long. "
+ "Please report this!\n");
+ return ret;
+ }
+
+ set_suspend_state(SUSPEND_RUNNING);
+
+ if (!resume2_file[0])
+ printk(KERN_WARNING name_suspend
+ "You need to use a resume2= command line parameter to "
+ "tell Software Suspend 2 where to look for an image.\n");
+
+ if (!(test_suspend_state(SUSPEND_RESUME_DEVICE_OK)))
+ attempt_to_parse_resume_device();
+
+ if (!(test_suspend_state(SUSPEND_RESUME_DEVICE_OK))) {
+ /*
+ * Without a usable storage device we can do nothing -
+ * even if noresume is given
+ */
+
+ if (!num_writers)
+ printk(KERN_ALERT name_suspend
+ "No writers have been registered.\n");
+ else
+ printk(KERN_ALERT name_suspend
+ "Missing or invalid storage location "
+ "(resume2= parameter). Please correct and "
+ "rerun lilo (or equivalent) before "
+ "suspending.\n");
+ clear_suspend_state(SUSPEND_RUNNING);
+ return ret;
+ }
+
+ /* We enable the possibility of machine suspend */
+ orig_mem_free = real_nr_free_pages();
+
+ suspend_task = current->pid;
+
+ read_image_result = read_primary_suspend_image(); /* non fatal error ignored */
+
+ if (test_suspend_state(SUSPEND_NORESUME_SPECIFIED))
+ printk(KERN_WARNING name_suspend "Resuming disabled as requested.\n");
+
+ if (read_image_result) {
+ suspend_task = 0;
+ clear_suspend_state(SUSPEND_RUNNING);
+ return ret;
+ }
+
+ /*
+ * Ensure our suspend device tree is configured (2.6) as
+ * at suspend time
+ */
+
+ suspend_version_specific_initialise();
+
+ freeze_processes(1);
+
+ prepare_status(0, 0,
+ "Copying original kernel back (no status - sensitive!)...");
+
+ do_suspend2_lowlevel(1);
+ BUG();
+
+ return ret;
+}
+
+extern int suspend_plugin_keypress(unsigned int keycode);
+extern void request_abort_suspend(void);
+extern void schedule_suspend_message(int message_number);
+
+int suspend_keypress(unsigned int keycode)
+{
+ /* These keys work even if no output is enabled.
+ * (To get this far, we must be suspending or resuming).
+ */
+ switch (keycode) {
+ case 27:
+ /* Abort suspend */
+ if (TEST_ACTION_STATE(SUSPEND_CAN_CANCEL))
+ request_abort_suspend();
+ break;
+ case 114:
+ /* Otherwise, if R pressed, toggle rebooting */
+ suspend_action ^= (1 << SUSPEND_REBOOT);
+ schedule_suspend_message(2);
+ break;
+ default:
+ return suspend_plugin_keypress(keycode);
+ }
+ return 1;
+}
+
+extern void suspend_early_boot_message_plugins(void);
+extern void cleanup_finished_suspend_io(void);
+
+struct suspend2_core_ops core_ops = {
+ .do_suspend = do_activate,
+ .do_resume = do_resume,
+ .resume1 = do_suspend2_resume_1,
+ .resume2 = do_suspend2_resume_2,
+ .suspend1 = do_suspend2_suspend_1,
+ .suspend2 = do_suspend2_suspend_2,
+ .free_pool_pages = free_suspend_pool_pages,
+ .get_pool_pages = get_suspend_pool_pages,
+ .get_grabbed_pages = get_grabbed_pages,
+ .cleanup_finished_io = cleanup_finished_suspend_io,
+ .suspend_message = __suspend_message,
+ .update_status = update_status,
+ .prepare_status = prepare_status,
+ .schedule_message = schedule_suspend_message,
+ .early_boot_plugins = suspend_early_boot_message_plugins,
+ .keypress = suspend_keypress,
+ .verify_checksums = __suspend2_verify_checksums,
+};
+
+static struct proc_dir_entry *compat_parent;
+extern void suspend_memory_pool_init(void);
+
+static __init int core_load(void)
+{
+ int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+ if (suspend2_register_core(&core_ops))
+ return -EBUSY;
+
+ printk("Software Suspend Core.\n");
+ for (i=0; i< numfiles; i++)
+ suspend_register_procfile(&proc_params[i]);
+
+ suspend_console_proc_init();
+
+ suspend_memory_pool_init();
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ return 0;
+#else
+ return (!!compat_parent);
+#endif
+}
+
+#ifdef MODULE
+static __exit void core_unload(void)
+{
+ int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+ printk("Software Suspend Core unloading.\n");
+ suspend_console_proc_exit();
+
+ for (i=0; i< numfiles; i++)
+ suspend_unregister_procfile(&proc_params[i]);
+
+ suspend2_unregister_core();
+}
+
+module_init(core_load);
+module_exit(core_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Suspend2 core");
+#else
+late_initcall(core_load);
+#endif
+EXPORT_SYMBOL(checksum_map);
+EXPORT_SYMBOL(attempt_to_parse_resume_device);