diff -ruN 20-old/arch/i386/config.in 20-new/arch/i386/config.in
--- 20-old/arch/i386/config.in	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/arch/i386/config.in	2004-11-24 15:24:21.000000000 +1100
@@ -337,6 +337,8 @@
 
 bool 'Power Management support' CONFIG_PM
 
+source kernel/power/Config.in
+
 dep_tristate '  Advanced Power Management BIOS support' CONFIG_APM $CONFIG_PM
 if [ "$CONFIG_APM" != "n" ]; then
    bool '    Ignore USER SUSPEND' CONFIG_APM_IGNORE_USER_SUSPEND
diff -ruN 20-old/arch/i386/defconfig 20-new/arch/i386/defconfig
--- 20-old/arch/i386/defconfig	2004-11-24 15:05:18.000000000 +1100
+++ 20-new/arch/i386/defconfig	2004-11-24 15:24:21.000000000 +1100
@@ -121,6 +121,7 @@
 # CONFIG_OOM_KILLER is not set
 CONFIG_PM=y
 # CONFIG_APM is not set
+# CONFIG_SOFTWARE_SUSPEND2 is not set
 
 #
 # ACPI Support
diff -ruN 20-old/arch/i386/kernel/apic.c 20-new/arch/i386/kernel/apic.c
--- 20-old/arch/i386/kernel/apic.c	2004-11-24 15:05:18.000000000 +1100
+++ 20-new/arch/i386/kernel/apic.c	2004-11-24 15:24:21.000000000 +1100
@@ -10,6 +10,7 @@
  *					for testing these extensively.
  *	Maciej W. Rozycki	:	Various updates and fixes.
  *	Mikael Pettersson	:	Power Management for UP-APIC.
+ *	Nigel Cunningham	:	APIC support for Software Suspend.
  */
 
 #include <linux/config.h>
@@ -131,6 +132,29 @@
 	}
 }
 
+/* For Software Suspend */
+void reenable_local_APIC(void)
+{
+	unsigned long value;
+
+	clear_local_APIC();
+
+	/*
+	 * Disable APIC (implies clearing of registers
+	 * for 82489DX!).
+	 */
+	value = apic_read(APIC_SPIV);
+	value |= APIC_SPIV_APIC_ENABLED;
+	apic_write_around(APIC_SPIV, value);
+
+	if (enabled_via_apicbase) {
+		unsigned int l, h;
+		rdmsr(MSR_IA32_APICBASE, l, h);
+		l |= MSR_IA32_APICBASE_ENABLE;
+		wrmsr(MSR_IA32_APICBASE, l, h);
+	}
+}
+
 void disable_local_APIC(void)
 {
 	unsigned long value;
@@ -448,7 +472,7 @@
 #include <linux/slab.h>
 #include <linux/pm.h>
 
-static struct {
+static struct apic_save_data {
 	/* 'active' is true if the local APIC was enabled by us and
 	   not the BIOS; this signifies that we are also responsible
 	   for disabling it before entering apm/acpi suspend */
@@ -1227,3 +1251,57 @@
 
 	return 0;
 }
+
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+static struct apic_save_data apic_saved_states[NR_CPUS];
+
+void swsusp_apic_save_state(void)
+{
+	struct apic_save_data * apic_data = &apic_saved_states[smp_processor_id()];
+
+	apic_data->apic_id = apic_read(APIC_ID);
+	apic_data->apic_taskpri = apic_read(APIC_TASKPRI);
+	apic_data->apic_ldr = apic_read(APIC_LDR);
+	apic_data->apic_dfr = apic_read(APIC_DFR);
+	apic_data->apic_spiv = apic_read(APIC_SPIV);
+	apic_data->apic_lvtt = apic_read(APIC_LVTT);
+	apic_data->apic_lvtpc = apic_read(APIC_LVTPC);
+	apic_data->apic_lvt0 = apic_read(APIC_LVT0);
+	apic_data->apic_lvt1 = apic_read(APIC_LVT1);
+	apic_data->apic_lvterr = apic_read(APIC_LVTERR);
+	apic_data->apic_tmict = apic_read(APIC_TMICT);
+	apic_data->apic_tdcr = apic_read(APIC_TDCR);
+}
+
+void swsusp_apic_reload_state(void)
+{
+	unsigned int l, h;
+	struct apic_save_data * apic_data = &apic_saved_states[smp_processor_id()];
+
+	/*
+	 * Make sure the APICBASE points to the right address
+	 */
+	rdmsr(MSR_IA32_APICBASE, l, h);
+	l &= ~MSR_IA32_APICBASE_BASE;
+	l |= MSR_IA32_APICBASE_ENABLE | mp_lapic_addr;
+	wrmsr(MSR_IA32_APICBASE, l, h);
+
+	apic_write(APIC_LVTERR, ERROR_APIC_VECTOR | APIC_LVT_MASKED);
+	apic_write(APIC_ID, apic_data->apic_id);
+	apic_write(APIC_DFR, apic_data->apic_dfr);
+	apic_write(APIC_LDR, apic_data->apic_ldr);
+	apic_write(APIC_TASKPRI, apic_data->apic_taskpri);
+	apic_write(APIC_SPIV, apic_data->apic_spiv);
+	apic_write(APIC_LVT0, apic_data->apic_lvt0);
+	apic_write(APIC_LVT1, apic_data->apic_lvt1);
+	apic_write(APIC_LVTPC, apic_data->apic_lvtpc);
+	apic_write(APIC_LVTT, apic_data->apic_lvtt);
+	apic_write(APIC_TDCR, apic_data->apic_tdcr);
+	apic_write(APIC_TMICT, apic_data->apic_tmict);
+	apic_write(APIC_ESR, 0);
+	apic_read(APIC_ESR);
+	apic_write(APIC_LVTERR, apic_data->apic_lvterr);
+	apic_write(APIC_ESR, 0);
+	apic_read(APIC_ESR);
+}
+#endif
diff -ruN 20-old/arch/i386/kernel/apm.c 20-new/arch/i386/kernel/apm.c
--- 20-old/arch/i386/kernel/apm.c	2003-08-26 06:59:43.000000000 +1000
+++ 20-new/arch/i386/kernel/apm.c	2004-11-24 15:24:21.000000000 +1100
@@ -1710,6 +1710,7 @@
 	daemonize();
 
 	strcpy(current->comm, "kapmd");
+	current->flags |= PF_NOFREEZE;
 	sigfillset(&current->blocked);
 
 #ifdef CONFIG_SMP
diff -ruN 20-old/arch/i386/kernel/irq.c 20-new/arch/i386/kernel/irq.c
--- 20-old/arch/i386/kernel/irq.c	2004-11-24 15:50:29.000000000 +1100
+++ 20-new/arch/i386/kernel/irq.c	2004-11-24 15:24:21.000000000 +1100
@@ -1108,7 +1108,7 @@
 
 static struct proc_dir_entry * smp_affinity_entry [NR_IRQS];
 
-static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };
+unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };
 static int irq_affinity_read_proc (char *page, char **start, off_t off,
 			int count, int *eof, void *data)
 {
diff -ruN 20-old/arch/i386/kernel/mtrr.c 20-new/arch/i386/kernel/mtrr.c
--- 20-old/arch/i386/kernel/mtrr.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/arch/i386/kernel/mtrr.c	2004-11-24 15:24:21.000000000 +1100
@@ -245,6 +245,10 @@
     20010327   Dave Jones <davej@suse.de>
 	       Adapted Cyrix III support to include VIA C3.
 
+  v1.41
+    20040107
+	       Nigel Cunningham <ncunningham@users.sourceforge.net>
+	       Made Software Suspend's MTRR support into a PM handler.
 */
 #include <linux/types.h>
 #include <linux/errno.h>
@@ -270,6 +274,9 @@
 #include <linux/init.h>
 #include <linux/smp.h>
 #include <linux/smp_lock.h>
+#ifdef CONFIG_PM
+#include <linux/pm.h>
+#endif
 
 #include <asm/uaccess.h>
 #include <asm/io.h>
@@ -284,7 +291,7 @@
 #include <asm/hardirq.h>
 #include <linux/irq.h>
 
-#define MTRR_VERSION            "1.40 (20010327)"
+#define MTRR_VERSION            "1.41 (20040107)"
 
 #define TRUE  1
 #define FALSE 0
@@ -2287,6 +2294,93 @@
 }   /*  End Function mtrr_init_secondary_cpu  */
 #endif  /*  CONFIG_SMP  */
 
+#ifdef CONFIG_PM
+struct mtrr_suspend_state
+{
+     mtrr_type ltype;
+     unsigned long lbase, lsize;
+};
+
+static struct mtrr_suspend_state *mtrr_suspend_buffers = NULL;
+static int buffer_size = 0;
+
+static void mtrr_suspend(void)
+{
+     int i, max;
+     static struct mtrr_suspend_state * current_buffer;
+     
+     max = get_num_var_ranges();
+     
+     if (!mtrr_suspend_buffers) {
+          /* Attempt to allocate new storage */
+	  
+	  buffer_size = max * sizeof(struct mtrr_suspend_state);
+	  
+	  mtrr_suspend_buffers = kmalloc(buffer_size, GFP_KERNEL);
+	  
+	  if (!mtrr_suspend_buffers) {
+		  printk("mtrr suspend: Failed to kmalloc %d bytes for mtrr suspend buffers.\n",
+			buffer_size);
+	       return;
+	  }
+     }
+
+     current_buffer = mtrr_suspend_buffers;
+
+     for (i = 0; i < max; ++i,current_buffer++)
+	  (*get_mtrr) (i,
+		       &(current_buffer->lbase),
+		       &(current_buffer->lsize),
+		       &(current_buffer->ltype));
+     
+}
+
+/* We restore mtrrs from buffer ptr */
+static void mtrr_resume(void)
+{
+     int i, max, len;
+     struct mtrr_suspend_state *current_buffer;
+     
+
+     if (!mtrr_suspend_buffers)
+        return;
+
+     max = get_num_var_ranges();
+     len = max * sizeof(struct mtrr_suspend_state);
+     if(buffer_size != len) {
+	printk ("mtrr_resume: Resuming failed due to different number of MTRRs\n");
+     	kfree(mtrr_suspend_buffers);
+	mtrr_suspend_buffers = NULL;
+	return;
+     }
+
+     current_buffer = mtrr_suspend_buffers;
+
+     for (i = 0; i < max; ++i,current_buffer++)     
+	  if (current_buffer->lsize)	  
+	       set_mtrr(i,
+			current_buffer->lbase,
+			current_buffer->lsize,
+			current_buffer->ltype);
+     
+     kfree(mtrr_suspend_buffers);
+     mtrr_suspend_buffers = NULL;
+     return;
+}
+
+static int mtrr_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data)
+{
+	switch (rqst) {
+		case PM_SUSPEND:
+			mtrr_suspend();
+			break;
+		case PM_RESUME:
+			mtrr_resume();
+	}
+	return 0;
+}
+#endif
+
 int __init mtrr_init(void)
 {
 #ifdef CONFIG_SMP
@@ -2313,10 +2407,15 @@
 				   S_IFREG | S_IRUGO | S_IWUSR,
 				   &mtrr_fops, NULL);
 #endif
+#ifdef CONFIG_PM
+	if (!pm_register(PM_SYS_DEV, 0, mtrr_pm_callback))
+		panic("Couldn't register with PM subsystem\n");
+#endif
     init_table ();
     return 0;
 }   /*  End Function mtrr_init  */
 
+
 /*
  * Local Variables:
  * mode:c
diff -ruN 20-old/arch/i386/kernel/signal.c 20-new/arch/i386/kernel/signal.c
--- 20-old/arch/i386/kernel/signal.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/arch/i386/kernel/signal.c	2004-11-24 15:24:21.000000000 +1100
@@ -21,6 +21,7 @@
 #include <linux/tty.h>
 #include <linux/config.h>
 #include <linux/personality.h>
+#include <linux/suspend.h>
 #include <asm/ucontext.h>
 #include <asm/uaccess.h>
 #include <asm/i387.h>
@@ -604,6 +605,12 @@
 		return 1;
 #endif
 
+	if (current->flags & PF_FREEZE) {
+		refrigerator(PF_FREEZE);
+		if (!signal_pending(current))
+			goto no_signal;
+	}
+
 	if (!oldset)
 		oldset = &current->blocked;
 
@@ -711,6 +718,7 @@
 		return 1;
 	}
 
+ no_signal:
 	/* Did we come from a system call? */
 	if (regs->orig_eax >= 0) {
 		/* Restart the system call - no handlers present */
diff -ruN 20-old/arch/i386/kernel/smp.c 20-new/arch/i386/kernel/smp.c
--- 20-old/arch/i386/kernel/smp.c	2004-11-24 15:50:29.000000000 +1100
+++ 20-new/arch/i386/kernel/smp.c	2004-11-24 15:24:21.000000000 +1100
@@ -508,7 +508,7 @@
 	preempt_enable();
 }
 
-static inline void do_flush_tlb_all_local(void)
+inline void do_flush_tlb_all_local(void)
 {
 	unsigned long cpu = smp_processor_id();
 
diff -ruN 20-old/arch/i386/kernel/suspend.c 20-new/arch/i386/kernel/suspend.c
--- 20-old/arch/i386/kernel/suspend.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/arch/i386/kernel/suspend.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,616 @@
+ /*
+  * Copyright 2001-2002 Pavel Machek <pavel@suse.cz>
+  * Based on code
+  * Copyright 2001 Patrick Mochel <mochel@osdl.org>
+  */
+#include <linux/suspend.h>
+#include "../../../kernel/power/suspend.h"
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <asm/desc.h>
+#include <asm/i387.h>
+#include <asm/apic.h>
+#include <asm/pgalloc.h>
+
+#define SUSPEND_C
+#define lowmem_page_address(struct_page) (page_address(struct_page))
+char __nosavedata swsusp_pg_dir[PAGE_SIZE]
+                  __attribute__ ((aligned (PAGE_SIZE)));
+typedef unsigned long cpumask_t;
+#define cpumask_of_cpu(cpu_no) (1 << cpu_no)
+extern struct pagedir pagedir_resume;
+extern int suspend_io_time[2][2];
+extern volatile struct suspend2_core_ops * suspend2_core_ops;
+#ifdef SUSPEND_C
+#include <asm/processor.h>
+#endif
+#undef inline
+#define inline	__inline__ __attribute__((always_inline))
+//#define DEBUG_LOWLEVEL_SUSPEND
+
+/* image of the saved processor states */
+struct suspend2_saved_context {
+	u32 eax, ebx, ecx, edx;
+	u32 esp, ebp, esi, edi;
+	u16 es, fs, gs, ss;
+	u32 cr0, cr2, cr3, cr4;
+	u16 gdt_pad;
+	u16 gdt_limit;
+	u32 gdt_base;
+	u16 idt_pad;
+	u16 idt_limit;
+	u32 idt_base;
+	u16 ldt;
+	u16 tss;
+	u32 tr;
+	u32 safety;
+	u32 return_address;
+	u32 eflags;
+} __attribute__((packed));
+
+#ifdef CONFIG_SMP
+static struct suspend2_saved_context suspend2_saved_contexts[NR_CPUS];
+#endif
+static struct suspend2_saved_context suspend2_saved_context;	/* temporary storage */
+
+#define loaddebug(thread,register) \
+               __asm__("movl %0,%%db" #register  \
+                       : /* no output */ \
+                       :"r" ((thread)->debugreg[register]))
+
+ 
+/*
+ * save_processor_context
+ * 
+ * Save the state of the processor before we go to sleep.
+ *
+ * return_stack is the value of the stack pointer (%esp) as the caller sees it.
+ * A good way could not be found to obtain it from here (don't want to make _too_
+ * many assumptions about the layout of the stack this far down.) Also, the 
+ * handy little __builtin_frame_pointer(level) where level > 0, is blatantly 
+ * buggy - it returns the value of the stack at the proper location, not the 
+ * location, like it should (as of gcc 2.91.66)
+ * 
+ * Note that the context and timing of this function is pretty critical.
+ * With a minimal amount of things going on in the caller and in here, gcc
+ * does a good job of being just a dumb compiler.  Watch the assembly output
+ * if anything changes, though, and make sure everything is going in the right
+ * place. 
+ */
+static inline void save_processor_context(void)
+{
+	kernel_fpu_begin();
+
+	/*
+	 * descriptor tables
+	 */
+	asm volatile ("sgdt (%0)" : "=m" (suspend2_saved_context.gdt_limit));
+	asm volatile ("sidt (%0)" : "=m" (suspend2_saved_context.idt_limit));
+	asm volatile ("sldt (%0)" : "=m" (suspend2_saved_context.ldt));
+	asm volatile ("str (%0)"  : "=m" (suspend2_saved_context.tr));
+
+	/*
+	 * save the general registers.
+	 * note that gcc has constructs to specify output of certain registers,
+	 * but they're not used here, because it assumes that you want to modify
+	 * those registers, so it tries to be smart and save them beforehand.
+	 * It's really not necessary, and kinda fishy (check the assembly output),
+	 * so it's avoided. 
+	 */
+	asm volatile ("movl %%esp, (%0)" : "=m" (suspend2_saved_context.esp));
+	asm volatile ("movl %%eax, (%0)" : "=m" (suspend2_saved_context.eax));
+	asm volatile ("movl %%ebx, (%0)" : "=m" (suspend2_saved_context.ebx));
+	asm volatile ("movl %%ecx, (%0)" : "=m" (suspend2_saved_context.ecx));
+	asm volatile ("movl %%edx, (%0)" : "=m" (suspend2_saved_context.edx));
+	asm volatile ("movl %%ebp, (%0)" : "=m" (suspend2_saved_context.ebp));
+	asm volatile ("movl %%esi, (%0)" : "=m" (suspend2_saved_context.esi));
+	asm volatile ("movl %%edi, (%0)" : "=m" (suspend2_saved_context.edi));
+
+	/*
+	 * segment registers
+	 */
+	asm volatile ("movw %%es, %0" : "=r" (suspend2_saved_context.es));
+	asm volatile ("movw %%fs, %0" : "=r" (suspend2_saved_context.fs));
+	asm volatile ("movw %%gs, %0" : "=r" (suspend2_saved_context.gs));
+	asm volatile ("movw %%ss, %0" : "=r" (suspend2_saved_context.ss));
+
+	/*
+	 * control registers 
+	 */
+	asm volatile ("movl %%cr0, %0" : "=r" (suspend2_saved_context.cr0));
+	asm volatile ("movl %%cr2, %0" : "=r" (suspend2_saved_context.cr2));
+	asm volatile ("movl %%cr3, %0" : "=r" (suspend2_saved_context.cr3));
+	asm volatile ("movl %%cr4, %0" : "=r" (suspend2_saved_context.cr4));
+
+	/*
+	 * eflags
+	 */
+	asm volatile ("pushfl ; popl (%0)" : "=m" (suspend2_saved_context.eflags));
+}
+
+static void fix_processor_context(void)
+{
+	int nr = smp_processor_id();
+ 	struct tss_struct * t = init_tss + nr;
+
+	set_tss_desc(nr,t);	/* This just modifies memory; should not be neccessary. But... This is neccessary, because 386 hardware has concept of busy tsc or some similar stupidity. */
+        gdt_table[__TSS(nr)].b &= 0xfffffdff;
+
+	load_TR(nr);		/* This does ltr */
+
+	load_LDT(&current->active_mm->context);	/* This does lldt */
+
+	/*
+	 * Now maybe reload the debug registers
+	 */
+	if (current->thread.debugreg[7]){
+                loaddebug(&current->thread, 0);
+                loaddebug(&current->thread, 1);
+                loaddebug(&current->thread, 2);
+                loaddebug(&current->thread, 3);
+                /* no 4 and 5 */
+                loaddebug(&current->thread, 6);
+                loaddebug(&current->thread, 7);
+	}
+
+}
+
+static void do_fpu_end(void)
+{
+        /* restore FPU regs if necessary */
+	/* Do it out of line so that gcc does not move cr0 load to some stupid place */
+        kernel_fpu_end();
+}
+
+/*
+ * restore_processor_context
+ * 
+ * Restore the processor context as it was before we went to sleep
+ * - descriptor tables
+ * - control registers
+ * - segment registers
+ * - flags
+ * 
+ * Note that it is critical that this function is declared inline.  
+ * It was separated out from restore_state to make that function
+ * a little clearer, but it needs to be inlined because we won't have a
+ * stack when we get here (so we can't push a return address).
+ */
+static inline void restore_processor_context(void)
+{
+	/*
+	 * first restore %ds, so we can access our data properly
+	 */
+	asm volatile (".align 4");
+	asm volatile ("movw %0, %%ds" :: "r" ((u16)__KERNEL_DS));
+
+
+	/*
+	 * control registers
+	 */
+	asm volatile ("movl %0, %%cr4" :: "r" (suspend2_saved_context.cr4));
+	asm volatile ("movl %0, %%cr3" :: "r" (suspend2_saved_context.cr3));
+	asm volatile ("movl %0, %%cr2" :: "r" (suspend2_saved_context.cr2));
+	asm volatile ("movl %0, %%cr0" :: "r" (suspend2_saved_context.cr0));
+
+	/*
+	 * segment registers
+	 */
+	asm volatile ("movw %0, %%es" :: "r" (suspend2_saved_context.es));
+	asm volatile ("movw %0, %%fs" :: "r" (suspend2_saved_context.fs));
+	asm volatile ("movw %0, %%gs" :: "r" (suspend2_saved_context.gs));
+	asm volatile ("movw %0, %%ss" :: "r" (suspend2_saved_context.ss));
+
+	/*
+	 * the other general registers
+	 *
+	 * note that even though gcc has constructs to specify memory 
+	 * input into certain registers, it will try to be too smart
+	 * and save them at the beginning of the function.  This is esp.
+	 * bad since we don't have a stack set up when we enter, and we 
+	 * want to preserve the values on exit. So, we set them manually.
+	 */
+	asm volatile ("movl %0, %%esp" :: "m" (suspend2_saved_context.esp));
+	asm volatile ("movl %0, %%ebp" :: "m" (suspend2_saved_context.ebp));
+	asm volatile ("movl %0, %%eax" :: "m" (suspend2_saved_context.eax));
+	asm volatile ("movl %0, %%ebx" :: "m" (suspend2_saved_context.ebx));
+	asm volatile ("movl %0, %%ecx" :: "m" (suspend2_saved_context.ecx));
+	asm volatile ("movl %0, %%edx" :: "m" (suspend2_saved_context.edx));
+	asm volatile ("movl %0, %%esi" :: "m" (suspend2_saved_context.esi));
+	asm volatile ("movl %0, %%edi" :: "m" (suspend2_saved_context.edi));
+
+	/*
+	 * now restore the descriptor tables to their proper values
+	 * ltr is done i fix_processor_context().
+	 */
+
+	asm volatile ("lgdt (%0)" :: "m" (suspend2_saved_context.gdt_limit));
+	asm volatile ("lidt (%0)" :: "m" (suspend2_saved_context.idt_limit));
+	asm volatile ("lldt (%0)" :: "m" (suspend2_saved_context.ldt));
+
+	fix_processor_context();
+
+	/*
+	 * the flags
+	 */
+	asm volatile ("pushl %0 ; popfl" :: "m" (suspend2_saved_context.eflags));
+
+	do_fpu_end();
+}
+
+#ifdef SUSPEND_C
+/* Local variables for do_suspend2_lowlevel */
+volatile static int loop __nosavedata = 0;
+volatile static int state1 __nosavedata = 0;
+volatile static int state2 __nosavedata = 0;
+volatile static int state3 __nosavedata = 0;
+volatile static struct range *origrange __nosavedata;
+volatile static struct range *copyrange __nosavedata;
+volatile static int origoffset __nosavedata;
+volatile static int copyoffset __nosavedata;
+volatile static unsigned long * origpage __nosavedata;
+volatile static unsigned long * copypage __nosavedata;
+volatile unsigned char * my_saved_context __nosavedata;
+static int io_speed_save[2][2] __nosavedata;
+#ifndef CONFIG_SMP
+static unsigned long c_loops_per_jiffy_ref __nosavedata = 0;
+static unsigned long cpu_khz_ref __nosavedata = 0;
+#endif
+extern atomic_t suspend_cpu_counter __nosavedata;
+
+/* 
+ * APIC support: These routines save the APIC
+ * configuration for the CPU on which they are
+ * being executed
+ */
+extern void suspend_apic_save_state(void);
+extern void suspend_apic_reload_state(void);
+
+#ifdef CONFIG_SMP
+/* ------------------------------------------------
+ * BEGIN Irq affinity code, based on code from LKCD.
+ *
+ * IRQ affinity support:
+ * Save and restore IRQ affinities, and set them
+ * all to CPU 0.
+ *
+ * Section between dashes taken from LKCD code.
+ * Perhaps we should be working toward a shared library
+ * of such routines for kexec, lkcd, software suspend
+ * and whatever other similar projects there are?
+ */
+
+extern irq_desc_t irq_desc[];
+extern cpumask_t irq_affinity[];
+cpumask_t saved_affinity[NR_IRQS];
+
+/*
+ * Routine to save the old irq affinities and change affinities of all irqs to
+ * the dumping cpu.
+ */
+static void save_and_set_irq_affinity(void)
+{
+	int i;
+	int cpu = smp_processor_id();
+
+	memcpy(saved_affinity, irq_affinity, NR_IRQS * sizeof(cpumask_t));
+	for (i = 0; i < NR_IRQS; i++) {
+		if (irq_desc[i].handler == NULL)
+			continue;
+		irq_affinity[i] = cpumask_of_cpu(cpu);
+		if (irq_desc[i].handler->set_affinity != NULL)
+			irq_desc[i].handler->set_affinity(i, irq_affinity[i]);
+	}
+}
+
+/*
+ * Restore old irq affinities.
+ */
+static void reset_irq_affinity(void)
+{
+	int i;
+
+	memcpy(irq_affinity, saved_affinity, NR_IRQS * sizeof(unsigned long));
+	for (i = 0; i < NR_IRQS; i++) {
+		if (irq_desc[i].handler == NULL)
+			continue;
+		if (irq_desc[i].handler->set_affinity != NULL)
+			irq_desc[i].handler->set_affinity(i, saved_affinity[i]);
+	}
+}
+
+/*
+ * END of IRQ affinity code, based on LKCD code.
+ * -----------------------------------------------------------------
+ */
+#endif
+
+/*
+ * FIXME: This function should really be written in assembly. Actually
+ * requirement is that it does not touch stack, because %esp will be
+ * wrong during resume before restore_processor_context(). Check
+ * assembly if you modify this.
+ *
+ * SMP support:
+ * All SMP processors enter this routine during suspend. The one through
+ * which the suspend is initiated (which, for simplicity, is always CPU 0)
+ * sends the others here using an IPI during do_suspend2_suspend_1. They
+ * remain here until after the atomic copy of the kernel is made, to ensure
+ * that they don't mess with memory in the meantime (even just idling will
+ * do that). Once the atomic copy is made, they are free to carry on idling.
+ * Note that we must let them go, because if we're using compression, the
+ * vfree calls in the compressors will result in IPIs being called and hanging
+ * because the CPUs are still here.
+ *
+ * At resume time, we do a similar thing. CPU 0 sends the others in here using
+ * an IPI. It then copies the original kernel back, restores its own processor
+ * context and flushes local tlbs before freeing the others to do the same.
+ * They can then go back to idling while CPU 0 reloads pageset 2, cleans up
+ * and unfreezes the processes.
+ *
+ * (Remember that freezing and thawing processes also uses IPIs, as may
+ * decompressing the data. Again, therefore, we cannot leave the other processors
+ * in here).
+ * 
+ * At the moment, we do nothing about APICs, even though the code is there.
+ */
+void do_suspend2_lowlevel(int resume)
+{
+	if (!resume) {
+#ifdef CONFIG_SMP
+		/*
+		 * Save the irq affinities before we freeze the
+		 * other processors!
+		 */
+		save_and_set_irq_affinity();
+#endif
+
+		suspend2_core_ops->suspend1();
+		save_processor_context();	/* We need to capture registers and memory at "same time" */
+		suspend2_core_ops->suspend2();		/* If everything goes okay, this function does not return */
+		return;
+	}
+
+	/* We want to run from swsusp_pg_dir, since swsusp_pg_dir is stored in constant
+	 * place in memory 
+	 */
+
+        __asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swsusp_pg_dir)));
+
+/*
+ * Final function for resuming: after copying the pages to their original
+ * position, it restores the register state.
+ *
+ * What about page tables? Writing data pages may toggle
+ * accessed/dirty bits in our page tables. That should be no problems
+ * with 4MB page tables. That's why we require have_pse.  
+ *
+ * This loops destroys stack from under itself, so it better should
+ * not use any stack space, itself. When this function is entered at
+ * resume time, we move stack to _old_ place.  This is means that this
+ * function must use no stack and no local variables in registers,
+ * until calling restore_processor_context();
+ *
+ * Critical section here: noone should touch saved memory after
+ * do_suspend2_resume_1; copying works, because nr_copy_pages,
+ * pagedir_resume, loop and loop2 are nosavedata.
+ */
+
+	suspend2_core_ops->resume1();
+
+	state1 = suspend_action;
+	state2 = suspend_debug_state;
+	state3 = console_loglevel;
+	for (loop = 0; loop < 4; loop++)
+		io_speed_save[loop/2][loop%2] = 
+			suspend_io_time[loop/2][loop%2];
+
+#ifdef CONFIG_SMP
+	/* Send all IRQs to CPU 0. We will replace the saved affinities
+	 * with the suspend-time ones when we copy the original kernel
+	 * back in place
+	 */
+	save_and_set_irq_affinity();
+#else
+	c_loops_per_jiffy_ref = cpu_data->loops_per_jiffy;
+	cpu_khz_ref = cpu_khz;
+#endif
+	
+	origrange = pagedir_resume.origranges.first;
+	copyrange = pagedir_resume.destranges.first;
+	origoffset = origrange->minimum;
+	copyoffset = copyrange->minimum;
+	origpage = (unsigned long *) (lowmem_page_address(mem_map + origoffset));
+	copypage = (unsigned long *) (lowmem_page_address(mem_map + copyoffset));
+
+	BUG_ON(!irqs_disabled());
+
+	/* As of 2.0.0.51, pageset1 can include highmem pages. If
+	 * !CONFIG_HIGHMEM, highstart_pfn == 0, hence the #ifdef.
+	 */
+#ifdef CONFIG_HIGHMEM
+	while ((origrange) && (origoffset < highstart_pfn)) {
+#else
+	while (origrange) {
+#endif
+		for (loop=0; loop < (PAGE_SIZE / sizeof(unsigned long)); loop++)
+			*(origpage + loop) = *(copypage + loop);
+		
+		if (origoffset < origrange->maximum) {
+			origoffset++;
+			origpage += (PAGE_SIZE / sizeof(unsigned long));
+		} else {
+			origrange = origrange->next;
+			if (origrange) {
+				origoffset = origrange->minimum;
+				origpage = (unsigned long *) (lowmem_page_address(mem_map + origoffset));
+			}
+		}
+
+		if (copyoffset < copyrange->maximum) {
+			copyoffset++;
+			copypage += (PAGE_SIZE / sizeof(unsigned long));
+		} else {
+			copyrange = copyrange->next;
+			if (copyrange) {
+				copyoffset = copyrange->minimum;
+				copypage = (unsigned long *) (lowmem_page_address(mem_map + copyoffset));
+			}
+		}
+	}
+	
+	restore_processor_context();
+	flush_tlb_all();
+	wbinvd();
+	
+	BUG_ON(!irqs_disabled());
+	
+	/* Now we are running with our old stack, and with registers copied
+	 * from suspend time. Let's copy back those remaining Highmem pages. */
+
+#ifdef CONFIG_HIGHMEM
+	while (origrange) {
+		unsigned long * origpage = (unsigned long *) KMAP_ATOMIC(mem_map + origoffset);
+		for (loop=0; loop < (PAGE_SIZE / sizeof(unsigned long)); loop++)
+			*(origpage + loop) = *(copypage + loop);
+		KUNMAP_ATOMIC(mem_map + origoffset);
+		
+		if (origoffset < origrange->maximum)
+			origoffset++;
+		else {
+			origrange = origrange->next;
+			if (origrange)
+				origoffset = origrange->minimum;
+		}
+
+		if (copyoffset < copyrange->maximum) {
+			copyoffset++;
+			copypage += (PAGE_SIZE / sizeof(unsigned long));
+		} else {
+			copyrange = copyrange->next;
+			if (copyrange) {
+				copyoffset = copyrange->minimum;
+				copypage = (unsigned long *) (page_address(mem_map + copyoffset));
+			}
+		}
+	}
+#endif
+
+#if defined(DEBUG_LOWLEVEL_SUSPEND) && #defined(CONFIG_SMP)
+	{
+		my_saved_context = (unsigned char *) (suspend2_saved_contexts + 1);
+		printk("Saved context for CPU 1:\n");
+		for (loop = 0; loop < sizeof(struct suspend2_saved_context); loop+=4) {
+			if (!(loop%16))
+				printk("%p: ", my_saved_context + loop);
+			printk("%lx %s",
+				*((unsigned long *) (my_saved_context + loop)),
+				(!((loop + 4)%16)) ? "\n" : "");
+		}
+	}
+#endif
+
+	BUG_ON(!irqs_disabled());
+
+	/* Get other CPUs to restore their contexts and flush their tlbs. */
+	clear_suspend_state(SUSPEND_FREEZE_SMP);
+	
+	smp_mb();
+	while (atomic_read(&suspend_cpu_counter)) {
+		cpu_relax();
+		smp_mb();
+	}
+	
+	flush_tlb_all();
+	wbinvd();
+
+#ifdef CONFIG_SMP
+	/* put the irq affinity tables back */
+	reset_irq_affinity();
+#else
+	cpu_data->loops_per_jiffy = c_loops_per_jiffy_ref;
+	loops_per_jiffy = c_loops_per_jiffy_ref;
+	cpu_khz = cpu_khz_ref;
+#endif
+	suspend_action = state1;
+	suspend_debug_state = state2;
+	console_loglevel = state3;
+
+	for (loop = 0; loop < 4; loop++)
+		suspend_io_time[loop/2][loop%2] =
+			io_speed_save[loop/2][loop%2];
+
+	suspend2_core_ops->resume2();
+#ifdef DEBUG_LOWLEVEL_SUSPEND
+	printk("CPU %d left lowlevel suspend.\n", smp_processor_id());
+#endif
+}
+
+#ifdef CONFIG_SMP
+/*
+ * Save and restore processor state for secondary processors.
+ * IRQs (and therefore preemption) are already disabled 
+ * when we enter here (IPI).
+ */
+
+void smp_suspend2_lowlevel(void * info)
+{
+	smp_mb();
+	barrier();
+  	if (test_suspend_state(SUSPEND_NOW_RESUMING)) {
+        	__asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swsusp_pg_dir)));
+
+		BUG_ON(!irqs_disabled());
+		kernel_fpu_begin();
+		atomic_inc(&suspend_cpu_counter);
+		smp_mb();
+		barrier();
+		while (test_suspend_state(SUSPEND_FREEZE_SMP) ||
+			(atomic_read(&suspend_cpu_counter) != smp_processor_id())) {
+			cpu_relax();
+			smp_mb();
+		}
+	       	my_saved_context = (unsigned char *) (suspend2_saved_contexts + smp_processor_id());
+		for (loop = sizeof(struct suspend2_saved_context); loop--; loop)
+			*(((unsigned char *) &suspend2_saved_context) + loop - 1) = *(my_saved_context + loop - 1);
+		restore_processor_context();
+		flush_tlb_all();
+		wbinvd();
+		atomic_dec(&suspend_cpu_counter);
+	} else {	/* suspending */
+		BUG_ON(!irqs_disabled());
+		/* 
+		 *Save context and go back to idling.
+		 * Note that we cannot leave the processor
+		 * here. It must be able to receive IPIs if
+		 * the LZF compression driver (eg) does a
+		 * vfree after compressing the kernel etc
+		 */
+		while ((test_suspend_state(SUSPEND_FREEZE_SMP)) &&
+			(atomic_read(&suspend_cpu_counter) != (smp_processor_id() - 1))) {
+			smp_mb();
+			barrier();
+		}
+		save_processor_context();
+		my_saved_context = (unsigned char *) (suspend2_saved_contexts + smp_processor_id());
+		for (loop = sizeof(struct suspend2_saved_context); loop--; loop)
+			*(my_saved_context + loop - 1) = *(((unsigned char *) &suspend2_saved_context) + loop - 1);
+		atomic_inc(&suspend_cpu_counter);
+		/* Now spin until the atomic copy of the kernel is made. */
+		while (test_suspend_state(SUSPEND_FREEZE_SMP)) {
+			cpu_relax();
+			smp_mb();
+		}
+		flush_tlb_all();
+		atomic_dec(&suspend_cpu_counter);
+		kernel_fpu_end();
+	}
+	barrier();
+	smp_mb();
+}
+
+EXPORT_SYMBOL(smp_suspend2_lowlevel);
+#endif  /* SMP */
+EXPORT_SYMBOL(do_suspend2_lowlevel);
+#endif
diff -ruN 20-old/arch/i386/mm/init.c 20-new/arch/i386/mm/init.c
--- 20-old/arch/i386/mm/init.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/arch/i386/mm/init.c	2004-11-24 15:24:21.000000000 +1100
@@ -26,6 +26,7 @@
 #include <linux/pagemap.h>
 #include <linux/bootmem.h>
 #include <linux/slab.h>
+#include <linux/module.h>
 
 #include <asm/processor.h>
 #include <asm/system.h>
@@ -42,6 +43,9 @@
 unsigned long highstart_pfn, highend_pfn;
 static unsigned long totalram_pages;
 static unsigned long totalhigh_pages;
+extern char __nosave_begin, __nosave_end;
+
+EXPORT_SYMBOL(highstart_pfn);
 
 int do_check_pgt_cache(int low, int high)
 {
@@ -307,6 +311,13 @@
 void __init zap_low_mappings (void)
 {
 	int i;
+
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+{
+	extern char swsusp_pg_dir[PAGE_SIZE];
+	memcpy(swsusp_pg_dir, swapper_pg_dir, PAGE_SIZE);
+}
+#endif
 	/*
 	 * Zap initial low-memory mappings.
 	 *
@@ -453,15 +464,18 @@
 {
 	if (!page_is_ram(pfn)) {
 		SetPageReserved(page);
+		SetPageNosave(page);
 		return;
 	}
 	
 	if (bad_ppro && page_kills_ppro(pfn)) {
 		SetPageReserved(page);
+		SetPageNosave(page);
 		return;
 	}
 	
 	ClearPageReserved(page);
+	ClearPageNosave(page);
 	set_bit(PG_highmem, &page->flags);
 	atomic_set(&page->count, 1);
 	__free_page(page);
@@ -484,6 +498,7 @@
 {
 	extern int ppro_with_ram_bug(void);
 	int bad_ppro, reservedpages, pfn;
+	unsigned long addr;
 
 	bad_ppro = ppro_with_ram_bug();
 
@@ -491,12 +506,24 @@
 	totalram_pages += free_all_bootmem();
 
 	reservedpages = 0;
-	for (pfn = 0; pfn < max_low_pfn; pfn++) {
-		/*
-		 * Only count reserved RAM pages
-		 */
-		if (page_is_ram(pfn) && PageReserved(mem_map+pfn))
-			reservedpages++;
+	addr = (unsigned long) __va(0);
+	for (pfn = 0; pfn < max_low_pfn; pfn++, addr += PAGE_SIZE) {
+		if (page_is_ram(pfn)) {
+			/*
+			 * Only count reserved RAM pages
+			 */
+			if (PageReserved(mem_map+pfn))
+				reservedpages++;
+			/*
+			 * Mark nosave pages
+			 */
+			if (addr >= (unsigned long)&__nosave_begin && addr < (unsigned long)&__nosave_end)
+				SetPageNosave(mem_map+pfn);
+		} else
+			/*
+			 * Non-RAM pages are always nosave
+			 */
+			SetPageNosave(mem_map+pfn);
 	}
 #ifdef CONFIG_HIGHMEM
 	for (pfn = highend_pfn-1; pfn >= highstart_pfn; pfn--)
@@ -594,6 +621,7 @@
 	addr = (unsigned long)(&__init_begin);
 	for (; addr < (unsigned long)(&__init_end); addr += PAGE_SIZE) {
 		ClearPageReserved(virt_to_page(addr));
+		ClearPageNosave(virt_to_page(addr));
 		set_page_count(virt_to_page(addr), 1);
 		free_page(addr);
 		totalram_pages++;
@@ -608,6 +636,7 @@
 		printk (KERN_INFO "Freeing initrd memory: %ldk freed\n", (end - start) >> 10);
 	for (; start < end; start += PAGE_SIZE) {
 		ClearPageReserved(virt_to_page(start));
+		ClearPageNosave(virt_to_page(start));
 		set_page_count(virt_to_page(start), 1);
 		free_page(start);
 		totalram_pages++;
diff -ruN 20-old/arch/i386/mm/Makefile 20-new/arch/i386/mm/Makefile
--- 20-old/arch/i386/mm/Makefile	2003-01-18 10:44:39.000000000 +1100
+++ 20-new/arch/i386/mm/Makefile	2004-11-24 15:24:21.000000000 +1100
@@ -10,6 +10,6 @@
 O_TARGET := mm.o
 
 obj-y	 := init.o fault.o ioremap.o extable.o pageattr.o
-export-objs := pageattr.o
+export-objs := pageattr.o init.o
 
 include $(TOPDIR)/Rules.make
diff -ruN 20-old/arch/i386/vmlinux.lds 20-new/arch/i386/vmlinux.lds
--- 20-old/arch/i386/vmlinux.lds	2004-11-24 15:50:29.000000000 +1100
+++ 20-new/arch/i386/vmlinux.lds	2004-11-24 15:24:21.000000000 +1100
@@ -60,6 +60,12 @@
   __init_end = .;
 
   . = ALIGN(4096);
+  __nosave_begin = .;
+  .data_nosave : { *(.data.nosave) }
+  . = ALIGN(4096);
+  __nosave_end = .;
+
+  . = ALIGN(4096);
   .data.page_aligned : { *(.data.idt) }
 
   . = ALIGN(32);
diff -ruN 20-old/CREDITS 20-new/CREDITS
--- 20-old/CREDITS	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/CREDITS	2004-11-24 15:24:21.000000000 +1100
@@ -509,6 +509,14 @@
 S: Fremont, California 94539
 S: USA
 
+N: Florent Chabaud
+E: florent.chabaud@polytechnique.org
+D: software suspend
+S: SGDN/DCSSI/SDS/LTI
+S: 58, Bd Latour-Maubourg
+S: 75700 Paris 07 SP
+S: France
+
 N: Gordon Chaffee
 E: chaffee@cs.berkeley.edu
 W: http://bmrc.berkeley.edu/people/chaffee/
@@ -639,6 +647,12 @@
 S: NN1 3QT
 S: United Kingdom
 
+N: Nigel Cunningham
+E: ncunningham@users.sourceforge.net
+L: http://lists.sourceforge.net/lists/listinfo/swsusp-devel
+D: Major Software Suspend enchancements
+S: Hastings, New Zealand
+
 N: Stephane Dalton
 E: sdalton@videotron.ca
 D: Tieman Voyager USB Braille display driver.
@@ -996,6 +1010,13 @@
 S: San Jose, California 95131
 S: USA
 
+N: Nathan Friess
+E: natmanz@shaw.ca
+D: software suspend
+S: 25 Tararidge Close NE
+S: Calgary, Alberta  T3J 2P4
+S: Canada
+
 N: Fernando Fuganti
 E: fuganti@conectiva.com.br
 E: fuganti@netbank.com.br
@@ -1745,6 +1766,11 @@
 S: D-91080 Uttenreuth
 S: Germany
 
+N: Gabor Kuti
+E: seasons@falcon.sch.bme.hu
+E: seasons@makosteszta.sote.hu
+D: software suspend
+
 N: Jaroslav Kysela
 E: perex@suse.cz
 W: http://www.perex.cz
diff -ruN 20-old/Documentation/Configure.help 20-new/Documentation/Configure.help
--- 20-old/Documentation/Configure.help	2004-11-24 15:50:29.000000000 +1100
+++ 20-new/Documentation/Configure.help	2004-11-24 15:24:21.000000000 +1100
@@ -109,6 +109,93 @@
 
   Unless you know what you are doing you *should not* enable this option.
 
+Software Suspend
+CONFIG_SOFTWARE_SUSPEND2
+  Enable the possibilty of suspending your machine to disk. No special
+  hardware support (BIOS, APM or ACPI) is required, but your mileage may
+  vary because support for hardware is currently limited. This is partly
+  due to the lack of a driver model in 2.4 and partly due to the fact
+  that it has been developed for x86+ide in the first instance.
+
+  Please read Documentation/swsusp.txt for more information.
+  
+Debugging info for Software Suspend
+CONFIG_SOFTWARE_SUSPEND_DEBUG
+  This option enables the inclusion of debugging info in the software
+  suspend code. Turning it off will reduce the kernel size but make
+  debugging suspend & resume issues harder to do.
+
+  For normal usage, this option can be turned off.
+
+GZIP-Compress the Software Suspend image
+CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION
+  This option enables compression of pages stored during the Software Suspend
+  process. Pages are compressed using the zlib library in its fastest mode.
+  If your swap device is significantly slower than your CPU, you may improve
+  the speed of a suspend/resume cycle by enabling this option.
+  
+  You may also benefit from it if your swap space is small. Note, however,
+  that since we can't know how big the image will be until we actually
+  compress it, the algorithm assumes by default that no compression will be
+  achieved and ensures that your data will fit on disk even if that happens.
+  You can change this behaviour using the expected_compression parameter.
+  If, however, the expected ratio is not acheived, the suspend will fail and
+  your computer will resume. You can see the ratio achieved in a cycle by
+  examining dmesg or /var/log/messages after a cycle.
+
+  This option should be off for most people.
+
+LZF-Compress the Software Suspend image
+CONFIG_SOFTWARE_SUSPEND_LZF_COMPRESSION
+  Suspend process. Pages are compressed using liblzf, which is a very
+  fast algorithm that should essentially be free to use on any "modern"
+  cpu. Which is good, since it does not compress so well.
+
+  You may also benefit from it if your swap space is small. Note, however,
+  that since we can't know how big the image will be until we actually
+  compress it, the algorithm assumes by default that no compression will be
+  achieved and ensures that your data will fit on disk even if that happens.
+  You can change this behaviour using the expected_compression parameter.
+  If, however, the expected ratio is not acheived, the suspend will fail and
+  your computer will resume. You can see the ratio achieved in a cycle by
+  examining dmesg or /var/log/messages after a cycle.
+
+  This option should be off for most people, as it is experimental.
+
+Support for writing image to swap
+CONFIG_SOFTWARE_SUSPEND_SWAPWRITER
+  This option enables support for writing your image to a swap partition
+  or swapfile.
+
+  Most people will want to say Yes here.
+
+Keep and reuse Software Suspend images.
+CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE
+  After resuming, Software Suspend normally frees the swap space it
+  used for storing your image and resets the swap signature to its
+  normal state. This stops a future boot from seeing the image and
+  loading it again, possibly resulting in corruption of your file
+  system.
+
+  If, however, you are running entirely from read-only filesystems,
+  it doesn't matter if you boot from the same image a number of times,
+  as nothing will have changed and there will be no possibility of 
+  corruption. In this case, you might want to enable this option and
+  read Documentation/swsusp.txt for details on how to use it.
+
+  Since usage of this option also requires explicitly setting a 
+  /proc/swsusp entry, it is safe to say 'Y' here. Most users will however
+  never use or want this option, and should therefore say 'N' to get an 
+  extra degree of protection against accidentally invoking the functionality.
+  
+Relaxed permissions on /proc/swsusp entries.
+CONFIG_SOFTWARE_SUSPEND_RELAXED_PROC
+  This option makes /proc/swsusp entries world-accessible, rather than
+  root-only. It is intended for systems where security is not a concern.
+
+  Networked machines, and particularly those where users are granted
+  shell access, should say 'N' here!
+
 Symmetric Multi-Processing support
 CONFIG_SMP
   This enables support for systems with more than one CPU. If you have
diff -ruN 20-old/Documentation/kernel-parameters.txt 20-new/Documentation/kernel-parameters.txt
--- 20-old/Documentation/kernel-parameters.txt	2004-08-20 12:39:52.000000000 +1000
+++ 20-new/Documentation/kernel-parameters.txt	2004-11-24 15:24:21.000000000 +1100
@@ -46,6 +46,7 @@
 	SERIAL	Serial support is enabled.
 	SMP 	The kernel is an SMP kernel.
 	SOUND	Appropriate sound system support is enabled.
+	SWSUSP  Software suspension is enabled.
 	V4L	Video For Linux support is enabled.
 	VGA 	The VGA console has been enabled.
 	VT	Virtual terminal support is enabled.
@@ -430,6 +431,8 @@
 			initial RAM disk.
 
 	nointroute	[IA-64]
+
+	noresume2	[SWSUSP] Disables resume and restore original swap space.
  
 	nolapic		[IA-32,APIC] Do not enable or use the local APIC.
 
@@ -551,6 +554,8 @@
 
 	reserve=	[KNL,BUGS] force the kernel to ignore some iomem area.
 
+	resume2=	[SWSUSP] specifies the storage method and location for Software Suspend..
+
 	riscom8=	[HW,SERIAL]
 
 	ro		[KNL] Mount root device read-only on boot.
diff -ruN 20-old/Documentation/power/internals.txt 20-new/Documentation/power/internals.txt
--- 20-old/Documentation/power/internals.txt	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/Documentation/power/internals.txt	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,364 @@
+		Software Suspend 2.0 Internal Documentation.
+				Version 1
+
+1.  Introduction.
+
+    Software Suspend 2.0 is an addition to the Linux Kernel, designed to
+    allow the user to quickly shutdown and quickly boot a computer, without
+    needing to close documents or programs. It is equivalent to the
+    hibernate facility in some laptops. This implementation, however,
+    requires no special BIOS or hardware support.
+
+    The code in these files is based upon the original implementation
+    prepared by Gabor Kuti and additional work by Pavel Machek and a
+    host of others. This code has been substantially reworked by Nigel
+    Cunningham, again with the help and testing of many others, not the
+    least of whom is Michael Frank, At its heart, however, the operation is
+    essentially the same as Gabor's version.
+
+2.  Overview of operation.
+
+    The basic sequence of operations is as follows:
+
+	a. Quiesce all other activity.
+	b. Ensure enough memory and storage space are available, and attempt
+	   to free memory/storage if necessary.
+	c. Allocate the required memory and storage space.
+	d. Write the image.
+	e. Power down.
+
+    There are a number of complicating factors which mean that things are
+    not as simple as the above would imply, however...
+
+    o The activity of each process must be stopped at a point where it will
+    not be holding locks necessary for saving the image, or unexpectedly
+    restart operations due to something like a timeout and thereby make
+    our image inconsistent.
+
+    o It is desirous that we sync outstanding I/O to disk before calculating
+    image statistics. This reduces corruption if one should suspend but
+    then not resume, and also makes later parts of the operation safer (see
+    below).
+
+    o We need to get as close as we can to an atomic copy of the data.
+    Inconsistencies in the image will result inconsistent memory contents at
+    resume time, and thus in instability of the system and/or file system
+    corruption. This would appear to imply a maximum image size of one half of
+    the amount of RAM, but we have a solution... (again, below).
+
+    o In 2.6, we must play nicely with the other suspend-to-disk
+    implementations.
+
+3.  Detailed description of internals.
+
+    a. Quiescing activity.
+
+    Safely quiescing the system is achieved in a number of steps. First, we
+    wait for existing activity to complete, while holding new activity until
+    post-resume. Second, we sync unwritten buffers. Third, we send a
+    'pseudo-signal' to all processes that have not yet entered the
+    'refrigerator' but should be frozen, causing them to be refrigerated.
+
+    Waiting for existing activity to complete is achieved by using hooks at
+    the beginning and end of critical paths in the kernel code. When a process
+    enters a section where it cannot be safely refrigerated, the process flag
+    PF_FRIDGE_WAIT is set from the SWSUSP_ACTIVITY_STARTING macro. In the same
+    routine, at completion of the critical region, a SWSUSP_ACTIVITY_END macro
+    resets the flag. The _STARTING and _ENDING macros also atomically adjust
+    the global counter swsusp_num_active. While the counter is non-zero, 
+    Software Suspend's freezer will wait.
+
+    These macros serve two other additional purposes. Local variables are used
+    to ensure that processes can safely pass through multiple  _STARTING and
+    _ENDING macros, and checks are made to ensure that the freezer is not
+    waiting for activity to finish. If a process wants to start on a critical
+    path when Suspend is waiting for activity to finish, it will be held at the
+    start of the critical path and refrigerated earlier than would normally be
+    the case. It will be allowed to continue operation after the Suspend cycle
+    is finished or aborted.
+
+    A process in a critical path may also have a section where it releases
+    locks and can be safely stopped until post-resume. For these cases, the
+    SWSUSP_ACTIVITY_PAUSING and _RESTARTING macros may be used. They function
+    in a similar manner to the _STARTING and _ENDING macros.
+
+    Finally, we remember that some threads may be necessary for syncing data to
+    storage. These threads have PF_SYNCTHREAD set, and may use the special macro
+    SWSUSP_ACTIVITY_SYNCTHREAD_PAUSING to indicate that Suspend can safely
+    continue, while not themselves entering the refrigerator.
+
+    Once activity is stopped, Suspend will initiate a fsync of all devices.
+    This aims to increase the integrity of the disk state, just in case
+    something should go wrong.
+
+    During the initial stage, Suspend indicates its desire that processes be
+    stopped by setting the FREEZE_NEW_ACTIVITY bit of swsusp_state.  Once the
+    sync is complete, SYNCTHREAD processes no longer need to run. The
+    FREEZE_UNREFRIGERATED bit is now set, causing them to be refrigerated as
+    well, should they attempt to start new activity. (There should be nothing
+    for them to do, but just-in-case).
+
+    Suspend can now put remaining processes in the refrigerator without fear
+    of deadlocking or leaving dirty data unsynced. The refrigerator is a
+    procedure where processes wait until the cycle is complete. While in there,
+    we can be sure that they will not perform activity that will make our
+    image inconsistent. Processes enter the refrigerator either by being
+    caught at one of the previously mentioned hooks, or by receiving a 'pseudo-
+    signal' from Suspend at this stage. I call it a pseudo signal because
+    signal_wake_up is called for the process when it actually hasn't been
+    signalled. A special hook in the signal handler then calls the refrigerator.
+    The refrigerator, in turn, recalculates the signal pending status to
+    ensure no ill effects result.
+
+    Not all processes are refrigerated. The Suspend thread itself, of course,
+    is one such thread. Others are flagged by setting PF_NOFREEZE, usually
+    because they are needed during suspend.
+
+    In 2.4, the dosexec thread (Win4Lin) is treated specially. It does not
+    handle us even pretending to send it a signal. This is worked-around by
+    us adjusting the can_schedule() macro in schedule.c to stop the task from
+    being scheduled during suspend. Ugly, but it works. The 2.6 version of
+    Win4Lin has been made compatible.
+
+    b. Ensure enough memory & storage are available.
+    c. Allocate the required memory and storage space.
+
+    These steps are merged together in the prepare_image function, found in
+    prepare_image.c. The functions are merged because of the cyclical nature
+    of the problem of calculating how much memory and storage is needed. Since
+    the data structures containing the information about the image must
+    themselves take memory and use storage, the amount of memory and storage
+    required changes as we prepare the image. Since the changes are not large,
+    only one or two iterations will be required to achieve a solution.
+
+    d. Write the image.
+
+    We previously mentioned the need to create an atomic copy of the data, and
+    the half-of-memory limitation that is implied in this. This limitation is
+    circumvented by dividing the memory to be saved into two parts, called
+    pagesets.
+
+    Pageset2 contains the page cache - the pages on the active and inactive
+    lists. These pages are saved first and reloaded last. While saving these
+    pages, the swapwriter plugin carefully ensures that the work of writing
+    the pages doesn't make the image inconsistent. Pages added to the LRU
+    lists are immediately shot down, and careful accounting for available
+    memory aids debugging. No atomic copy of these pages needs to be made.
+
+    Writing the image requires memory, of course, and at this point we have
+    also not yet suspended the drivers. To avoid the possibility of remaining
+    activity corrupting the image, we allocate a special memory pool. Calls
+    to __alloc_pages and __free_pages_ok are then diverted to use our memory
+    pool. Pages in the memory pool are saved as part of pageset1 regardless of
+    whether or not they are used.
+
+    Once pageset2 has been saved, we suspend the drivers and save the CPU
+    context before making an atomic copy of pageset1, resuming the drivers
+    and saving the atomic copy. After saving the two pagesets, we just need to
+    save our metadata before powering down.
+
+    Having saved pageset2 pages, we can safely overwrite their contents with
+    the atomic copy of pageset1. This is how we manage to overcome the half of
+    memory limitation. Pageset2 is normally far larger than pageset1, and
+    pageset1 is normally much smaller than half of the memory, with the result
+    that pageset2 pages can be safely overwritten with the atomic copy of
+    pageset1. This is where we need to be careful about syncing, however.
+    Pageset2 will probably contain filesystem meta data. If this is overwritten
+    with pageset1 and then a sync occurs, the filesystem will be corrupted -
+    at least until resume time and another sync of the restored data. Since
+    there is a possibility that the user might not resume or (may it never be!)
+    that suspend might oops, we do our utmost to avoid syncing filesystems after
+    copying pageset1.
+
+    e. Power down.
+
+    Powering down uses standard kernel routines. Prior to this, however, we
+    suspend drivers again, ensuring that write caches are flushed.
+
+4.  The method of writing the image.
+
+    Software Suspend 2.0rc3 and later contain an internal API which is
+    designed to simplify the implementation of new methods of transforming
+    the image to be written and writing the image itself. Prior to rc3,
+    compression support was inlined in the image writing code, and the data
+    structures and code for managing swap were intertwined with the rest of
+    the code. A number of people had expressed interest in implementing
+    image encryption, and alternative methods of storing the image. This
+    internal API makes that possible by implementing 'plugins'.
+
+    A plugin is a single file which encapsulates the functionality needed
+    to transform a pageset of data (encryption or compression, for example),
+    or to write the pageset to a device. The former type of plugin is called
+    a 'page-transformer', the later a 'writer'.
+
+    Plugins are linked together in pipeline fashion. There may be zero or more
+    page transformers in a pipeline, and there is always exactly one writer.
+    The pipeline follows this pattern:
+
+		---------------------------------
+		|     Software Suspend Core     |
+		---------------------------------
+				|
+				|
+		---------------------------------
+		|	Page transformer 1	|
+		---------------------------------
+				|
+				|
+		---------------------------------
+		|	Page transformer 2	|
+		---------------------------------
+				|
+				|
+		---------------------------------
+		|            Writer		|
+		---------------------------------
+
+    During the writing of an image, the core code feeds pages one at a time
+    to the first plugin. This plugin performs whatever transformations it
+    implements on the incoming data, completely consuming the incoming data and
+    feeding output in a similar manner to the next plugin. A plugin may buffer
+    its output.
+
+    During reading, the pipeline works in the reverse direction. The core code
+    calls the first plugin with the address of a buffer which should be filled.
+    (Note that the buffer size is always PAGE_SIZE at this time). This plugin
+    will in turn request data from the next plugin and so on down until the
+    writer is made to read from the stored image.
+
+    Part of definition of the structure of a plugin thus looks like this:
+
+	/* Writing the image proper */
+	int (*write_init) (int stream_number);
+	int (*write_chunk) (char * buffer_start);
+	int (*write_cleanup) (void);
+
+	/* Reading the image proper */
+	int (*read_init) (int stream_number);
+	int (*read_chunk) (char * buffer_start, int sync);
+	int (*read_cleanup) (void);
+
+    It should be noted that the _cleanup routines may be called before the
+    full stream of data has been read or written. While writing the image,
+    the user may (depending upon settings) choose to abort suspending, and
+    if we are in the midst of writing the last portion of the image, a portion
+    of the second pageset may be reread.
+
+    In addition to the above routines for writing the data, all plugins have a
+    number of other routines:
+
+    TYPE indicates whether the plugin is a page transformer or a writer.
+    #define TRANSFORMER_PLUGIN 1
+    #define WRITER_PLUGIN 2
+
+    NAME is the name of the plugin, used in generic messages.
+
+    PLUGIN_LIST is used to link the plugin into the list of all plugins.
+
+    MEMORY_NEEDED returns the number of pages of memory required by the plugin
+    to do its work.
+
+    STORAGE_NEEDED returns the number of pages in the suspend header required
+    to store the plugin's configuration data.
+
+    PRINT_DEBUG_INFO fills a buffer with information to be displayed about the
+    operation or settings of the plugin.
+
+    SAVE_CONFIG_INFO returns a buffer of PAGE_SIZE or smaller (the size is the
+    return code), containing the plugin's configuration info. This information
+    will be written in the image header and restored at resume time. Since this
+    buffer is allocated after the atomic copy of the kernel is made, you don't
+    need to worry about the buffer being freed.
+
+    LOAD_CONFIG_INFO gives the plugin a pointer to the the configuration info
+    which was saved during suspending. Once again, the plugin doesn't need to
+    worry about freeing the buffer. The kernel will be overwritten with the
+    original kernel, so no memory leak will occur.
+
+    OPS contains the operations specific to transformers and writers. These are
+    described below.
+
+    The complete definition of struct swsusp_plugin_ops is:
+
+	struct swsusp_plugin_ops {
+		/* Functions common to transformers and writers */
+		int type;
+		char * name;
+		struct list_head plugin_list;
+		unsigned long (*memory_needed) (void);
+		unsigned long (*storage_needed) (void);
+		int (*print_debug_info) (char * buffer, int size);
+		int (*save_config_info) (char * buffer);
+		void (*load_config_info) (char * buffer, int len);
+	
+		/* Writing the image proper */
+		int (*write_init) (int stream_number);
+		int (*write_chunk) (char * buffer_start);
+		int (*write_cleanup) (void);
+
+		/* Reading the image proper */
+		int (*read_init) (int stream_number);
+		int (*read_chunk) (char * buffer_start, int sync);
+		int (*read_cleanup) (void);
+
+		union {
+			struct swsusp_transformer_ops transformer;
+			struct swsusp_writer_ops writer;
+		} ops;
+	};
+
+
+	The operations specific to transformers are few in number:
+
+	struct swsusp_transformer_ops {
+		int (*expected_compression) (void);
+		struct list_head transformer_list;
+	};
+
+	Expected compression returns the expected ratio between the amount of
+	data sent to this plugin and the amount of data it passes to the next
+	plugin. The value is used by the core code to calculate the amount of
+	space required to write the image. If the ratio is not achieved, the
+	writer will complain when it runs out of space with data still to
+	write, and the core code will abort the suspend.
+
+	transformer_list links together page transformers, in the order in
+	which they register, which is in turn determined by order in the
+	Makefile.
+	
+	There are many more operations specific to a writer:
+
+	struct swsusp_writer_ops {
+
+		long (*storage_available) (void);
+	
+		unsigned long (*storage_allocated) (void);
+		
+		int (*release_storage) (void);
+
+		long (*allocate_header_space) (unsigned long space_requested);
+		int (*allocate_storage) (unsigned long space_requested);
+
+		int (*write_header_init) (void);
+		int (*write_header_chunk) (char * buffer_start, int buffer_size);
+		int (*write_header_cleanup) (void);
+
+		int (*read_header_init) (void);
+		int (*read_header_chunk) (char * buffer_start, int buffer_size);
+		int (*read_header_cleanup) (void);
+
+		int (*prepare_save) (void);
+		int (*post_load) (void);
+
+		int (*parse_image_location) (char * buffer);
+
+		int (*image_exists) (void);
+
+		int (*invalidate_image) (void);
+
+		int (*wait_on_io) (int flush_all);
+
+		struct list_head writer_list;
+	};
+
+	STORAGE_AVAILABLE is 
diff -ruN 20-old/Documentation/power/swsusp.txt 20-new/Documentation/power/swsusp.txt
--- 20-old/Documentation/power/swsusp.txt	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/Documentation/power/swsusp.txt	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,516 @@
+   --- Software Suspend ('swsusp') for Linux, version 2.0 ---
+
+1.  What is it?
+2.  Why would you want it?
+3.  What do you need to use it?
+4.  How do you use it?
+5.  What do all those entries in /proc/swsusp do?
+6.  How do you get support?
+7.  I think I've found a bug. What should I do?
+8.  What about 2.6 kernels?
+9.  When will XXX be supported?
+10. How does it work?
+11. Who wrote Software Suspend?
+
+1. What is it?
+
+   Imagine you're sitting at your computer, working away. For some reason, you
+   need to turn off your computer for a while - perhaps it's time to go home
+   for the day. When you come back to your computer next, you're going to want
+   to carry on where you left off. Now imagine that you could push a button and
+   have your computer store the contents of its memory to disk and power down.
+   Then, when you next start up your computer, it loads that image back into
+   memory and you can carry on from where you were, just as if you'd never
+   turned the computer off. Far less time to start up, no reopening
+   applications and finding what directory you put that file in yesterday.
+   That's what Software Suspend does.
+
+2. Why would you want it?
+
+   Why wouldn't you want it?
+   
+   Being able to save the state of your system and quickly restore it improves
+   your productivity - you get a useful system in far less time than through
+   the normal boot process.
+   
+3. What do you need to use it?
+
+   a. Kernel Support.
+   
+   Software Suspend is part of the Linux Kernel. It is not part of Marcelo's
+   2.4 tree at the moment, so you will need to download the kernel source and
+   apply the latest patches. Having done that, enable the appropriate options
+   in make [menu|x]config (under General Setup), compile and install your
+   kernel. Software Suspend is incompatible with SMP, scsi and non x86 hardware
+   but works with preempt support and HighMem. It requires your swap 
+   partitions/files to be on IDE devices.
+
+   Software Suspend patches are available from http://swsusp.sf.net.
+
+   You may also want to apply the optional patches. At the time of writing,
+   option patches are available to support Bootsplash (www.bootsplash.org, for
+   an even nicer display during suspend), Laptop mode and Win4Lin. The laptop
+   mode patch is a variation on Jens Axboe's patch, which disables laptop mode
+   when suspending. The Win4Lin option patch provides support for Win4Lin.
+   
+   Option patches should be applied after the main patches and after Win4Lin
+   or Bootsplash.
+   
+   b. Swapspace.
+
+   Software Suspend can store the suspend image in your swap partition,
+   a swap file or a combination thereof. Whichever combination you choose, you
+   will probably want to create enough swap space to store the largest image
+   you could have, plus the space you'd normally use for swap. A good rule of
+   thumb would be to calculate the amount of swap you'd want without using
+   Software Suspend, and then add the amount of memory you have. This swap
+   space can be arranged in any way you'd like. It can be in one partition or
+   file, or spread over a number. The only requirement is that they be active
+   when you start a suspend cycle.
+   
+   There is one exception to this requirement. Software Suspend has
+   the ability to turn on one swap file or partition at the start of
+   suspending and turn it back off at the end. If you want to ensure you have
+   enough memory to store a image when your memory is fully used, you might 
+   want to make one swap partition/file for 'normal' use, and another for
+   Software Suspend to activate & deactivate automatically. (Further details
+   below).
+
+   c. Bootloader configuration.
+   
+   Using Software Suspend also requires that you add an extra parameter to 
+   your lilo.conf or equivalent. Here's an example for writing the image to a
+   swap partition:
+
+   append="resume2=swap:/dev/hda1"
+
+   This would tell Software Suspend that /dev/hda1 is a swap partition you 
+   have. Software Suspend will use the swap signature of this partition as a
+   pointer to your data when you suspend. This means that (in this example)
+   /dev/hda1 doesn't need to be _the_ swap partition where all of your data
+   is actually stored. It just needs to be a pointer to a block on the disk
+   that has a valid swap signature. This swap partition doesn't even need to
+   be turned on at suspend time.
+
+   You don't need to have a swap partition for this purpose. Software Suspend
+   can also use a swap file, but usage is a little more complex. Having made
+   your swap file, turn it on and do "cat /proc/swsusp/header_locations"
+   (this assumes you've already compiled your kernel with Software Suspend
+   support and booted it). The results of the cat command will tell you
+   what you need to put in lilo.conf:
+
+   For swap partitions like /dev/hda1, simply use resume2=swap:/dev/hda1.
+   For swapfile `swapfile`, use resume2=swap:/dev/hda2 resume_block=0x242d.
+
+   If the swapfile changes for any reason (it is moved to a different
+   location, it is deleted and recreated, or the filesystem is
+   defragmented) then you will have to check
+   /proc/swsusp/header_locations for a new resume_block value.
+
+   Once you've compiled and installed the kernel, adjusted your lilo.conf
+   and rerun lilo, you should only need to reboot for the most basic part
+   of Software Suspend to be ready.
+
+   As the 'swap:' portion of the "resume2=swap:/dev/hda1" applies, Suspend
+   supports alternative methods of storing your image. At the time of
+   writing, no alternatives have been implemented, but it is envisaged that
+   a NFS 'writer' will appear in the near future.
+
+   d. A suspend script.
+
+   Since 2.4 kernels don't have the driver model that's being developed for
+   2.6 and 2.6 support is (currently) incomplete, you may need to do more than
+   just patching. Users of Software Suspend usually start the process via a
+   script which prepares for the suspend, tells the kernel to do its stuff and
+   then restore things afterwards. This script might involve:
+
+   - Switching to a text console and back if X doesn't like the video card
+     status on resume.
+   - Running /sbin/hwclock [--directisa] to update the clock on resume
+   - Un/reloading PCMCIA support since it doesn't play well with swsusp.
+  
+   Note that you might not be able to unload some drivers if there are 
+   processes using them. You might have to kill off processes that hold
+   devices open. Hint: if your X server accesses an USB mouse, doing a
+   'chvt' to a text console releases the device and you can unload the
+   module.
+
+   Check out the latest script (available on Sourceforge).
+   
+4. How do you use it?
+
+   Once your script is properly set up, you should just be able to start it
+   and everything should go like clockwork. Of course things aren't always
+   that easy out of the box.
+
+   Check out (in the kernel source tree) include/linux/suspend-debug for
+   settings you can use to get detailed information about what swsusp is doing.
+   /proc/sys/kernel/swsusp and the kernel parameters swsusp_act, swsusp_dbg
+   and swsusp_lvl allow you to set the action and debugging parameters prior
+   to starting a suspend and/or at the lilo prompt before resuming. There is
+   also a nice little program that should be available from Sourceforge which
+   makes it easier to turn these debugging settings on and off. Note that to
+   get any debugging output, you need to enable it when compiling the kernel.
+   If the file /proc/swsusp/debug_sections is missing, you didn't do that.
+
+   A neat feature of Software Suspend is possibility that you can press Escape
+   at any time during suspending, and the process will be aborted. Since this
+   will be considered a security risk in some contexts, this code is disabled
+   by default, but you can enable it by:
+
+   echo 1 > /proc/swsusp/enable_escape.
+   
+   Due to the way swsusp works, this means you'll be able have your system back
+   and perfectly usable almost instantly. The only exception is when suspend is
+   at the very end of writing the image. Then it will need to reload a small
+   (generally less than 10% of the image size) portion first.
+
+   If you run into problems with resuming, adding the "noresume2" option to
+   the kernel command line will let you skip the resume step and recover your
+   system.
+
+5. What do all those entries in /proc/swsusp do?
+
+   /proc/swsusp is the directory which contains files you can use to tune
+   and configure Software Suspend to your liking. The exact contents of
+   the directory will depend upon the version of Software Suspend you're
+   running, and the options you selected at compile time. In the following
+   descriptions, names in brackets refer to compile time options that
+   control whether the file exists. (Note that they're all dependant upon
+   you having selected CONFIG_SOFTWARE_SUSPEND2 in the first place!)
+
+   Since the values of these settings can open potential security risks, they
+   are usually accessible only to the root user. You can, however, enable a
+   compile time option which makes all of these files world-accessible. This
+   should only be done if you trust everyone with shell access to this
+   computer!
+  
+   - activate:
+
+   When anything is written to this file swsusp will be activated and suspend
+   the system. The value is completely ignored. It is just the fact that you
+   write to the file that initiates the suspend.
+
+   - async_io_limit (CONFIG_SOFTWARE_SUSPEND_SWAPWRITER):
+
+   This value is the limit on the number of pages Software Suspend will submit
+   for reading or writing at once. The ideal value depends upon the speed of
+   your hard disks, but the default of 32 should be fine.
+
+   - beeping:
+
+   Set this value to 1 to hear beeps at the different stages of suspending and
+   resuming.
+
+   - debug_info:
+  
+   This file returns information about your configuration that may be helpful
+   in diagnosing problems with suspending.
+
+   - debug_sections (CONFIG_SOFTWARE_SUSPEND_DEBUG):
+
+   This value, together with the console log level, controls what debugging
+   information is displayed. The console log level determines the level of
+   detail, and this value determines what detail is displayed. This value is
+   a bit vector, and the meaning of the bits can be found in the kernel tree
+   in include/linux/suspend-debug.h. It can be over-ridden using the kernel's
+   command line option swsusp_dbg.
+
+   - default_console_level (CONFIG_SOFTWARE_SUSPEND_DEBUG):
+
+   This determines the value of the console log level at the start of a
+   suspend cycle. If debugging is compiled in, the console log level can be
+   changed during a cycle by pressing the digit keys. Meanings are:
+
+   0: Nice display.
+   1: Nice display plus numerical progress.
+   2: Errors only.
+   3: Low level debugging info.
+   4: Medium level debugging info.
+   5: High level debugging info.
+   6: Verbose debugging info.
+
+   This value can be over-ridden using the kernel command line option 
+   swsusp_lvl.
+
+   - disable_gzip_compression (CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION):
+
+   If gzip compression support is compiled in, this option can be used to 
+   disable this plugin.
+
+   - disable_lzf_compression (CONFIG_SOFTWARE_SUSPEND_LZF_COMPRESSION):
+
+   If lzf compression support is compiled in, this option can be used to 
+   disable this plugin.
+
+   - enable_escape:
+
+   Setting this to "1" will enable you abort a suspend by
+   pressing escape, "0" (default) disables this feature. Note that enabling
+   this option means that you cannot initiate a suspend and then walk away
+   from your computer, expecting it to be secure. With feature disabled,
+   you can validly have this expectation once Suspend begins to write the
+   image to disk. (Prior to this point, it is possible that Suspend might
+   about because of failure to freeze all processes or because constraints
+   on its ability to save the image are not met).
+
+   - expected_gzip_compression (CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION):
+   - expected_lzf_compression (CONFIG_SOFTWARE_SUSPEND_LZF_COMPRESSION):
+
+   These values allow you to set an expected compression ratio, which Software
+   Suspend will use in calculating whether it meets constraints on the image
+   size. If this expected compression ratio is not attained, the suspend will
+   abort, so it is wise to allow some spare. You can see what compression
+   ratio is achieved in the logs after suspending.
+
+   Note that the values are cumulative. If you compile in both gzip and lzf
+   compression, have both enabled, and set both expected compression ratios
+   to 20, Suspend will expect that the storage required  will be at most 
+   .8 * .8 = 64% of the number of pages to be written.
+
+   - header_locations (CONFIG_SOFTWARE_SUSPEND_SWAPWRITER):
+
+   This option tells you the resume2= options to use for swap devices you
+   currently have activated. It is particularly useful when you only want to
+   use a swap file to store your image. See above for further details.
+
+   - image_size_limit:
+
+   The maximum size of suspend image written to disk, measured in megabytes
+   (1024*1024).
+
+   - interface_version:
+
+   The value returned by this file can be used by scripts and configuration
+   tools to determine what entries should be looked for. The value is
+   incremented whenever an entry in /proc/swsusp is obsoleted or added.
+
+   - last_result:
+
+   The result of the last suspend, as defined in
+   include/linux/suspend-debug.h with the values SUSPEND_ABORTED to
+   SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY. This is a bitmask.
+
+   - log_everything (CONFIG_SOFTWARE_SUSPEND_DEBUG):
+
+   Setting this option results in all messages printed being logged. Normally,
+   only a subset are logged, so as to not slow the process and not clutter the
+   logs. Useful for debugging. It can be toggled during a cycle by pressing
+   'L'.
+
+   - no_output:
+
+   Setting this to "1" disables all output from suspend. It may be useful if a
+   distribution wants to implement a static display while suspending.
+
+   - pause_between_steps (CONFIG_SOFTWARE_SUSPEND_DEBUG):
+
+   This option is used during debugging, to make Software Suspend pause between
+   each step of the process. It is ignored when the nice display is on.
+
+   - progressbar_granularity_limit (CONFIG_FBCON_SPLASHSCREEN):
+
+   This option can be used to limit the granularity of the progress bar
+   displayed with a bootsplash screen. The value is the maximum number of
+   steps. That is, 10 will make the progress bar jump in 10% increments.
+
+   - reboot (CONFIG_SOFTWARE_SUSPEND_DEBUG):
+
+   This option causes Software Suspend to reboot rather than powering down
+   at the end of saving an image. It can be toggled during a cycle by pressing
+   'R'.
+
+   - slow:
+
+   This option inserts a couple of one+ second delays in the code. It should
+   not be needed, and may disappear in a future version.
+
+   - swapfile (CONFIG_SOFTWARE_SUSPEND_SWAPWRITER):
+
+   This entry is used to specify the swapfile or partition that
+   Software Suspend will attempt to swapon/swapoff automatically. Thus, if
+   I normally use /dev/hda1 for swap, and want to use /dev/hda2 for specifically
+   for my suspend image, I would
+  
+   echo /dev/hda2 > /proc/swsusp/swapfile
+
+   /dev/hda2 would then be automatically swapon'd and swapoff'd. Note that the
+   swapon and swapoff occur while other processes are frozen (including kswapd)
+   so this swap file will not be used up when attempting to free memory. The
+   parition/file is also given the highest priority, so other swapfiles/partitions
+   will only be used to save the image when this one is filled.
+
+   The value of this file is used by header_locations along with any currently
+   activated swapfiles/partitions.
+
+   - version:
+  
+   The version of swsusp you have compiled into the currently running kernel.
+
+6. How do you get support?
+
+   Glad you asked. Software Suspend is being actively maintained and supported,
+   both by Nigel (the guy doing most of the coding at the moment) and its
+   users. You can find the mailing list via the Sourceforge project page.
+
+7. I think I've found a bug. What should I do?
+
+   If you're seeing Software Suspend hang at some point, and especially if
+   lights are flashing on your keyboard, you should compile in debugging 
+   support and try...
+   
+   echo 1 > /proc/swsusp/debug_sections
+   echo 3 > /proc/swsusp/default_console_level
+   echo > /proc/swsusp/activate
+
+   You should then see low level debugging information and eventually an
+   oops.
+
+   Good information on how to provide us with useful information from an
+   oops is found in the file REPORTING-BUGS, in the top level directory
+   of the kernel tree. If you get an oops, please especially note the
+   information about running what is printed on the screen through ksymoops.
+   The raw information is useless.
+
+   You might also read the FAQ and HOWTO on the web site for known issues,
+   and subscribe to the mailing list.
+
+   Beginning with 1.1rc10, you should include the contents of 
+   /proc/swsusp/debug_info in your report. Prior to this version, similar
+   information is written to /var/log/messages at the end of a successful
+   resume and should be sent. It is also a good idea to check /var/log/messages
+   for relevant information as well. Information from the unloading and 
+   reloading of drivers and modules  prior to and after suspending is sometimes
+   helpful.
+
+8. What about 2.6 kernels?
+
+   There are two versions of Software Suspend already included in the 2.6 kernel
+   tree. Unfortunately, they lack a large proportion of the features in 2.4.
+   For this reason, patches are available for the 2.6 kernel as well. You can
+   use the same core patches as for the 2.4 version. Work is underway to merge
+   this version with Patrick's code.
+
+   Note that the existing implementations in the 2.6 kernel are the reason why
+   we call the parameters resume2 and noresume2.
+   
+9. When will XXX be supported?
+
+   Software Suspend currently lacks support for SMP, non x86 and SCSI.
+
+   Patches for the other items (and anything that's been missed) are welcome. 
+   Please send them to the list or directly to Nigel.
+
+   Because Nigel's main task is definitely not Software Suspend and he doesn't
+   have the hardware, he will be unlikely to develop support for any of these
+   in the near future. His development work to date has been driven by the
+   desire to be a user of a more feature complete Software Suspend.
+
+10. How does it work?
+
+   Software Suspend does its work in a number of steps. In the following
+   description, I'll talk specifically about the swapwriter, but the same
+   general pattern will apply to other writers that are implemented.
+
+   a. Freezing system activity.
+
+   The first main stage in suspending is to stop all other activity. This is
+   achieved in stages. First, we stop tasks from submitting new I/O using hooks
+   in the system calls for reading, writing and at a number of other places as
+   well as at the kernel threads that start I/O. If any tasks are syncing,
+   we wait for them to complete. We then do our own sync, just in case no
+   syncs were running. Next, we stop all the others tasks. Some are signalled
+   and put in a 'refrigerator'. Others are simply not scheduled again until we
+   decide to wake them up.
+
+   b. Image preparation.
+
+   For a successful suspend, you need to have enough disk space to store the
+   image and enough memory for the various limitations of Software Suspend's
+   algorithm. You can also specify a maximum image size. In order to attain
+   to those constraints, Software Suspend may 'eat' memory. If, after freezing
+   processes, the constraints aren't met, Software Suspend will thaw all the
+   other processes and begin to eat memory until its calculations indicate
+   the constraints are met. It will then freeze processes again and recheck
+   its calculations. During this cycle, it also allocates the storage needed to
+   save the image and prepares all the meta data that records what will be
+   saved and where.
+
+   c. Storage of meta data and image.
+
+   Software Suspend stores data in two pagesets. Pageset 2 contains pages on the
+   active and inactive lists; essentially the page cache. Pageset 1 contains all
+   other pages, including the kernel. We use two pagesets for one important
+   reason: We need to make an atomic copy of the kernel to ensure consistency of
+   the image. Without a second pagedir, we would be limited to an image that was
+   at most half the amount of memory available. Using two pagesets allows us to
+   store a full image of memory in most cases. Since pageset 2 pages won't be
+   needed in saving pageset 1, we first save pageset 2 pages. We can then
+   suspend drivers and make our atomic copy of the remaining pages using both
+   pageset 2 pages and any other pages that are free. While saving both
+   pagesets, we are careful not to corrupt the image. We immediately shoot down
+   pages that are added to the page cache, and we allocate a special memory pool
+   of extra pages that can be used by during suspending. All of the pages in
+   this pool are saved along with the rest of the pageset 1 pages, even if
+   they're not used. This saves us having to worry about the image becoming
+   inconsistent while we're saving pageset 2.
+
+   d. Save a second copy of the pagedirs.
+
+   To reload pagedir 1 at resume time, we need to know where the data is
+   stored. This requires the saving of a second copy of the pagedirs.
+
+   e. Save the suspend header.
+
+   Nearly there! We save our settings and other parameters needed for
+   reloading pagedir 1 in a 'suspend header' this is a single swap page.
+
+   f. Set the swap header.
+
+   Finally, we edit the swap header for our resume2= swap file/partition. The
+   swap signature is changed to record what kind of header it originally was
+   (swapspace 1 or 2) and the bdev and first block and block size details of
+   the suspend header.
+
+   g. Power down.
+
+   Or reboot if we're debugging and the appropriate option is selected.
+
+   Whew!
+
+   Reloading the image.
+   --------------------
+
+   Reloading the image is essentially the reverse of all the above. We load
+   our copy of pagedir 1, being careful to choose locations that aren't going
+   to be overwritten as we copy it back (We start very early in the boot
+   process, so there are no other processes to quiesce here). We then copy
+   pagedir 1 back to its original location in memory and restore the process
+   context. We are now running with the original kernel. Next, we reload the
+   pageset 2 pages, free the memory and swap used by Software Suspend, restore
+   the pagedir header and restart processes. Sounds easy in comparison to
+   suspending, doesn't it!
+
+   There is of course more to Software Suspend than this, but this explanation
+   should be a good start. If there's interest, I'll write further
+   documentation on range pages and the low level I/O.
+
+11. Who wrote Software Suspend?
+
+   (Answer based on the writings of Florent Chabaud, credits in files and
+   Nigel's limited knowledge; apologies to anyone missed out!)
+
+   The main developers of Software Suspend have been...
+
+   Gabor Kuti
+   Pavel Machek
+   Florent Chabaud
+   Nigel Cunningham
+
+   They have been aided in their efforts by a host of hundreds, if not thousands
+   of testers and people who have submitted bug fixes & suggestions. Of special
+   note are the efforts of Michael Frank, who had his computers repetitively
+   suspend and resume for literally tens of thousands of cycles and developed
+   scripts to stress the system and test Software Suspend far beyond the point
+   most of us (Nigel included!) would consider testing. His efforts have
+   contributed as much to Software Suspend as any of the names above.
diff -ruN 20-old/Documentation/power/todo.txt 20-new/Documentation/power/todo.txt
--- 20-old/Documentation/power/todo.txt	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/Documentation/power/todo.txt	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,28 @@
+Suspend2 todo list
+
+20040128
+  2.0 known issues:
+  ----------------
+- DRI support for 2.4 & 2.6
+- USB support under 2.4 and 2.6
+- Incomplete support in other drivers
+- No support for discontig memory
+- Currently requires PSE extension (/proc/cpuinfo)
+- Highmem >4GB not supported
+- SMP suffers from lost interrupts during resuming
+- 2.6 does not currently flush caches properly before powering down.
+
+20040107
+- Further cleaning up.
+
+20040106
+- Fix lost interrupts on SMP.
+
+20031216
+- Include progress-bar-granularity in all_settings.
+
+20031202
+- Bounds checking on all_settings.
+
+20031201
+- Remove /proc/sys/kernel/swsusp
diff -ruN 20-old/Documentation/sysctl/kernel.txt 20-new/Documentation/sysctl/kernel.txt
--- 20-old/Documentation/sysctl/kernel.txt	2001-12-25 18:44:25.000000000 +1100
+++ 20-new/Documentation/sysctl/kernel.txt	2004-11-24 15:24:21.000000000 +1100
@@ -39,6 +39,7 @@
 - rtsig-max
 - sg-big-buff                 [ generic SCSI device (sg) ]
 - shmmax                      [ sysv ipc ]
+- swsusp		      ==> Documentation/swsusp.txt
 - tainted
 - version
 - zero-paged                  [ PPC only ]
@@ -221,6 +222,12 @@
 
 ==============================================================
 
+swsusp:
+
+Please see Documentation/swsusp.txt for up-to-date documentation.
+
+==============================================================
+
 tainted: 
 
 Non-zero if the kernel has been tainted.  Numeric values, which
diff -ruN 20-old/drivers/acpi/system.c 20-new/drivers/acpi/system.c
--- 20-old/drivers/acpi/system.c	2004-11-24 15:05:19.000000000 +1100
+++ 20-new/drivers/acpi/system.c	2004-11-24 15:24:21.000000000 +1100
@@ -54,6 +54,7 @@
 	return AE_OK;
 }
 #endif /* !CONFIG_ACPI_SLEEP */
+#include <linux/suspend.h>
 
 #define _COMPONENT		ACPI_SYSTEM_COMPONENT
 ACPI_MODULE_NAME		("acpi_system")
@@ -141,7 +142,10 @@
 		 * interrupts.
 		 */
 #ifdef CONFIG_X86
-		init_8259A(0);
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+		if (state != ACPI_STATE_S4)
+#endif
+			init_8259A(0);
 #endif
 		/* wait for power to come back */
 		mdelay(1000);
@@ -318,7 +322,7 @@
 	u32			state)
 {
 	acpi_status status;
-
+	int swsusp=0;
 	/* only support S1 and S5 on kernel 2.4 */
 	if (state != ACPI_STATE_S1 && state != ACPI_STATE_S4
 	    && state != ACPI_STATE_S5)
@@ -326,6 +330,9 @@
 
 
 	if (ACPI_STATE_S4 == state) {
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+		swsusp=1;
+#else
 		/* For s4bios, we need a wakeup address. */
 		if (1 == acpi_gbl_FACS->S4bios_f &&
 		    0 != acpi_gbl_FADT->smi_cmd) {
@@ -335,6 +342,7 @@
 		} else
 			/* We don't support S4 under 2.4.  Give up */
 			return AE_ERROR;
+#endif
 	}
 
 	status = acpi_system_save_state(state);
@@ -348,12 +356,17 @@
 	ACPI_FLUSH_CPU_CACHE();
 
 	/* perform OS-specific sleep actions */
-	status = acpi_system_suspend(state);
-
-	/* Even if we failed to go to sleep, all of the devices are in an suspended
-	 * mode. So, we run these unconditionaly to make sure we have a usable system
-	 * no matter what.
-	 */
+	if(swsusp) { /* we just ignore acpi architecture for the moment */
+		suspend_try_suspend(); /* when we return, this is resume */
+		status = AE_OK;
+	} else {
+		status = acpi_system_suspend(state);
+
+		/* Even if we failed to go to sleep, all of the devices are in an suspended
+		 * mode. So, we run these unconditionaly to make sure we have a usable system
+		 * no matter what.
+		 */
+	}
 	acpi_leave_sleep_state(state);
 	acpi_system_restore_state(state);
 
@@ -398,10 +411,15 @@
 	for (i=0; i<ACPI_S_STATE_COUNT; i++) {
 		if (system->states[i]) {
 			p += sprintf(p, "S%d ", i);
-			if (i == ACPI_STATE_S4 &&
-			    acpi_gbl_FACS->S4bios_f &&
-			    0 != acpi_gbl_FADT->smi_cmd)
-				p += sprintf(p, "S4Bios ");
+			if (i == ACPI_STATE_S4) {
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+                                p += sprintf(p, "(swsusp) ");
+#else
+			  	if(acpi_gbl_FACS->S4bios_f &&
+				   0 != acpi_gbl_FADT->smi_cmd)
+				  	p += sprintf(p, "(Bios) ");
+#endif
+			}
 		}
 	}
 	p += sprintf(p, "\n");
@@ -704,9 +722,15 @@
 	for (i = 0; i <= ACPI_STATE_S5; i++) {
 		if (system->states[i]) {
 			p += sprintf(p,"S%d ", i);
-			if (i == ACPI_STATE_S4 && acpi_gbl_FACS->S4bios_f &&
-			    acpi_gbl_FADT->smi_cmd != 0)
-				p += sprintf(p, "S4Bios ");
+			if (i == ACPI_STATE_S4) {
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+				p += sprintf(p, "(swsusp) ");
+#else
+				if(acpi_gbl_FACS->S4bios_f &&
+				   acpi_gbl_FADT->smi_cmd != 0)
+					p += sprintf(p, "(Bios) ");
+#endif
+			}
 		}
 	}
 
@@ -808,6 +832,7 @@
 	mo = CMOS_READ(RTC_MONTH);
 	yr = CMOS_READ(RTC_YEAR);
 #endif
+#include <linux/suspend.h>
 
 	spin_unlock(&rtc_lock);
 
@@ -1268,10 +1293,16 @@
 		case ACPI_STATE_S4:
 			if (acpi_gbl_FACS->S4bios_f &&
 			    0 != acpi_gbl_FADT->smi_cmd) {
-				printk(" S4bios");
+				printk(" S4 (bios)");
+				system->states[i] = 1;
+			}
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+			else {
+				printk(" S4 (swsusp)");
 				system->states[i] = 1;
 			}
-			/* no break */
+#endif
+			break;
 		default: 
 			if (ACPI_SUCCESS(status)) {
 				system->states[i] = 1;
diff -ruN 20-old/drivers/block/loop.c 20-new/drivers/block/loop.c
--- 20-old/drivers/block/loop.c	2003-08-26 07:00:01.000000000 +1000
+++ 20-new/drivers/block/loop.c	2004-11-24 15:24:21.000000000 +1100
@@ -581,7 +581,9 @@
 	atomic_inc(&lo->lo_pending);
 	spin_unlock_irq(&lo->lo_lock);
 
-	current->flags |= PF_NOIO;
+	current->flags |= PF_NOIO | PF_NOFREEZE; /* loop can be used in an encrypted device
+						    hence, it mustn't be stopped at all because it could
+						    be indirectly used during suspension */
 
 	/*
 	 * up sem, we are running
diff -ruN 20-old/drivers/char/agp/agpgart_be.c 20-new/drivers/char/agp/agpgart_be.c
--- 20-old/drivers/char/agp/agpgart_be.c	2004-11-24 15:05:19.000000000 +1100
+++ 20-new/drivers/char/agp/agpgart_be.c	2004-11-24 15:24:21.000000000 +1100
@@ -589,7 +589,7 @@
 
 	agp_bridge.gatt_table_real = (u32 *) table;
 	agp_gatt_table = (void *)table;
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	err = change_page_attr(virt_to_page(table), 1<<page_order, PAGE_KERNEL_NOCACHE);
 #endif
 	if (!err) 
@@ -622,6 +622,7 @@
 
 static void agp_generic_resume(void)
 {
+  	agp_bridge.configure();
 	return;
 }
 
@@ -661,7 +662,7 @@
 	 * from the table.
 	 */
 
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	change_page_attr(virt_to_page(agp_bridge.gatt_table_real), 1<<page_order, 
 			 PAGE_KERNEL);
 #endif
@@ -787,7 +788,7 @@
 	if (page == NULL) {
 		return 0;
 	}
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	if (change_page_attr(page, 1, PAGE_KERNEL_NOCACHE) < 0) {
 		__free_page(page); 
 		return 0;
@@ -831,7 +832,7 @@
 	}
 	
 	page = virt_to_page(pt);
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	change_page_attr(page, 1, PAGE_KERNEL); 
 #endif	
 	put_page(page);
@@ -2603,11 +2604,6 @@
 	return addr | agp_bridge.masks[0].mask;
 }
 
-static void intel_resume(void)
-{
-	intel_configure();
-}
-
 /* Setup function */
 static gatt_mask intel_generic_masks[] =
 {
@@ -2674,7 +2670,7 @@
 	agp_bridge.agp_alloc_page = agp_generic_alloc_page;
 	agp_bridge.agp_destroy_page = agp_generic_destroy_page;
 	agp_bridge.suspend = agp_generic_suspend;
-	agp_bridge.resume = intel_resume;
+	agp_bridge.resume = agp_generic_resume;
 	agp_bridge.cant_use_aperture = 0;
 
 	return 0;
@@ -3193,7 +3189,7 @@
 	}
 	SetPageReserved(virt_to_page(page_map->real));
 	CACHE_FLUSH();
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	err = change_page_attr(virt_to_page(page_map->real), 1, PAGE_KERNEL_NOCACHE);
 #endif
 	if (!err) 
@@ -3217,7 +3213,7 @@
 static void amd_free_page_map(amd_page_map *page_map)
 {
 	iounmap(page_map->remapped);
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	change_page_attr(virt_to_page(page_map->real), 1, PAGE_KERNEL);
 #endif
 	ClearPageReserved(virt_to_page(page_map->real));
@@ -4353,7 +4349,7 @@
 		return -ENOMEM;
 	}
 	SetPageReserved(virt_to_page(page_map->real));
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	err = change_page_attr(virt_to_page(page_map->real), 1, PAGE_KERNEL_NOCACHE);
 #endif
 	CACHE_FLUSH();
@@ -4377,7 +4373,7 @@
 
 static void serverworks_free_page_map(serverworks_page_map *page_map)
 {
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND2)
 	change_page_attr(virt_to_page(page_map->real),1,PAGE_KERNEL); 
 #endif
 	iounmap(page_map->remapped);
diff -ruN 20-old/drivers/char/console.c 20-new/drivers/char/console.c
--- 20-old/drivers/char/console.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/drivers/char/console.c	2004-11-24 15:24:21.000000000 +1100
@@ -149,11 +149,11 @@
 static void blank_screen(unsigned long dummy);
 static void gotoxy(int currcons, int new_x, int new_y);
 static void save_cur(int currcons);
-static void reset_terminal(int currcons, int do_clear);
+void reset_terminal(int currcons, int do_clear);
 static void con_flush_chars(struct tty_struct *tty);
 static void set_vesa_blanking(unsigned long arg);
 static void set_cursor(int currcons);
-static void hide_cursor(int currcons);
+void hide_cursor(int currcons);
 static void unblank_screen_t(unsigned long dummy);
 static void console_callback(void *ignored);
 
@@ -531,7 +531,7 @@
 		sw->con_putc(vc_cons[currcons].d, i, y, x);
 }
 
-static void hide_cursor(int currcons)
+void hide_cursor(int currcons)
 {
 	if (currcons == sel_cons)
 		clear_selection();
@@ -1398,7 +1398,7 @@
 	ESpalette };
 
 /* console_sem is held (except via vc_init()) */
-static void reset_terminal(int currcons, int do_clear)
+void reset_terminal(int currcons, int do_clear)
 {
 	top		= 0;
 	bottom		= video_num_lines;
@@ -2991,7 +2991,7 @@
 
 void putconsxy(int currcons, char *p)
 {
-	gotoxy(currcons, p[0], p[1]);
+	gotoxy(currcons, (unsigned char) p[0], (unsigned char) p[1]);
 	set_cursor(currcons);
 }
 
@@ -3063,6 +3063,8 @@
 EXPORT_SYMBOL(vc_resize);
 EXPORT_SYMBOL(fg_console);
 EXPORT_SYMBOL(console_blank_hook);
+EXPORT_SYMBOL(hide_cursor);
+EXPORT_SYMBOL(reset_terminal);
 #ifdef CONFIG_VT
 EXPORT_SYMBOL(vt_cons);
 #endif
@@ -3070,3 +3072,9 @@
 EXPORT_SYMBOL(take_over_console);
 EXPORT_SYMBOL(give_up_console);
 #endif
+EXPORT_SYMBOL(redraw_screen);
+EXPORT_SYMBOL(unblank_screen);
+EXPORT_SYMBOL(console_blanked);
+EXPORT_SYMBOL(poke_blanked_console);
+EXPORT_SYMBOL(vc_cons);
+EXPORT_SYMBOL(kmsg_redirect);
diff -ruN 20-old/drivers/char/hvc_console.c 20-new/drivers/char/hvc_console.c
--- 20-old/drivers/char/hvc_console.c	2004-03-05 14:35:04.000000000 +1100
+++ 20-new/drivers/char/hvc_console.c	2004-11-24 15:24:21.000000000 +1100
@@ -235,6 +235,7 @@
 	reparent_to_init();
 	strcpy(current->comm, "khvcd");
 	sigfillset(&current->blocked);
+	current->flags |= PF_NOFREEZE;
 
 	for (;;) {
 		for (i = 0; i < MAX_NR_HVC_CONSOLES; ++i)
diff -ruN 20-old/drivers/char/keyboard.c 20-new/drivers/char/keyboard.c
--- 20-old/drivers/char/keyboard.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/drivers/char/keyboard.c	2004-11-24 15:24:21.000000000 +1100
@@ -32,6 +32,8 @@
 #include <linux/string.h>
 #include <linux/random.h>
 #include <linux/init.h>
+#include <linux/completion.h>
+#include <linux/suspend.h>
 
 #include <asm/keyboard.h>
 #include <asm/bitops.h>
@@ -155,7 +157,7 @@
 struct pt_regs * kbd_pt_regs;
 
 #ifdef CONFIG_MAGIC_SYSRQ
-static int sysrq_pressed;
+int sysrq_pressed;  /* Made non static so suspend can reset on resume */
 #endif
 
 static struct pm_dev *pm_kbd;
@@ -294,6 +296,13 @@
 	}
 #endif
 
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (down && (test_suspend_state(SUSPEND_RUNNING))) {
+		suspend_handle_keypress(keycode, SUSPEND_KEY_KEYBOARD);
+		return;
+	}
+#endif
+
 	if (kbd->kbdmode == VC_MEDIUMRAW) {
 		/* soon keycodes will require more than one byte */
 		put_queue(keycode + up_flag);
diff -ruN 20-old/drivers/char/Makefile 20-new/drivers/char/Makefile
--- 20-old/drivers/char/Makefile	2004-08-20 12:39:54.000000000 +1000
+++ 20-new/drivers/char/Makefile	2004-11-24 15:24:21.000000000 +1100
@@ -25,7 +25,7 @@
 			misc.o pty.o random.o selection.o serial.o \
 			sonypi.o tty_io.o tty_ioctl.o generic_serial.o \
 			au1000_gpio.o vac-serial.o hp_psaux.o nvram.o \
-			scx200.o fetchop.o
+			scx200.o fetchop.o vt.o
 
 mod-subdirs	:=	joystick ftape drm drm-4.0 pcmcia
 
diff -ruN 20-old/drivers/char/n_tty.c 20-new/drivers/char/n_tty.c
--- 20-old/drivers/char/n_tty.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/drivers/char/n_tty.c	2004-11-24 15:24:21.000000000 +1100
@@ -1177,6 +1177,8 @@
 	add_wait_queue(&tty->write_wait, &wait);
 	while (1) {
 		set_current_state(TASK_INTERRUPTIBLE);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if (signal_pending(current)) {
 			retval = -ERESTARTSYS;
 			break;
diff -ruN 20-old/drivers/char/serial.c 20-new/drivers/char/serial.c
--- 20-old/drivers/char/serial.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/drivers/char/serial.c	2004-11-24 15:24:21.000000000 +1100
@@ -62,10 +62,14 @@
  *        Robert Schwebel <robert@schwebel.de>,
  *        Juergen Beisert <jbeisert@eurodsn.de>,
  *        Theodore Ts'o <tytso@mit.edu>
+ *
+ * 01/04: Verion: 5.1
+ *        Add PM suspend/resume support
+ *        Michael Frank <mhf@linuxmail.org>
  */
 
-static char *serial_version = "5.05c";
-static char *serial_revdate = "2001-07-08";
+static char *serial_version = "5.1";
+static char *serial_revdate = "2004-01-29";
 
 /*
  * Serial driver configuration section.  Here are the various options:
@@ -203,6 +207,9 @@
 #include <linux/ioport.h>
 #include <linux/mm.h>
 #include <linux/slab.h>
+#ifdef CONFIG_PM
+#include <linux/pm.h>
+#endif
 #if (LINUX_VERSION_CODE >= 131343)
 #include <linux/init.h>
 #endif
@@ -263,6 +270,11 @@
 #define _INLINE_
 #endif
 
+#if defined(CONFIG_SERIAL_CONSOLE)
+#include <linux/completion.h>
+#include <linux/suspend.h>
+#endif
+
 static char *serial_name = "Serial driver";
 
 static DECLARE_TASK_QUEUE(tq_serial);
@@ -676,7 +688,8 @@
 			else if (*status & UART_LSR_FE)
 				*tty->flip.flag_buf_ptr = TTY_FRAME;
 		}
-#if defined(CONFIG_SERIAL_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#if defined(CONFIG_SERIAL_CONSOLE)
+#if defined(CONFIG_MAGIC_SYSRQ)
 		if (break_pressed && info->line == sercons.index) {
 			if (ch != 0 &&
 			    time_before(jiffies, break_pressed + HZ*5)) {
@@ -686,7 +699,15 @@
 			}
 			break_pressed = 0;
 		}
-#endif
+#endif	/* CONFIG_MAGIC_SYSRQ */
+
+	if (test_suspend_state(SUSPEND_SANITY_CHECK_PROMPT) ||
+	    test_suspend_state(SUSPEND_RUNNING)) {
+		suspend_handle_keypress(ch, SUSPEND_KEY_SERIAL);
+		goto ignore_char;
+	}
+#endif /* CONFIG_SERIAL_CONSOLE */
+
 		if ((*status & info->ignore_status_mask) == 0) {
 			tty->flip.flag_buf_ptr++;
 			tty->flip.char_buf_ptr++;
@@ -704,7 +725,7 @@
 			tty->flip.flag_buf_ptr++;
 			tty->flip.char_buf_ptr++;
 		}
-#if defined(CONFIG_SERIAL_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
+#if defined(CONFIG_SERIAL_CONSOLE) && ( defined(CONFIG_MAGIC_SYSRQ) || defined(CONFIG_PM))
 	ignore_char:
 #endif
 		*status = serial_inp(info, UART_LSR);
@@ -1246,44 +1267,16 @@
 }
 #endif /* CONFIG_SERIAL_RSA */
 
-static int startup(struct async_struct * info)
+/*
+ * Starts the uart
+ */
+
+int start_uart(struct async_struct * info)
 {
-	unsigned long flags;
 	int	retval=0;
-	void (*handler)(int, void *, struct pt_regs *);
 	struct serial_state *state= info->state;
-	unsigned long page;
-#ifdef CONFIG_SERIAL_MANY_PORTS
-	unsigned short ICP;
-#endif
-
-	page = get_zeroed_page(GFP_KERNEL);
-	if (!page)
-		return -ENOMEM;
-
-	save_flags(flags); cli();
-
-	if (info->flags & ASYNC_INITIALIZED) {
-		free_page(page);
-		goto errout;
-	}
-
-	if (!CONFIGURED_SERIAL_PORT(state) || !state->type) {
-		if (info->tty)
-			set_bit(TTY_IO_ERROR, &info->tty->flags);
-		free_page(page);
-		goto errout;
-	}
-	if (info->xmit.buf)
-		free_page(page);
-	else
-		info->xmit.buf = (unsigned char *) page;
-
-#ifdef SERIAL_DEBUG_OPEN
-	printk("starting up ttys%d (irq %d)...", info->line, state->irq);
-#endif
 
-	if (uart_config[state->type].flags & UART_STARTECH) {
+  if (uart_config[state->type].flags & UART_STARTECH) {
 		/* Wake up UART */
 		serial_outp(info, UART_LCR, 0xBF);
 		serial_outp(info, UART_EFR, UART_EFR_ECB);
@@ -1379,69 +1372,19 @@
 			retval = -ENODEV;
 		goto errout;
 	}
-	
-	/*
-	 * Allocate the IRQ if necessary
-	 */
-	if (state->irq && (!IRQ_ports[state->irq] ||
-			  !IRQ_ports[state->irq]->next_port)) {
-		if (IRQ_ports[state->irq]) {
-#ifdef CONFIG_SERIAL_SHARE_IRQ
-			free_irq(state->irq, &IRQ_ports[state->irq]);
-#ifdef CONFIG_SERIAL_MULTIPORT				
-			if (rs_multiport[state->irq].port1)
-				handler = rs_interrupt_multi;
-			else
-#endif
-				handler = rs_interrupt;
-#else
-			retval = -EBUSY;
-			goto errout;
-#endif /* CONFIG_SERIAL_SHARE_IRQ */
-		} else 
-			handler = rs_interrupt_single;
-
-		retval = request_irq(state->irq, handler, SA_SHIRQ,
-				     "serial", &IRQ_ports[state->irq]);
-		if (retval) {
-			if (capable(CAP_SYS_ADMIN)) {
-				if (info->tty)
-					set_bit(TTY_IO_ERROR,
-						&info->tty->flags);
-				retval = 0;
-			}
-			goto errout;
-		}
-	}
+errout:
+	return retval;
+}
 
-	/*
-	 * Insert serial port into IRQ chain.
-	 */
-	info->prev_port = 0;
-	info->next_port = IRQ_ports[state->irq];
-	if (info->next_port)
-		info->next_port->prev_port = info;
-	IRQ_ports[state->irq] = info;
-	figure_IRQ_timeout(state->irq);
+int init_uart(struct async_struct * info)
+{
+	int	retval=0;
+#ifdef CONFIG_SERIAL_MANY_PORTS
+	unsigned short ICP;
+#endif
 
-	/*
-	 * Now, initialize the UART 
-	 */
 	serial_outp(info, UART_LCR, UART_LCR_WLEN8);	/* reset DLAB */
 
-	info->MCR = 0;
-	if (info->tty->termios->c_cflag & CBAUD)
-		info->MCR = UART_MCR_DTR | UART_MCR_RTS;
-#ifdef CONFIG_SERIAL_MANY_PORTS
-	if (info->flags & ASYNC_FOURPORT) {
-		if (state->irq == 0)
-			info->MCR |= UART_MCR_OUT1;
-	} else
-#endif
-	{
-		if (state->irq != 0)
-			info->MCR |= UART_MCR_OUT2;
-	}
 	info->MCR |= ALPHA_KLUDGE_MCR; 		/* Don't ask */
 	serial_outp(info, UART_MCR, info->MCR);
 	
@@ -1497,6 +1440,108 @@
 	 * and set the speed of the serial port
 	 */
 	change_speed(info, 0);
+	return retval;
+}
+
+static int startup(struct async_struct * info)
+{
+	unsigned long flags;
+	int	retval=0;
+	void (*handler)(int, void *, struct pt_regs *);
+	struct serial_state *state=info->state;
+	unsigned long page;
+
+	page = get_zeroed_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	save_flags(flags); cli();
+
+	if (info->flags & ASYNC_INITIALIZED) {
+		free_page(page);
+		goto errout;
+	}
+
+	if (!CONFIGURED_SERIAL_PORT(state) || !state->type) {
+		if (info->tty)
+			set_bit(TTY_IO_ERROR, &info->tty->flags);
+		free_page(page);
+		goto errout;
+	}
+	if (info->xmit.buf)
+		free_page(page);
+	else
+		info->xmit.buf = (unsigned char *) page;
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("starting up ttys%d (irq %d)...", info->line, state->irq);
+#endif
+
+	info->MCR = 0;
+	if (info->tty->termios->c_cflag & CBAUD)
+		info->MCR = UART_MCR_DTR | UART_MCR_RTS;
+#ifdef CONFIG_SERIAL_MANY_PORTS
+	if (info->flags & ASYNC_FOURPORT) {
+		if (state->irq == 0)
+			info->MCR |= UART_MCR_OUT1;
+	} else
+#endif
+	{
+		if (state->irq != 0)
+			info->MCR |= UART_MCR_OUT2;
+	}
+
+	start_uart(info);
+
+	/*
+	 * Allocate the IRQ if necessary
+	 */
+	if (state->irq && (!IRQ_ports[state->irq] ||
+			  !IRQ_ports[state->irq]->next_port)) {
+		if (IRQ_ports[state->irq]) {
+#ifdef CONFIG_SERIAL_SHARE_IRQ
+			free_irq(state->irq, &IRQ_ports[state->irq]);
+#ifdef CONFIG_SERIAL_MULTIPORT				
+			if (rs_multiport[state->irq].port1)
+				handler = rs_interrupt_multi;
+			else
+#endif
+				handler = rs_interrupt;
+#else
+			retval = -EBUSY;
+			goto errout;
+#endif /* CONFIG_SERIAL_SHARE_IRQ */
+		} else 
+			handler = rs_interrupt_single;
+
+		retval = request_irq(state->irq, handler, SA_SHIRQ,
+				     "serial", &IRQ_ports[state->irq]);
+		if (retval) {
+			if (capable(CAP_SYS_ADMIN)) {
+				if (info->tty)
+					set_bit(TTY_IO_ERROR,
+						&info->tty->flags);
+				retval = 0;
+			}
+			goto errout;
+		}
+	}
+
+	/*
+	 * Insert serial port into IRQ chain.
+	 */
+	info->prev_port = 0;
+	info->next_port = IRQ_ports[state->irq];
+	if (info->next_port)
+		info->next_port->prev_port = info;
+	IRQ_ports[state->irq] = info;
+	figure_IRQ_timeout(state->irq);
+
+	/*
+	 * Now, initialize the UART 
+	 */
+
+	init_uart(info);
 
 	info->flags |= ASYNC_INITIALIZED;
 	restore_flags(flags);
@@ -1508,6 +1553,33 @@
 }
 
 /*
+ * Sleep capable UART
+ */
+
+static void sleep_uart(struct async_struct * info)
+{
+	(void)serial_in(info, UART_RX);    /* read data port to reset things */
+	
+	if (info->tty)
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	if (uart_config[info->state->type].flags & UART_STARTECH) {
+		/* Arrange to enter sleep mode */
+		serial_outp(info, UART_LCR, 0xBF);
+		serial_outp(info, UART_EFR, UART_EFR_ECB);
+		serial_outp(info, UART_LCR, 0);
+		serial_outp(info, UART_IER, UART_IERX_SLEEP);
+		serial_outp(info, UART_LCR, 0xBF);
+		serial_outp(info, UART_EFR, 0);
+		serial_outp(info, UART_LCR, 0);
+	}
+	if (info->state->type == PORT_16750) {
+		/* Arrange to enter sleep mode */
+		serial_outp(info, UART_IER, UART_IERX_SLEEP);
+	}
+}
+
+/*
  * This routine will shutdown a serial port; interrupts are disabled, and
  * DTR is dropped if the hangup on close termio flag is on.
  */
@@ -1604,27 +1676,9 @@
 	     disable_rsa(info)))
 		state->baud_base = SERIAL_RSA_BAUD_BASE_LO;
 #endif
-	
 
-	(void)serial_in(info, UART_RX);    /* read data port to reset things */
-	
-	if (info->tty)
-		set_bit(TTY_IO_ERROR, &info->tty->flags);
+	sleep_uart(info);
 
-	if (uart_config[info->state->type].flags & UART_STARTECH) {
-		/* Arrange to enter sleep mode */
-		serial_outp(info, UART_LCR, 0xBF);
-		serial_outp(info, UART_EFR, UART_EFR_ECB);
-		serial_outp(info, UART_LCR, 0);
-		serial_outp(info, UART_IER, UART_IERX_SLEEP);
-		serial_outp(info, UART_LCR, 0xBF);
-		serial_outp(info, UART_EFR, 0);
-		serial_outp(info, UART_LCR, 0);
-	}
-	if (info->state->type == PORT_16750) {
-		/* Arrange to enter sleep mode */
-		serial_outp(info, UART_IER, UART_IERX_SLEEP);
-	}
 	info->flags &= ~ASYNC_INITIALIZED;
 	restore_flags(flags);
 }
@@ -3288,6 +3342,110 @@
 	return 0;
 }
 
+#ifdef CONFIG_PM
+
+/*
+ * Suspend an UART
+ */
+
+static int rs_suspend_uart(struct async_struct *info)
+{
+	int retval = 0;
+	printk("serial.c: Suspending %lx\n", info->port);
+
+	/* disable all intrs */
+	serial_outp(info, UART_IER, 0x00);
+
+	/* disable break condition */
+	serial_out(info, UART_LCR, serial_inp(info, UART_LCR) & ~UART_LCR_SBC);
+
+	/* Disable line */
+	if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+		serial_outp(info, UART_MCR,(info->MCR & ~(UART_MCR_DTR|UART_MCR_RTS)));
+
+	/* put UART to sleep */
+	sleep_uart(info);
+
+	if (retval)
+		info->flags &= ~ASYNC_INITIALIZED;
+	return retval;
+}
+static int rs_suspend(struct tty_struct *tty)
+{
+	int retval = 0;
+	unsigned long flags;
+	save_flags(flags); cli();
+
+	retval = rs_suspend_uart((struct async_struct *)tty->driver_data);
+
+	restore_flags(flags);
+	return retval;
+}
+
+/*
+ * Resume an UART
+ */
+
+/* Todo: eval multiport interrupt resume */
+
+static int rs_resume_uart(struct async_struct *info)
+{
+	int retval = 0;
+
+	printk("serial.c: Resuming %lx\n", info->port);
+
+  if ((retval = start_uart(info)))
+		goto out;
+	retval = init_uart(info);
+out:
+	if (retval)
+		info->flags &= ~ASYNC_INITIALIZED;
+	return retval;
+}
+
+static int rs_resume(struct tty_struct *tty)
+{
+	int retval = 0;
+	unsigned long flags;
+	save_flags(flags); cli();
+
+	retval = rs_resume_uart((struct async_struct *)tty->driver_data);
+
+	restore_flags(flags);
+	return retval;
+}
+
+/*
+ * Suspend or Resume all UARTs
+ */
+
+static int rs_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data)
+{
+	struct serial_state *state = rs_table;
+	int i;
+	unsigned long flags; /* Interrupts will be reenabled once per iteration */
+
+	for (i =0; i < NR_PORTS; i++, state++) {
+		if (!state->info) /* info is NULL unless port opened */
+			continue;
+		if (state->type == PORT_UNKNOWN) /* Skip unknown/unregistered ports */
+      continue;
+
+		save_flags(flags); cli();
+
+		switch (rqst) {
+		case PM_SUSPEND:
+			rs_suspend_uart(state->info);
+			break;
+		case PM_RESUME:
+			rs_resume_uart(state->info);
+		}
+		restore_flags(flags);
+	}
+	return 0;
+}
+#endif
+
 /*
  * /proc fs routines....
  */
@@ -5521,6 +5679,10 @@
 	serial_driver.wait_until_sent = rs_wait_until_sent;
 	serial_driver.read_proc = rs_read_proc;
 #endif
+#ifdef CONFIG_PM
+	serial_driver.suspend = rs_suspend;
+	serial_driver.resume = rs_resume;
+#endif
 	
 	/*
 	 * The callout device is just like normal device except for
@@ -5600,8 +5762,13 @@
 	probe_serial_pci();
 #endif
 #ifdef ENABLE_SERIAL_PNP
-       probe_serial_pnp();
+	probe_serial_pnp();
+#endif
+#ifdef CONFIG_PM
+	if (!pm_register(PM_UNKNOWN_DEV, 0, rs_pm_callback))
+		panic("Couldn't register with PM subsystem\n");
 #endif
+
 	return 0;
 }
 
@@ -5772,6 +5939,9 @@
 	struct async_struct *info;
 
 	/* printk("Unloading %s: version %s\n", serial_name, serial_version); */
+#ifdef CONFIG_PM
+	pm_unregister_all(rs_pm_callback);
+#endif
 	del_timer_sync(&serial_timer);
 	save_flags(flags); cli();
         remove_bh(SERIAL_BH);
diff -ruN 20-old/drivers/char/sysrq.c 20-new/drivers/char/sysrq.c
--- 20-old/drivers/char/sysrq.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/drivers/char/sysrq.c	2004-11-24 15:24:21.000000000 +1100
@@ -27,6 +27,7 @@
 #include <linux/quotaops.h>
 #include <linux/smp_lock.h>
 #include <linux/module.h>
+#include <linux/suspend.h>
 
 #include <linux/spinlock.h>
 
@@ -47,7 +48,8 @@
 	int i;
 	i = key - '0';
 	console_loglevel = 7;
-	printk("Loglevel set to %d\n", i);
+	if (!suspend_task)
+		printk("Loglevel set to %d\n", i);
 	console_loglevel = i;
 }	
 static struct sysrq_key_op sysrq_loglevel_op = {
@@ -137,8 +139,19 @@
 static void go_sync(struct super_block *sb, int remount_flag)
 {
 	int orig_loglevel;
+
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (suspend_task) {
+		printk(KERN_INFO "Not %sing device %s. Suspend may have used memory with dirty data!",
+		       remount_flag ? "remount" : "sync",
+		       kdevname(sb->s_dev));
+		return;
+	}
+#endif
+
 	orig_loglevel = console_loglevel;
 	console_loglevel = 7;
+
 	printk(KERN_INFO "%sing device %s ... ",
 	       remount_flag ? "Remount" : "Sync",
 	       kdevname(sb->s_dev));
@@ -461,7 +474,8 @@
 		return;
 
 	orig_log_level = console_loglevel;
-	console_loglevel = 7;
+	if (!suspend_task) /* Not if nice display on */
+		console_loglevel = 7;
 	printk(KERN_INFO "SysRq : ");
 
         op_p = __sysrq_get_key_op(key);
diff -ruN 20-old/drivers/char/vt.c 20-new/drivers/char/vt.c
--- 20-old/drivers/char/vt.c	2003-01-18 10:44:57.000000000 +1100
+++ 20-new/drivers/char/vt.c	2004-11-24 15:24:21.000000000 +1100
@@ -24,6 +24,7 @@
 #include <linux/major.h>
 #include <linux/fs.h>
 #include <linux/console.h>
+#include <linux/module.h>
 
 #include <asm/io.h>
 #include <asm/uaccess.h>
@@ -1314,3 +1315,4 @@
 	complete_change_console(new_console);
 }
 
+EXPORT_SYMBOL(kd_mksound);
diff -ruN 20-old/drivers/hotplug/cpqphp_ctrl.c 20-new/drivers/hotplug/cpqphp_ctrl.c
--- 20-old/drivers/hotplug/cpqphp_ctrl.c	2004-11-24 15:05:20.000000000 +1100
+++ 20-new/drivers/hotplug/cpqphp_ctrl.c	2004-11-24 15:24:21.000000000 +1100
@@ -1739,6 +1739,8 @@
 	while (1) {
 		dbg("!!!!event_thread sleeping\n");
 		down_interruptible (&event_semaphore);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		dbg("event_thread woken finished = %d\n", event_finished);
 		if (event_finished) break;
 		/* Do stuff here */
diff -ruN 20-old/drivers/hotplug/ibmphp_hpc.c 20-new/drivers/hotplug/ibmphp_hpc.c
--- 20-old/drivers/hotplug/ibmphp_hpc.c	2004-11-24 15:05:20.000000000 +1100
+++ 20-new/drivers/hotplug/ibmphp_hpc.c	2004-11-24 15:24:21.000000000 +1100
@@ -911,6 +911,8 @@
 		/* sleep for a short time just for good measure */
 		set_current_state (TASK_INTERRUPTIBLE);
 		schedule_timeout (HZ/10);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 	}
 	up (&sem_exit);
 	debug ("%s - Exit\n", __FUNCTION__);
diff -ruN 20-old/drivers/ide/ide-disk.c 20-new/drivers/ide/ide-disk.c
--- 20-old/drivers/ide/ide-disk.c	2004-08-20 12:39:54.000000000 +1000
+++ 20-new/drivers/ide/ide-disk.c	2004-11-24 15:24:21.000000000 +1100
@@ -1875,67 +1875,89 @@
 	return ide_stopped;
 }
 
-int ide_disks_busy(void)
+int ide_disks_busy(int no_warning)
 {
 	int i;
-	for (i=0; i<MAX_HWIFS; i++) {
-		struct hwgroup_s *hwgroup = ide_hwifs[i].hwgroup;
-		if (!hwgroup) continue;
-		if ((hwgroup->handler) && (hwgroup->handler != panic_box))
-			return 1;
-	}
+	
+	for (i=0; i<MAX_HWIFS; i++)
+		if(ide_hwifs[i].present) { /* when using pcmcia ide-disk hwgroup is non NULL but the
+					      disk can be absent */
+			struct hwgroup_s *hwgroup = ide_hwifs[i].hwgroup;
+			if (!hwgroup) continue;
+			if ((hwgroup->handler) && (hwgroup->handler != panic_box)) {
+				if(!no_warning) /* busy ide disks is not an error */
+					printk("ide_disks_busy: %6s: handler not null: %p\n",
+					       ide_hwifs[i].name,hwgroup->handler);
+				return 1;
+			}
+		}
 	return 0;
 }
 
 void ide_disk_suspend(void)
 {
-	int i;
-	while (ide_disks_busy()) {
-		printk("*");
+	int i, drivenum = 0;
+	ide_drive_t *drive;
+
+	if (driver_blocked)
+		return;
+
+	i=1;
+	while (ide_disks_busy(i++)) {
 		schedule();
+		i &= (unsigned int)((1<<21)-1);	/* CBD: we print a warning only after a great number of errors, no need to frighten the newbie ;-) */
 	}
-	for (i=0; i<MAX_HWIFS; i++) {
-		struct hwgroup_s *hwgroup = ide_hwifs[i].hwgroup;
-
-		if (!hwgroup) continue;
-		hwgroup->handler_save = hwgroup->handler;
-		hwgroup->handler = panic_box;
+	
+	/* Ensure caches are purged too (derived from module unload) - Nigel Cunningham */
+	while ((drive = ide_scan_devices(ide_disk, idedisk_driver.name,
+			&idedisk_driver, drivenum)) != NULL) {
+		if ((drive->id->command_set_1 & 0x20) && drive->id->cfs_enable_1 & 0x20) {
+			if (do_idedisk_flushcache(drive))
+				printk(KERN_ERR "Failed!");
+		}
+		drivenum++;
 	}
+	
 	driver_blocked = 1;
-	if (ide_disks_busy())
+	
+	for (i=0; i<MAX_HWIFS; i++)
+		if(ide_hwifs[i].present) { /* when using pcmcia ide-disk hwgroup is non NULL but the
+					      disk can be absent */		
+			struct hwgroup_s *hwgroup = ide_hwifs[i].hwgroup;
+
+			if (!hwgroup) continue;
+			hwgroup->handler = panic_box;
+		}
+#if 1
+	if (ide_disks_busy(0))
 		panic("How did you get that request through?!");
+#endif
 }
 
-/* unsuspend and resume should be equal in the ideal world */
-
-void ide_disk_unsuspend(void)
+void ide_disk_unsuspend(int resume)
 {
 	int i;
-	for (i=0; i<MAX_HWIFS; i++) {
-		struct hwgroup_s *hwgroup = ide_hwifs[i].hwgroup;
-
-		if (!hwgroup) continue;
-		hwgroup->handler = NULL; /* hwgroup->handler_save; */
-		hwgroup->handler_save = NULL;
-	}
-	driver_blocked = 0;
-}
 
-void ide_disk_resume(void)
-{
-	int i;
-	for (i=0; i<MAX_HWIFS; i++) {
-		struct hwgroup_s *hwgroup = ide_hwifs[i].hwgroup;
+	if (!driver_blocked)
+		return;
 
-		if (!hwgroup) continue;
-		if (hwgroup->handler != panic_box)
-			panic("Handler was not set to panic?");
-		hwgroup->handler_save = NULL;
-		hwgroup->handler = NULL;
-	}
+	for (i=0; i<MAX_HWIFS; i++)
+		if(ide_hwifs[i].present) { /* when using pcmcia ide-disk hwgroup is non NULL but the
+					      disk can be absent */
+
+			struct hwgroup_s *hwgroup = ide_hwifs[i].hwgroup;
+
+			if (!hwgroup) continue;
+			if (hwgroup->handler != panic_box)
+				printk(KERN_ERR "ide_disk_unsuspend: %6s: Handler was not set to panic? %p",
+				       ide_hwifs[i].name,hwgroup->handler);
+			hwgroup->handler = NULL;
+		}
 	driver_blocked = 0;
 }
 
+EXPORT_SYMBOL(ide_disk_unsuspend);
+
 module_init(idedisk_init);
 module_exit(idedisk_exit);
 MODULE_LICENSE("GPL");
diff -ruN 20-old/drivers/ieee1394/nodemgr.c 20-new/drivers/ieee1394/nodemgr.c
--- 20-old/drivers/ieee1394/nodemgr.c	2004-03-05 14:35:05.000000000 +1100
+++ 20-new/drivers/ieee1394/nodemgr.c	2004-11-24 15:24:21.000000000 +1100
@@ -1296,6 +1296,7 @@
 
 	/* No userlevel access needed */
 	daemonize();
+	current->flags |= PF_NOFREEZE;
 
 	strcpy(current->comm, hi->daemon_name);
 	
diff -ruN 20-old/drivers/md/md.c 20-new/drivers/md/md.c
--- 20-old/drivers/md/md.c	2003-08-26 07:00:04.000000000 +1000
+++ 20-new/drivers/md/md.c	2004-11-24 15:24:21.000000000 +1100
@@ -3588,6 +3588,8 @@
 	mdp_disk_t *spare;
 	struct md_list_head *tmp;
 
+	current->flags |= PF_NOFREEZE;
+
 	printk(KERN_INFO "md: recovery thread got woken up ...\n");
 restart:
 	ITERATE_MDDEV(mddev,tmp) {
@@ -3797,6 +3799,15 @@
 		detected_devices[dev_cnt++] = dev;
 }
 
+void md_autostart_arrays(void)
+{
+  	struct md_list_head *tmp;
+	mddev_t *mddev;
+	
+	autostart_arrays();
+	ITERATE_MDDEV(mddev, tmp)
+  		restart_array(mddev);
+}
 
 static void autostart_arrays(void)
 {
@@ -4127,4 +4138,5 @@
 MD_EXPORT_SYMBOL(mddev_map);
 MD_EXPORT_SYMBOL(md_check_ordering);
 MD_EXPORT_SYMBOL(get_spare);
+MD_EXPORT_SYMBOL(md_autostart_arrays);
 MODULE_LICENSE("GPL");
diff -ruN 20-old/drivers/md/raid1.c 20-new/drivers/md/raid1.c
--- 20-old/drivers/md/raid1.c	2004-11-24 15:05:21.000000000 +1100
+++ 20-new/drivers/md/raid1.c	2004-11-24 15:24:21.000000000 +1100
@@ -1191,6 +1191,7 @@
 
 	if (mddev->sb_dirty)
 		md_update_sb(mddev);
+	current->flags |= PF_NOFREEZE;
 
 	for (;;) {
 		md_spin_lock_irqsave(&retry_list_lock, flags);
@@ -1317,6 +1318,8 @@
 	raid1_conf_t *conf = data;
 	mddev_t *mddev = conf->mddev;
 
+	current->flags |= PF_NOFREEZE;
+
 	if (!conf->resync_mirrors)
 		return;
 	if (conf->resync_mirrors == 2)
diff -ruN 20-old/drivers/md/raid5.c 20-new/drivers/md/raid5.c
--- 20-old/drivers/md/raid5.c	2004-08-20 12:39:54.000000000 +1000
+++ 20-new/drivers/md/raid5.c	2004-11-24 15:24:21.000000000 +1100
@@ -1299,6 +1299,8 @@
 
 	PRINTK("+++ raid5d active\n");
 
+	current->flags |= PF_NOFREEZE;
+
 	handled = 0;
 
 	if (mddev->sb_dirty)
@@ -1348,6 +1350,8 @@
 	raid5_conf_t *conf = data;
 	mddev_t *mddev = conf->mddev;
 
+	current->flags |= PF_NOFREEZE;
+
 	if (!conf->resync_parity)
 		return;
 	if (conf->resync_parity == 2)
diff -ruN 20-old/drivers/media/video/msp3400.c 20-new/drivers/media/video/msp3400.c
--- 20-old/drivers/media/video/msp3400.c	2003-12-17 08:48:22.000000000 +1100
+++ 20-new/drivers/media/video/msp3400.c	2004-11-24 15:24:21.000000000 +1100
@@ -791,8 +791,16 @@
 		interruptible_sleep_on(&msp->wq);
 		if (debug > 1)
 			printk("msp3400: thread: wakeup\n");
+#ifdef CONFIG_PREEMPT
+		preempt_disable();
+#endif
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if (msp->rmmod || signal_pending(current))
 			goto done;
+#ifdef CONFIG_PREEMPT
+		preempt_enable();
+#endif
 
 		msp->active = 1;
 
@@ -805,8 +813,16 @@
 		/* some time for the tuner to sync */
 		current->state   = TASK_INTERRUPTIBLE;
 		schedule_timeout(HZ/5);
+#ifdef CONFIG_PREEMPT
+		preempt_disable();
+#endif
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if (signal_pending(current))
 			goto done;
+#ifdef CONFIG_PREEMPT
+		preempt_enable();
+#endif
 		
 	restart:
 		if (VIDEO_MODE_RADIO == msp->norm ||
@@ -840,8 +856,16 @@
 
 			current->state   = TASK_INTERRUPTIBLE;
 			schedule_timeout(HZ/10);
+#ifdef CONFIG_PREEMPT
+			preempt_disable();
+#endif
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 			if (signal_pending(current))
 				goto done;
+#ifdef CONFIG_PREEMPT
+			preempt_enable();
+#endif
 			if (msp->restart)
 				msp->restart = 0;
 
@@ -877,8 +901,16 @@
 
 			current->state   = TASK_INTERRUPTIBLE;
 			schedule_timeout(HZ/10);
+#ifdef CONFIG_PREEMPT
+			preempt_disable();
+#endif
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 			if (signal_pending(current))
 				goto done;
+#ifdef CONFIG_PREEMPT
+			preempt_enable();
+#endif
 			if (msp->restart)
 				goto restart;
 
@@ -976,6 +1008,9 @@
 	}
 
 done:
+#ifdef CONFIG_PREEMPT
+	preempt_enable();
+#endif
 	dprintk(KERN_DEBUG "msp3400: thread: exit\n");
 	msp->active = 0;
 	msp->thread = NULL;
diff -ruN 20-old/drivers/media/video/tvaudio.c 20-new/drivers/media/video/tvaudio.c
--- 20-old/drivers/media/video/tvaudio.c	2003-12-17 08:48:22.000000000 +1100
+++ 20-new/drivers/media/video/tvaudio.c	2004-11-24 15:24:21.000000000 +1100
@@ -28,6 +28,7 @@
 #include <linux/i2c-algo-bit.h>
 #include <linux/init.h>
 #include <linux/smp_lock.h>
+#include <linux/suspend.h>
 
 #include "audiochip.h"
 #include "id.h"
@@ -287,8 +288,16 @@
 	for (;;) {
 		interruptible_sleep_on(&chip->wq);
 		dprintk("%s: thread wakeup\n", i2c_clientname(&chip->c));
+#ifdef CONFIG_PREEMPT
+		preempt_disable();
+#endif
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if (chip->done || signal_pending(current))
 			break;
+#ifdef CONFIG_PREEMPT
+		preempt_enable();
+#endif
 
 		/* don't do anything for radio or if mode != auto */
 		if (chip->norm == VIDEO_MODE_RADIO || chip->mode != 0)
@@ -301,6 +310,9 @@
 		mod_timer(&chip->wt, jiffies+2*HZ);
 	}
 
+#ifdef CONFIG_PREEMPT
+	preempt_enable();
+#endif
 	chip->thread = NULL;
 	dprintk("%s: thread exiting\n", i2c_clientname(&chip->c));
 	if(chip->notify != NULL)
diff -ruN 20-old/drivers/message/i2o/i2o_block.c 20-new/drivers/message/i2o/i2o_block.c
--- 20-old/drivers/message/i2o/i2o_block.c	2003-01-18 10:43:54.000000000 +1100
+++ 20-new/drivers/message/i2o/i2o_block.c	2004-11-24 15:24:21.000000000 +1100
@@ -710,11 +710,15 @@
 
 	strcpy(current->comm, "i2oblock");
 	evt_running = 1;
-
+	
 	while(1)
 	{
 		if(down_interruptible(&i2ob_evt_sem))
 		{
+			if (current->flags & PF_FREEZE) {
+				refrigerator(PF_FREEZE);
+				continue;
+			}
 			evt_running = 0;
 			printk("exiting...");
 			break;
diff -ruN 20-old/drivers/message/i2o/i2o_core.c 20-new/drivers/message/i2o/i2o_core.c
--- 20-old/drivers/message/i2o/i2o_core.c	2003-06-21 08:16:40.000000000 +1000
+++ 20-new/drivers/message/i2o/i2o_core.c	2004-11-24 15:24:21.000000000 +1100
@@ -884,6 +884,10 @@
 	{
 		if(down_interruptible(&evt_sem))
 		{
+			if (current->flags & PF_FREEZE) {
+				refrigerator(PF_FREEZE);
+				continue;
+			}
 			dprintk(KERN_INFO "I2O event thread dead\n");
 			printk("exiting...");
 			evt_running = 0;
@@ -1055,6 +1059,12 @@
 	while(1)
 	{
 		down_interruptible(&c->lct_sem);
+		
+		if (current->flags & PF_FREEZE) {
+			refrigerator(PF_FREEZE);
+			continue;
+		}
+
 		if(signal_pending(current))
 		{
 			dprintk(KERN_ERR "%s: LCT thread dead\n", c->name);
diff -ruN 20-old/drivers/mtd/mtdblock.c 20-new/drivers/mtd/mtdblock.c
--- 20-old/drivers/mtd/mtdblock.c	2003-06-21 08:16:40.000000000 +1000
+++ 20-new/drivers/mtd/mtdblock.c	2004-11-24 15:24:21.000000000 +1100
@@ -492,6 +492,7 @@
 	recalc_sigpending(tsk);
 	spin_unlock_irq(&tsk->sigmask_lock);
 	daemonize();
+	current->flags |= PF_NOFREEZE;
 
 	while (!leaving) {
 		add_wait_queue(&thr_wq, &wait);
diff -ruN 20-old/drivers/net/8139too.c 20-new/drivers/net/8139too.c
--- 20-old/drivers/net/8139too.c	2004-11-24 15:05:21.000000000 +1100
+++ 20-new/drivers/net/8139too.c	2004-11-24 15:24:21.000000000 +1100
@@ -1607,6 +1607,8 @@
 		timeout = next_tick;
 		do {
 			timeout = interruptible_sleep_on_timeout (&tp->thr_wait, timeout);
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 		} while (!signal_pending (current) && (timeout > 0));
 
 		if (signal_pending (current)) {
diff -ruN 20-old/drivers/net/eepro100.c 20-new/drivers/net/eepro100.c
--- 20-old/drivers/net/eepro100.c	2003-08-26 07:00:07.000000000 +1000
+++ 20-new/drivers/net/eepro100.c	2004-11-24 15:24:21.000000000 +1100
@@ -529,6 +529,8 @@
 static int eepro100_init_one(struct pci_dev *pdev,
 		const struct pci_device_id *ent);
 static void eepro100_remove_one (struct pci_dev *pdev);
+static int eepro100_suspend (struct pci_dev *pdev, u32 state);
+static int eepro100_resume (struct pci_dev *pdev);
 
 static int do_eeprom_cmd(long ioaddr, int cmd, int cmd_len);
 static int mdio_read(struct net_device *dev, int phy_id, int location);
diff -ruN 20-old/drivers/net/via-rhine.c 20-new/drivers/net/via-rhine.c
--- 20-old/drivers/net/via-rhine.c	2004-08-20 12:39:55.000000000 +1000
+++ 20-new/drivers/net/via-rhine.c	2004-11-24 15:24:21.000000000 +1100
@@ -1906,12 +1906,40 @@
 	pci_set_drvdata(pdev, NULL);
 }
 
+#ifdef CONFIG_PM
+static int via_rhine_suspend(struct pci_dev *pdev, u32 state)
+{
+	printk("Suspending via_rhine...\n");
+#if 0
+	via_rhine_remove_one(pdev);
+#endif
+	return 0;
+}
+
+static int via_rhine_resume(struct pci_dev *pdev)
+{
+	long ioaddr;
+	
+	printk("Resuming via_rhine...\n");
+#if 1
+	ioaddr = pci_resource_start (pdev, 0);
+	/* Reset the chip. */
+	writew(CmdReset, ioaddr + ChipCmd);
+#endif
+	return 0;
+}
+#endif
+					    
 
 static struct pci_driver via_rhine_driver = {
 	.name		= "via-rhine",
 	.id_table	= via_rhine_pci_tbl,
 	.probe		= via_rhine_init_one,
 	.remove		= __devexit_p(via_rhine_remove_one),
+#ifdef CONFIG_PM
+	.suspend	= via_rhine_suspend,
+	.resume		= via_rhine_resume
+#endif
 };
 
 
diff -ruN 20-old/drivers/scsi/scsi_error.c 20-new/drivers/scsi/scsi_error.c
--- 20-old/drivers/scsi/scsi_error.c	2004-11-24 15:05:23.000000000 +1100
+++ 20-new/drivers/scsi/scsi_error.c	2004-11-24 15:24:21.000000000 +1100
@@ -1866,6 +1866,7 @@
 	 */
 
 	sprintf(current->comm, "scsi_eh_%d", host->host_no);
+	current->flags |= PF_NOFREEZE;
 
 	host->eh_wait = &sem;
 	host->ehandler = current;
diff -ruN 20-old/drivers/usb/host/usb-ohci.c 20-new/drivers/usb/host/usb-ohci.c
--- 20-old/drivers/usb/host/usb-ohci.c	2004-04-16 23:46:16.000000000 +1000
+++ 20-new/drivers/usb/host/usb-ohci.c	2004-11-24 15:24:21.000000000 +1100
@@ -65,9 +65,11 @@
 #include <linux/init.h>
 #include <linux/timer.h>
 #include <linux/list.h>
+#include <linux/suspend.h>
 #include <linux/interrupt.h>  /* for in_interrupt() */
 #undef DEBUG
 #include <linux/usb.h>
+#include <linux/notifier.h>
 
 #include <asm/io.h>
 #include <asm/irq.h>
@@ -90,6 +92,9 @@
 #endif
 #endif
 
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+static ohci_t *ohci_save = NULL;
+#endif /* CONFIG_SOFTWARE_SUSPEND2 */
 
 /*
  * Version Information
@@ -2627,6 +2632,14 @@
 	int temp;
 	int i;
 
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	/* dont do anything till we're out of a software suspend */
+	if (suspend_task) {
+		ohci_save = ohci;
+		return;
+	}
+#endif /* CONFIG_SOFTWARE_SUSPEND2 */
+
 	if (ohci->pci_latency)
 		pci_write_config_byte (ohci->ohci_dev, PCI_LATENCY_TIMER, ohci->pci_latency);
 
diff -ruN 20-old/drivers/usb/hub.c 20-new/drivers/usb/hub.c
--- 20-old/drivers/usb/hub.c	2003-08-26 07:00:13.000000000 +1000
+++ 20-new/drivers/usb/hub.c	2004-11-24 15:24:21.000000000 +1100
@@ -914,6 +914,7 @@
 
 	/* Setup a nice name */
 	strcpy(current->comm, "khubd");
+	current->flags |= PF_NOFREEZE;
 
 	/* Send me a signal to get me die (for debugging) */
 	do {
diff -ruN 20-old/drivers/usb/storage/usb.c 20-new/drivers/usb/storage/usb.c
--- 20-old/drivers/usb/storage/usb.c	2004-11-24 15:05:24.000000000 +1100
+++ 20-new/drivers/usb/storage/usb.c	2004-11-24 15:24:21.000000000 +1100
@@ -332,8 +332,7 @@
 
 	/* set our name for identification purposes */
 	sprintf(current->comm, "usb-storage-%d", us->host_number);
-	
-	current->flags |= PF_MEMALLOC;
+	current->flags |= PF_NOFREEZE | PF_MEMALLOC;
 
 	unlock_kernel();
 
diff -ruN 20-old/fs/buffer.c 20-new/fs/buffer.c
--- 20-old/fs/buffer.c	2004-11-24 15:05:24.000000000 +1100
+++ 20-new/fs/buffer.c	2004-11-24 15:50:21.000000000 +1100
@@ -47,6 +47,7 @@
 #include <linux/highmem.h>
 #include <linux/module.h>
 #include <linux/completion.h>
+#include <linux/suspend.h>
 
 #include <asm/uaccess.h>
 #include <asm/io.h>
@@ -76,7 +77,7 @@
 static spinlock_cacheline_t lru_list_lock_cacheline = {SPIN_LOCK_UNLOCKED};
 #define lru_list_lock  lru_list_lock_cacheline.lock
 
-static int nr_buffers_type[NR_LIST];
+int nr_buffers_type[NR_LIST];
 static unsigned long size_buffers_type[NR_LIST];
 
 static struct buffer_head * unused_list;
@@ -376,6 +377,12 @@
 int fsync_super(struct super_block *sb)
 {
 	kdev_t dev = sb->s_dev;
+
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (unlikely(suspend_task))
+		return 0;
+#endif
+
 	sync_buffers(dev, 0);
 
 	lock_kernel();
@@ -400,6 +407,10 @@
 
 int fsync_dev(kdev_t dev)
 {
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (unlikely(suspend_task))
+		return 0;
+#endif
 	sync_buffers(dev, 0);
 
 	lock_kernel();
@@ -420,12 +431,23 @@
 	fsync_dev(dev);
 }
 
+/*
+ * When trying to freeze processes (for Software Suspend),
+ * we want to put new attempts at syncing on hold until
+ * resume time (yes, they'll be redundant then, but that
+ * doesn't matter) and we want to wait for current attempts
+ * to finish.
+ */
 asmlinkage long sys_sync(void)
 {
+	current->flags |= PF_SYNCTHREAD;
 	fsync_dev(0);
+	current->flags &= ~PF_SYNCTHREAD;
 	return 0;
 }
 
+EXPORT_SYMBOL(sys_sync);
+
 /*
  *	filp may be NULL if called via the msync of a vma.
  */
@@ -462,6 +484,8 @@
 	struct inode * inode;
 	int ret, err;
 
+	current->flags |= PF_SYNCTHREAD;
+
 	ret = -EBADF;
 	file = fget(fd);
 	if (!file)
@@ -490,6 +514,7 @@
 out_putf:
 	fput(file);
 out:
+	current->flags &= ~PF_SYNCTHREAD;
 	return ret;
 }
 
@@ -521,6 +546,8 @@
 	struct inode *inode;
 	int ret;
 
+	current->flags |= PF_SYNCTHREAD;
+
 	ret = -EBADF;
 	file = fget(fd);
 	if (!file)
@@ -533,6 +560,7 @@
 
 	fput(file);
 out:
+	current->flags &= ~PF_SYNCTHREAD;
 	return ret;
 }
 
@@ -790,7 +818,8 @@
 {
 	balance_dirty();
 	wakeup_bdflush();
-	try_to_free_pages(GFP_NOIO);
+	if (likely(suspend_task != current->pid))
+		try_to_free_pages(GFP_NOIO);
 	run_task_queue(&tq_disk);
 	yield();
 }
@@ -1366,6 +1395,10 @@
 	run_task_queue(&tq_disk);
 
 	free_more_memory();
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (suspend_task == current->pid)
+		suspend2_core_ops->cleanup_finished_io();
+#endif
 	goto try_again;
 }
 
@@ -2552,7 +2585,12 @@
 			goto failed;
 	}
 
-	bh = create_buffers(page, size, 0);
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (suspend_task == current->pid)
+		bh = create_buffers(page, size, 1);
+	else
+#endif
+		bh = create_buffers(page, size, 0);
 	if (!bh)
 		goto failed;
 	link_dev_buffers(page, bh);
@@ -2928,10 +2966,17 @@
  * otherwise there would be no way of ensuring that these quantities ever 
  * get written back.  Ideally, we would have a timestamp on the inodes
  * and superblocks so that we could write back only the old ones as well
+ *
+ * Not static so it can be used during suspend process freezing.
  */
 
-static int sync_old_buffers(void)
+int sync_old_buffers(void)
 {
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (unlikely(suspend_task))
+		return 0;
+#endif
+
 	lock_kernel();
 	sync_unlocked_inodes();
 	sync_supers(0, 0);
@@ -3054,6 +3099,8 @@
 	for (;;) {
 		int ndirty = bdf_prm.b_un.ndirty;
 
+		current->flags |= PF_SYNCTHREAD;
+
 		CHECK_EMERGENCY_SYNC
 
 		while (ndirty > 0) {
@@ -3062,8 +3109,13 @@
 				break;
 			ndirty -= NRSYNC;
 		}
+		current->flags &= ~PF_SYNCTHREAD;
+
 		if (ndirty > 0 || bdflush_stop())
 			interruptible_sleep_on(&bdflush_wait);
+
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 	}
 }
 
@@ -3106,6 +3158,12 @@
 			schedule(); /* wait for SIGCONT */
 		}
 		remove_wait_queue(&kupdate_wait, &wait);
+		
+		if (current->flags & PF_FREEZE) {
+			refrigerator(PF_FREEZE);
+			continue;
+		}
+
 		/* check for sigstop */
 		if (signal_pending(tsk)) {
 			int sig, stopped = 0;
@@ -3124,10 +3182,12 @@
 #ifdef DEBUG
 		printk(KERN_DEBUG "kupdate() activated...\n");
 #endif
+		current->flags |= PF_SYNCTHREAD;
 		sync_old_buffers();
 		if (laptop_mode)
 			fsync_dev(NODEV);
 		run_task_queue(&tq_disk);
+		current->flags &= ~PF_SYNCTHREAD;
 	}
 }
 
diff -ruN 20-old/fs/ioctl.c 20-new/fs/ioctl.c
--- 20-old/fs/ioctl.c	2003-08-26 07:00:16.000000000 +1000
+++ 20-new/fs/ioctl.c	2004-11-24 15:24:21.000000000 +1100
@@ -7,6 +7,7 @@
 #include <linux/mm.h>
 #include <linux/smp_lock.h>
 #include <linux/file.h>
+#include <linux/module.h>
 
 #include <asm/uaccess.h>
 #include <asm/ioctls.h>
@@ -124,3 +125,4 @@
 out:
 	return error;
 }
+EXPORT_SYMBOL(sys_ioctl);
diff -ruN 20-old/fs/jbd/journal.c 20-new/fs/jbd/journal.c
--- 20-old/fs/jbd/journal.c	2004-11-24 15:05:24.000000000 +1100
+++ 20-new/fs/jbd/journal.c	2004-11-24 15:24:21.000000000 +1100
@@ -211,6 +211,7 @@
 	spin_unlock_irq(&current->sigmask_lock);
 
 	sprintf(current->comm, "kjournald");
+	current->flags |= PF_SYNCTHREAD;
 
 	/* Set up an interval timer which can be used to trigger a
            commit wakeup after the commit interval expires */
@@ -248,6 +249,8 @@
 
 		wake_up(&journal->j_wait_done_commit);
 		interruptible_sleep_on(&journal->j_wait_commit);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 
 		jbd_debug(1, "kjournald wakes\n");
 
diff -ruN 20-old/fs/jffs/intrep.c 20-new/fs/jffs/intrep.c
--- 20-old/fs/jffs/intrep.c	2003-06-21 08:16:49.000000000 +1000
+++ 20-new/fs/jffs/intrep.c	2004-11-24 15:24:21.000000000 +1100
@@ -3355,6 +3355,7 @@
 	recalc_sigpending(current);
 	spin_unlock_irq(&current->sigmask_lock);
 	strcpy(current->comm, "jffs_gcd");
+	current->flags |= PF_SYNCTHREAD;
 
 	D1(printk (KERN_NOTICE "jffs_garbage_collect_thread(): Starting infinite loop.\n"));
 
@@ -3381,6 +3382,11 @@
 			siginfo_t info;
 			unsigned long signr;
 
+			if (current->flags & PF_FREEZE) {
+				refrigerator(PF_FREEZE);
+				continue;
+			}
+
 			spin_lock_irq(&current->sigmask_lock);
 			signr = dequeue_signal(&current->blocked, &info);
 			spin_unlock_irq(&current->sigmask_lock);
diff -ruN 20-old/fs/jffs2/background.c 20-new/fs/jffs2/background.c
--- 20-old/fs/jffs2/background.c	2001-12-25 18:47:52.000000000 +1100
+++ 20-new/fs/jffs2/background.c	2004-11-24 15:24:21.000000000 +1100
@@ -105,6 +105,7 @@
 	up(&c->gc_thread_start);
 
         sprintf(current->comm, "jffs2_gcd_mtd%d", c->mtd->index);
+	current->flags |= PF_SYNCTHREAD;
 
 	/* FIXME in the 2.2 backport */
 	current->nice = 10;
@@ -134,6 +135,11 @@
                         siginfo_t info;
                         unsigned long signr;
 
+			if (current->flags & PF_FREEZE) {
+				refrigerator(PF_FREEZE);
+				continue;
+			}
+
                         spin_lock_irq(&current->sigmask_lock);
                         signr = dequeue_signal(&current->blocked, &info);
                         spin_unlock_irq(&current->sigmask_lock);
diff -ruN 20-old/fs/jfs/jfs_logmgr.c 20-new/fs/jfs/jfs_logmgr.c
--- 20-old/fs/jfs/jfs_logmgr.c	2004-08-20 12:39:59.000000000 +1000
+++ 20-new/fs/jfs/jfs_logmgr.c	2004-11-24 15:24:21.000000000 +1100
@@ -2187,6 +2187,7 @@
 	daemonize();
 	current->tty = NULL;
 	strcpy(current->comm, "jfsIO");
+	current->flags |= PF_SYNCTHREAD;
 
 	unlock_kernel();
 
@@ -2212,6 +2213,8 @@
 		set_current_state(TASK_INTERRUPTIBLE);
 		spin_unlock_irq(&log_redrive_lock);
 		schedule();
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		current->state = TASK_RUNNING;
 		remove_wait_queue(&jfs_IO_thread_wait, &wq);
 	} while (!jfs_stop_threads);
diff -ruN 20-old/fs/jfs/jfs_txnmgr.c 20-new/fs/jfs/jfs_txnmgr.c
--- 20-old/fs/jfs/jfs_txnmgr.c	2004-08-20 12:39:59.000000000 +1000
+++ 20-new/fs/jfs/jfs_txnmgr.c	2004-11-24 15:24:21.000000000 +1100
@@ -2697,6 +2697,7 @@
 	daemonize();
 	current->tty = NULL;
 	strcpy(current->comm, "jfsCommit");
+	current->flags |= PF_SYNCTHREAD;
 
 	unlock_kernel();
 
@@ -2743,6 +2744,8 @@
 			 * are adding transactions to this list
 			 */
 			cond_resched();
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 			LAZY_LOCK(flags);
 		}
 
@@ -2753,6 +2756,8 @@
 		set_current_state(TASK_INTERRUPTIBLE);
 		LAZY_UNLOCK(flags);
 		schedule();
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		current->state = TASK_RUNNING;
 		remove_wait_queue(&jfs_commit_thread_wait, &wq);
 	} while (!jfs_stop_threads);
@@ -2898,6 +2903,7 @@
 	daemonize();
 	current->tty = NULL;
 	strcpy(current->comm, "jfsSync");
+	current->flags |= PF_SYNCTHREAD;
 
 	unlock_kernel();
 
@@ -2942,6 +2948,8 @@
 				 * long we can run without blocking
 				 */
 				cond_resched();
+				if (current->flags & PF_FREEZE)
+					refrigerator(PF_FREEZE);
 				TXN_LOCK();
 			} else {
 				/* We can't get the commit semaphore.  It may
@@ -2971,6 +2979,8 @@
 		set_current_state(TASK_INTERRUPTIBLE);
 		TXN_UNLOCK();
 		schedule();
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		current->state = TASK_RUNNING;
 		remove_wait_queue(&jfs_sync_thread_wait, &wq);
 	} while (!jfs_stop_threads);
diff -ruN 20-old/fs/lockd/clntlock.c 20-new/fs/lockd/clntlock.c
--- 20-old/fs/lockd/clntlock.c	2004-03-05 14:35:13.000000000 +1100
+++ 20-new/fs/lockd/clntlock.c	2004-11-24 15:24:21.000000000 +1100
@@ -208,6 +208,8 @@
 		 "%s-reclaim",
 		 host->h_name);
 
+	current->flags |= PF_SYNCTHREAD;
+
 	/* This one ensures that our parent doesn't terminate while the
 	 * reclaim is in progress */
 	lock_kernel();
@@ -228,6 +230,8 @@
 
 		fl->fl_u.nfs_fl.flags &= ~NFS_LCK_RECLAIM;
 		nlmclnt_reclaim(host, fl);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if (signalled())
 			break;
 		goto restart;
diff -ruN 20-old/fs/lockd/clntproc.c 20-new/fs/lockd/clntproc.c
--- 20-old/fs/lockd/clntproc.c	2003-08-26 07:00:17.000000000 +1000
+++ 20-new/fs/lockd/clntproc.c	2004-11-24 15:24:21.000000000 +1100
@@ -258,6 +258,8 @@
 	do {
 		if (host->h_reclaiming && !argp->reclaim) {
 			interruptible_sleep_on(&host->h_gracewait);
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 			continue;
 		}
 
@@ -299,6 +301,8 @@
 
 		/* Back off a little and try again */
 		interruptible_sleep_on_timeout(&host->h_gracewait, 15*HZ);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 
 		/* When the lock requested by F_SETLKW isn't available,
 		   we will wait until the request can be satisfied. If
diff -ruN 20-old/fs/lockd/svc.c 20-new/fs/lockd/svc.c
--- 20-old/fs/lockd/svc.c	2003-06-21 08:16:49.000000000 +1000
+++ 20-new/fs/lockd/svc.c	2004-11-24 15:24:21.000000000 +1100
@@ -95,6 +95,7 @@
 	daemonize();
 	reparent_to_init();
 	sprintf(current->comm, "lockd");
+	current->flags |= PF_SYNCTHREAD;
 
 	/* Process request with signals blocked.  */
 	spin_lock_irq(&current->sigmask_lock);
@@ -121,6 +122,10 @@
 	while ((nlmsvc_users || !signalled()) && nlmsvc_pid == current->pid)
 	{
 		long timeout = MAX_SCHEDULE_TIMEOUT;
+		
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+
 		if (signalled()) {
 			spin_lock_irq(&current->sigmask_lock);
 			flush_signals(current);
diff -ruN 20-old/fs/Makefile 20-new/fs/Makefile
--- 20-old/fs/Makefile	2004-03-05 14:35:13.000000000 +1100
+++ 20-new/fs/Makefile	2004-11-24 15:24:21.000000000 +1100
@@ -7,7 +7,8 @@
 
 O_TARGET := fs.o
 
-export-objs :=	filesystems.o open.o dcache.o buffer.o dquot.o
+export-objs :=	filesystems.o open.o dcache.o buffer.o dquot.o namei.o \
+		namespace.o ioctl.o read_write.o
 mod-subdirs :=	nls
 
 obj-y :=	open.o read_write.o devices.o file_table.o buffer.o \
diff -ruN 20-old/fs/namei.c 20-new/fs/namei.c
--- 20-old/fs/namei.c	2004-11-24 15:05:24.000000000 +1100
+++ 20-new/fs/namei.c	2004-11-24 15:24:21.000000000 +1100
@@ -22,6 +22,7 @@
 #include <linux/dnotify.h>
 #include <linux/smp_lock.h>
 #include <linux/personality.h>
+#include <linux/module.h>
 
 #include <asm/namei.h>
 #include <asm/uaccess.h>
@@ -1360,6 +1361,7 @@
 
 	return error;
 }
+EXPORT_SYMBOL(sys_mkdir);
 
 /*
  * We try to drop the dentry early: we should have
@@ -1467,6 +1469,7 @@
 	putname(name);
 	return error;
 }
+EXPORT_SYMBOL(sys_rmdir);
 
 int vfs_unlink(struct inode *dir, struct dentry *dentry)
 {
diff -ruN 20-old/fs/namespace.c 20-new/fs/namespace.c
--- 20-old/fs/namespace.c	2004-03-05 14:35:13.000000000 +1100
+++ 20-new/fs/namespace.c	2004-11-24 15:24:21.000000000 +1100
@@ -385,6 +385,7 @@
 out:
 	return retval;
 }
+EXPORT_SYMBOL(sys_umount);
 
 /*
  *	The 2.0 compatible umount. No flags. 
@@ -866,6 +867,7 @@
 	free_page(type_page);
 	return retval;
 }
+EXPORT_SYMBOL(sys_mount);
 
 static void chroot_fs_refs(struct nameidata *old_nd, struct nameidata *new_nd)
 {
diff -ruN 20-old/fs/nfsd/nfssvc.c 20-new/fs/nfsd/nfssvc.c
--- 20-old/fs/nfsd/nfssvc.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/fs/nfsd/nfssvc.c	2004-11-24 15:24:21.000000000 +1100
@@ -164,6 +164,7 @@
 	daemonize();
 	sprintf(current->comm, "nfsd");
 	current->rlim[RLIMIT_FSIZE].rlim_cur = RLIM_INFINITY;
+	current->flags |= PF_SYNCTHREAD;
 
 	nfsdstats.th_cnt++;
 	/* Let svc_process check client's authentication. */
diff -ruN 20-old/fs/open.c 20-new/fs/open.c
--- 20-old/fs/open.c	2004-03-05 14:35:13.000000000 +1100
+++ 20-new/fs/open.c	2004-11-24 15:24:21.000000000 +1100
@@ -824,6 +824,7 @@
 	fd = error;
 	goto out;
 }
+EXPORT_SYMBOL(sys_open);
 
 #ifndef __alpha__
 
diff -ruN 20-old/fs/proc/generic.c 20-new/fs/proc/generic.c
--- 20-old/fs/proc/generic.c	2004-08-20 12:39:59.000000000 +1000
+++ 20-new/fs/proc/generic.c	2004-11-24 15:24:21.000000000 +1100
@@ -590,3 +590,5 @@
 out:
 	return;
 }
+
+EXPORT_SYMBOL(proc_match);
diff -ruN 20-old/fs/proc/Makefile 20-new/fs/proc/Makefile
--- 20-old/fs/proc/Makefile	2001-06-01 23:43:49.000000000 +1000
+++ 20-new/fs/proc/Makefile	2004-11-24 15:24:21.000000000 +1100
@@ -9,7 +9,7 @@
 
 O_TARGET := proc.o
 
-export-objs := root.o
+export-objs := root.o generic.o
 
 obj-y    := inode.o root.o base.o generic.o array.o \
 		kmsg.o proc_tty.o proc_misc.o kcore.o
diff -ruN 20-old/fs/read_write.c 20-new/fs/read_write.c
--- 20-old/fs/read_write.c	2003-08-26 07:00:17.000000000 +1000
+++ 20-new/fs/read_write.c	2004-11-24 15:24:21.000000000 +1100
@@ -26,6 +26,7 @@
 #include <linux/uio.h>
 #include <linux/smp_lock.h>
 #include <linux/dnotify.h>
+#include <linux/module.h>
 
 #include <asm/uaccess.h>
 
@@ -209,6 +210,7 @@
 	}
 	return ret;
 }
+EXPORT_SYMBOL(sys_write);
 
 
 static ssize_t do_readv_writev(int type, struct file *file,
diff -ruN 20-old/fs/reiserfs/journal.c 20-new/fs/reiserfs/journal.c
--- 20-old/fs/reiserfs/journal.c	2004-08-20 12:40:00.000000000 +1000
+++ 20-new/fs/reiserfs/journal.c	2004-11-24 15:24:21.000000000 +1100
@@ -1897,6 +1897,7 @@
   spin_unlock_irq(&current->sigmask_lock);
 
   sprintf(current->comm, "kreiserfsd") ;
+  current->flags |= PF_SYNCTHREAD;
   lock_kernel() ;
   while(1) {
 
@@ -1911,6 +1912,8 @@
     }
     wake_up(&reiserfs_commit_thread_done) ;
     interruptible_sleep_on_timeout(&reiserfs_commit_thread_wait, 5 * HZ) ;
+    if (current->flags & PF_FREEZE)
+	    refrigerator(PF_FREEZE);
   }
   unlock_kernel() ;
   wake_up(&reiserfs_commit_thread_done) ;
diff -ruN 20-old/fs/xfs/linux-2.4/xfs_buf.c 20-new/fs/xfs/linux-2.4/xfs_buf.c
--- 20-old/fs/xfs/linux-2.4/xfs_buf.c	2004-11-24 15:05:24.000000000 +1100
+++ 20-new/fs/xfs/linux-2.4/xfs_buf.c	2004-11-24 15:24:21.000000000 +1100
@@ -51,6 +51,7 @@
 #include <linux/locks.h>
 #include <linux/sysctl.h>
 #include <linux/proc_fs.h>
+#include <linux/suspend.h>
 
 #include "xfs_linux.h"
 
@@ -1980,6 +1981,7 @@
 
 	/*  Set up the thread  */
 	daemonize();
+	current->flags |= PF_SYNCTHREAD;
 
 	/* Avoid signals */
 	sigmask_lock();
@@ -2011,6 +2013,8 @@
 		if (TQ_ACTIVE(pagebuf_iodone_tq[cpu]))
 			__set_task_state(current, TASK_RUNNING);
 		schedule();
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		remove_wait_queue(&pagebuf_iodone_wait[cpu], &wait);
 		run_task_queue(&pagebuf_iodone_tq[cpu]);
 		if (pagebuf_daemons[cpu] == 0)
@@ -2095,12 +2099,14 @@
 	sigmask_unlock();
 
 	strcpy(current->comm, "xfsbufd");
-	current->flags |= PF_MEMALLOC;
+	current->flags |= PF_MEMALLOC | PF_SYNCTHREAD;
 
 	INIT_LIST_HEAD(&tmp);
 	do {
 		set_current_state(TASK_INTERRUPTIBLE);
 		schedule_timeout((xfs_buf_timer_centisecs * HZ) / 100);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 
 		count = 0;
 		age = (xfs_buf_age_centisecs * HZ) / 100;
diff -ruN 20-old/fs/xfs/linux-2.4/xfs_super.c 20-new/fs/xfs/linux-2.4/xfs_super.c
--- 20-old/fs/xfs/linux-2.4/xfs_super.c	2004-11-24 15:05:24.000000000 +1100
+++ 20-new/fs/xfs/linux-2.4/xfs_super.c	2004-11-24 15:24:21.000000000 +1100
@@ -68,6 +68,7 @@
 #include "xfs_version.h"
 
 #include <linux/init.h>
+#include <linux/suspend.h>
 
 STATIC struct quotactl_ops linvfs_qops;
 STATIC struct super_operations linvfs_sops;
@@ -444,6 +445,7 @@
 	sigmask_unlock();
 
 	sprintf(current->comm, "xfssyncd");
+	current->flags |= PF_SYNCTHREAD;
 
 	vfsp->vfs_sync_task = current;
 	wmb();
@@ -452,6 +454,8 @@
 	for (;;) {
 		set_current_state(TASK_INTERRUPTIBLE);
 		timeleft = schedule_timeout(timeleft);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if (vfsp->vfs_flag & VFS_UMOUNT)
 			break;
 
diff -ruN 20-old/include/asm-generic/bitops.h 20-new/include/asm-generic/bitops.h
--- 20-old/include/asm-generic/bitops.h	2000-11-28 12:47:38.000000000 +1100
+++ 20-new/include/asm-generic/bitops.h	2004-11-24 15:24:21.000000000 +1100
@@ -51,6 +51,12 @@
 	return ((mask & *addr) != 0);
 }
 
+/*
+ * fls: find last bit set.
+ */
+
+#define fls(x) generic_fls(x)
+
 #ifdef __KERNEL__
 
 /*
diff -ruN 20-old/include/asm-i386/bitops.h 20-new/include/asm-i386/bitops.h
--- 20-old/include/asm-i386/bitops.h	2004-08-20 12:40:00.000000000 +1000
+++ 20-new/include/asm-i386/bitops.h	2004-11-24 15:35:52.000000000 +1100
@@ -329,6 +329,12 @@
 	return word;
 }
 
+/*
+ * fls: find last bit set.
+ */
+
+#define fls(x) generic_fls(x)
+
 #ifdef __KERNEL__
 
 /**
diff -ruN 20-old/include/asm-i386/cpufeature.h 20-new/include/asm-i386/cpufeature.h
--- 20-old/include/asm-i386/cpufeature.h	2003-12-17 08:48:31.000000000 +1100
+++ 20-new/include/asm-i386/cpufeature.h	2004-11-24 15:24:21.000000000 +1100
@@ -84,6 +84,7 @@
 #define cpu_has_vme		boot_cpu_has(X86_FEATURE_VME)
 #define cpu_has_de		boot_cpu_has(X86_FEATURE_DE)
 #define cpu_has_pse		boot_cpu_has(X86_FEATURE_PSE)
+#define cpu_has_pse36		boot_cpu_has(X86_FEATURE_PSE36)
 #define cpu_has_tsc		boot_cpu_has(X86_FEATURE_TSC)
 #define cpu_has_pae		boot_cpu_has(X86_FEATURE_PAE)
 #define cpu_has_pge		boot_cpu_has(X86_FEATURE_PGE)
diff -ruN 20-old/include/linux/bitops.h 20-new/include/linux/bitops.h
--- 20-old/include/linux/bitops.h	2001-02-22 11:09:57.000000000 +1100
+++ 20-new/include/linux/bitops.h	2004-11-24 15:35:52.000000000 +1100
@@ -1,6 +1,6 @@
 #ifndef _LINUX_BITOPS_H
 #define _LINUX_BITOPS_H
-
+#include <asm/bitops.h>
 
 /*
  * ffs: find first bit set. This is defined the same way as
@@ -38,6 +38,47 @@
 }
 
 /*
+ * fls: find last bit set.
+ */
+
+extern __inline__ int generic_fls(unsigned x)
+{
+	int r = 32;
+
+	if (!x)
+		return 0;
+	if (!(x & 0xffff0000)) {
+		x <<= 16;
+		r -= 16;
+	}
+	if (!(x & 0xff000000)) {
+		x <<= 8;
+		r -= 8;
+	}
+	if (!(x & 0xf0000000)) {
+		x <<= 4;
+		r -= 4;
+	}
+	if (!(x & 0xc0000000)) {
+		x <<= 2;
+		r -= 2;
+	}
+	if (!(x & 0x80000000)) {
+		x <<= 1;
+		r -= 1;
+	}
+	return r;
+}
+
+extern __inline__ int get_bitmask_order(unsigned int count)
+{
+	int order;
+	
+	order = fls(count);
+	return order;	/* We could be slightly more clever with -1 here... */
+}
+
+/*
  * hweightN: returns the hamming weight (i.e. the number
  * of bits set) of a N-bit word
  */
@@ -66,7 +107,5 @@
         return (res & 0x0F) + ((res >> 4) & 0x0F);
 }
 
-#include <asm/bitops.h>
-
 
 #endif
diff -ruN 20-old/include/linux/init.h 20-new/include/linux/init.h
--- 20-old/include/linux/init.h	2004-04-16 23:46:20.000000000 +1000
+++ 20-new/include/linux/init.h	2004-11-24 15:35:52.000000000 +1100
@@ -112,6 +112,9 @@
  */
 #define module_exit(x)	__exitcall(x);
 
+/* Data marked not to be saved by software_suspend() */
+#define __nosavedata __attribute__ ((__section__ (".data.nosave")))
+
 #else	/* MODULE */
 
 #define __init
@@ -124,6 +127,9 @@
 #define __FINIT
 #define __INITDATA
 
+/* Data marked not to be saved by software_suspend() */
+#define __nosavedata __attribute__ ((__section__ (".data.nosave")))
+
 /* These macros create a dummy inline: gcc 2.9x does not count alias
  as usage, hence the `unused function' warning when __init functions
  are declared static. We use the dummy __*_module_inline functions
diff -ruN 20-old/include/linux/mm.h 20-new/include/linux/mm.h
--- 20-old/include/linux/mm.h	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/include/linux/mm.h	2004-11-24 15:35:52.000000000 +1100
@@ -300,6 +300,7 @@
 #define PG_reserved		14
 #define PG_launder		15	/* written out by VM pressure.. */
 #define PG_fs_1			16	/* Filesystem specific */
+#define PG_nosave		17
 
 #ifndef arch_set_page_uptodate
 #define arch_set_page_uptodate(page)
@@ -417,6 +418,10 @@
 #define SetPageReserved(page)		set_bit(PG_reserved, &(page)->flags)
 #define ClearPageReserved(page)		clear_bit(PG_reserved, &(page)->flags)
 
+#define PageNosave(page)	test_bit(PG_nosave, &(page)->flags)
+#define SetPageNosave(page)	set_bit(PG_nosave, &(page)->flags)
+#define ClearPageNosave(page)	clear_bit(PG_nosave, &(page)->flags)
+
 /*
  * Error return values for the *_nopage functions
  */
diff -ruN 20-old/include/linux/raid/md.h 20-new/include/linux/raid/md.h
--- 20-old/include/linux/raid/md.h	2003-06-21 08:16:50.000000000 +1000
+++ 20-new/include/linux/raid/md.h	2004-11-24 15:41:02.000000000 +1100
@@ -86,6 +86,8 @@
 
 extern void md_print_devices (void);
 
+extern void md_autostart_arrays(void);
+
 #define MD_BUG(x...) { printk("md: bug in file %s, line %d\n", __FILE__, __LINE__); md_print_devices(); }
 
 #endif 
diff -ruN 20-old/include/linux/reboot.h 20-new/include/linux/reboot.h
--- 20-old/include/linux/reboot.h	2001-02-10 09:46:13.000000000 +1100
+++ 20-new/include/linux/reboot.h	2004-11-24 15:24:21.000000000 +1100
@@ -20,6 +20,7 @@
  * CAD_OFF     Ctrl-Alt-Del sequence sends SIGINT to init task.
  * POWER_OFF   Stop OS and remove all power from system, if possible.
  * RESTART2    Restart system using given command string.
+ * SW_SUSPEND  Suspend system using Software Suspend if compiled in
  */
 
 #define	LINUX_REBOOT_CMD_RESTART	0x01234567
@@ -28,6 +29,7 @@
 #define	LINUX_REBOOT_CMD_CAD_OFF	0x00000000
 #define	LINUX_REBOOT_CMD_POWER_OFF	0x4321FEDC
 #define	LINUX_REBOOT_CMD_RESTART2	0xA1B2C3D4
+#define	LINUX_REBOOT_CMD_SW_SUSPEND	0xD000FCE2
 
 
 #ifdef __KERNEL__
diff -ruN 20-old/include/linux/sched.h 20-new/include/linux/sched.h
--- 20-old/include/linux/sched.h	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/include/linux/sched.h	2004-11-24 15:35:52.000000000 +1100
@@ -459,8 +459,14 @@
 #define PF_FREE_PAGES	0x00002000	/* per process page freeing */
 #define PF_NOIO		0x00004000	/* avoid generating further I/O */
 #define PF_FSTRANS	0x00008000	/* inside a filesystem transaction */
+#define PF_FROZEN	0x00010000	/* frozen for system suspend */
+#define PF_FREEZE	0x00020000	/* trying to freeze this task */
+#define PF_SYNCTHREAD	0x00040000	/* this thread can start activity during the 
+					   early part of freezing processes */
+#define PF_FRIDGE_WAIT	0x00080000	/* this thread is currently doing I/O */
 
 #define PF_USEDFPU	0x00100000	/* task used FPU this quantum (SMP) */
+#define PF_NOFREEZE	0x00200000	/* this thread should never be frozen */
 
 /*
  * Ptrace flags
@@ -990,5 +996,11 @@
 #include <linux/tqueue.h>
 #include <linux/fs_struct.h>
 
+#ifdef CONFIG_PM
+extern void refrigerator(unsigned long);
+#else
+#define refrigerator(a)			do { BUG(); } while(0)
+#endif
+
 #endif /* __KERNEL__ */
 #endif
diff -ruN 20-old/include/linux/suspend.h 20-new/include/linux/suspend.h
--- 20-old/include/linux/suspend.h	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/include/linux/suspend.h	2004-11-24 15:35:52.000000000 +1100
@@ -0,0 +1,266 @@
+#ifndef _LINUX_SWSUSP_H
+#define _LINUX_SWSUSP_H
+
+#ifdef CONFIG_X86
+//#include <asm/suspend.h>
+#endif
+
+#include <linux/kernel.h>
+extern char __nosave_begin, __nosave_end;
+
+#ifdef CONFIG_PM
+
+#include <linux/init.h>
+
+#define SUSPEND_CORE_VERSION "2.1.5.7"
+#ifndef KERNEL_POWER_SWSUSP_C
+#define name_suspend "Software Suspend " SUSPEND_CORE_VERSION ": "
+#endif
+
+extern unsigned long suspend_action;
+extern unsigned long suspend_result;
+extern unsigned long suspend_debug_state;
+
+#define TEST_RESULT_STATE(bit) (test_bit(bit, &suspend_result))
+#define SET_RESULT_STATE(bit) (test_and_set_bit(bit, &suspend_result))
+#define CLEAR_RESULT_STATE(bit) (test_and_clear_bit(bit, &suspend_result))
+
+#define TEST_ACTION_STATE(bit) (test_bit(bit, &suspend_action))
+#define SET_ACTION_STATE(bit) (test_and_set_bit(bit, &suspend_action))
+#define CLEAR_ACTION_STATE(bit) (test_and_clear_bit(bit, &suspend_action))
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+#define TEST_DEBUG_STATE(bit) (test_bit(bit, &suspend_debug_state))
+#define SET_DEBUG_STATE(bit) (test_and_set_bit(bit, &suspend_debug_state))
+#define CLEAR_DEBUG_STATE(bit) (test_and_clear_bit(bit, &suspend_debug_state))
+#else
+#define TEST_DEBUG_STATE(bit) (0)
+#define SET_DEBUG_STATE(bit) (0)
+#define CLEAR_DEBUG_STATE(bit) (0)
+#endif
+
+/* first status register - this is suspend's return code. */
+#define SUSPEND_ABORTED			0
+#define SUSPEND_ABORT_REQUESTED		1
+#define SUSPEND_NOSTORAGE_AVAILABLE	2
+#define SUSPEND_INSUFFICIENT_STORAGE	3
+#define SUSPEND_FREEZING_FAILED		4
+#define SUSPEND_UNEXPECTED_ALLOC	5
+#define SUSPEND_KEPT_IMAGE		6
+#define SUSPEND_WOULD_EAT_MEMORY	7
+#define SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY 8
+
+/* second status register */
+#define SUSPEND_REBOOT		0
+#define SUSPEND_PAUSE		2
+#define SUSPEND_SLOW		3
+#define SUSPEND_NOPAGESET2	7
+#define SUSPEND_LOGALL		8
+/* Set to disable compression when compiled in */
+#define SUSPEND_NO_COMPRESSION	9
+//#define SUSPEND_ENABLE_KDB	10
+#define SUSPEND_CAN_CANCEL	11
+#define SUSPEND_KEEP_IMAGE	13
+#define SUSPEND_FREEZER_TEST	14
+#define SUSPEND_FREEZER_TEST_SHOWALL 15
+#define SUSPEND_SINGLESTEP	16
+#define SUSPEND_PAUSE_NEAR_PAGESET_END 17
+#define SUSPEND_USE_ACPI_S4	18
+#define SUSPEND_KEEP_METADATA	19
+#define SUSPEND_TEST_FILTER_SPEED	20
+#define SUSPEND_FREEZE_TIMERS	21
+#define SUSPEND_DISABLE_SYSDEV_SUPPORT 22
+
+/* debug sections  - if debugging compiled in */
+#define SUSPEND_ANY_SECTION	0
+#define SUSPEND_FREEZER		1
+#define SUSPEND_EAT_MEMORY 	2
+#define SUSPEND_PAGESETS	3
+#define SUSPEND_IO		4
+#define SUSPEND_BMAP		5
+#define SUSPEND_HEADER		6
+#define SUSPEND_WRITER		9
+#define SUSPEND_MEMORY		10
+#define SUSPEND_RANGES		11
+#define SUSPEND_SPINLOCKS	12
+#define SUSPEND_MEM_POOL	13
+#define SUSPEND_RANGE_PARANOIA	14
+#define SUSPEND_NOSAVE		15
+#define SUSPEND_INTEGRITY	16
+/* debugging levels. */
+#define SUSPEND_STATUS		0
+#define SUSPEND_ERROR		2
+#define SUSPEND_LOW	 	3
+#define SUSPEND_MEDIUM	 	4
+#define SUSPEND_HIGH	  	5
+#define SUSPEND_VERBOSE		6
+
+extern void __suspend_message(unsigned long section, unsigned long level, int log_normally,
+		const char *fmt, ...);
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+extern int suspend_memory_pool_level(int only_lowmem);
+#define suspend_message(sn, lev, log, fmt, a...) \
+do { \
+	if (TEST_DEBUG_STATE(sn)) \
+		suspend2_core_ops->suspend_message(sn, lev, log, fmt, ##a); \
+} while(0)
+#define PRINTFREEMEM(desn) \
+	suspend_message(SUSPEND_MEMORY, SUSPEND_MEDIUM, 1, \
+		"Free memory %s: %d+%d.\n", desn, \
+		nr_free_pages() + suspend_amount_grabbed, \
+		suspend_memory_pool_level(0));
+#else /* CONFIG_SOFTWARE_SUSPEND_DEBUG */
+#define PRINTFREEMEM(desn) do { } while(0)
+#define suspend_message(sn, lev, log, fmt, a...) \
+do { \
+	if (lev == 0) \
+		suspend2_core_ops->suspend_message(sn, lev, log, fmt, ##a); \
+} while(0)
+#endif /* CONFIG_SOFTWARE_SUSPEND_DEBUG */
+  
+#ifdef CONFIG_SMP
+extern void disable_nonboot_cpus(void);
+extern void enable_nonboot_cpus(void);
+#else
+static inline void disable_nonboot_cpus(void) {}
+static inline void enable_nonboot_cpus(void) {}
+#endif
+
+extern int software_suspend(void);
+	
+/* Suspend 2 */
+
+#define SUSPEND_DISABLED		0
+#define SUSPEND_RUNNING			1
+#define SUSPEND_RESUME_DEVICE_OK	2
+#define SUSPEND_NORESUME_SPECIFIED	3
+#define SUSPEND_COMMANDLINE_ERROR 	4
+#define SUSPEND_IGNORE_IMAGE		5
+#define SUSPEND_SANITY_CHECK_PROMPT	6
+#define SUSPEND_FREEZER_ON		7
+#define SUSPEND_DISABLE_SYNCING		8
+#define SUSPEND_BLOCK_PAGE_ALLOCATIONS	9
+#define SUSPEND_USE_MEMORY_POOL		10
+#define SUSPEND_STAGE2_CONTINUE		11
+#define SUSPEND_FREEZE_SMP		12
+#define SUSPEND_PAGESET2_NOT_LOADED	13
+#define SUSPEND_CONTINUE_REQ		14
+#define SUSPEND_RESUMED_BEFORE		15
+#define SUSPEND_RUNNING_INITRD		16
+#define SUSPEND_RESUME_NOT_DONE		17
+#define SUSPEND_BOOT_TIME		18
+#define SUSPEND_NOW_RESUMING		19
+#define SUSPEND_SLAB_ALLOC_FALLBACK	20
+#define SUSPEND_IGNORE_LOGLEVEL		21
+#define SUSPEND_TIMER_FREEZER_ON	22
+
+extern unsigned long software_suspend_state;
+#define test_suspend_state(bit) \
+	(test_bit(bit, &software_suspend_state))
+
+#define clear_suspend_state(bit) \
+	(clear_bit(bit, &software_suspend_state))
+
+#define set_suspend_state(bit) \
+	(set_bit(bit, &software_suspend_state))
+
+#define get_suspend_state() 		(software_suspend_state)
+#define restore_suspend_state(saved_state) \
+	do { software_suspend_state = saved_state; } while(0)
+	
+/* kernel/suspend.c */
+extern void suspend_try_suspend(void);
+extern unsigned int suspend_task;
+
+/* Kernel threads are type 3 */
+#define FREEZER_ALL_THREADS 0
+#define FREEZER_KERNEL_THREADS 3
+
+extern int freeze_processes(int no_progress);
+extern void thaw_processes(int which_threads);
+
+extern int pm_prepare_console(void);
+extern void pm_restore_console(void);
+
+#define SUSPEND_KEY_KEYBOARD 1
+#define SUSPEND_KEY_SERIAL 2
+
+struct page;
+
+struct suspend2_core_ops {
+	/* Entry points for suspending & resuming */
+	void (* do_suspend) (void);
+	int (* do_resume) (void);
+
+	/* Pre and post lowlevel routines */
+	void (* suspend1) (void);
+	void (* suspend2) (void);
+	void (* resume1) (void);
+	void (* resume2) (void);
+	
+	void (* free_pool_pages) (struct page *page, unsigned int order);
+	struct page * (* get_pool_pages) (unsigned int gfp_mask, unsigned int order);
+
+	unsigned long (* get_grabbed_pages) (int order);
+	void (* cleanup_finished_io) (void);
+
+	void (* suspend_message) (unsigned long, unsigned long, int, const char *, ...);
+	unsigned long (* update_status) (unsigned long value, unsigned long maximum,
+		const char *fmt, ...);
+	void (*prepare_status) (int printalways, int clearbar, const char *fmt, ...);
+	void (* schedule_message) (int message_number);
+	void (* early_boot_plugins) (void);
+	int (* keypress) (unsigned int keycode);
+
+	void (* verify_checksums) (void);
+};
+extern volatile struct suspend2_core_ops * suspend2_core_ops;
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+extern void software_suspend_try_resume(void);
+extern void suspend_handle_keypress(unsigned int keycode, int source);
+#else
+#define software_suspend_try_resume()	do { } while(0)
+#define suspend_handle_keypress(a, b) do { } while(0)
+#endif
+
+#define suspend2_free_pool_pages(page, order)	suspend2_core_ops->free_pool_pages(page, order)
+#define suspend2_get_pool_pages(mask, order)	suspend2_core_ops->get_pool_pages(mask, order)
+#define suspend2_get_grabbed_pages(order)	suspend2_core_ops->get_grabbed_pages(order)
+#define suspend2_cleanup_finished_io()		suspend2_core_ops->cleanup_finished_io()
+#define suspend2_verify_checksums()		suspend2_core_ops->verify_checksums()
+
+#else /* CONFIG_PM off */
+
+#define suspend_try_suspend()		do { } while(0)
+#define suspend_task			(0)
+#define software_suspend_state		(0)
+#define test_suspend_state(bit) 	(0)
+#define clear_suspend_state(bit)	do { } while (0)
+#define set_suspend_state(bit)		do { } while(0)
+#define get_suspend_state() 		(0)
+#define restore_suspend_state(saved_state) do { } while(0)
+#define software_suspend_try_resume()	do { } while(0)
+
+static inline int suspend_bug(void)
+{
+	BUG();
+	return 0;
+}
+
+#define suspend2_free_pool_pages(page, order) suspend_bug()
+#define suspend2_get_pool_pages(mask, order) (struct page *) suspend_bug()
+#define suspend2_get_grabbed_pages(order) (struct page *) suspend_bug()
+#define suspend2_cleanup_finished_io()	do { BUG(); } while(0)
+#define suspend2_verify_checksums() do { BUG(); } while(0)
+
+static inline int software_suspend(void)
+{
+	printk("Warning: fake suspend called\n");
+	return -EPERM;
+}
+#define software_resume()		do { } while(0)
+#define suspend_handle_keypress(a, b) do { } while(0)
+#endif
+
+#endif /* _LINUX_SWSUSP_H */
diff -ruN 20-old/include/linux/sysctl.h 20-new/include/linux/sysctl.h
--- 20-old/include/linux/sysctl.h	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/include/linux/sysctl.h	2004-11-24 15:36:01.000000000 +1100
@@ -129,6 +129,7 @@
 	KERN_PPC_L3CR=57,       /* l3cr register on PPC */
 	KERN_EXCEPTION_TRACE=58, /* boolean: exception trace */
  	KERN_CORE_SETUID=59,	/* int: set to allow core dumps of setuid apps */
+	KERN_SWSUSP=60,		/* struct: interface to activate software suspension */
 	KERN_SPARC_SCONS_PWROFF=64, /* int: serial console power-off halt */
 };
 
diff -ruN 20-old/include/linux/tty_driver.h 20-new/include/linux/tty_driver.h
--- 20-old/include/linux/tty_driver.h	2004-01-03 08:23:55.000000000 +1100
+++ 20-new/include/linux/tty_driver.h	2004-11-24 15:35:52.000000000 +1100
@@ -170,7 +170,10 @@
 			  int count, int *eof, void *data);
 	int (*write_proc)(struct file *file, const char *buffer,
 			  unsigned long count, void *data);
-
+#ifdef CONFIG_PM
+	int (*suspend) (struct tty_struct *tty);
+	int (*resume) (struct tty_struct *tty);
+#endif
 	/*
 	 * linked list pointers
 	 */
diff -ruN 20-old/include/net/sctp/compat.h 20-new/include/net/sctp/compat.h
--- 20-old/include/net/sctp/compat.h	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/include/net/sctp/compat.h	2004-11-24 15:39:07.000000000 +1100
@@ -95,6 +95,7 @@
 #define sk_wmem_queued wmem_queued
 #define sk_bound_dev_if bound_dev_if
 
+#ifndef fls
 /*
  * find last bit set.
  */
@@ -126,5 +127,6 @@
 	}
 	return r;
 }
+#endif
 
 #endif /* __net_sctp_compat_h__ */
diff -ruN 20-old/init/do_mounts.c 20-new/init/do_mounts.c
--- 20-old/init/do_mounts.c	2003-12-17 08:48:34.000000000 +1100
+++ 20-new/init/do_mounts.c	2004-11-24 15:24:21.000000000 +1100
@@ -8,6 +8,7 @@
 #include <linux/fd.h>
 #include <linux/tty.h>
 #include <linux/init.h>
+#include <linux/suspend.h>
 
 #include <linux/nfs_fs.h>
 #include <linux/nfs_fs_sb.h>
@@ -33,6 +34,8 @@
 extern asmlinkage long sys_umount(char *name, int flags);
 extern asmlinkage long sys_ioctl(int fd, int cmd, unsigned long arg);
 
+unsigned char resume_initrd = 0; /* Run initrd before resuming from swsusp */
+
 #ifdef CONFIG_BLK_DEV_INITRD
 unsigned int real_root_dev;	/* do_proc_dointvec cannot handle kdev_t */
 static int __initdata mount_initrd = 1;
@@ -44,6 +47,16 @@
 }
 
 __setup("noinitrd", no_initrd);
+
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+
+static int __init set_resume_initrd(char *str)
+{
+	resume_initrd = 1;
+	return 1;
+}
+__setup("resume_initrd", set_resume_initrd);
+#endif
 #else
 static int __initdata mount_initrd = 0;
 #endif
@@ -87,7 +100,7 @@
 static struct dev_name_struct {
 	const char *name;
 	const int num;
-} root_dev_names[] __initdata = {
+} root_dev_names[] = {
 	{ "nfs",     MKDEV(NFS_MAJOR, NFS_MINOR) },
 	{ "hda",     0x0300 },
 	{ "hdb",     0x0340 },
@@ -258,7 +271,8 @@
 	{ NULL, 0 }
 };
 
-kdev_t __init name_to_kdev_t(char *line)
+/* Also used by Software Suspend */
+kdev_t name_to_kdev_t(char *line)
 {
 	int base = 0, offs;
 	char *end;
@@ -831,9 +845,14 @@
 
 	pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
 	if (pid > 0) {
-		while (pid != wait(&i))
+		while (pid != wait(&i)) {
 			yield();
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
+		}
 	}
+	if (test_suspend_state(SUSPEND_RESUME_NOT_DONE))
+		software_suspend_try_resume();
 
 	/* move initrd to rootfs' /old */
 	sys_fchdir(old_fd);
@@ -914,6 +933,10 @@
 		}
 	} else if (is_floppy && rd_doload && rd_load_disk(0))
 		ROOT_DEV = MKDEV(RAMDISK_MAJOR, 0);
+
+	if (test_suspend_state(SUSPEND_RESUME_NOT_DONE))
+		software_suspend_try_resume();
+	
 	mount_root();
 out:
 	sys_umount("/dev", 0);
diff -ruN 20-old/init/main.c 20-new/init/main.c
--- 20-old/init/main.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/init/main.c	2004-11-24 15:24:21.000000000 +1100
@@ -28,6 +28,7 @@
 #include <linux/bootmem.h>
 #include <linux/file.h>
 #include <linux/tty.h>
+#include <linux/suspend.h>
 
 #include <asm/io.h>
 #include <asm/bugs.h>
@@ -121,6 +122,10 @@
 extern void ipc_init(void);
 #endif
 
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+extern unsigned char resume_initrd;
+#endif
+
 /*
  * Boot command-line arguments
  */
@@ -592,11 +597,14 @@
 static int init(void * unused)
 {
 	struct files_struct *files;
+
 	lock_kernel();
 	do_basic_setup();
 
 	prepare_namespace();
 
+	clear_suspend_state(SUSPEND_BOOT_TIME);
+
 	/*
 	 * Ok, we have completed the initial bootup, and
 	 * we're essentially up and running. Get rid of the
diff -ruN 20-old/kernel/context.c 20-new/kernel/context.c
--- 20-old/kernel/context.c	2001-12-25 18:45:32.000000000 +1100
+++ 20-new/kernel/context.c	2004-11-24 15:24:21.000000000 +1100
@@ -102,6 +102,10 @@
 		run_task_queue(&tq_context);
 		wake_up(&context_task_done);
 		if (signal_pending(curtask)) {
+			if (current->flags & PF_FREEZE) {
+				refrigerator(PF_FREEZE);
+				continue;
+			}
 			while (waitpid(-1, (unsigned int *)0, __WALL|WNOHANG) > 0)
 				;
 			spin_lock_irq(&curtask->sigmask_lock);
diff -ruN 20-old/kernel/exit.c 20-new/kernel/exit.c
--- 20-old/kernel/exit.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/kernel/exit.c	2004-11-24 15:24:21.000000000 +1100
@@ -16,6 +16,7 @@
 #ifdef CONFIG_BSD_PROCESS_ACCT
 #include <linux/acct.h>
 #endif
+#include <linux/suspend.h>
 
 #include <asm/uaccess.h>
 #include <asm/pgtable.h>
@@ -437,6 +438,8 @@
 		panic("Attempted to kill the idle task!");
 	if (tsk->pid == 1)
 		panic("Attempted to kill init!");
+	if (test_suspend_state(SUSPEND_RUNNING))
+		refrigerator(PF_FREEZE);
 	tsk->flags |= PF_EXITING;
 	del_timer_sync(&tsk->real_timer);
 
diff -ruN 20-old/kernel/Makefile 20-new/kernel/Makefile
--- 20-old/kernel/Makefile	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/kernel/Makefile	2004-11-24 15:24:21.000000000 +1100
@@ -9,14 +9,19 @@
 
 O_TARGET := kernel.o
 
-export-objs = signal.o sys.o kmod.o context.o ksyms.o pm.o exec_domain.o printk.o
+export-objs = signal.o sys.o kmod.o context.o ksyms.o pm.o exec_domain.o printk.o \
+	panic.o module.o timer.o sched.o
 
 obj-y     = sched.o dma.o fork.o exec_domain.o panic.o printk.o \
 	    module.o exit.o itimer.o info.o time.o softirq.o resource.o \
 	    sysctl.o acct.o capability.o ptrace.o timer.o user.o \
 	    signal.o sys.o kmod.o context.o
 
+subdir-$(CONFIG_PM) += power
+subdir-m += power
+
 obj-$(CONFIG_UID16) += uid16.o
+obj-$(CONFIG_PM) += power/suspending.o
 obj-$(CONFIG_MODULES) += ksyms.o
 obj-$(CONFIG_PM) += pm.o
 obj-$(CONFIG_KALLSYMS) += kallsyms.o
diff -ruN 20-old/kernel/module.c 20-new/kernel/module.c
--- 20-old/kernel/module.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/kernel/module.c	2004-11-24 15:24:21.000000000 +1100
@@ -55,6 +55,7 @@
 };
 
 struct module *module_list = &kernel_module;
+EXPORT_SYMBOL(module_list);
 
 #endif	/* defined(CONFIG_MODULES) || defined(CONFIG_KALLSYMS) */
 
diff -ruN 20-old/kernel/panic.c 20-new/kernel/panic.c
--- 20-old/kernel/panic.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/kernel/panic.c	2004-11-24 15:24:21.000000000 +1100
@@ -17,6 +17,8 @@
 #include <linux/sysrq.h>
 #include <linux/interrupt.h>
 #include <linux/console.h>
+#include <linux/suspend.h>
+#include <linux/module.h>
 
 asmlinkage void sys_sync(void);	/* it's really int */
 
@@ -66,6 +68,10 @@
 		printk(KERN_EMERG "In interrupt handler - not syncing\n");
 	else if (!current->pid)
 		printk(KERN_EMERG "In idle task - not syncing\n");
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	else if (suspend_task)
+		printk(KERN_EMERG "In software suspend - not syncing.\n");
+#endif
 	else
 		sys_sync();
 	bust_spinlocks(0);
@@ -135,9 +141,10 @@
 {
 	static char buf[20];
 	if (tainted) {
-		snprintf(buf, sizeof(buf), "Tainted: %c%c",
+		snprintf(buf, sizeof(buf), "Tainted: %c%c%c",
 			tainted & 1 ? 'P' : 'G',
-			tainted & 2 ? 'F' : ' ');
+			tainted & 2 ? 'F' : ' ',
+			tainted & 4 ? 'Z' : ' ');
 	}
 	else
 		snprintf(buf, sizeof(buf), "Not tainted");
@@ -165,3 +172,5 @@
 	for ( ; ; )
 		;
 }
+
+EXPORT_SYMBOL(tainted);
diff -ruN 20-old/kernel/power/block_io.h 20-new/kernel/power/block_io.h
--- 20-old/kernel/power/block_io.h	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/block_io.h	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,52 @@
+/*
+ * block_io.h
+ *
+ * Copyright 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * Distributed under GPLv2.
+ *
+ * This file contains declarations for functions exported from
+ * block_io.c, which contains low level io functions.
+ */
+
+/* 8192 4k pages = 32MB */
+#define MAX_READAHEAD (int) (8192)
+
+/* Forward Declarations */
+
+struct submit_params {
+	swp_entry_t swap_address;
+	struct page * page;
+	kdev_t dev;
+	long blocks[PAGE_SIZE/512];
+	int blocks_used;
+	int readahead_index;
+	struct submit_params * next;
+};
+
+
+extern int max_async_ios;
+#define REAL_MAX_ASYNC ((max_async_ios ? max_async_ios : 128))
+
+/* 
+ * Our exported interface so the swapwriter and NFS writer don't
+ * need these functions built in.
+ */
+struct suspend_bio_ops {
+	int (*set_block_size) (kdev_t bdev, int size);
+	int (*get_block_size) (kdev_t bdev);
+	int (*submit_io) (int rw, 
+		struct submit_params * submit_info, int syncio);
+	int (*bdev_page_io) (int rw, kdev_t bdev, long pos,
+			struct page * page);
+	void (*wait_on_readahead) (int readahead_index);
+	void (*check_io_stats) (void);
+	void (*reset_io_stats) (void);
+	void (*finish_all_io) (void);
+	int (*prepare_readahead) (int index);
+	void (*cleanup_readahead) (int index);
+	struct page ** readahead_pages;
+	int (*readahead_ready) (int readahead_index);
+};
+
+extern struct suspend_bio_ops suspend_bio_ops;
diff -ruN 20-old/kernel/power/Changelog 20-new/kernel/power/Changelog
--- 20-old/kernel/power/Changelog	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/Changelog	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,143 @@
+(This list is not complete!)
+
+20040909
+- Added device_shutdown/power_up calls for devices not used during
+  suspend. Won't add them for devices used at the moment.
+- Separated disabling IRQs & preemption from smp_pause/continue.
+- Tidied device power management code.
+
+20040908
+- Completed changes to memory eating to improve performance in
+  the case where memory must be eaten. We no longer completely
+  regenerate the map of pageset2 pages for each iteration. Instead,
+  pageset2 pages are only removed from the pageset when freed.
+- Changed some printks to suspend_messages; the cases where memory
+  is low don't require us to be noisy normally.
+- Added display of page cache and LRU list sizes to display_stats.
+- Corrected storage and display of pageset sizes in debugging info.
+- Added check that enough header space is allocated in update_image
+  loop.
+- Made sys_sync call conditional on eating memory.
+- Fixed freezer so it will not oops if used in S3 and core not loaded.
+- Removed checksumming support
+- Include swap allocated in display of available swap in debug info.
+- Optimise clear_map to use memset.
+- (2.4) disable oom killer while suspend is running.
+- (2.6) Updated fb driver code to neater version from kronos@people.it
+  (being pushed to Linus in the meantime).
+
+20040906
+- (2.6) Completed addition of code to keep classes of devices alive while
+  suspending. This was needed for fb drivers, and other devices might
+  want it too (usb keyboards?) 
+- (2.6) Changed ACPI S4 support from a compile-time option to run-time
+  configurable.
+
+20040903
+(Not that nothing has been done for 8 months - I just haven't updated this!)
+- Added wbinvd call to lowlevel suspend code.
+- Applied Michael Franks's gcc 2.9.5 compile fixes.
+- Applied ALPs patch from Nicholas Brouard
+- Applied ne2k patch from Eric Bruner
+
+20040115-28
+Further bug fixes, cleanups, testing etc.
+
+20040114
+CDrom driver fixes.
+
+20040113
+Fixed Michael's first-time oops.
+Added support for setting resume2= parameter after booting.
+Further cleanups and small bug fixes.
+Updated and tested 2.6 port.
+Made 2.4 MTRR support into a PM handler.
+
+20040107
+Renamed console.c to ui.c.
+Further cleanup and commenting of files.
+Add more helpful makefile error if core patch not applied.
+
+20040101-06
+Further SMP testing and tweaking.
+Plenty of smaller cleanups and bug fixes.
+Work on getting the lowlevel code for 2.6 SMP compatible.
+
+20031230-31
+Completed initial working SMP version.
+Further building and testing.
+
+20031225-29
+
+Further work on SMP.
+Fixed compressors so they work with HighMem (vmalloc_32 instead of vmalloc).
+
+20031221-24
+Further work on SMP support.
+Separated out apm-poweroff functions for use by Software Suspend.
+Merged Marc's LZF support.
+Implemented readahead.
+Built and tested under 2.4.21, 2.4.22, 2.4.23 and 2.6.0.
+Fixed storage of compression statistics.
+Fixed oops when aborting while writing pageset 2.
+Fixed expected_compression functionality.
+Cleaned up compressor debugging info.
+Fixed compilation error in mm/page_alloc when CONFIG_PM off.
+Further work on SMP support.
+
+20031213-20
+Planning for merging.
+Initial SMP support.
+Investigation of AGP/ DRI-DRM issues & whether setting pages nosave would
+help.
+
+20031212
+Created swsusp25.bkbits.net/merge for merging to Patrick.
+Learnt and applied correct way of initialising union structs.
+
+20031211
+Miscellaneous bug fixes resulting from release.
+Beginnings of kernel/power/Internals document.
+Preparatory work for merging.
+
+20031208-10
+Preparation and testing of core and version specific patches for 2.4.21/22/23
+and 2.6.0-test11.
+Found and fixed long standing (but previously unnoticed) mtrr resume bug.
+
+20031208
+Completed getting swsusp2 to play nicely with pmdisk and swsusp
+implementations in the 2.6 kernel.
+
+20031204-6
+Moved union initialisers to avoid gcc 2.96 bug/missing feature.
+Converted swap partition to ext3 and tested swapfile support.
+Also tested and improved operation running with a swapfile on a normal
+partition.
+Released 2L for testing.
+
+20031203
+Finished getting compression working again after changes.
+Tested working when no transformers compiled in.
+Rewrote Makefile to enforce choosing a writer.
+Made the system not oops even if you should get suspend to compile without a
+writer.
+Got compiling and running under 2.6.
+Began work on compilation under gcc 2.96.
+
+20031202
+Tested suspend with simple config. ~111 cycles, no problems.
+Fixed math error in clear_pagemap.
+Fixed noresume2 (broken in new API changes)
+Completed changes to proc.c.
+Put console specific entries into console.c and plugin specific entries into
+the relevant plugins.
+Added /proc/swsusp/all_settings; which is equivalent to
+/proc/sys/kernel/swsusp. The later will be removed soon.
+
+20031201
+
+Started this file.
+Changing proc.c to make adding a new entry simpler, and to enable registering
+new entries from plugins. This will provide a basis for kobj functionality in
+the 2.6 variation
diff -ruN 20-old/kernel/power/Config.in 20-new/kernel/power/Config.in
--- 20-old/kernel/power/Config.in	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/Config.in	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,42 @@
+
+if [ "$CONFIG_PM" = "y" ]; then
+   mainmenu_option next_comment
+   comment 'Software Suspend support'
+
+   tristate 'Software Suspend 2 core' CONFIG_SOFTWARE_SUSPEND2_CORE
+   if [ "$CONFIG_SOFTWARE_SUSPEND2_CORE" != "n" ]; then
+      define_bool CONFIG_SOFTWARE_SUSPEND2 y
+   else
+      define_bool CONFIG_SOFTWARE_SUSPEND2 n
+   fi
+   if [ "$CONFIG_SOFTWARE_SUSPEND2_CORE" = "y" ]; then
+      comment 'Image Storage (you need at least one writer)'
+      dep_tristate '  Swap Writer' CONFIG_SOFTWARE_SUSPEND_SWAPWRITER $CONFIG_SOFTWARE_SUSPEND2_CORE
+      comment 'Page Transformers'
+      dep_tristate '  LZF Compress image (Slow)' CONFIG_SOFTWARE_SUSPEND_LZF_COMPRESSION $CONFIG_SOFTWARE_SUSPEND2_CORE
+      dep_tristate '  GZIP Compress image (Preferred)' CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION $CONFIG_SOFTWARE_SUSPEND2_CORE
+      comment 'Output plugins'
+      dep_tristate '  Text mode output' CONFIG_SOFTWARE_SUSPEND_TEXT_MODE $CONFIG_SOFTWARE_SUSPEND2_CORE
+      dep_tristate '  Bootsplash output' CONFIG_SOFTWARE_SUSPEND_BOOTSPLASH $CONFIG_SOFTWARE_SUSPEND2_CORE
+   else 
+      if [ "$CONFIG_SOFTWARE_SUSPEND2_CORE" = "m" ]; then
+         comment 'Image Storage (you need at least one writer)'
+         dep_tristate '  Swap Writer' CONFIG_SOFTWARE_SUSPEND_SWAPWRITER $CONFIG_SOFTWARE_SUSPEND2_CORE
+         comment 'Page Transformers'
+         dep_tristate '  GZIP Compress image' CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION  $CONFIG_SOFTWARE_SUSPEND2_CORE
+         dep_tristate '  LZF Compress image' CONFIG_SOFTWARE_SUSPEND_LZF_COMPRESSION $CONFIG_SOFTWARE_SUSPEND2_CORE
+         comment 'Output plugins'
+         dep_tristate '  Text mode output' CONFIG_SOFTWARE_SUSPEND_TEXT_MODE $CONFIG_SOFTWARE_SUSPEND2_CORE
+         dep_tristate '  Bootsplash output' CONFIG_SOFTWARE_SUSPEND_BOOTSPLASH $CONFIG_SOFTWARE_SUSPEND2_CORE
+      fi
+   fi
+   if [ "$CONFIG_SOFTWARE_SUSPEND2" = "y" ]; then
+      comment 'General Options'
+      bool ' Compile in debugging output' CONFIG_SOFTWARE_SUSPEND_DEBUG
+      bool ' Allow Keep Image Mode' CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE
+      bool ' Relaxed /proc/swsusp permissions' CONFIG_SOFTWARE_SUSPEND_RELAXED_PROC
+      string 'Default resume2= setting' CONFIG_SOFTWARE_SUSPEND_DEFAULT_RESUME2
+   fi
+   endmenu
+fi
+
diff -ruN 20-old/kernel/power/io.c 20-new/kernel/power/io.c
--- 20-old/kernel/power/io.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/io.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,1087 @@
+/*
+ * kernel/power/io.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.
+ *
+ * It contains high level IO routines for suspend.
+ *
+ */
+
+#define SUSPEND_IO_C
+
+#include <linux/suspend.h>
+#include <linux/version.h>
+#include <linux/mm.h>
+#include <linux/utsname.h>
+
+#include "suspend.h"
+#include "plugins.h"
+
+/* Variables saved in the suspend header */
+extern unsigned long orig_mem_free;
+extern int suspend_act_used;
+extern int suspend_lvl_used;
+extern int suspend_dbg_used;
+extern volatile int suspend_io_time[2][2];
+
+extern struct pagedir __nosavedata pagedir_resume;
+extern struct range * unused_ranges;
+extern int suspend2_prepare_console(void);
+
+/* Routines we call when reloading the original kernel */
+extern void warmup_collision_cache(void);
+extern int get_pageset1_load_addresses(void);
+
+extern void get_next_pbe(struct pbe2 * pbe);
+extern void get_first_pbe(struct pbe2 * pbe, struct pagedir * pagedir);
+
+static void noresume_reset_plugins(void);
+
+/* cleanup_finished_suspend_io
+ *
+ * Description:	Very simple helper function to save #including all the
+ * 		suspend code in fs/buffer.c and anywhere else we might
+ * 		want to wait on suspend I/O in future.
+ */
+
+void cleanup_finished_suspend_io(void)
+{
+	active_writer->ops.writer.wait_on_io(0);
+}
+
+/* fill_suspend_header()
+ * 
+ * Description:	Fill the suspend header structure.
+ * Arguments:	struct suspend_header: Header data structure to be filled.
+ */
+
+static __inline__ void fill_suspend_header(struct suspend_header *sh)
+{
+	int i;
+	
+	memset((char *)sh, 0, sizeof(*sh));
+
+	sh->version_code = LINUX_VERSION_CODE;
+	sh->num_physpages = num_physpages;
+	sh->orig_mem_free = orig_mem_free;
+	strncpy(sh->machine, system_utsname.machine, 65);
+	strncpy(sh->version, system_utsname.version, 65);
+	sh->num_cpus = smp_num_cpus;
+	sh->page_size = PAGE_SIZE;
+	sh->pagedir = pagedir1;
+	sh->pagedir.origranges.first = pagedir1.origranges.first;
+	sh->pagedir.destranges.first = pagedir1.destranges.first;
+	sh->pagedir.allocdranges.first = pagedir1.allocdranges.first;
+	sh->unused_ranges = unused_ranges;
+	sh->num_range_pages = num_range_pages;
+	sh->pageset_2_size = pagedir2.pageset_size;
+	sh->param0 = suspend_result;
+	sh->param1 = suspend_action;
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+	sh->param2 = suspend_debug_state;
+#endif
+	sh->param3 = console_loglevel;
+	for (i = 0; i < 4; i++)
+		sh->io_time[i/2][i%2] =
+		       suspend_io_time[i/2][i%2];
+}
+
+/* write_pageset()
+ *
+ * Description:	Write a pageset to disk.
+ * Arguments:	pagedir:	Pointer to the pagedir to be saved.
+ * 		whichtowrite:	Controls what debugging output is printed.
+ * Returns:	Zero on success or -1 on failure.
+ */
+
+int write_pageset(struct pagedir * pagedir, int whichtowrite)
+{
+	int nextupdate = 0, size, ret = 0, i, base = 0;
+	int barmax = pagedir1.pageset_size + pagedir2.pageset_size;
+	int start_time, end_time;
+	long error = 0;
+	struct pbe2 pbe;
+	unsigned int origfree = nr_free_pages();
+	struct suspend_plugin_ops * this_filter, * first_filter = get_next_filter(NULL);
+
+	PRINTFREEMEM("at start of write pageset");
+
+	size = pagedir->pageset_size;
+	if (!size)
+		return 0;
+
+	if (whichtowrite == 1) {
+		prepare_status(1, 0, "Writing kernel & process data...");
+		base = pagedir2.pageset_size;
+	} else {
+		prepare_status(1, 1, "Writing caches...");
+	}	
+	
+	start_time = jiffies;
+
+	/* Initialise page transformers */
+	list_for_each_entry(this_filter, &suspend_filters, ops.filter.filter_list) {
+		if (this_filter->disabled)
+			continue;
+		if (this_filter->ops.filter.write_init)
+			this_filter->ops.filter.write_init(whichtowrite);
+	}
+
+	PRINTFREEMEM("after initialising page transformers");
+
+	/* Initialise writer */
+	active_writer->ops.filter.write_init(whichtowrite);
+	PRINTFREEMEM("after initialising writer");
+
+	get_first_pbe(&pbe, pagedir);
+
+	/* Write the data */
+	for (i=0; i<size; i++) {
+		/* Status update */
+		if (!(i&0x1FF)) 
+			suspend_message(SUSPEND_IO, SUSPEND_LOW, 1, ".");
+		if (((i+base) >= nextupdate) || 
+				(!(i%(1 << (20 - PAGE_SHIFT)))))
+			nextupdate = update_status(i + base, barmax, 
+				" %d/%d MB ", MB(base+i+1), MB(barmax));
+		if ((i == (size - 5)) &&
+			TEST_ACTION_STATE(SUSPEND_PAUSE_NEAR_PAGESET_END))
+			check_shift_keys(1, "Five more pages to write.");
+		suspend_message(SUSPEND_IO, SUSPEND_VERBOSE, 1,
+				"Submitting page %d/%d.\n", i, size);
+
+		/* Write */
+		if (TEST_ACTION_STATE(SUSPEND_TEST_FILTER_SPEED))
+			ret = first_filter->ops.filter.write_chunk(pbe.origaddress);
+		else
+			ret = first_filter->ops.filter.write_chunk(pbe.address);
+
+		if (ret) {
+			printk("Write chunk returned %d.\n", ret);
+			abort_suspend("Failed to write a chunk of the "
+					"image.");
+			error = -1;
+			goto write_pageset_free_buffers;
+		}
+
+		/* Interactivity */
+		check_shift_keys(0, NULL);
+
+		if (TEST_RESULT_STATE(SUSPEND_ABORTED)) {
+			abort_suspend("Aborting as requested.");
+			error = -1;
+			goto write_pageset_free_buffers;
+		}
+
+		/* Prepare next */
+		get_next_pbe(&pbe);
+	}
+
+	update_status(base+size, barmax, " %d/%d MB ",
+			MB(base+size), MB(barmax));
+	suspend_message(SUSPEND_IO, SUSPEND_LOW, 1, "|\n");
+	PRINTFREEMEM("after writing data");
+
+write_pageset_free_buffers:
+	
+	/* Flush data and cleanup */
+	list_for_each_entry(this_filter, &suspend_filters, ops.filter.filter_list) {
+		if (this_filter->disabled)
+			continue;
+		if (this_filter->ops.filter.write_cleanup)
+			this_filter->ops.filter.write_cleanup();
+	}
+	PRINTFREEMEM("after cleaning up transformers");
+	active_writer->ops.writer.write_cleanup();
+	PRINTFREEMEM("after cleaning up writer");
+
+	/* Statistics */
+	end_time = jiffies;
+	
+	if ((end_time - start_time) && (!TEST_RESULT_STATE(SUSPEND_ABORTED))) {
+		suspend_message(SUSPEND_IO, SUSPEND_LOW, 1,
+			"Time to write data: %d pages in %d jiffies => "
+			"MB written per second: %lu.\n", 
+			size,
+			(end_time - start_time), 
+			(MB((unsigned long) size) * HZ / (end_time - start_time)));
+		suspend_io_time[0][0] += size,
+		suspend_io_time[0][1] += (end_time - start_time);
+	}
+
+	PRINTFREEMEM("at end of write pageset");
+
+	/* Sanity checking */
+	if (nr_free_pages() != origfree) {
+		abort_suspend("Number of free pages at start and end of write "
+			"pageset don't match! (%d != %d)",
+			origfree, nr_free_pages());
+	}
+
+	suspend_store_free_mem(SUSPEND_FREE_IO, 0);
+	return error;
+}
+
+/* read_pageset()
+ *
+ * Description:	Read a pageset from disk.
+ * Arguments:	pagedir:	Pointer to the pagedir to be saved.
+ * 		whichtowrite:	Controls what debugging output is printed.
+ * 		overwrittenpagesonly: Whether to read the whole pageset or
+ * 		only part.
+ * Returns:	Zero on success or -1 on failure.
+ */
+
+int read_pageset(struct pagedir * pagedir, int whichtoread,
+		int overwrittenpagesonly)
+{
+	int nextupdate = 0, result = 0, base = 0;
+	int start_time, end_time, finish_at = pagedir->pageset_size;
+	int barmax = pagedir1.pageset_size + pagedir2.pageset_size;
+	int i;
+	struct pbe2 pbe;
+	struct suspend_plugin_ops * this_filter, * first_filter = get_next_filter(NULL);
+
+	PRINTFREEMEM("at start of read pageset");
+
+	if (whichtoread == 1) {
+		prepare_status(1, 1, "Reading kernel & process data...");
+	} else {
+		prepare_status(1, 0, "Reading caches...");
+		if (overwrittenpagesonly)
+			barmax = finish_at = min(pageset1_size, pageset2_size);
+		else {
+			base = pagedir1.pageset_size;
+		}
+	}	
+	
+	start_time=jiffies;
+
+	/* Initialise page transformers */
+	list_for_each_entry(this_filter, &suspend_filters, ops.filter.filter_list) {
+		if (this_filter->disabled)
+			continue;
+		if (this_filter->ops.filter.read_init && 
+				this_filter->ops.filter.read_init(whichtoread)) {
+			abort_suspend("Failed to initialise a filter.");
+			result = 1;
+			goto read_pageset_free_buffers;
+		}
+	}
+
+	/* Initialise writer */
+	if (active_writer->ops.writer.read_init(whichtoread)) {
+		abort_suspend("Failed to initialise the writer."); 
+		result = 1;
+		goto read_pageset_free_buffers;
+	}
+
+	get_first_pbe(&pbe, pagedir);
+
+	suspend_message(SUSPEND_IO, SUSPEND_LOW, 1,
+		"Attempting to read %d pages.\n", finish_at);
+
+	/* Read the pages */
+	for (i=0; i< finish_at; i++) {
+		/* Status */
+		if (!(i&0x1FF)) 
+			suspend_message(SUSPEND_IO, SUSPEND_LOW, 1, ".");
+		if (((i+base) >= nextupdate) ||
+				(!(i%(1 << (20 - PAGE_SHIFT)))))
+			nextupdate = update_status(i+base, barmax,
+				" %d/%d MB ", MB(base+i+1), MB(barmax));
+		if ((i == (finish_at - 5)) &&
+			TEST_ACTION_STATE(SUSPEND_PAUSE_NEAR_PAGESET_END))
+			check_shift_keys(1, "Five more pages to read.");
+		suspend_message(SUSPEND_IO, SUSPEND_VERBOSE, 1,
+				"Submitting page %d/%d.\n", i, finish_at);
+
+		result = first_filter->ops.filter.read_chunk(pbe.address, SUSPEND_ASYNC);
+
+		if (result) {
+			panic("Failed to read chunk %d/%d of the image.",
+					i, finish_at);
+			goto read_pageset_free_buffers;
+		}
+
+		/* Interactivity*/
+		check_shift_keys(0, NULL);
+
+		/* Prepare next */
+		get_next_pbe(&pbe);
+	}
+
+	update_status(base+finish_at, barmax, " %d/%d MB ",
+			MB(base+finish_at), MB(barmax));
+	suspend_message(SUSPEND_IO, SUSPEND_LOW, 1, "|\n");
+
+read_pageset_free_buffers:
+
+	/* Finish I/O, flush data and cleanup reads. */
+	list_for_each_entry(this_filter, &suspend_filters, ops.filter.filter_list) {
+		if (this_filter->disabled)
+			continue;
+		if (this_filter->ops.filter.read_cleanup &&
+				this_filter->ops.filter.read_cleanup()) {
+			abort_suspend("Failed to cleanup a filter.");
+			result = 1;
+		}
+	}
+
+	if (active_writer->ops.writer.read_cleanup()) {
+		abort_suspend("Failed to cleanup the writer.");
+		result = 1;
+	}
+
+	/* Statistics */
+	end_time=jiffies;
+	if ((end_time - start_time) && (!TEST_RESULT_STATE(SUSPEND_ABORTED))) {
+		suspend_message(SUSPEND_IO, SUSPEND_LOW, 1,
+			"Time to read data: %d pages in %d jiffies => "
+			"MB read per second: %lu.\n",
+			finish_at,
+			(end_time - start_time), 
+			(MB((unsigned long) finish_at) * HZ / 
+			 	(end_time - start_time)));
+		suspend_io_time[1][0] += finish_at,
+		suspend_io_time[1][1] += (end_time - start_time);
+	}
+
+	PRINTFREEMEM("at end of read pageset");
+
+	suspend_store_free_mem(SUSPEND_FREE_IO, 1);
+	return result;
+}
+
+/* write_plugin_configs()
+ *
+ * Description:	Store the configuration for each plugin in the image header.
+ * Returns:	Int: Zero on success, Error value otherwise.
+ */
+static int write_plugin_configs(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+	char * buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	int len, index = 1;
+	struct plugin_header plugin_header;
+
+	if (!buffer) {
+		printk("Failed to allocate a buffer for saving "
+				"plugin configuration info.\n");
+		return -ENOMEM;
+	}
+		
+	/* 
+	 * 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 */
+		len = 0;
+		if (this_plugin->save_config_info)
+			len = this_plugin->save_config_info(buffer);
+
+		/* Save the details of the plugin */
+		plugin_header.disabled = this_plugin->disabled;
+		plugin_header.type = this_plugin->type;
+		plugin_header.index = index++;
+		strncpy(plugin_header.name, this_plugin->name, 
+					sizeof(plugin_header.name));
+		active_writer->ops.writer.write_header_chunk(
+				(char *) &plugin_header,
+				sizeof(plugin_header));
+
+		/* Save the size of the data and any data returned */
+		active_writer->ops.writer.write_header_chunk((char *) &len,
+				sizeof(int));
+		if (len)
+			active_writer->ops.writer.write_header_chunk(
+					buffer, len);
+	}
+
+	/* Write a blank header to terminate the list */
+	plugin_header.name[0] = '\0';
+	active_writer->ops.writer.write_header_chunk(
+			(char *) &plugin_header,
+			sizeof(plugin_header));
+
+	free_pages((unsigned long) buffer, 0);
+	return 0;
+}
+
+/* read_plugin_configs()
+ *
+ * Description:	Reload plugin configurations from the image header.
+ * Returns:	Int. Zero on success, error value otherwise.
+ */
+
+static int read_plugin_configs(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+	char * buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	int len, result = 0;
+	struct plugin_header plugin_header;
+
+	if (!buffer) {
+		printk("Failed to allocate a buffer for reloading plugin "
+				"configuration info.\n");
+		return -ENOMEM;
+	}
+		
+	/* All plugins are initially disabled. That way, if we have a plugin
+	 * loaded now that wasn't loaded when we suspended, it won't be used
+	 * in trying to read the data.
+	 */
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list)
+		this_plugin->disabled = 1;
+	
+	/* Get the first plugin header */
+	result = active_writer->ops.writer.read_header_chunk(
+			(char *) &plugin_header, sizeof(plugin_header));
+	if (!result) {
+		printk("Failed to read the next plugin header.\n");
+		free_pages((unsigned long) buffer, 0);
+		return -EINVAL;
+	}
+
+	/* For each plugin (in registration order) */
+	while (plugin_header.name[0]) {
+		
+		/* Find the plugin */
+		this_plugin = find_plugin_given_name(plugin_header.name);
+		
+		if (!this_plugin) {
+			/* 
+			 * Is it used? Only need to worry about filters. The active
+			 * writer must be loaded!
+			 */
+			if ((!plugin_header.disabled) && (plugin_header.type == FILTER_PLUGIN)) {
+				suspend_early_boot_message(1, "It looks like we need plugin %s for reading the image "
+						"but it hasn't been registered.\n",
+						plugin_header.name);
+				if (!(test_suspend_state(SUSPEND_CONTINUE_REQ))) {
+					active_writer->ops.writer.invalidate_image();
+					result = -EINVAL;
+					noresume_reset_plugins();
+					free_pages((unsigned long) buffer, 0);
+					return -EINVAL;
+				}
+			} else
+				printk("Plugin %s configuration data found, but the plugin "
+					"hasn't registered. Looks like it was disabled, so "
+					"we're ignoring it's data.",
+					plugin_header.name);
+		}
+		
+		/* Get the length of the data (if any) */
+		result = active_writer->ops.writer.read_header_chunk(
+				(char *) &len, sizeof(int));
+		if (!result) {
+			printk("Failed to read the length of the plugin %s's"
+					" configuration data.\n",
+					plugin_header.name);
+			free_pages((unsigned long) buffer, 0);
+			return -EINVAL;
+		}
+
+		/* Read any data and pass to the plugin (if we found one) */
+		if (len) {
+			active_writer->ops.writer.read_header_chunk(buffer, len);
+			if (this_plugin) {
+				if (!this_plugin->save_config_info) {
+					printk("Huh? Plugin %s appears to have a "
+						"save_config_info, but not a "
+						"load_config_info function!\n",
+						this_plugin->name);
+				} else
+					this_plugin->load_config_info(buffer, len);
+			}
+		}
+
+		if (this_plugin) {
+			/* Now move this plugin to the tail of its lists. This will put it
+			 * in order. Any new plugins will end up at the top of the lists.
+			 * They should have been set to disabled when loaded (people will
+			 * normally not edit an initrd to load a new module and then
+			 * suspend without using it!).
+			 */
+
+			suspend_move_plugin_tail(this_plugin);
+
+			/* 
+			 * We apply the disabled state; plugins don't need to save whether they
+			 * were disabled and if they do, we override them anyway.
+			 */
+			this_plugin->disabled = plugin_header.disabled;
+		}
+
+		/* Get the next plugin header */
+		result = active_writer->ops.writer.read_header_chunk(
+				(char *) &plugin_header, sizeof(plugin_header));
+
+		if (!result) {
+			printk("Failed to read the next plugin header.\n");
+			free_pages((unsigned long) buffer, 0);
+			return -EINVAL;
+		}
+
+	}
+
+	free_pages((unsigned long) buffer, 0);
+	return 0;
+}
+
+/* write_image_header()
+ *
+ * Description:	Write the image header after write the image proper.
+ * Returns:	Int. Zero on success or -1 on failure.
+ */
+
+int write_image_header(void)
+{
+	int i, nextupdate = 0, ret;
+	int total = pagedir1.pageset_size+pagedir2.pageset_size+2;
+	int progress = total-1;
+	char * header_buffer = NULL;
+
+	/* First, relativise all range information */
+	if (get_rangepages_list())
+		return -1;
+
+	if (unused_ranges)
+		unused_ranges = RANGE_RELATIVE(unused_ranges);
+	
+	relativise_chain(&pagedir1.origranges);
+	relativise_chain(&pagedir1.destranges);
+	relativise_chain(&pagedir1.allocdranges);
+
+	if ((ret = active_writer->ops.writer.prepare_save_ranges())) {
+		abort_suspend("Active writer's prepare_save_ranges "
+				"function failed.");
+		goto write_image_header_abort1;
+	}
+
+	relativise_ranges();
+
+	/* Now prepare to write the header */
+	if ((ret = active_writer->ops.writer.write_header_init())) {
+		abort_suspend("Active writer's write_header_init"
+				" function failed.");
+		goto write_image_header_abort2;
+	}
+
+	/* Get a buffer */
+	header_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	if (!header_buffer) {
+		abort_suspend("Out of memory when trying to get page "
+				"for header!");
+		goto write_image_header_abort3;
+	}
+
+	/* Write the meta data */
+	fill_suspend_header((struct suspend_header *) header_buffer);
+	active_writer->ops.writer.write_header_chunk(header_buffer,
+			sizeof(struct suspend_header));
+
+	/* Write plugin configurations */
+	if ((ret = write_plugin_configs())) {
+		abort_suspend("Failed to write plugin configs.");
+		goto write_image_header_abort3;
+	}
+
+	/* Write range pages */
+	suspend_message(SUSPEND_HEADER, SUSPEND_LOW, 1,
+		name_suspend "Writing %d range pages.\n",
+		num_range_pages);
+
+	for (i=1; i<=num_range_pages; i++) {
+		unsigned long * this_range_page = get_rangepages_list_entry(i);
+		/* Status update */
+		suspend_message(SUSPEND_HEADER, SUSPEND_VERBOSE, 1, "%d/%d: %p.\n",
+			i, num_range_pages, this_range_page);
+
+		if (i >= nextupdate)
+			nextupdate = update_status(progress + i, total, NULL);
+
+		/* Check for aborting/pausing */
+		check_shift_keys(0, NULL);
+
+		if (TEST_RESULT_STATE(SUSPEND_ABORTED)) {
+			abort_suspend("Aborting as requested.");
+			goto write_image_header_abort3;
+		}
+		
+		/* Write one range page */
+		active_writer->ops.writer.write_header_chunk(
+				(char *) this_range_page, PAGE_SIZE);
+		
+		if (ret) {
+			abort_suspend("Failed writing a page. "
+					"Error number was %d.", ret);
+			goto write_image_header_abort3;
+		}
+	}
+
+	update_status(total - 1, total, NULL);
+
+	/* Flush data and let writer cleanup */
+	if (active_writer->ops.writer.write_header_cleanup()) {
+		abort_suspend("Failed to cleanup writing header.");
+		goto write_image_header_abort2;
+	}
+
+	if (TEST_RESULT_STATE(SUSPEND_ABORTED))
+		goto write_image_header_abort2;
+
+	suspend_message(SUSPEND_IO, SUSPEND_VERBOSE, 1, "|\n");
+	update_status(total, total, NULL);
+
+	MDELAY(1000);
+	free_pages((unsigned long) header_buffer, 0);
+
+	return 0;
+
+	/* 
+	 * Aborting. We need to...
+	 * - let the writer cleanup (if necessary)
+	 * - revert ranges to absolute values
+	 */
+write_image_header_abort3:
+	active_writer->ops.writer.write_header_cleanup();
+	
+write_image_header_abort2:
+	absolutise_ranges();
+
+	put_rangepages_list();
+
+	if (active_writer->ops.writer.post_load_ranges)
+		active_writer->ops.writer.post_load_ranges();
+
+write_image_header_abort1:
+	if (get_rangepages_list())
+		panic("Unable to allocate rangepageslist.");
+
+	absolutise_chain(&pagedir1.origranges);
+	absolutise_chain(&pagedir1.destranges);
+	absolutise_chain(&pagedir1.allocdranges);
+
+	put_rangepages_list();
+
+	free_pages((unsigned long) header_buffer, 0);
+	return -1;
+}
+
+extern int suspend_early_boot_message(int can_erase_image, char *reason, ...);
+
+/* sanity_check()
+ *
+ * Description:	Perform a few checks, seeking to ensure that the kernel being
+ * 		booted matches the one suspended. They need to match so we can
+ * 		be _sure_ things will work. It is not absolutely impossible for
+ * 		resuming from a different kernel to work, just not assured.
+ * Arguments:	Struct suspend_header. The header which was saved at suspend
+ * 		time.
+ */
+static int sanity_check(struct suspend_header *sh)
+{
+	if (sh->version_code != LINUX_VERSION_CODE)
+		return suspend_early_boot_message(1, "Incorrect kernel version");
+	
+	if (sh->num_physpages != num_physpages)
+		return suspend_early_boot_message(1, "Incorrect memory size");
+
+	if (strncmp(sh->machine, system_utsname.machine, 65))
+		return suspend_early_boot_message(1, "Incorrect machine type");
+
+	if (strncmp(sh->version, system_utsname.version, 65))
+		return suspend_early_boot_message(1, "Incorrect version");
+
+	if (sh->num_cpus != smp_num_cpus)
+		return suspend_early_boot_message(1, "Incorrect number of cpus");
+
+	if (sh->page_size != PAGE_SIZE)
+		return suspend_early_boot_message(1, "Incorrect PAGE_SIZE");
+
+	return 0;
+}
+
+/* noresume_reset_plugins
+ *
+ * Description:	When we read the start of an image, plugins (and especially the
+ * 		active writer) might need to reset data structures if we decide
+ * 		to invalidate the image rather than resuming from it.
+ */
+
+static void noresume_reset_plugins(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+	
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+		if (this_plugin->ops.filter.noresume_reset)
+			this_plugin->ops.filter.noresume_reset();
+	}
+}
+
+/* __read_primary_suspend_image
+ *
+ * Description:	Test for the existence of an image and attempt to load it.
+ * Returns:	Int. Zero if image found and pageset1 successfully loaded.
+ * 		Error if no image found or loaded.
+ */
+static int __read_primary_suspend_image(void)
+{			
+	int i, result = 0;
+	char * header_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	struct suspend_header * suspend_header;
+	struct range * last_range_page = NULL;
+
+	if (!header_buffer)
+		return -ENOMEM;
+	
+	set_suspend_state(SUSPEND_RUNNING);
+
+	/* Check for an image */
+	if (!(result = active_writer->ops.writer.image_exists())) {
+		result = -ENODATA;
+		noresume_reset_plugins();
+		goto out;
+	}
+
+	/* Check for noresume command line option */
+	if (test_suspend_state(SUSPEND_NORESUME_SPECIFIED)) {
+		active_writer->ops.writer.invalidate_image();
+		result = -EINVAL;
+		noresume_reset_plugins();
+		goto out;
+	}
+
+	/* Check whether we've resumed before */
+	if (test_suspend_state(SUSPEND_RESUMED_BEFORE)) {
+		suspend_early_boot_message(1, NULL);
+		if (!(test_suspend_state(SUSPEND_CONTINUE_REQ))) {
+			active_writer->ops.writer.invalidate_image();
+			result = -EINVAL;
+			noresume_reset_plugins();
+			goto out;
+		}
+	}
+
+	clear_suspend_state(SUSPEND_CONTINUE_REQ);
+
+	/* 
+	 * Prepare the active writer for reading the image header. The
+	 * activate writer might read its own configuration or set up
+	 * a network connection here.
+	 * 
+	 * NB: This call may never return because there might be a signature
+	 * for a different image such that we warn the user and they choose
+	 * to reboot. (If the device ids look erroneous (2.4 vs 2.6) or the
+	 * location of the image might be unavailable if it was stored on a
+	 * network connection.
+	 */
+
+	if ((result = active_writer->ops.writer.read_header_init())) {
+		noresume_reset_plugins();
+		goto out;
+	}
+	
+	/* Read suspend header */
+	if ((result = active_writer->ops.writer.read_header_chunk(
+			header_buffer, sizeof(struct suspend_header))) < 0) {
+		noresume_reset_plugins();
+		goto out;
+	}
+	
+	suspend_header = (struct suspend_header *) header_buffer;
+
+	/*
+	 * NB: This call may also result in a reboot rather than returning.
+	 */
+
+	if (sanity_check(suspend_header)) { /* Is this the same machine? */
+		active_writer->ops.writer.invalidate_image();
+		result = -EINVAL;
+		noresume_reset_plugins();
+		goto out;
+	}
+
+	/*
+	 * ---------------------------------------------------- 
+	 * We have an image and it looks like it will load okay.
+	 * ---------------------------------------------------- 
+	 */
+
+	/* Get metadata from header. Don't override commandline parameters.
+	 *
+	 * We don't need to save the image size limit because it's not used
+	 * during resume and will be restored with the image anyway.
+	 */
+	
+	orig_mem_free = suspend_header->orig_mem_free;
+	memcpy((char *) &pagedir_resume,
+		(char *) &suspend_header->pagedir, sizeof(pagedir_resume));
+	unused_ranges = suspend_header->unused_ranges;
+	num_range_pages = suspend_header->num_range_pages;
+	suspend_result = suspend_header->param0;
+	if (!suspend_act_used)
+		suspend_action = suspend_header->param1;
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+	if (!suspend_dbg_used)
+		suspend_debug_state = suspend_header->param2;
+#endif
+	if (!suspend_lvl_used)
+		suspend_default_console_level = console_loglevel = suspend_header->param3;
+	clear_suspend_state(SUSPEND_IGNORE_LOGLEVEL);
+	pagedir1.pageset_size = pagedir_resume.pageset_size;
+	pagedir2.pageset_size = suspend_header->pageset_2_size;
+	for (i = 0; i < 4; i++)
+		suspend_io_time[i/2][i%2] =
+			suspend_header->io_time[i/2][i%2];
+
+	set_suspend_state(SUSPEND_NOW_RESUMING);
+
+	/* Read plugin configurations */
+	if ((result = read_plugin_configs())) {
+		noresume_reset_plugins();
+		num_range_pages = pagedir1.pageset_size =
+			pagedir2.pageset_size = 0;
+		unused_ranges = NULL;
+		goto out;
+	}
+
+	suspend2_prepare_console();
+
+	/* Read range pages */
+	check_shift_keys(1, "About to read pagedir.");
+
+	for (i=0; i < num_range_pages; i++) {
+		/* Get a page into which we will load the data */
+		struct range * this_range_page = 
+			(struct range *) get_zeroed_page(GFP_ATOMIC);
+		if (!this_range_page) {
+			abort_suspend("Unable to allocate a pagedir.");
+			result = -ENOMEM;
+			noresume_reset_plugins();
+			goto outfreeingrangepages;
+		}
+
+		/* Link to previous page */
+		if (i == 0)
+			first_range_page = this_range_page;
+		else
+			*RANGEPAGELINK(last_range_page) =
+				(i | (unsigned long) this_range_page);
+
+		/* Read this page */
+		if ((result = active_writer->ops.writer.read_header_chunk(
+				(char *) this_range_page, PAGE_SIZE)) < 0) {
+			printk("Active writer's read_header_chunk routine "
+					"returned %d.\n", result);
+			free_page((unsigned long) this_range_page);
+			noresume_reset_plugins();
+			goto outfreeingrangepages;
+		}
+		
+		last_range_page = this_range_page;
+	}
+	
+	/* Set the last page's link to its index */
+	*RANGEPAGELINK(last_range_page) = i;
+
+	/* Clean up after reading the header */
+	if ((result = active_writer->ops.writer.read_header_cleanup())) {
+		noresume_reset_plugins();
+		goto outfreeingrangepages;
+	}
+
+	/* Okay.
+	 *
+	 * Now we need to move the range pages to a place such that they won't
+	 * get overwritten while being used when copying the original kernel
+	 * back. To achieve this, we need to absolutise them where they are
+	 * now, prepare a bitmap of pages that collide and then relativise the
+	 * range pages again. Having done that, we can relocate the range
+	 * pages so that they don't collide with the image being restored,
+	 * and absolutise them in that location.
+	 */
+	
+	if (get_rangepages_list()) {
+		result = -ENOMEM;
+		noresume_reset_plugins();
+		goto outfreeingrangepages;
+	}
+	
+	/* Absolutise ranges so they can be used for building the map of
+	 * pages that will be overwritten. */
+	absolutise_ranges();
+	absolutise_chain(&pagedir_resume.origranges);
+
+	/* Mark the pages used by the current and original kernels */
+ 	warmup_collision_cache();
+
+	/* Prepare to move the pages so they don't conflict */
+	relativise_chain(&pagedir_resume.origranges);
+	relativise_ranges();
+ 
+	/* Relocate the pages */
+	relocate_rangepages();
+
+	/* Make sure the rangepages list is correct */
+	put_rangepages_list();
+	get_rangepages_list();
+
+	/* Absolutise in final place */
+	absolutise_ranges();
+	
+	/* Done.
+	 *
+	 * Now we can absolutise all the pointers to the range chains.
+	 */
+
+	set_chain_names(&pagedir_resume);
+	
+	absolutise_chain(&pagedir_resume.origranges);
+	
+	/*
+	 * We don't want the original destination ranges (the locations where
+	 * the atomic copy of pageset1 was stored at suspend time); we release
+	 * the chain's elements before getting new ones. (The kernel running
+	 * right now could be using pages that were free when we suspended).
+	 */
+
+	absolutise_chain(&pagedir_resume.destranges);
+	
+	absolutise_chain(&pagedir_resume.allocdranges);
+
+	if (unused_ranges)
+		unused_ranges = RANGE_ABSOLUTE(unused_ranges);
+	
+	put_rangepages_list();
+
+	/* 
+	 * The active writer should be using chains to record where it stored
+	 * the data. Give it a chance to absolutise them.
+	 */
+	if (active_writer->ops.writer.post_load_ranges)
+		active_writer->ops.writer.post_load_ranges();
+
+	/* 
+	 * Get the addresses of pages into which we will load the kernel to
+	 * be copied back
+	 */
+	put_range_chain(&pagedir_resume.destranges);
+
+	if (get_pageset1_load_addresses()) {
+		result = -ENOMEM;
+		noresume_reset_plugins();
+		goto outfreeingrangepages;
+	}
+
+	/* Read the original kernel back */
+	check_shift_keys(1, "About to read pageset 1.");
+
+	if (read_pageset(&pagedir_resume, 1, 0)) {
+		prepare_status(1, 1, "Failed to read pageset 1.");
+		result = -EPERM;
+		noresume_reset_plugins();
+		goto outfreeingrangepages;
+	}
+
+	PRINTFREEMEM("after loading image.");
+	check_shift_keys(1, "About to restore original kernel.");
+	result = 0;
+
+	if (active_writer->ops.writer.mark_resume_attempted)
+		active_writer->ops.writer.mark_resume_attempted();
+
+out:
+	free_pages((unsigned long) header_buffer, 0);
+	return result;
+outfreeingrangepages:
+	//FIXME Test i post loop and reset memory structures.
+	{
+		int j;
+		struct range * this_range_page = first_range_page;
+		struct range * next_range_page;
+		for (j = 0; j < i; j++) {
+			next_range_page = (struct range *) 
+				(((unsigned long)
+				  *RANGEPAGELINK(this_range_page)) & PAGE_MASK);
+			free_page((unsigned long) this_range_page);
+			this_range_page = next_range_page;
+		}
+	}
+	goto out;
+}
+
+/* read_primary_suspend_image()
+ *
+ * Description:	Attempt to read the header and pageset1 of a suspend image.
+ * 		Handle the outcome, complaining where appropriate.
+ */
+int read_primary_suspend_image(void)
+{
+	int error;
+
+	error = __read_primary_suspend_image();
+
+	switch (error) {
+		case 0:
+		case -ENODATA:
+		case -EINVAL:	/* non fatal error */
+			MDELAY(1000);
+			return error;
+		case -EIO:
+			printk(KERN_CRIT name_suspend "I/O error\n");
+			break;
+		case -ENOENT:
+			printk(KERN_CRIT name_suspend "No such file or directory\n");
+			break;
+		case -EPERM:
+			printk(KERN_CRIT name_suspend "Sanity check error\n");
+			break;
+		default:
+			printk(KERN_CRIT name_suspend "Error %d resuming\n", error);
+			break;
+	}
+	abort_suspend("Error %d in read_primary_suspend_image",error);
+	return error;
+}
+
+/* read_secondary_pagedir()
+ *
+ * Description:	Read in part or all of pageset2 of an image, depending upon
+ * 		whether we are suspending and have only overwritten a portion
+ * 		with pageset1 pages, or are resuming and need to read them 
+ * 		all.
+ * Arguments:	Int. Boolean. Read only pages which would have been
+ * 		overwritten by pageset1?
+ * Returns:	Int. Zero if no error, otherwise the error value.
+ */
+int read_secondary_pagedir(int overwrittenpagesonly)
+{
+	int result = 0;
+
+	if (!pageset2_size)
+		return 0;
+
+	suspend_message(SUSPEND_IO, SUSPEND_VERBOSE, 1,
+		      "Beginning of read_secondary_pagedir: ");
+
+	result = read_pageset(&pagedir2, 2, overwrittenpagesonly);
+
+	update_status(100, 100, NULL);
+	check_shift_keys(1, "Pagedir 2 read.");
+
+	suspend_message(SUSPEND_IO, SUSPEND_VERBOSE, 1, "\n");
+	return result;
+}
diff -ruN 20-old/kernel/power/lzf/lzf_c.c 20-new/kernel/power/lzf/lzf_c.c
--- 20-old/kernel/power/lzf/lzf_c.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/lzf/lzf_c.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2000-2003 Marc Alexander Lehmann <pcg@goof.com>
+ * 
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ * 
+ *   1.  Redistributions of source code must retain the above copyright notice,
+ *       this list of conditions and the following disclaimer.
+ * 
+ *   2.  Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ *   3.  The name of the author may not be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License version 2 (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of the above. If you wish to
+ * allow the use of your version of this file only under the terms of the
+ * GPL and not to allow others to use your version of this file under the
+ * BSD license, indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by the GPL. If
+ * you do not delete the provisions above, a recipient may use your version
+ * of this file under either the BSD or the GPL.
+ */
+
+#define HSIZE (1 << (HLOG))
+
+/*
+ * don't play with this unless you benchmark!
+ * decompression is not dependent on the hash function
+ * the hashing function might seem strange, just believe me
+ * it works ;)
+ */
+#define FRST(p) (((p[0]) << 8) + p[1])
+#define NEXT(v,p) (((v) << 8) + p[2])
+#define IDX(h) ((((h ^ (h << 5)) >> (3*8 - HLOG)) + h*3) & (HSIZE - 1))
+/*
+ * IDX works because it is very similar to a multiplicative hash, e.g.
+ * (h * 57321 >> (3*8 - HLOG))
+ * the next one is also quite good, albeit slow ;)
+ * (int)(cos(h & 0xffffff) * 1e6)
+ */
+
+#if 0
+/* original lzv-like hash function */
+# define FRST(p) (p[0] << 5) ^ p[1]
+# define NEXT(v,p) ((v) << 5) ^ p[2]
+# define IDX(h) ((h) & (HSIZE - 1))
+#endif
+
+#define        MAX_LIT        (1 <<  5)
+#define        MAX_OFF        (1 << 13)
+#define        MAX_REF        ((1 <<  8) + (1 << 3))
+
+/*
+ * compressed format
+ *
+ * 000LLLLL <L+1>    ; literal
+ * LLLOOOOO oooooooo ; backref L
+ * 111OOOOO LLLLLLLL oooooooo ; backref L+7
+ *
+ */
+
+unsigned int
+lzf_compress (const void *const in_data, unsigned int in_len,
+	      void *out_data, unsigned int out_len, void *hbuf)
+{
+  const u8 **htab = hbuf;
+  const u8 **hslot;
+  const u8 *ip = (const u8 *)in_data;
+        u8 *op = (u8 *)out_data;
+  const u8 *in_end  = ip + in_len;
+        u8 *out_end = op + out_len;
+  const u8 *ref;
+
+  unsigned int hval = FRST (ip);
+  unsigned long off;
+           int lit = 0;
+
+#if INIT_HTAB
+# if USE_MEMCPY
+    memset (htab, 0, sizeof (htab));
+# else
+    for (hslot = htab; hslot < htab + HSIZE; hslot++)
+      *hslot++ = ip;
+# endif
+#endif
+
+  for (;;)
+    {
+      if (ip < in_end - 2)
+        {
+          hval = NEXT (hval, ip);
+          hslot = htab + IDX (hval);
+          ref = *hslot; *hslot = ip;
+
+          if (1
+#if INIT_HTAB && !USE_MEMCPY
+              && ref < ip /* the next test will actually take care of this, but this is faster */
+#endif
+              && (off = ip - ref - 1) < MAX_OFF
+              && ip + 4 < in_end
+              && ref > (u8 *)in_data
+#if STRICT_ALIGN
+              && ref[0] == ip[0]
+              && ref[1] == ip[1]
+              && ref[2] == ip[2]
+#else
+              && *(u16 *)ref == *(u16 *)ip
+              && ref[2] == ip[2]
+#endif
+            )
+            {
+              /* match found at *ref++ */
+              unsigned int len = 2;
+              unsigned int maxlen = in_end - ip - len;
+              maxlen = maxlen > MAX_REF ? MAX_REF : maxlen;
+
+              do
+                len++;
+              while (len < maxlen && ref[len] == ip[len]);
+
+              if (op + lit + 1 + 3 >= out_end)
+                return 0;
+
+              if (lit)
+                {
+                  *op++ = lit - 1;
+                  lit = -lit;
+                  do
+                    *op++ = ip[lit];
+                  while (++lit);
+                }
+
+              len -= 2;
+              ip++;
+
+              if (len < 7)
+                {
+                  *op++ = (off >> 8) + (len << 5);
+                }
+              else
+                {
+                  *op++ = (off >> 8) + (  7 << 5);
+                  *op++ = len - 7;
+                }
+
+              *op++ = off;
+
+#if ULTRA_FAST
+              ip += len;
+              hval = FRST (ip);
+              hval = NEXT (hval, ip);
+              htab[IDX (hval)] = ip;
+              ip++;
+#else
+              do
+                {
+                  hval = NEXT (hval, ip);
+                  htab[IDX (hval)] = ip;
+                  ip++;
+                }
+              while (len--);
+#endif
+              continue;
+            }
+        }
+      else if (ip == in_end)
+        break;
+
+      /* one more literal byte we must copy */
+      lit++;
+      ip++;
+
+      if (lit == MAX_LIT)
+        {
+          if (op + 1 + MAX_LIT >= out_end)
+            return 0;
+
+          *op++ = MAX_LIT - 1;
+#if USE_MEMCPY
+          memcpy (op, ip - MAX_LIT, MAX_LIT);
+          op += MAX_LIT;
+          lit = 0;
+#else
+          lit = -lit;
+          do
+            *op++ = ip[lit];
+          while (++lit);
+#endif
+        }
+    }
+
+  if (lit)
+    {
+      if (op + lit + 1 >= out_end)
+	return 0;
+
+      *op++ = lit - 1;
+      lit = -lit;
+      do
+	*op++ = ip[lit];
+      while (++lit);
+    }
+
+  return op - (u8 *) out_data;
+}
diff -ruN 20-old/kernel/power/lzf/lzf_d.c 20-new/kernel/power/lzf/lzf_d.c
--- 20-old/kernel/power/lzf/lzf_d.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/lzf/lzf_d.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2000-2002 Marc Alexander Lehmann <pcg@goof.com>
+ * 
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ * 
+ *   1.  Redistributions of source code must retain the above copyright notice,
+ *       this list of conditions and the following disclaimer.
+ * 
+ *   2.  Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 
+ *   3.  The name of the author may not be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
+ * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
+ * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
+ * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License version 2 (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of the above. If you wish to
+ * allow the use of your version of this file only under the terms of the
+ * GPL and not to allow others to use your version of this file under the
+ * BSD license, indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by the GPL. If
+ * you do not delete the provisions above, a recipient may use your version
+ * of this file under either the BSD or the GPL.
+ */
+
+unsigned int 
+lzf_decompress (const void *const in_data,  unsigned int in_len,
+                void             *out_data, unsigned int out_len)
+{
+  u8 const *ip = in_data;
+  u8       *op = out_data;
+  u8 const *const in_end  = ip + in_len;
+  u8       *const out_end = op + out_len;
+
+  do
+    {
+      unsigned int ctrl = *ip++;
+
+      if (ctrl < (1 << 5)) /* literal run */
+        {
+          ctrl++;
+
+          if (op + ctrl > out_end)
+            return 0;
+
+#if USE_MEMCPY
+          memcpy (op, ip, ctrl);
+          op += ctrl;
+          ip += ctrl;
+#else
+          do
+            *op++ = *ip++;
+          while (--ctrl);
+#endif
+        }
+      else /* back reference */
+        {
+          unsigned int len = ctrl >> 5;
+
+          u8 *ref = op - ((ctrl & 0x1f) << 8) - 1;
+
+          if (len == 7)
+            len += *ip++;
+          
+          ref -= *ip++;
+
+          if (op + len + 2 > out_end)
+            return 0;
+
+          if (ref < (u8 *)out_data)
+            return 0;
+
+          *op++ = *ref++;
+          *op++ = *ref++;
+
+          do
+            *op++ = *ref++;
+          while (--len);
+        }
+    }
+  while (op < out_end && ip < in_end);
+
+  return op - (u8 *)out_data;
+}
+
diff -ruN 20-old/kernel/power/Makefile 20-new/kernel/power/Makefile
--- 20-old/kernel/power/Makefile	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/Makefile	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,35 @@
+#
+# Makefile for software suspend.
+#
+
+O_TARGET    := suspending.o
+
+export-objs := suspend_ui.o process.o suspend.o utility.o suspend_builtin.o \
+	proc.o suspend_block_io.o memory_pool.o pagedir.o prepare_image.o \
+	range.o plugins.o suspend_builtin_24.o
+
+obj-$(CONFIG_PM)	+= process.o
+ifeq ($(CONFIG_SOFTWARE_SUSPEND2),y)
+obj-y	+= suspend_builtin.o proc.o suspend_builtin_24.o
+endif
+
+
+suspend_core-objs		:= io.o memory_pool.o \
+	pagedir.o prepare_image.o range.o suspend.o plugins.o \
+	suspend_ui.o utility.o
+
+obj-$(CONFIG_SOFTWARE_SUSPEND2_CORE)		+= suspend_core.o 
+obj-$(CONFIG_SOFTWARE_SUSPEND_BOOTSPLASH)	+= suspend_bootsplash.o
+obj-$(CONFIG_SOFTWARE_SUSPEND_TEXT_MODE)	+= suspend_text.o
+obj-$(CONFIG_SOFTWARE_SUSPEND_LZF_COMPRESSION)	+= suspend_lzf.o
+obj-$(CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION)	+= suspend_gzip.o
+obj-$(CONFIG_SOFTWARE_SUSPEND_SWAPWRITER)	+= suspend_block_io.o suspend_swap.o
+
+include $(TOPDIR)/Rules.make
+
+suspend_ui.c:
+	@(echo -e "\n\n *** It looks like you have not applied the core Software Suspend patch. *** \n\n"; \
+	  exit 1)
+
+suspend_core.o: $(suspend_core-objs)
+	$(LD) -r -o $@ $(suspend_core-objs)
diff -ruN 20-old/kernel/power/memory_pool.c 20-new/kernel/power/memory_pool.c
--- 20-old/kernel/power/memory_pool.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/memory_pool.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,379 @@
+/*
+ * kernel/power/memory_pool.c
+ *  
+ * Copyright (C) 2003,2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * It contains routines for managing the memory pool during software suspend
+ * operation.
+ * 
+ * The memory pool is a pool of pages from which page allocations
+ * are satisfied while we are suspending, and into which freed pages are
+ * released. In this way, we can keep the image size static and consistent
+ * while still using normal I/O routines to save the image and while saving
+ * the image in two parts.
+ * 
+ * During suspend, almost all of the page allocations are order zero. Provision
+ * is made for one order one and one order two allocation. This provision is 
+ * utilised by the swapwriter for allocating memory which is used for structures
+ * containing header page. (It could be made to use order zero allocations; this
+ * just hasn't been done yet).
+ */
+
+#define SUSPEND_MEMORY_POOL_C
+
+#include "suspend.h"
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/highmem.h>
+
+#include "plugins.h"
+#include "pageflags.h"
+
+/* We keep high memory pages that are freed, but don't use them */
+struct memory_pool {
+	struct list_head contents[MAX_ORDER];
+	int level[MAX_ORDER];
+};
+
+static struct memory_pool normal_pool, highmem_pool;
+
+static int suspend_pool_level_limit[MAX_ORDER];
+static spinlock_t suspend_memory_pool_lock = SPIN_LOCK_UNLOCKED;
+
+static int min_pool_level = 0;
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+/* display_memory_pool_pages()
+ *
+ * Description:	Display the current contents of the memory pool.
+ */	
+static void __display_memory_pool_pages(struct memory_pool * pool)
+{
+	int order;
+
+	for (order = 0; order < MAX_ORDER; order++) {
+		struct page * page;
+		int index = 0;
+		suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1,
+				"- Order %d:\n", order);
+		list_for_each_entry(page, &pool->contents[order], lru) {
+			suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1,
+					"[%p] ", page);
+			index++;
+			if (!(index%8))
+				suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1,
+					"\n");
+		}
+
+		if (pool->level[order])
+			suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1,
+					"(%d entries)\n", pool->level[order]);
+		else
+			suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1,
+					"(empty)\n");
+	}
+}
+
+void display_memory_pool_pages(void)
+{
+	if (!TEST_DEBUG_STATE(SUSPEND_MEM_POOL))
+		return;
+
+	suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1, "Memory pool:\n");
+	suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1, "Normal pages:\n");
+	__display_memory_pool_pages(&normal_pool);
+	suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1, "High pages:\n");
+	__display_memory_pool_pages(&highmem_pool);
+}
+#else
+#define display_memory_pool_pages() do { } while(0)
+#endif
+
+__init void initialise_pool(struct memory_pool * pool)
+{
+	int i;
+
+	for (i = 0; i < MAX_ORDER; i++) {
+		pool->level[i] = 0;
+		INIT_LIST_HEAD(&pool->contents[i]);
+	}
+
+	suspend_pool_level_limit[1] = 1;
+	suspend_pool_level_limit[2] = 1;
+}
+
+__init void suspend_memory_pool_init(void)
+{
+	/* Initialise lists */
+	initialise_pool(&normal_pool);
+	initialise_pool(&highmem_pool);
+}
+
+/* get_from_pool()
+ *
+ * Description: Remove head of a pool list
+ */
+
+static struct page * get_from_pool(struct memory_pool * pool, int order)
+{
+	struct page * page;
+	int j;
+
+	if (!pool->level[order])
+		return 0;
+
+	page = list_entry(pool->contents[order].next, struct page, lru);
+	list_del_init(&page->lru);
+	pool->level[order]--;
+
+	for (j = 0; j < (1 << order); j++)
+		ClearPageChecksumIgnore(page + j);
+
+	if (page_count(page) != 1)
+		printk("Error getting page %p from memory pool. "
+			"Page count is %d (should be 1).\n",
+			page,
+			page_count(page));
+
+	BUG_ON(PageLRU(page) || PageActive(page));
+
+	display_memory_pool_pages();
+	suspend_message(SUSPEND_MEM_POOL, SUSPEND_MEDIUM, 0,
+			"\r%4d %4d %4d.",
+			normal_pool.level[0],
+			normal_pool.level[1],
+			normal_pool.level[2]);
+
+	return page;
+}
+
+/* add_to_pool()
+ *
+ * Description: Insert new head in a pool list
+ */
+
+static void add_to_pool(struct memory_pool * pool, int order, struct page * this)
+{
+	int j;
+
+	pool->level[order]++;
+	list_add(&this->lru, &pool->contents[order]);
+	for (j = 0; j < (1 << order); j++)
+		SetPageChecksumIgnore(this + j);
+	
+	display_memory_pool_pages();
+	suspend_message(SUSPEND_MEM_POOL, SUSPEND_MEDIUM, 0,
+			"\r%4d %4d %4d.",
+			normal_pool.level[0],
+			normal_pool.level[1],
+			normal_pool.level[2]);
+}
+
+/* suspend_memory_pool_level()
+ *
+ * Description:	Returns the number of pages currently in the pool.
+ * Returns:	Int.		Number of pages in the pool.
+ */
+int suspend_memory_pool_level(int only_lowmem)
+{
+	int order, sum = 0;
+
+	for (order = 0; order < MAX_ORDER; order++)
+		sum += normal_pool.level[order] * (1 << order);
+
+	if (!only_lowmem)
+		for (order = 0; order < MAX_ORDER; order++)
+			sum += highmem_pool.level[order] * (1 << order);
+	return sum;
+}
+
+/* fill_suspend_memory_pool()
+ *
+ * Description:	Fill the memory pool from the main free memory pool in the
+ * 		first instance, or grabbed pages if that fails.
+ * 		We allocate @sizesought order 0 pages, plus 1 each
+ * 		of the higher order allocations.
+ * Arguments:	int.		Number of order zero pages requested.
+ * Returns:	int.		Number of order zero pages obtained.
+ */
+int fill_suspend_memory_pool(int sizesought)
+{
+	int i = 0, order, orig_state = 
+		test_suspend_state(SUSPEND_USE_MEMORY_POOL);
+	unsigned long *this = NULL;
+	unsigned long flags;
+
+	spin_lock_irqsave(&suspend_memory_pool_lock, flags);
+
+	/* Pools must not be active for this to work */
+	clear_suspend_state(SUSPEND_USE_MEMORY_POOL);
+
+	suspend_pool_level_limit[0] = sizesought;
+
+	for (order = MAX_ORDER; order >= 0; order--) {
+		int wanted = suspend_pool_level_limit[order] -
+			normal_pool.level[order];
+		for (i = normal_pool.level[order]; 
+				i < suspend_pool_level_limit[order]; i++) {
+			this = (unsigned long *) get_grabbed_pages(order);
+			if (!this) {
+				suspend_message(SUSPEND_MEM_POOL, SUSPEND_ERROR, 1,
+					"%d order %d pages wanted for suspend "
+					"memory pool, got %d.\n",
+					wanted, order, i - 1);
+				break;
+			}
+			add_to_pool(&normal_pool, order, virt_to_page(this));
+		}
+	}
+
+	if (orig_state)
+		set_suspend_state(SUSPEND_USE_MEMORY_POOL);
+
+	min_pool_level = normal_pool.level[0];
+
+	spin_unlock_irqrestore(&suspend_memory_pool_lock, flags);
+
+	return 0;
+}
+
+/* empty_suspend_memory_pool()
+ *
+ * Description:	Drain our memory pool.
+ */
+void __empty_suspend_memory_pool(struct memory_pool * pool)
+{
+	int order;
+	struct page * this;
+
+	for (order = 0; order < MAX_ORDER; order++)
+		while ((this = get_from_pool(pool, order)))
+			__free_pages(this, order);
+
+	suspend_message(SUSPEND_MEM_POOL, SUSPEND_LOW, 1,
+		"Min pool level was %d/%d.\n", min_pool_level, suspend_pool_level_limit[0]);
+}
+
+void empty_suspend_memory_pool(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&suspend_memory_pool_lock, flags);
+
+	display_memory_pool_pages();
+	
+	/* Pool must not be active for this to work */
+	clear_suspend_state(SUSPEND_USE_MEMORY_POOL);
+
+	__empty_suspend_memory_pool(&normal_pool);
+	__empty_suspend_memory_pool(&highmem_pool);
+	
+	spin_unlock_irqrestore(&suspend_memory_pool_lock, flags);
+}
+
+/* get_suspend_pool_pages()
+ * 
+ * Description:	Our equivalent to __alloc_pages (minus zone mask).
+ * 		May be called from interrupt context.
+ * Arguments:	unsigned int:	Mask. We really only care about __GFP_WAIT.
+ * 				We're giving normal zone pages regardless.
+ * 		order:		The number of pages (1 << order) wanted.
+ * Returns:	struct page *:	Pointer (possibly NULL) to pages allocated.
+ */
+struct page * get_suspend_pool_pages(unsigned int gfp_mask, unsigned int order)
+{
+	unsigned long flags;
+	struct page * page;
+
+	if (order > 0) {
+		spin_lock_irqsave(&suspend_memory_pool_lock, flags);
+		if (!normal_pool.level[order]) {
+			printk("No order %d allocation available.\n",
+					order);
+			display_memory_pool_pages();
+			spin_unlock_irqrestore(
+					&suspend_memory_pool_lock,
+					flags);
+			return NULL;
+		}
+		goto check_and_return;
+	}
+
+try_again:
+	if ((!normal_pool.level[order]) && (!(gfp_mask & __GFP_WAIT))) {
+		spin_lock_irqsave(&suspend_memory_pool_lock, flags);
+		display_memory_pool_pages();
+		spin_unlock_irqrestore(&suspend_memory_pool_lock, flags);
+		return NULL;
+	}
+
+	while(!normal_pool.level[order]) {
+		if (active_writer->ops.writer.wait_on_io)
+			active_writer->ops.writer.wait_on_io(0);
+		schedule();
+	}
+
+	spin_lock_irqsave(&suspend_memory_pool_lock, flags);
+	if (!normal_pool.level[order]) {
+		spin_unlock_irqrestore(&suspend_memory_pool_lock, flags);
+		goto try_again;
+	}
+check_and_return:
+	page = get_from_pool(&normal_pool, order);
+	
+	if (normal_pool.level[0] < min_pool_level)
+		min_pool_level = normal_pool.level[0];
+	if (!normal_pool.level[0])
+		printk("Normal pool empty.\n");
+
+	spin_unlock_irqrestore(&suspend_memory_pool_lock, flags);
+	
+	return page;
+}
+
+/* free_suspend_pool_pages()
+ *
+ * Description:	Our equivalent to  __free_pages. Put freed pages into the pool.
+ * 		HighMem pages do still get freed to the normal pool because they
+ * 		aren't going to affect the consistency of our image - worse case,
+ * 		we write a few free pages.
+ * Arguments:	Struct page *:	First page to be freed.
+ * 		Unsigned int:	Size of allocation being freed.
+ */
+void free_suspend_pool_pages(struct page *page, unsigned int order)
+{
+	unsigned long flags;
+	int i;
+	struct memory_pool * pool = &normal_pool;
+
+	suspend_message(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, 1, 
+		"Freeing page %p (%p), order %d.\n",
+		page_address(page), page, order);
+	
+	if (PageHighMem(page))
+		pool = &highmem_pool;
+
+#ifdef CONFIG_MMU
+	set_page_count(page, 1);
+#else
+	for (i = 0; i < (1 << order); i++)
+		set_page_count(page + i, 1);
+#endif
+	if (pool == &normal_pool) {
+		char * address = page_address(page);
+		for (i = 0; i < (1 << order); i++) {
+			clear_page(address);
+			address += PAGE_SIZE;
+		}
+	}
+
+	spin_lock_irqsave(&suspend_memory_pool_lock, flags);
+	add_to_pool(pool, order, page);
+	spin_unlock_irqrestore(&suspend_memory_pool_lock, flags);
+	return;
+}
+
+EXPORT_SYMBOL(suspend_memory_pool_level);
diff -ruN 20-old/kernel/power/pagedir.c 20-new/kernel/power/pagedir.c
--- 20-old/kernel/power/pagedir.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/pagedir.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,506 @@
+/*
+ * kernel/power/pagedir.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.
+ *
+ * Routines for handling pagesets.
+ * Note that pbes aren't actually stored as such. They're stored as
+ * ranges (extents is the term, I'm told).
+ */
+
+#define SUSPEND_PAGEDIR_C
+#include <linux/suspend.h>
+//#include <linux/highmem.h>
+#include <linux/module.h>
+
+extern struct pagedir pagedir1, pagedir2, pagedir_resume;
+
+#include "suspend.h"
+#include "pageflags.h"
+
+/* setup_pbe_variable
+ *
+ * Description:	Set up one variable in a page backup entry from the range list.
+ * Arguments:	unsigned long:		The variable which will contain the 
+ * 					value.
+ * 		struct range**:		Address of the pointer to the current
+ * 					range.
+ * 		struct rangechain*:	Address of the rangechain we are 
+ * 					traversing.
+ */
+static inline void setup_pbe_variable(unsigned long * variable, struct range ** currentrange,
+		struct rangechain * chain)
+{
+	*currentrange = chain->first;
+	if (chain->first)
+		*variable = chain->first->minimum;
+	else
+		*variable = 0;
+}
+
+/* get_first_pbe
+ *
+ * Description:	Get the first page backup entry for a pagedir.
+ * Arguments:	struct pbe2 *:	Address of the page backup entry we're 
+ * 				populating.
+ * 		struct pagedir: Pagedir providing the data.
+ */
+void get_first_pbe(struct pbe2 * pbe, struct pagedir * pagedir)
+{
+	unsigned long currentorig, currentaddress;
+
+	pbe->pagedir = pagedir;
+
+	/* Get raw initial values */
+	setup_pbe_variable((unsigned long *) &pbe->origaddress, 
+			&pbe->currentorigrange, &pagedir->origranges);
+	setup_pbe_variable((unsigned long *) &pbe->address,
+			&pbe->currentdestrange, &pagedir->destranges);
+
+	/* Convert to range values */
+	currentorig = (unsigned long) pbe->origaddress;
+	currentaddress = (unsigned long) pbe->address;
+
+	pbe->origaddress = mem_map + currentorig;
+	pbe->address = mem_map + currentaddress;
+
+	if ((currentaddress < 0) || (currentaddress > max_mapnr))
+		panic("Argh! Destination range value %ld is invalid!",
+				currentaddress);
+}
+
+/* get_next_pbe
+ *
+ * Description:	Get the next page backup entry in a pagedir.
+ * Arguments:	struct pbe2 *:	Address of the pbe we're updating.
+ */
+void get_next_pbe(struct pbe2 * pbe)
+{
+	unsigned long currentorig, currentaddress;
+
+	/* Convert to range values */
+	currentorig = (pbe->origaddress - mem_map);
+	currentaddress = (pbe->address - mem_map);
+
+	/* Update values */
+	GET_RANGE_NEXT(pbe->currentorigrange, currentorig);
+	GET_RANGE_NEXT(pbe->currentdestrange, currentaddress);
+	
+	pbe->origaddress = mem_map + currentorig;
+	pbe->address = mem_map + currentaddress;
+}
+
+/*
+ * --------------------------------------------------------------------------------------
+ *
+ * 	Local Page Flags routines.
+ *
+ * 	Rather than using the rare and precious flags in struct page, we allocate
+ * 	our own bitmaps dynamically.
+ * 
+ */
+
+/* ------------------------------------------------------------------------- */
+
+/* copy_pageset1
+ *
+ * Description:	Make the atomic copy of pageset1. We can't use copy_page (as we
+ * 		once did) because we can't be sure what side effects it has. On
+ * 		my old Duron, with 3DNOW, kernel_fpu_begin increments preempt
+ * 		count, making our preempt count at resume time 4 instead of 3.
+ * 		
+ * 		We don't want to call kmap_atomic unconditionally because it has
+ * 		the side effect of incrementing the preempt count, which will
+ * 		leave it one too high post resume (the page containing the
+ * 		preempt count will be copied after its incremented. This is
+ * 		essentially the same problem.
+ */
+
+void copy_pageset1(void)
+{
+	int i = 0;
+	struct pbe2 pbe;
+
+	get_first_pbe(&pbe, &pagedir1);
+
+	for (i = 0; i < pageset1_size; i++) {
+		int loop;
+		unsigned long * origpage;
+		unsigned long * copypage = page_address(pbe.address);
+
+#ifdef CONFIG_HIGHMEM
+	       	if (PageHighMem(pbe.origaddress))
+			origpage = kmap_atomic(pbe.origaddress, KM_USER1);
+		else
+#endif
+			origpage = page_address(pbe.origaddress);
+
+		for (loop=0; loop < (PAGE_SIZE / sizeof(unsigned long)); loop++)
+			*(copypage + loop) = *(origpage + loop);
+#ifdef CONFIG_HIGHMEM
+	       	if (PageHighMem(pbe.origaddress))
+			kunmap_atomic(origpage, KM_USER1);
+#endif
+
+		get_next_pbe(&pbe);
+	}
+}
+
+/* free_pagedir
+ *
+ * Description:	Free a previously allocated pagedir.
+ * Arguments:	struct pagedir *:	Pointer to the pagedir being freed.
+ */
+void free_pagedir(struct pagedir * p)
+{
+	PRINTFREEMEM("at start of free_pagedir");
+
+	if (p->allocdranges.first) {
+		/* Free allocated pages */
+		struct range * rangepointer;
+		unsigned long pagenumber;
+		range_for_each(&p->allocdranges, rangepointer, pagenumber) {
+			ClearPageNosave(mem_map+pagenumber);
+			free_page((unsigned long) page_address(mem_map+pagenumber));
+		}
+	}
+
+	suspend_store_free_mem(SUSPEND_FREE_EXTRA_PD1, 1);
+
+	/* For pagedir 2, destranges == origranges */
+	if (p->pagedir_num == 2)
+		p->destranges.first = NULL;
+	
+	put_range_chain(&p->origranges);
+	put_range_chain(&p->destranges);
+	put_range_chain(&p->allocdranges);
+
+	PRINTFREEMEM("at end of free_pagedir");
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_MEDIUM, 0,
+			"Pageset size was %d.\n", p->pageset_size);
+	p->pageset_size = 0;
+}
+
+/* PageInPagedir
+ *
+ * Description:	Determine whether a page is in a pagedir.
+ * Arguments:	struct pagedir *	The pagedir to search.
+ * 		struct page *		The page to look for.
+ * Result:	int			Bitmap of state:
+ * 					Bit 0: Source page
+ * 					Bit 1: Dest page
+ * 					Bit 2: Allocated
+ * 					(Should only result in 0, 1, 2 or 6).
+ */
+
+int PageInPagedir(struct pagedir * p, struct page * page)
+{
+	int page_sought = page - mem_map;
+	int result = 0;
+
+	if (p->origranges.first) {
+		struct range * rangepointer;
+		unsigned long pagenumber;
+		range_for_each(&p->origranges, rangepointer, pagenumber) {
+			if (pagenumber == page_sought)
+				result |=  1;
+			if (pagenumber >= page_sought)
+				break;
+		}
+	}
+
+	if (p->destranges.first) {
+		/* Free allocated pages */
+		struct range * rangepointer;
+		unsigned long pagenumber;
+		range_for_each(&p->destranges, rangepointer, pagenumber) {
+			if (pagenumber == page_sought)
+				result |= 2;
+			if (pagenumber >= page_sought)
+				break;
+		}
+	}
+
+	if (p->allocdranges.first) {
+		/* Free allocated pages */
+		struct range * rangepointer;
+		unsigned long pagenumber;
+		range_for_each(&p->allocdranges, rangepointer, pagenumber) {
+			if (pagenumber == page_sought)
+				result |= 4;
+			if (pagenumber >= page_sought)
+				break;
+		}
+	}
+
+	return result;
+}
+
+/* allocate_extra_pagedir_memory
+ *
+ * Description:	Allocate memory for making the atomic copy of pagedir1 in the
+ * 		case where it is bigger than pagedir2.
+ * Arguments:	struct pagedir *: 	The pagedir for which we should 
+ * 					allocate memory.
+ * 		int:			Size of pageset 1.
+ * 		int:			Size of pageset 2.
+ * Result:	int. Zero on success. One if unable to allocate enough memory.
+ */
+int allocate_extra_pagedir_memory(struct pagedir * p, int pageset_size,
+		int alloc_from)
+{
+	int num_to_alloc = pageset_size - alloc_from - p->allocdranges.size;
+	int j, order;
+
+	prepare_status(0, 0, "Preparing page directory.");
+
+	PRINTFREEMEM("at start of allocate_extra_pagedir_memory");
+
+	if (num_to_alloc < 1)
+		num_to_alloc = 0;
+
+	if (num_to_alloc) {
+		int num_added = 0, numnosaveallocated=0;
+		int origallocd = alloc_from + p->allocdranges.size;
+	
+		PRINTFREEMEM("prior to attempt");
+
+		order = generic_fls(num_to_alloc);
+		if (order >= MAX_ORDER)
+			order = MAX_ORDER - 1;
+
+		while (num_added < num_to_alloc) {
+			struct page * newpage;
+			unsigned long virt;
+			
+			while ((1 << order) > (num_to_alloc - num_added))
+				order--;
+
+			virt = get_grabbed_pages(order);
+			while ((!virt) && (order > 0)) {
+				order--;
+				virt = get_grabbed_pages(order);
+			}
+
+			if (!virt) {
+				p->pageset_size += num_added;
+				suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+					"   Allocated (extra) memory for pages"
+					" from %d-%d (%d pages).\n",
+					origallocd + 1, pageset_size, 
+					pageset_size - origallocd);
+				printk("Couldn't get enough yet."
+						" %d pages short.\n",
+						num_to_alloc - num_added);
+				PRINTFREEMEM("at abort of "
+					"allocate_extra_pagedir_memory");
+				suspend_store_free_mem(SUSPEND_FREE_EXTRA_PD1, 0);
+				return 1;
+			}
+
+			newpage = virt_to_page(virt);
+			suspend_store_free_mem(SUSPEND_FREE_EXTRA_PD1, 0);
+			for (j = 0; j < (1 << order); j++) {
+				SetPageNosave(newpage + j);
+				/* Pages will be freed one at a time. */
+				set_page_count(newpage + j, 1);
+				add_to_range_chain(&p->allocdranges, newpage - mem_map + j);
+				numnosaveallocated++;
+			}
+			suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0);
+			num_added+= (1 << order);
+		}
+		suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+			"   Allocated (extra) memory for pages "
+			"from %d-%d (%d pages).\n",
+			origallocd + 1, pageset_size, 
+			pageset_size - origallocd);
+	}
+
+	p->pageset_size = pageset_size;
+
+	suspend_store_free_mem(SUSPEND_FREE_EXTRA_PD1, 0);
+	PRINTFREEMEM("at end of allocate_extra_pagedir_memory");
+	return 0;
+}
+
+/* mark_pages_for_pageset2
+ *
+ * Description:	Mark unshared pages in processes not needed for suspend as
+ * 		being able to be written out in a separate pagedir.
+ * 		HighMem pages are simply marked as pageset2. They won't be
+ * 		needed during suspend.
+ */
+
+void mark_pages_for_pageset2(void)
+{
+	int i, numpageset2 = 0;
+	unsigned long flags;
+
+	if (max_mapnr != num_physpages) {
+		abort_suspend("mapnr is not expected");
+		return;
+	}
+	
+	clear_map(pageset2_map);
+
+	/* 
+	 * Note that we don't clear the map to begin with!
+	 * This is because if we eat memory, we loose track
+	 * of LRU pages that are still in use but taken off
+	 * the LRU. If I can figure out how the VM keeps
+	 * track of them, I might be able to tweak this a
+	 * little further and decrease pageset one's size
+	 * further.
+	 *
+	 * (Memory grabbing clears the pageset2 flag on
+	 * pages that are really freed!).
+	 */
+	
+
+	spin_lock_irqsave(&pagemap_lru_lock, flags);
+
+	do {
+		if (nr_inactive_pages) {
+			struct page * page;
+			list_for_each_entry(page, &inactive_list, lru)
+				SetPagePageset2(page);
+		}
+	} while(0);
+	
+	do {
+		if (nr_active_pages) {
+			struct page * page;
+			list_for_each_entry(page, &active_list, lru)
+				SetPagePageset2(page);
+		}
+	} while(0);
+
+	spin_unlock_irqrestore(&pagemap_lru_lock, flags);
+
+	/* Ensure range pages are not Pageset2 */
+	if (num_range_pages) {
+		if (get_rangepages_list())
+			return;
+
+		for (i = 1; i <= num_range_pages; i++) {
+			struct page * page;
+			page = virt_to_page(get_rangepages_list_entry(i));
+			// Must be assigned by the time recalc stats is called
+			if (PagePageset2(page)) {
+				suspend_message(SUSPEND_PAGESETS, SUSPEND_ERROR, 1,
+					"Pagedir[%d] was marked as pageset2 -"
+					" unmarking.\n", i);
+				ClearPagePageset2(page);
+				numpageset2--;
+			}
+		}
+	}
+
+	/* Finally, ensure that Slab pages are not Pageset2. */
+
+	for (i = 0; i < max_mapnr; i++) {
+		if (PageSlab(mem_map+i)) {
+			if (TestAndClearPagePageset2(mem_map+i)) {
+				//suspend_message(SUSPEND_PAGESETS, SUSPEND_ERROR, 1,
+				printk(
+					"Found page %d is slab page "
+						"but marked pageset 2.\n", i);
+				numpageset2--;
+			}
+		}
+	}
+}
+
+/* warmup_collision_cache
+ *
+ * Description:	Mark the pages which are used by the original kernel.
+ */
+void warmup_collision_cache(void) {
+	int i;
+	struct range * rangepointer = NULL;
+	unsigned long pagenumber;
+	
+	/* Allocatemap doesn't get deallocated because it's forgotten when we
+	 * copy PageDir1 back. It doesn't matter if it collides because it is
+	 * not used during the copy back itself.
+	 */
+	allocate_local_pageflags(&in_use_map, 0);
+	suspend_message(SUSPEND_IO, SUSPEND_VERBOSE, 1, "Setting up pagedir cache...");
+	for (i = 0; i < max_mapnr; i++)
+		ClearPageInUse(mem_map+i);
+
+	range_for_each(&pagedir_resume.origranges, rangepointer, pagenumber)
+		SetPageInUse(mem_map+pagenumber);
+}
+
+/* get_pageset1_load_addresses
+ * 
+ * Description: We check here that pagedir & pages it points to won't collide
+ * 		with pages where we're going to restore from the loaded pages
+ * 		later.
+ * Returns:	Zero on success, one if couldn't find enough pages (shouldn't
+ * 		happen).
+ */
+
+int get_pageset1_load_addresses(void)
+{
+	int i, nrdone = 0, result = 0;
+	void **eaten_memory = NULL, **this;
+	struct page * pageaddr = NULL;
+
+	/*
+	 * Because we're trying to make this work when we're saving as much
+	 * memory as possible we need to remember the pages we reject here
+	 * and then free them when we're done.
+	 */
+	
+	for(i=0; i < pagedir_resume.pageset_size; i++) {
+		while ((this = (void *) get_zeroed_page(GFP_ATOMIC))) {
+			memset(this, 0, PAGE_SIZE);
+			pageaddr = virt_to_page(this);
+			if (!PageInUse(pageaddr)) {
+				break;
+			}
+			*this = eaten_memory;
+			eaten_memory = this;
+		}
+		if (!this) {
+			abort_suspend("Error: Ran out of memory seeking locations for reloading data.");
+			result = 1;
+			break;
+		}
+		add_to_range_chain(&pagedir_resume.destranges, pageaddr - mem_map);
+		nrdone++;
+	}
+
+	/* Free unwanted memory */
+	while(eaten_memory) {
+		this = eaten_memory;
+		eaten_memory = *eaten_memory;
+		free_page((unsigned long) this);
+	}
+	
+	return result;
+}
+
+/* set_chain_names
+ *
+ * Description:	Set the chain names for a pagedir. (For debugging).
+ * Arguments:	struct pagedir: The pagedir on which we want to set the names.
+ */
+
+void set_chain_names(struct pagedir * p)
+{
+	p->origranges.name = "original addresses";
+	p->destranges.name =  "destination addresses";
+	p->allocdranges.name = "allocated addresses";
+}
+
+EXPORT_SYMBOL(get_first_pbe);
+EXPORT_SYMBOL(get_next_pbe);
diff -ruN 20-old/kernel/power/pageflags.h 20-new/kernel/power/pageflags.h
--- 20-old/kernel/power/pageflags.h	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/pageflags.h	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,80 @@
+/*
+ * kernel/power/pageflags.h
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * Suspend2 needs a few pageflags while working that aren't otherwise
+ * used. To save the struct page pageflags, we dynamically allocate
+ * a bitmap and use that. These are the only non order-0 allocations
+ * we do.
+ */
+extern unsigned long * in_use_map;
+extern unsigned long * pageset2_map;
+extern unsigned long * checksum_map;
+#ifdef CONFIG_DEBUG_PAGEALLOC
+extern unsigned long * unmap_map;
+#endif
+
+#define PAGENUMBER(page) (page-mem_map)
+#define PAGEINDEX(page) ((PAGENUMBER(page))/(8*sizeof(unsigned long)))
+#define PAGEBIT(page) ((int) ((PAGENUMBER(page))%(8 * sizeof(unsigned long))))
+
+/* 
+ * freepagesmap is used in two ways: 
+ * - During suspend, to tag pages which are not used (to speed up 
+ *   count_data_pages);
+ * - During resume, to tag pages which are in pagedir1. This does not tag 
+ *   pagedir2 pages, so !== first use.
+ */
+#define PageInUse(page) \
+	test_bit(PAGEBIT(page), &in_use_map[PAGEINDEX(page)])
+#define SetPageInUse(page) \
+	set_bit(PAGEBIT(page), &in_use_map[PAGEINDEX(page)])
+#define ClearPageInUse(page) \
+	clear_bit(PAGEBIT(page), &in_use_map[PAGEINDEX(page)])
+
+#define PagePageset2(page) \
+	(pageset2_map ? \
+		test_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)]) : \
+		0)
+			
+#define SetPagePageset2(page) \
+	set_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)])
+#define TestAndSetPagePageset2(page) \
+	test_and_set_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)])
+#define TestAndClearPagePageset2(page) \
+	test_and_clear_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)])
+#define ClearPagePageset2(page)	\
+do { \
+	if (pageset2_map) \
+		clear_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)]); \
+} while(0)
+
+#define PageChecksumIgnore(page) \
+	(checksum_map ? \
+		test_bit(PAGEBIT(page), &checksum_map[PAGEINDEX(page)]) : \
+		0)
+
+#define SetPageChecksumIgnore(page) \
+do { \
+	if (checksum_map) \
+		set_bit(PAGEBIT(page), &checksum_map[PAGEINDEX(page)]); \
+} while(0)
+
+#define ClearPageChecksumIgnore(page) \
+do { \
+	if (checksum_map) \
+		clear_bit(PAGEBIT(page), &checksum_map[PAGEINDEX(page)]); \
+} while(0)
+
+
+#define SetPageUnmap(page) \
+	set_bit(PAGEBIT(page), &unmap_map[PAGEINDEX(page)])
+#define PageUnmap(page) \
+	test_bit(PAGEBIT(page), &unmap_map[PAGEINDEX(page)])
+
+extern int allocate_local_pageflags(unsigned long ** pagemap, int setnosave);
+extern int free_local_pageflags(unsigned long ** pagemap);
+extern void clear_map(unsigned long * pagemap);
diff -ruN 20-old/kernel/power/plugins.c 20-new/kernel/power/plugins.c
--- 20-old/kernel/power/plugins.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/plugins.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,377 @@
+/*
+ * kernel/power/plugin.c
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ */
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+
+#include "suspend.h"
+#include "plugins.h"
+
+struct list_head suspend_filters, suspend_writers, suspend_plugins, suspend_ui;
+int num_filters = 0, num_writers = 0, num_ui = 0, num_plugins = 0;
+struct suspend_plugin_ops * active_writer = NULL;
+struct suspend_plugin_ops * checksum_plugin = NULL;
+
+/*
+ * header_storage_for_plugins
+ *
+ * Returns the amount of space needed to store configuration
+ * data needed by the plugins prior to copying back the original
+ * kernel. We can exclude data for pageset2 because it will be
+ * available anyway once the kernel is copied back.
+ */
+unsigned long header_storage_for_plugins(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+	unsigned long bytes = 0;
+	
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->storage_needed)
+			bytes += this_plugin->storage_needed();
+	}
+
+	return bytes;
+}
+
+/*
+ * expected_compression_ratio
+ *
+ * Returns the expected ratio between the amount of memory
+ * to be saved and the amount of space required on the
+ * storage device.
+ */
+int expected_compression_ratio(void)
+{
+	struct suspend_plugin_ops * this_filter;
+	unsigned long ratio = 100;
+	
+	list_for_each_entry(this_filter, &suspend_filters, ops.filter.filter_list) {
+		if (this_filter->disabled)
+			continue;
+		if (this_filter->ops.filter.expected_compression)
+			ratio = ratio * this_filter->ops.filter.expected_compression() / 100;
+	}
+
+	return (int) ratio;
+}
+
+/*
+ * memory_for_plugins
+ *
+ * Returns the amount of memory requested by plugins for
+ * doing their work during the cycle.
+ */
+
+unsigned long memory_for_plugins(void)
+{
+	unsigned long bytes = 0;
+	struct suspend_plugin_ops * this_plugin;
+
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->memory_needed)
+			bytes += this_plugin->memory_needed();
+	}
+
+	return ((bytes + PAGE_SIZE - 1) >> PAGE_SHIFT);
+}
+
+/* suspend_early_boot_message_plugins
+ *
+ * Call early_boot_message methods for plugins.
+ */
+void suspend_early_boot_message_plugins(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.early_boot_message_prep)
+			this_plugin->ops.ui.early_boot_message_prep();
+	}
+}
+
+/* suspend_plugin_keypress
+ *
+ * Pass the keycode to plugins until one handles it.
+ */
+void suspend_plugin_keypress(unsigned int keycode)
+{
+	struct suspend_plugin_ops * this_plugin;
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.keypress)
+			if (this_plugin->ops.ui.keypress(keycode))
+				return;
+	}
+}
+
+/* post_kernel_restore_redraw
+ *
+ * Call UI plugins to allow them to redraw the screen after a restoration
+ * of the original kernel
+ */
+
+/* suspend_ui.c generic routine */
+void post_resume_console_redraw(void);
+
+void suspend_post_restore_redraw(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+
+	post_resume_console_redraw();
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.post_kernel_restore_redraw)
+			this_plugin->ops.ui.post_kernel_restore_redraw();
+	}
+}
+
+
+/* find_plugin_given_name
+ * Functionality :	Return a plugin (if found), given a pointer
+ * 			to its name
+ */
+
+struct suspend_plugin_ops * find_plugin_given_name(char * name)
+{
+	struct suspend_plugin_ops * this_plugin, * found_plugin = NULL;
+	
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+		if (!strcmp(name, this_plugin->name)) {
+			found_plugin = this_plugin;
+			break;
+		}			
+	}
+
+	return found_plugin;
+}
+
+/*
+ * print_plugin_debug_info
+ * Functionality   : Get debugging info from plugins into a buffer.
+ */
+int print_plugin_debug_info(char * buffer, int buffer_size)
+{
+	struct suspend_plugin_ops *this_plugin;
+	int len = 0;
+
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->print_debug_info) {
+			int result;
+			result = this_plugin->print_debug_info(buffer + len, 
+					buffer_size - len);
+			len += result;
+		}
+	}
+
+	return len;
+}
+
+extern int attempt_to_parse_resume_device(void);
+
+int suspend_initialise_plugin_lists(void) {
+	INIT_LIST_HEAD(&suspend_filters);
+	INIT_LIST_HEAD(&suspend_writers);
+	INIT_LIST_HEAD(&suspend_ui);
+	INIT_LIST_HEAD(&suspend_plugins);
+	return 0;
+}
+
+int suspend_register_plugin(struct suspend_plugin_ops * plugin)
+{
+	if (!num_plugins)
+		suspend_initialise_plugin_lists();
+	
+	if (find_plugin_given_name(plugin->name))
+		return -EBUSY;
+
+	switch (plugin->type) {
+		case FILTER_PLUGIN:
+			list_add_tail(&plugin->ops.filter.filter_list,
+					&suspend_filters);
+			num_filters++;
+			break;
+
+		case WRITER_PLUGIN:
+			list_add_tail(&plugin->ops.writer.writer_list,
+					&suspend_writers);
+			num_writers++;
+			if ((!active_writer) &&
+			    (!(test_suspend_state(SUSPEND_BOOT_TIME))))
+				attempt_to_parse_resume_device();
+			break;
+
+		case UI_PLUGIN:
+			list_add_tail(&plugin->ops.ui.ui_list,
+					&suspend_ui);
+			num_ui++;
+			break;
+
+		case MISC_PLUGIN:
+			break;
+
+		case CHECKSUM_PLUGIN:
+			if (!checksum_plugin)
+				checksum_plugin = plugin;
+			else
+				printk("Checksum plugin already registered!");
+			break;
+				
+		default:
+			printk("Hmmm. Plugin '%s' has an invalid type."
+				" It has been ignored.\n", plugin->name);
+			return -EINVAL;
+	}
+	list_add_tail(&plugin->plugin_list, &suspend_plugins);
+	num_plugins++;
+
+	return 0;	
+}
+
+void suspend_unregister_plugin(struct suspend_plugin_ops * plugin)
+{
+	switch (plugin->type) {
+		case FILTER_PLUGIN:
+			list_del(&plugin->ops.filter.filter_list);
+			num_filters--;
+			break;
+
+		case WRITER_PLUGIN:
+			list_del(&plugin->ops.writer.writer_list);
+			num_writers--;
+			if (active_writer == plugin)
+				attempt_to_parse_resume_device();
+			break;
+		
+		case UI_PLUGIN:
+			list_del(&plugin->ops.ui.ui_list);
+			num_ui--;
+			break;
+		
+		case MISC_PLUGIN:
+			break;
+
+		case CHECKSUM_PLUGIN:
+			if (plugin == checksum_plugin)
+				checksum_plugin = NULL;
+			break;
+		default:
+			printk("Hmmm. Plugin '%s' has an invalid type."
+				" It has been ignored.\n", plugin->name);
+			return;
+	}
+	list_del(&plugin->plugin_list);
+	num_plugins--;
+}
+
+void suspend_move_plugin_tail(struct suspend_plugin_ops * plugin)
+{
+	switch (plugin->type) {
+		case FILTER_PLUGIN:
+			if (num_filters > 1)
+				list_move_tail(&plugin->ops.filter.filter_list,
+						&suspend_filters);
+			break;
+
+		case WRITER_PLUGIN:
+			if (num_writers > 1)
+				list_move_tail(&plugin->ops.writer.writer_list,
+						&suspend_writers);
+			break;
+		
+		case UI_PLUGIN:
+			if (num_ui > 1)
+				list_move_tail(&plugin->ops.ui.ui_list,
+						&suspend_ui);
+			break;
+		
+		case MISC_PLUGIN:
+			break;
+		default:
+			printk("Hmmm. Plugin '%s' has an invalid type."
+				" It has been ignored.\n", plugin->name);
+			return;
+	}
+	if ((num_filters + num_writers + num_ui) > 1)
+		list_move_tail(&plugin->plugin_list, &suspend_plugins);
+}
+
+int initialise_suspend_plugins(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+	int result;
+	
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->initialise) {
+			suspend_message(SUSPEND_MEMORY, SUSPEND_MEDIUM, 1,
+				"Initialising plugin %s.\n",
+				this_plugin->name);
+			if ((result = this_plugin->initialise()))
+				return result;
+		}
+		PRINTFREEMEM("after initialising plugin");
+	}
+
+	return 0;
+}
+
+void cleanup_suspend_plugins(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+	
+	list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->cleanup) {
+			suspend_message(SUSPEND_MEMORY, SUSPEND_MEDIUM, 1,
+				"Cleaning up plugin %s.\n",
+				this_plugin->name);
+			this_plugin->cleanup();
+			PRINTFREEMEM("after cleaning up plugin");
+		}
+	}
+}
+
+struct suspend_plugin_ops * 
+get_next_filter(struct suspend_plugin_ops * filter_sought)
+{
+	struct suspend_plugin_ops * last_filter = NULL, *this_filter = NULL;
+
+	list_for_each_entry(this_filter, &suspend_filters, ops.filter.filter_list) {
+		if (this_filter->disabled)
+			continue;
+		if ((last_filter == filter_sought) || (!filter_sought))
+			return this_filter;
+		last_filter = this_filter;
+	}
+
+	return active_writer;
+}
+
+EXPORT_SYMBOL(get_next_filter);
+EXPORT_SYMBOL(suspend_register_plugin);
+EXPORT_SYMBOL(suspend_unregister_plugin);
+EXPORT_SYMBOL(max_async_ios);
+EXPORT_SYMBOL(active_writer);
+EXPORT_SYMBOL(suspend_filters);
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+EXPORT_SYMBOL(suspend_store_free_mem);
+#endif
+EXPORT_SYMBOL(suspend_post_restore_redraw);
diff -ruN 20-old/kernel/power/plugins.h 20-new/kernel/power/plugins.h
--- 20-old/kernel/power/plugins.h	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/plugins.h	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,205 @@
+/*
+ * kernel/power/plugin.h
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * It contains declarations for plugins. Plugins are additions to
+ * suspend2 that provide facilities such as image compression or
+ * encryption, backends for storage of the image and user interfaces.
+ *
+ */
+
+/* This is the maximum size we store in the image header for a plugin name */
+#define SUSPEND_MAX_PLUGIN_NAME_LENGTH 30
+
+struct plugin_header {
+	char name[SUSPEND_MAX_PLUGIN_NAME_LENGTH];
+	int disabled;
+	int type;
+	int index;
+	int data_length;
+	unsigned long magic;
+};
+
+extern unsigned long memory_for_plugins(void);
+extern int num_plugins;
+
+#define FILTER_PLUGIN 1
+#define WRITER_PLUGIN 2
+#define UI_PLUGIN 3
+#define MISC_PLUGIN 4 // Block writer, eg.
+#define CHECKSUM_PLUGIN 5
+
+#define SUSPEND_ASYNC 0
+#define SUSPEND_SYNC  1
+
+#define SUSPEND_COMMON_IO_OPS \
+	/* Writing the image proper */ \
+	int (*write_init) (int stream_number); \
+	int (*write_chunk) (struct page * buffer_page); \
+	int (*write_cleanup) (void); \
+\
+	/* Reading the image proper */ \
+	int (*read_init) (int stream_number); \
+	int (*read_chunk) (struct page * buffer_page, int sync); \
+	int (*read_cleanup) (void); \
+\
+	/* Reset plugin if image exists but reading aborted */ \
+	void (*noresume_reset) (void);
+
+struct suspend_filter_ops {
+	SUSPEND_COMMON_IO_OPS
+	int (*expected_compression) (void);
+	struct list_head filter_list;
+};
+
+struct suspend_writer_ops {
+	
+	SUSPEND_COMMON_IO_OPS
+
+	/* Calls for allocating storage */
+
+	long (*storage_available) (void); // Maximum size of image we can save
+					  // (incl. space already allocated).
+	
+	unsigned long (*storage_allocated) (void);
+					// Amount of storage already allocated
+	int (*release_storage) (void);
+	
+	/* 
+	 * Header space is allocated separately. Note that allocation
+	 * of space for the header might result in allocated space 
+	 * being stolen from the main pool if there is no unallocated
+	 * space. We have to be able to allocate enough space for
+	 * the header. We can eat memory to ensure there is enough
+	 * for the main pool.
+	 */
+	long (*allocate_header_space) (unsigned long space_requested);
+	int (*allocate_storage) (unsigned long space_requested);
+	
+	/* Read and write the metadata */	
+	int (*write_header_init) (void);
+	int (*write_header_chunk) (char * buffer_start, int buffer_size);
+	int (*write_header_cleanup) (void);
+
+	int (*read_header_init) (void);
+	int (*read_header_chunk) (char * buffer_start, int buffer_size);
+	int (*read_header_cleanup) (void);
+
+	/* Prepare metadata to be saved (relativise/absolutise ranges) */
+	int (*prepare_save_ranges) (void);
+	int (*post_load_ranges) (void);
+	
+	/* Attempt to parse an image location */
+	int (*parse_image_location) (char * buffer, int only_writer);
+
+	/* Determine whether image exists that we can restore */
+	int (*image_exists) (void);
+	
+	/* Mark the image as having tried to resume */
+	void (*mark_resume_attempted) (void);
+
+	/* Destroy image if one exists */
+	int (*invalidate_image) (void);
+	
+	/* Wait on I/O */
+	int (*wait_on_io) (int flush_all);
+
+	struct list_head writer_list;
+};
+
+struct suspend_ui_ops {
+	void (*early_boot_message_prep) (void);
+	void (*prepare) (void);
+	void (*message) (
+		unsigned long type, unsigned long level,
+		int normally_logged,
+		const char *format, va_list args);
+	void (*log_level_change) (void);
+	unsigned long (*update_progress) (
+		unsigned long value, unsigned long maximum,
+		const char *fmt, va_list args);
+	void (*cleanup) (void);
+	int (*keypress) (unsigned int key);
+	void (*post_kernel_restore_redraw) (void);
+
+	struct list_head ui_list;
+};
+
+struct suspend_checksum_ops {
+	void (*calculate_checksums) (void);
+	void (*check_checksums) (void);
+	void (*print_differences) (void);
+	int (*allocate_pages) (void);
+};
+
+struct suspend_plugin_ops {
+	/* Functions common to all plugins */
+	int type;
+	char * name;
+	int disabled;
+	struct list_head plugin_list;
+	unsigned long (*memory_needed) (void);
+	unsigned long (*storage_needed) (void);
+	int (*print_debug_info) (char * buffer, int size);
+	int (*save_config_info) (char * buffer);
+	void (*load_config_info) (char * buffer, int len);
+	
+	/* Initialise & cleanup - general routines called
+	 * at the start and end of a cycle. */
+	int (*initialise) (void);
+	void (*cleanup) (void);
+
+	/* Set list of devices not to be suspended/resumed */
+	void (*dpm_set_devices) (void);
+
+	union {
+		struct suspend_filter_ops filter;
+		struct suspend_writer_ops writer;
+		struct suspend_ui_ops ui;
+		struct suspend_checksum_ops checksum;
+	} ops;
+};
+
+extern struct suspend_plugin_ops * active_writer;
+extern struct list_head suspend_filters, suspend_writers, suspend_plugins, suspend_ui;
+extern struct suspend_plugin_ops * checksum_plugin;
+extern void prepare_console_plugins(void);
+extern void cleanup_console_plugins(void);
+extern struct suspend_plugin_ops * find_plugin_given_name(char * name);
+extern struct suspend_plugin_ops * get_next_filter(struct suspend_plugin_ops *);
+extern int suspend_register_plugin(struct suspend_plugin_ops * plugin);
+extern void suspend_move_plugin_tail(struct suspend_plugin_ops * plugin);
+
+extern int initialise_suspend_plugins(void);
+extern void cleanup_suspend_plugins(void);
+extern unsigned long header_storage_for_plugins(void);
+extern unsigned long memory_for_plugins(void);
+extern int print_plugin_debug_info(char * buffer, int buffer_size);
+extern int suspend_register_plugin(struct suspend_plugin_ops * plugin);
+extern void suspend_unregister_plugin(struct suspend_plugin_ops * plugin);
+extern int initialise_suspend_plugins(void);
+extern void cleanup_suspend_plugins(void);
+extern void suspend_post_restore_redraw(void);
+
+static inline void suspend_checksum_calculate_checksums(void)
+{
+	if (checksum_plugin)
+		checksum_plugin->ops.checksum.calculate_checksums();
+}
+
+static inline void suspend_checksum_print_differences(void)
+{
+	if (checksum_plugin)
+		checksum_plugin->ops.checksum.print_differences();
+}
+
+static inline int suspend_allocate_checksum_pages(void)
+{
+	if (checksum_plugin)
+		return checksum_plugin->ops.checksum.allocate_pages();
+	else
+		return 0;
+}
diff -ruN 20-old/kernel/power/prepare_image.c 20-new/kernel/power/prepare_image.c
--- 20-old/kernel/power/prepare_image.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/prepare_image.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,1033 @@
+/*
+ * kernel/power/prepare_image.c
+ *
+ * Copyright (C) 2003-2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * We need to eat memory until we can:
+ * 1. Perform the save without changing anything (RAM_NEEDED < max_mapnr)
+ * 2. Fit it all in available space (active_writer->available_space() >= STORAGE_NEEDED)
+ * 3. Reload the pagedir and pageset1 to places that don't collide with their
+ *    final destinations, not knowing to what extent the resumed kernel will
+ *    overlap with the one loaded at boot time. I think the resumed kernel should overlap
+ *    completely, but I don't want to rely on this as it is an unproven assumption. We
+ *    therefore assume there will be no overlap at all (worse case).
+ * 4. Meet the user's requested limit (if any) on the size of the image.
+ *    The limit is in MB, so pages/256 (assuming 4K pages).
+ *
+ *    (Final test in save_image doesn't use EATEN_ENOUGH_MEMORY)
+ */
+
+#define SUSPEND_PREPARE_IMAGE_C
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/suspend.h>
+#include <linux/notifier.h>
+
+#include "suspend.h"
+#include "pageflags.h"
+#include "plugins.h"
+#include "proc.h"
+
+extern int pageset1_sizelow, pageset2_sizelow;
+extern unsigned long orig_mem_free;
+extern void mark_pages_for_pageset2(void);
+extern int image_size_limit;
+extern int fill_suspend_memory_pool(int sizesought);
+
+int suspend_amount_grabbed = 0;
+static int arefrozen = 0, numnosave = 0;
+static int header_space_allocated = 0;
+extern unsigned long forced_ps1_size, forced_ps2_size;
+
+/*
+ * generate_free_page_map
+ *
+ * Description:	This routine generates a bitmap of free pages from the
+ * 		lists used by the memory manager. We then use the bitmap
+ * 		to quickly calculate which pages to save and in which
+ * 		pagesets.
+ */
+static void generate_free_page_map(void) 
+{
+	int i, order, loop;
+	struct page * page;
+	unsigned long flags;
+	zone_t *zone;
+
+	for(i=0; i < max_mapnr; i++)
+		SetPageInUse(mem_map+i);
+	
+	for_each_zone(zone) {
+		if (!zone->free_pages)
+			continue;
+		spin_lock_irqsave(&zone->lock, flags);
+		for (order = MAX_ORDER - 1; order >= 0; --order) {
+			list_for_each_entry(page, &zone->free_area[order].free_list, list)
+				for(loop=0; loop < (1 << order); loop++) {
+					ClearPageInUse(page+loop);
+					ClearPagePageset2(page+loop);
+				}
+		}
+		spin_unlock_irqrestore(&zone->lock, flags);
+	}
+}
+
+/* size_of_free_region
+ * 
+ * Description:	Return the number of pages that are free, beginning with and 
+ * 		including this one.
+ */
+static int size_of_free_region(struct page * page)
+{
+	struct page * posn = page;
+
+	while (((posn-mem_map) < max_mapnr) && (!PageInUse(posn))) 
+		posn++;
+	return (posn - page);
+}
+
+static void display_reserved_pages(void)
+{
+	int loop;
+	int rangemin = -1;
+
+	for (loop = 0; loop < max_mapnr; loop++) {
+		if (PageReserved(mem_map+loop)) {
+			if (rangemin == -1)
+				rangemin = loop;
+		} else {
+			if (rangemin > -1) {
+				printk("Reserved pages from %p to %p.\n",
+					page_address(mem_map+rangemin),
+					((char *) page_address(mem_map + loop)) - 1);
+				rangemin = -1;
+			}
+		}
+	}
+
+	if (rangemin > -1)
+		printk("Reserved pages from %p to %p.\n",
+			page_address(mem_map+rangemin),
+			((char *) page_address(mem_map + max_mapnr)) - 1);
+}
+
+/* 
+ * Description:	Display which pages are marked Nosave.
+ */
+void display_nosave_pages(void)
+{
+	int loop;
+	int rangemin = -1;
+
+	if (!TEST_DEBUG_STATE(SUSPEND_NOSAVE))
+		return;
+
+	display_reserved_pages();
+
+	for (loop = 0; loop < max_mapnr; loop++) {
+		if (PageNosave(mem_map+loop)) {
+			if (rangemin == -1)
+				rangemin = loop;
+		} else {
+			if (rangemin > -1) {
+				printk("Nosave pages from %p to %p.\n",
+					page_address(mem_map+rangemin),
+					((char *) page_address(mem_map + loop)) - 1);
+				rangemin = -1;
+			}
+		}
+	}
+
+	if (rangemin > -1)
+		printk("Nosave pages from %p to %p.\n",
+			page_address(mem_map+rangemin),
+			((char *) page_address(mem_map + max_mapnr)) - 1);
+}
+
+/*
+ * count_data_pages
+ *
+ * This routine generates our lists of pages to be stored in each
+ * pageset. Since we store the data using ranges, and adding new
+ * ranges might allocate a new range page, this routine may well
+ * be called more than once.
+ */
+static struct pageset_sizes_result count_data_pages(void)
+{
+	int chunk_size, loop, numfree = 0;
+	int ranges = 0, currentrange = 0;
+	int usepagedir2;
+	int rangemin = 0;
+	struct pageset_sizes_result result;
+	struct range * rangepointer;
+	unsigned long value;
+#ifdef CONFIG_HIGHMEM
+	unsigned long highstart_pfn = get_highstart_pfn();
+#endif
+
+	result.size1 = 0;
+	result.size1low = 0;
+	result.size2 = 0;
+	result.size2low = 0;
+	result.needmorespace = 0;
+
+	numnosave = 0;
+
+	put_range_chain(&pagedir1.origranges);
+	put_range_chain(&pagedir1.destranges);
+	put_range_chain(&pagedir2.origranges);
+	pagedir2.destranges.first = NULL;
+	pagedir2.destranges.size = 0;
+
+	generate_free_page_map();
+
+	if (TEST_RESULT_STATE(SUSPEND_ABORTED)) {
+		result.size1 = -1;
+		result.size1low = -1;
+		result.size2 = -1;
+		result.size2low = -1;
+		result.needmorespace = 0;
+		return result;
+	}
+
+	if (max_mapnr != num_physpages) {
+		abort_suspend("Max_mapnr is not equal to num_physpages.");
+		result.size1 = -1;
+		result.size1low = -1;
+		result.size2 = -1;
+		result.size2low = -1;
+		result.needmorespace = 0;
+		return result;
+	}
+	/*
+	 * Pages not to be saved are marked Nosave irrespective of being reserved
+	 */
+	for (loop = 0; loop < max_mapnr; loop++) {
+		if (PageNosave(mem_map+loop)) {
+			numnosave++;
+			if (currentrange) {
+				append_to_range_chain(currentrange, rangemin, loop - 1);
+				rangemin = loop;
+				currentrange = 0;
+			}
+			continue;
+		}
+
+		if (!PageReserved(mem_map+loop)) {
+			if ((chunk_size=size_of_free_region(mem_map+loop))!=0) {
+				if (currentrange) {
+					append_to_range_chain(currentrange, rangemin, loop - 1);
+					rangemin = loop;
+					currentrange = 0;
+				}
+				numfree += chunk_size;
+				loop += chunk_size - 1;
+				continue;
+			}
+		} else {
+#ifdef CONFIG_HIGHMEM
+			if (loop >= highstart_pfn) {
+				/* HighMem pages may be marked Reserved. We ignore them. */
+				numnosave++;
+				if (currentrange) {
+					append_to_range_chain(currentrange, rangemin, loop - 1);
+					rangemin = loop;
+					currentrange = 0;
+				}
+				continue;
+			}
+#endif
+		};
+
+		usepagedir2 = !!PagePageset2(mem_map+loop);
+
+		if (currentrange != (1 + usepagedir2)) {
+			if (currentrange)
+				append_to_range_chain(currentrange, rangemin, loop - 1);
+			currentrange = usepagedir2 + 1;
+			rangemin = loop;
+			ranges++;
+		}
+		
+		if (usepagedir2) {
+			result.size2++;
+			if (!PageHighMem(mem_map+loop))
+				result.size2low++;
+		} else {
+			result.size1++;
+			if (!PageHighMem(mem_map+loop))
+				result.size1low++;
+		}
+	}
+	
+	if (currentrange)
+		append_to_range_chain(currentrange, rangemin, loop - 1);
+
+	if ((pagedir1.pageset_size) && (result.size1 > pagedir1.pageset_size))
+		result.needmorespace = 1;
+	if ((pagedir2.pageset_size) && (result.size2 > pagedir2.pageset_size))
+		result.needmorespace = 1;
+	suspend_message(SUSPEND_RANGES, SUSPEND_MEDIUM, 0, "Counted %d ranges.\n", ranges);
+	pagedir2.destranges.first = pagedir2.origranges.first;
+	pagedir2.destranges.size = pagedir2.origranges.size;
+	range_for_each(&pagedir1.allocdranges, rangepointer, value) {
+		add_to_range_chain(&pagedir1.destranges, value);
+	}
+
+	suspend_message(SUSPEND_EAT_MEMORY, SUSPEND_MEDIUM, 0,
+		"Count data pages: Set1 (%d) + Set2 (%d) + Nosave (%d) + NumFree (%d) = %d.\n",
+		result.size1, result.size2, numnosave, numfree,
+		result.size1 + result.size2 + numnosave + numfree);
+	return result;
+}
+
+#ifndef CONFIG_HIGHMEM
+#define nr_free_highpages() (0)
+#endif
+
+/* amount_needed
+ *
+ * Calculates the amount by which the image size needs to be reduced to meet
+ * our constraints.
+ */
+static int amount_needed(int use_image_size_limit)
+{
+
+	int max1 = max( (int) (RAM_TO_SUSPEND - nr_free_pages() - 
+			  nr_free_highpages() - suspend_amount_grabbed),
+			((int) (STORAGE_NEEDED(1) -  
+			  active_writer->ops.writer.storage_available())));
+	if (use_image_size_limit)
+		return max( max1,
+			    (image_size_limit > 0) ? 
+			    ((int) (STORAGE_NEEDED(1) - (image_size_limit << 8))) : 0);
+	return max1;
+}
+
+#define EATEN_ENOUGH_MEMORY() (amount_needed(1) < 1)
+unsigned long storage_available = 0;
+
+/* display_stats
+ *
+ * Display the vital statistics.
+ */
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+static void display_stats(void)
+{ 
+	unsigned long storage_allocated = active_writer->ops.writer.storage_allocated();
+	suspend_message(SUSPEND_EAT_MEMORY, SUSPEND_MEDIUM, 1,
+		"Free:%d+%d+%d=%d(%d). Sets:%d(%d),%d(%d). Header:%d. Nosave:%d-%d-%d=%d. Storage:%d/%lu(%lu). Needed:%d|%d|%d.\n", 
+		
+		/* Free */
+		nr_free_pages(), suspend_amount_grabbed, suspend_memory_pool_level(0),
+		nr_free_pages() + suspend_amount_grabbed + suspend_memory_pool_level(0),
+		nr_free_pages() - nr_free_highpages(),
+		
+		/* Sets */
+		pageset1_size, pageset1_sizelow,
+		pageset2_size, pageset2_sizelow,
+
+		/* Header */
+		num_range_pages,
+
+		/* Nosave */
+		numnosave, pagedir1.allocdranges.size, suspend_amount_grabbed,
+		numnosave - pagedir1.allocdranges.size - suspend_amount_grabbed,
+
+		/* Storage - converted to pages for comparison */
+		storage_allocated,
+		STORAGE_NEEDED(1),
+		storage_available,
+
+		/* Needed */
+		RAM_TO_SUSPEND - nr_free_pages() - nr_free_highpages() - suspend_amount_grabbed,
+		STORAGE_NEEDED(1) - storage_available, 
+		(image_size_limit > 0) ? (STORAGE_NEEDED(1) - (image_size_limit << 8)) : 0);
+}
+#else
+#define display_stats() do { } while(0)
+#endif
+
+struct bloat_pages {
+	struct bloat_pages * next;
+	int order;
+};
+
+static struct bloat_pages * bloat_pages = NULL;
+
+void free_pageset_size_bloat(void)
+{
+	while (bloat_pages) {
+		struct bloat_pages * next = bloat_pages->next;
+		free_pages((unsigned long) bloat_pages, bloat_pages->order);
+		bloat_pages = next;
+	}
+}
+
+#define redo_counts() \
+{ \
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, \
+		"Recalculating counts. Currently %ld & %ld. ", \
+		ps1_get, ps2_get);  \
+	result = count_data_pages(); \
+	if (forced_ps1_size) \
+		ps1_get = forced_ps1_size - result.size1 - drop_one; \
+	if (forced_ps2_size) \
+		ps2_get = forced_ps2_size - result.size2; \
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, \
+		"Now %ld and %ld.\n", ps1_get, ps2_get); \
+}
+
+void increase_pageset_size(struct pageset_sizes_result result)
+{
+	long ps1_get = 0, ps2_get = 0, order, j;
+	int drop_one = 0;
+
+	if (forced_ps1_size)
+		ps1_get = forced_ps1_size - result.size1;
+
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_HIGH, 1,
+		"1: Forced size = %ld. Have %d -> ps1_get = %ld.\n",
+		forced_ps1_size, result.size1, ps1_get);
+	
+	/* 
+	 * We can make ps2 size exactly what was requested, but
+	 * not both.
+	 */
+	if (forced_ps2_size) {
+		ps2_get = forced_ps2_size - result.size2;
+		suspend_message(SUSPEND_PAGESETS, SUSPEND_HIGH, 1,
+			"2: Forced size = %ld. Have %d -> ps2_get = %ld.\n",
+			forced_ps2_size, result.size2, ps2_get);
+
+		if (ps2_get > 0) {
+			order = generic_fls(ps2_get);
+			if (order >= MAX_ORDER)
+				order = MAX_ORDER - 1;
+
+			while(ps2_get > 0) {
+				struct page * newpage;
+				unsigned long virt;
+				struct bloat_pages * link;
+			
+				if ((ps1_get - (1 << order)) < (1 << order))
+					redo_counts();
+				
+				while ((1 << order) > (ps2_get))
+					order--;
+
+				virt = get_grabbed_pages(order);
+
+				while ((!virt) && (order > 0)) {
+					order--;
+					if ((ps1_get - (1 << order)) < (1 << order))
+						redo_counts();
+					virt = get_grabbed_pages(order);
+				}
+
+				if (!virt) {
+					suspend_message(SUSPEND_PAGESETS, SUSPEND_MEDIUM, 1,
+						" Failed to allocate enough memory for"
+						" requested pageset sizes.\n");
+					return;
+				}
+	
+				newpage = virt_to_page(virt);
+				for (j = 0; j < (1 << order); j++)
+					SetPagePageset2(newpage + j);
+
+				link = (struct bloat_pages *) virt;
+				link->next = bloat_pages;
+				link->order = order;
+				bloat_pages = link;
+
+				ps2_get -= (1 << order);
+				suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+					"Allocated %d for ps2. To get %ld.\n",
+					1 << order, ps2_get);
+			}
+		} else
+		{
+			/* Here, we're making ps2 pages into ps1 pages */
+			int i;
+
+			suspend_message(SUSPEND_PAGESETS, SUSPEND_HIGH, 1,
+				"Moving %ld ps2 pages to ps1.\n", -ps2_get);
+			for (i = 0; i < max_mapnr; i++) {
+				if PagePageset2(mem_map + i) {
+					ClearPagePageset2(mem_map + i);
+					ps2_get++;
+					ps1_get--;
+				}
+				if (!ps2_get)
+					break;
+			}
+		}
+	} else {
+		suspend_message(SUSPEND_PAGESETS, SUSPEND_HIGH, 1,
+			"2: Forced size = %ld. Have %d -> ps2_get = %ld.\n",
+			forced_ps2_size, result.size2, ps2_get);
+	}
+	
+	if (ps1_get > 0) {
+
+		suspend_message(SUSPEND_PAGESETS, SUSPEND_HIGH, 1,
+			"Still to get %ld pages for ps1.\n", ps1_get);
+		
+		/* We might allocate an extra range page later. */
+		if (ps1_get > 1) {
+			suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+				"Reducing ps1_get by one.\n");
+			drop_one = 1;
+			ps1_get--;
+		}
+		
+		order = generic_fls(ps1_get);
+		if (order >= MAX_ORDER)
+			order = MAX_ORDER - 1;
+
+		while(ps1_get > 0) {
+			unsigned long virt;
+			struct bloat_pages * link;
+		
+			if ((ps1_get - (1 << order)) < (1 << order))
+				redo_counts();
+				
+			while ((1 << order) > (ps1_get))
+				order--;
+
+			virt = get_grabbed_pages(order);
+
+			while ((!virt) && (order > 0)) {
+				order--;
+				if ((ps1_get - (1 << order)) < (1 << order))
+					redo_counts();
+				virt = get_grabbed_pages(order);
+			}
+
+			if (!virt) {
+				suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+					"Couldn't get enough pages. Need %ld more.\n",
+					ps1_get);
+				return;
+			}
+	
+			link = (struct bloat_pages *) virt;
+			link->next = bloat_pages;
+			link->order = order;
+			bloat_pages = link;
+
+			ps1_get -= (1 << order);
+			suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+				"Allocated %d for ps1. To get %ld.\n", 1 << order, ps1_get);
+		}
+	}
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+		"Exiting increase pageset size.\n\n");
+}
+
+/*
+ * Eaten is the number of pages which have been eaten.
+ * Pagedirincluded is the number of pages which have been allocated for the pagedir.
+ */
+extern int allocate_extra_pagedir_memory(struct pagedir * p, int pageset_size, int alloc_from);
+
+struct pageset_sizes_result recalculate_stats(void) 
+{
+	struct pageset_sizes_result result;
+
+	mark_pages_for_pageset2();  /* Need to call this before getting pageset1_size! */
+	result = count_data_pages();
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+		"Forced sizes %ld and %ld. Result %d and %d.\n",
+		forced_ps1_size, forced_ps2_size,
+		result.size1, result.size2);
+	if ((forced_ps1_size && forced_ps1_size != result.size1) || 
+	    (forced_ps2_size && forced_ps2_size != result.size2)) {
+		increase_pageset_size(result);
+		result = count_data_pages();
+	}
+	pageset1_sizelow = result.size1low;
+	pageset2_sizelow = result.size2low;
+	pagedir1.lastpageset_size = pageset1_size = result.size1;
+	pagedir2.lastpageset_size = pageset2_size = result.size2;
+	storage_available = active_writer->ops.writer.storage_available();
+	suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0);
+	return result;
+}
+
+/* update_image
+ *
+ * Allocate [more] memory and storage for the image.
+ * Remember, this is iterative!
+ */
+static int update_image(void) 
+{ 
+	struct pageset_sizes_result result;
+	int iteration = 0, orig_num_range_pages;
+
+	result = recalculate_stats();
+
+	suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0);
+
+	do {
+		iteration++;
+
+		orig_num_range_pages = num_range_pages;
+
+		suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+				"-- Iteration %d.\n", iteration);
+
+		if (suspend_allocate_checksum_pages()) {
+			suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+				"Still need to get more pages for checksum pages.\n");
+			return 1;
+		}
+
+		if (allocate_extra_pagedir_memory(&pagedir1, pageset1_size, pageset2_sizelow)) {
+			suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+				"Still need to get more pages for pagedir 1.\n");
+			return 1;
+		}
+
+		if (active_writer->ops.writer.allocate_storage(MAIN_STORAGE_NEEDED(1))) {
+			suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+				"Still need to get more storage space for the image proper.\n");
+			suspend_store_free_mem(SUSPEND_FREE_WRITER_STORAGE, 0);
+			return 1;
+		}
+
+		suspend_store_free_mem(SUSPEND_FREE_WRITER_STORAGE, 0);
+
+		set_suspend_state(SUSPEND_SLAB_ALLOC_FALLBACK);
+
+		if (active_writer->ops.writer.allocate_header_space(HEADER_STORAGE_NEEDED)) {
+			suspend_message(SUSPEND_ANY_SECTION, SUSPEND_LOW, 1,
+				"Still need to get more storage space for header.\n");
+			return 1;
+		}
+
+		header_space_allocated = HEADER_STORAGE_NEEDED;
+
+		clear_suspend_state(SUSPEND_SLAB_ALLOC_FALLBACK);
+
+		/* 
+		 * Allocate remaining storage space, if possible, up to the
+		 * maximum we know we'll need. It's okay to allocate the
+		 * maximum if the writer is the swapwriter, but
+		 * we don't want to grab all available space on an NFS share.
+		 * We therefore ignore the expected compression ratio here,
+		 * thereby trying to allocate the maximum image size we could
+		 * need (assuming compression doesn't expand the image), but
+		 * don't complain if we can't get the full amount we're after.
+		 */
+
+		active_writer->ops.writer.allocate_storage(
+			max((long)(active_writer->ops.writer.storage_available() -
+				active_writer->ops.writer.storage_allocated()),
+			     (long)(HEADER_STORAGE_NEEDED + MAIN_STORAGE_NEEDED(1))));
+
+		suspend_store_free_mem(SUSPEND_FREE_WRITER_STORAGE, 0);
+
+		result = recalculate_stats();
+		display_stats();
+
+	} while (((orig_num_range_pages < num_range_pages) || 
+		   result.needmorespace ||
+		   header_space_allocated < HEADER_STORAGE_NEEDED ||
+		   active_writer->ops.writer.storage_allocated() < (HEADER_STORAGE_NEEDED + MAIN_STORAGE_NEEDED(1))) 
+		 && (!TEST_RESULT_STATE(SUSPEND_ABORTED)));
+	
+	suspend_message(SUSPEND_ANY_SECTION, SUSPEND_MEDIUM, 1, "-- Exit loop.\n");
+
+	return (amount_needed(0) > 0);
+}
+
+/* ----------------------- Memory grabbing --------------------------
+ *
+ * All of the memory that is available, we grab.
+ * This enables us to get the image size down, even when other
+ * processes might be trying to increase their memory usage. (We
+ * have a hook to disable the OOM killer).
+ *
+ * At the same time, suspend's own routines get memory from this
+ * pool, and so does slab growth. Only get_zeroed_page and siblings
+ * see no memory available.
+ */
+
+static spinlock_t suspend_grabbed_memory_lock = SPIN_LOCK_UNLOCKED;
+
+struct eaten_memory_t
+{
+	void * next;
+};
+
+struct eaten_memory_t *eaten_memory[MAX_ORDER];
+
+static void __grab_free_memory(void)
+{
+	int order, k;
+
+	/*
+	 * First, quickly eat all memory that's already free.
+	 */
+	
+	for (order = MAX_ORDER - 1; order > -1; order--) {
+		struct eaten_memory_t *prev = eaten_memory[order];
+		eaten_memory[order] = (struct eaten_memory_t *) __get_free_pages(GFP_ATOMIC, order);
+		while (eaten_memory[order]) {
+			struct page * page = virt_to_page(eaten_memory[order]);
+			eaten_memory[order]->next = prev;
+			prev = eaten_memory[order];
+			suspend_amount_grabbed += (1 << order);
+			for (k=0; k < (1 << order); k++) {
+				SetPageNosave(page + k);
+				ClearPagePageset2(page + k);
+			}
+			eaten_memory[order] = (struct eaten_memory_t *) __get_free_pages(GFP_ATOMIC, order);
+		}
+		eaten_memory[order] = prev;
+	}
+}
+
+static void grab_free_memory(void)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&suspend_grabbed_memory_lock, flags);
+	__grab_free_memory();
+	spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags);
+}
+
+static void free_grabbed_memory(void)
+{
+	struct eaten_memory_t *next = NULL, *this = NULL;
+	int j, num_freed = 0, order;
+	unsigned long flags;
+
+	spin_lock_irqsave(&suspend_grabbed_memory_lock, flags);
+
+	/* Free all eaten pages immediately */
+	for (order = MAX_ORDER - 1; order > -1; order--) {
+		this=eaten_memory[order];
+		while(this) {
+			struct page * page = virt_to_page(this);
+			next = this->next;
+			for (j=0; j < (1 << order); j++)
+				ClearPageNosave(page + j);
+			free_pages((unsigned long) this, order);
+			num_freed+= (1 << order);
+			this = next;
+		}
+		eaten_memory[order] = NULL;
+	}
+	suspend_amount_grabbed -= num_freed;
+	BUG_ON(suspend_amount_grabbed);
+	spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags);
+}
+
+unsigned long get_grabbed_pages(int order)
+{
+	unsigned long this = (unsigned long) eaten_memory[order];
+	int alternative, j;
+	unsigned long flags;
+	struct page * page;
+
+	/* Get grabbed lowmem pages for suspend's use */
+	spin_lock_irqsave(&suspend_grabbed_memory_lock, flags);
+
+try_again:	
+	if (this) {
+		page = virt_to_page(this);
+		eaten_memory[order] = eaten_memory[order]->next;
+		for (j=0; j < (1 << order); j++) {
+			ClearPageNosave(page + j);
+			ClearPagePageset2(page + j);
+			clear_page(page_address(page + j));
+		}
+		suspend_amount_grabbed -= (1 << order);
+		spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags);
+		return this;
+	}
+
+	alternative = order+1;
+	while ((!eaten_memory[alternative]) && (alternative < MAX_ORDER))
+		alternative++;
+
+	/* Maybe we didn't eat any memory - try normal get */
+	if (alternative == MAX_ORDER) {
+		this = __get_free_pages(GFP_ATOMIC, order);
+		if (this) {
+			page = virt_to_page(this);
+			for (j=0; j < (1 << order); j++) {
+				clear_page((char *) this + j * PAGE_SIZE);
+				ClearPagePageset2(page + j);
+			}
+		}
+		spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags);
+		return this;
+	}
+
+	{
+		unsigned long virt = (unsigned long) eaten_memory[alternative];
+		page = virt_to_page(eaten_memory[alternative]);
+		eaten_memory[alternative] = eaten_memory[alternative]->next;
+		for (j=0; j < (1 << (alternative)); j++) {
+			ClearPageNosave(page + j);
+			clear_page(page_address(page + j));
+			ClearPagePageset2(page + j);
+		}
+		free_pages(virt, alternative);
+		suspend_amount_grabbed -= (1 << alternative);
+	}
+
+	/* Get the chunk we want to return. May fail if something grabs
+	 * the memory before us. */
+	this = __get_free_pages(GFP_ATOMIC, order);
+	if (!this)
+		goto try_again;
+
+	page = virt_to_page(this);
+
+	/* Grab the rest */
+	__grab_free_memory();
+	
+	spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags);
+
+	return this;
+}
+
+/* --------------------------------------------------------------------------- */
+
+extern int freeze_processes(int no_progress);
+
+static int attempt_to_freeze(void)
+{
+	int result;
+	
+	/* Stop processes before checking again */
+	thaw_processes(FREEZER_ALL_THREADS);
+	prepare_status(1, 1, "Freezing processes");
+	result = freeze_processes(0);
+	suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "- Freeze_processes returned %d.\n",
+		result);
+
+	if (result) {
+		SET_RESULT_STATE(SUSPEND_ABORTED);
+		SET_RESULT_STATE(SUSPEND_FREEZING_FAILED);
+	} else
+		arefrozen = 1;
+
+	return result;
+}
+
+extern asmlinkage long sys_sync(void);
+extern int try_to_free_pages_suspend(int amount_needed);
+
+static int eat_memory(void)
+{
+	int orig_memory_still_to_eat, last_amount_needed = 0, times_criteria_met = 0;
+	int free_flags = 0, did_eat_memory = 0;
+	
+	/*
+	 * Note that if we have enough storage space and enough free memory, we may
+	 * exit without eating anything. We give up when the last 10 iterations ate
+	 * no extra pages because we're not going to get much more anyway, but
+	 * the few pages we get will take a lot of time.
+	 *
+	 * We freeze processes before beginning, and then unfreeze them if we
+	 * need to eat memory until we think we have enough. If our attempts
+	 * to freeze fail, we give up and abort.
+	 */
+
+	/* ----------- Stage 1: Freeze Processes ------------- */
+
+	
+	prepare_status(0, 1, "Eating memory.");
+
+	recalculate_stats();
+	display_stats();
+
+	orig_memory_still_to_eat = amount_needed(1);
+	last_amount_needed = orig_memory_still_to_eat;
+
+	switch (image_size_limit) {
+		case -1: /* Don't eat any memory */
+			if (orig_memory_still_to_eat) {
+				SET_RESULT_STATE(SUSPEND_ABORTED);
+				SET_RESULT_STATE(SUSPEND_WOULD_EAT_MEMORY);
+			}
+			break;
+		case -2:  /* Free caches only */
+			free_flags = GFP_NOIO | __GFP_HIGHMEM;
+			break;
+		default:
+			free_flags = GFP_ATOMIC | __GFP_HIGHMEM;
+	}
+		
+	/* ----------- Stage 2: Eat memory ------------- */
+
+	while (((!EATEN_ENOUGH_MEMORY()) || (image_size_limit == -2)) && (!TEST_RESULT_STATE(SUSPEND_ABORTED)) && (times_criteria_met < 10)) {
+		int amount_freed;
+		int amount_wanted = orig_memory_still_to_eat - amount_needed(1);
+		if (amount_wanted < 1)
+			amount_wanted = 1; /* image_size_limit == -2 */
+
+		suspend_message(SUSPEND_EAT_MEMORY, SUSPEND_VERBOSE, 1,
+			"Times met criteria is %d.\n", times_criteria_met);
+		if (orig_memory_still_to_eat)
+			update_status(orig_memory_still_to_eat - amount_needed(1), orig_memory_still_to_eat, " Image size %d ", MB(STORAGE_NEEDED(1)));
+		else
+			update_status(0, 1, "Image size %d ", MB(STORAGE_NEEDED(1)));
+		
+		if ((last_amount_needed - amount_needed(1)) < 10)
+			times_criteria_met++;
+		else
+			times_criteria_met = 0;
+		last_amount_needed = amount_needed(1);
+		amount_freed = try_to_free_pages_suspend(last_amount_needed);
+		suspend_message(SUSPEND_EAT_MEMORY, SUSPEND_VERBOSE, 1,
+			"Given %d, shrink_all_memory returned %d.\n", last_amount_needed, amount_freed);
+		grab_free_memory();
+		recalculate_stats();
+		display_stats();
+
+		did_eat_memory = 1;
+
+		check_shift_keys(0, NULL);
+	}
+
+	grab_free_memory();
+	
+	suspend_message(SUSPEND_EAT_MEMORY, SUSPEND_VERBOSE, 1,
+		"Out of main eat memory loop.\n");
+
+	if (did_eat_memory) {
+		unsigned long orig_state = get_suspend_state();
+		suspend_message(SUSPEND_EAT_MEMORY, SUSPEND_VERBOSE, 1,
+			"Ate memory; letting kjournald etc run.\n");
+		clear_suspend_state(SUSPEND_USE_MEMORY_POOL);
+		thaw_processes(FREEZER_KERNEL_THREADS);
+		/* Freeze_processes will call sys_sync too */
+		freeze_processes(1);
+		grab_free_memory();
+		restore_suspend_state(orig_state);
+		recalculate_stats();
+		display_stats();
+	}
+
+	suspend_message(SUSPEND_EAT_MEMORY, 1, SUSPEND_VERBOSE, "\n");
+	
+	suspend_message(SUSPEND_EAT_MEMORY, SUSPEND_VERBOSE, 1,
+		"(Freezer exit:) Swap needed calculated as (%d+%d)*%d/100+%d+1+%d=%d.\n",
+		pageset1_size,
+		pageset2_size,
+		expected_compression_ratio(),
+		num_range_pages,
+	 	HEADER_STORAGE_NEEDED,
+		STORAGE_NEEDED(1));
+
+	/* Blank out image size display */
+	update_status(100, 100, "                   ");
+
+	/* Include image size limit when checking what to report */
+	if (amount_needed(1) > 0) 
+		SET_RESULT_STATE(SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY);
+
+	/* But don't include it when deciding whether to abort (soft limit) */
+	if ((amount_needed(0) > 0)) {
+		printk("Unable to free sufficient memory to suspend. Still need %d pages. "
+			"You may be able to avoid this problem by reducing the async_io_limit\n",
+			amount_needed(1));
+		SET_RESULT_STATE(SUSPEND_ABORTED);
+	}
+	
+	check_shift_keys(1, "Memory eating completed.");
+	return 0;
+}
+
+/* prepare_image
+ *
+ * Entry point to the whole image preparation section.
+ *
+ * We do four things:
+ * - Freeze processes;
+ * - Ensure image size constraints are met;
+ * - Complete all the preparation for saving the image,
+ *   including allocation of storage. The only memory
+ *   that should be needed when we're finished is that
+ *   for actually storing the image (and we know how
+ *   much is needed for that because the plugins tell
+ *   us).
+ * - Make sure that all dirty buffers are written out.
+ */
+int prepare_image(void)
+{
+	int result = 1, sizesought;
+
+	arefrozen = 0;
+
+	header_space_allocated = 0;
+
+	sizesought = 100 + memory_for_plugins();
+
+	PRINTFREEMEM("prior to filling the memory pool");
+	
+	if (fill_suspend_memory_pool(sizesought))
+		return 1;
+
+	PRINTFREEMEM("after filling the memory pool");
+	suspend_store_free_mem(SUSPEND_FREE_MEM_POOL, 0);
+	
+	if (attempt_to_freeze())
+		return 1;
+
+	PRINTFREEMEM("after freezing processes");
+	suspend_store_free_mem(SUSPEND_FREE_FREEZER, 0);
+	
+	if (!active_writer->ops.writer.storage_available()) {
+		printk(KERN_ERR "You need some storage available to be able to suspend.\n");
+		SET_RESULT_STATE(SUSPEND_ABORTED);
+		SET_RESULT_STATE(SUSPEND_NOSTORAGE_AVAILABLE);
+		return 1;
+	}
+
+	do {
+		if (eat_memory() || TEST_RESULT_STATE(SUSPEND_ABORTED))
+			break;
+
+		PRINTFREEMEM("after eating memory");
+		suspend_store_free_mem(SUSPEND_FREE_EAT_MEMORY, 0);
+	
+		/* Top up */
+		if (fill_suspend_memory_pool(sizesought))
+			continue;
+	
+		PRINTFREEMEM("after refilling memory pool");
+		suspend_store_free_mem(SUSPEND_FREE_MEM_POOL, 0);
+	
+		result = update_image();
+		PRINTFREEMEM("after updating the image");
+
+	} while ((result) && (!TEST_RESULT_STATE(SUSPEND_ABORTED)) &&
+		(!TEST_RESULT_STATE(SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY)));
+
+	PRINTFREEMEM("after preparing image");
+
+	/* Release memory that has been eaten */
+	free_grabbed_memory();
+	
+	PRINTFREEMEM("after freeing grabbed memory");
+	suspend_store_free_mem(SUSPEND_FREE_GRABBED_MEMORY, 1);
+	
+	set_suspend_state(SUSPEND_USE_MEMORY_POOL);
+
+	check_shift_keys(1, "Image preparation complete.");
+
+	return result;
+}
+
+EXPORT_SYMBOL(suspend_amount_grabbed);
diff -ruN 20-old/kernel/power/proc.c 20-new/kernel/power/proc.c
--- 20-old/kernel/power/proc.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/proc.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,359 @@
+/*
+ * /kernel/power/proc.c
+ *
+ * Copyright (C) 2002-2003 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * This file contains support for proc entries for tuning Software Suspend.
+ *
+ * We have a generic handler that deals with the most common cases, and
+ * hooks for special handlers to use.
+ *
+ * Versions:
+ * 1: /proc/sys/kernel/suspend the only tuning interface
+ * 2: Initial version of this file
+ * 3: Removed kernel debugger parameter.
+ *    Added checkpage parameter (for checking checksum of a page over time).
+ * 4: Added entry for maximum granularity in splash screen progress bar.
+ *    (Progress bar is slow, but the right setting will vary with disk &
+ *    processor speed and the user's tastes).
+ * 5: Added enable_escape to control ability to cancel aborting by pressing
+ *    ESC key.
+ * 6: Removed checksumming and checkpage parameter. Made all debugging proc
+ *    entries dependant upon debugging being compiled in.
+ *    Meaning of some flags also changed in this version.
+ * 7: Added header_locations entry to simplify getting the resume= parameter for
+ *    swapfiles easy and swapfile entry for automatically doing swapon/off from
+ *    swapfiles as well as partitions.
+ * 8: Added option for marking process pages as pageset 2 (processes_pageset2).
+ * 9: Added option for keep image mode.
+ *    Enumeration patch from Michael Frank applied.
+ * 10: Various corrections to when options are disabled/enabled;
+ *     Added option for specifying expected compression.
+ * 11: Added option for freezer testing. Debug only.
+ * 12: Removed test entries no_async_[read|write], processes_pageset2 and
+ *     NoPageset2.
+ * 13: Make default_console_level available when debugging disabled, but limited
+ *     to 0 or 1.
+ * 14: Rewrite to allow for dynamic registration of proc entries and smooth the
+ *     transition to kobjects in 2.6.
+ * 15: Add setting resume2 parameter without rebooting (still need to run lilo
+ *     though!). Add support for generic string handling and switch resume2 to use
+ *     it.
+ */
+
+#define SUSPEND_PROC_C
+
+static int suspend_proc_version = 15;
+static int suspend_proc_initialised = 0;
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <asm/uaccess.h>
+
+#include "suspend.h"
+#include "proc.h"
+
+static struct list_head suspend_proc_entries;
+static struct proc_dir_entry *suspend_dir;
+
+extern char resume2_file[256];	/* For resume= kernel option */
+
+/*
+ * proc_try_suspend
+ *
+ * This routine initiates a suspend cycle when /proc/software_suspend/do_suspend is
+ * written to. The value written is ignored.
+ */
+
+static int proc_try_suspend(struct file *file, const char *buffer,
+        unsigned long count, void *data)
+{
+	suspend_try_suspend();
+	return count;
+}
+
+/*
+ * proc_try_resume
+ *
+ * This routine initiates a suspend cycle when /proc/software_suspend/do_resume is
+ * written to. The value written is ignored.
+ */
+
+static int proc_try_resume(struct file *file, const char *buffer,
+        unsigned long count, void *data)
+{
+	software_suspend_try_resume();
+	return count;
+}
+
+/*
+ * generic_read_proc
+ *
+ * Generic handling for reading the contents of bits, integers,
+ * unsigned longs and strings.
+ */
+static int generic_read_proc(char * page, char ** start, off_t off, int count,
+		int *eof, void *data)
+{
+	int len = 0;
+	struct suspend_proc_data * proc_data = (struct suspend_proc_data *) data;
+
+	switch (proc_data->type) {
+		case SUSPEND_PROC_DATA_CUSTOM:
+			printk("Error! /proc/suspend/%s marked as having custom"
+				" routines, but the generic read routine has"
+				" been invoked.\n",
+				proc_data->filename);
+			break;
+		case SUSPEND_PROC_DATA_BIT:
+			len = sprintf(page, "%d\n", 
+				-test_bit(proc_data->data.bit.bit,
+					proc_data->data.bit.bit_vector));
+			break;
+		case SUSPEND_PROC_DATA_INTEGER:
+			{
+				int * variable = proc_data->data.integer.variable;
+				len = sprintf(page, "%d\n", *variable);
+				break;
+			}
+		case SUSPEND_PROC_DATA_UL:
+			{
+				long * variable = proc_data->data.ul.variable;
+				len = sprintf(page, "%lu\n", *variable);
+				break;
+			}
+		case SUSPEND_PROC_DATA_STRING:
+			{
+				char * variable = proc_data->data.string.variable;
+				len = sprintf(page, "%s\n", variable);
+				break;
+			}
+	}
+	/* Side effect routine? */
+	if (proc_data->read_proc)
+		proc_data->read_proc();
+	*eof = 1;
+	return len;
+}
+/*
+ * generic_write_proc
+ *
+ * Generic routine for handling writing to files representing
+ * bits, integers and unsigned longs.
+ */
+
+static int generic_write_proc(struct file *file, const char * buffer,
+		unsigned long count, void * data)
+{
+	struct suspend_proc_data * proc_data = (struct suspend_proc_data *) data;
+	char * my_buf = (char *) get_zeroed_page(GFP_ATOMIC);
+	int result = count;
+
+	if (!my_buf)
+		return -ENOMEM;
+
+	if (count > PAGE_SIZE)
+		count = PAGE_SIZE;
+
+	if (copy_from_user(my_buf, buffer, count))
+		return -EFAULT;
+	
+	my_buf[count] = 0;
+
+	switch (proc_data->type) {
+		case SUSPEND_PROC_DATA_CUSTOM:
+			printk("Error! /proc/suspend/%s marked as having custom"
+				" routines, but the generic write routine has"
+				" been invoked.\n",
+				proc_data->filename);
+			break;
+		case SUSPEND_PROC_DATA_BIT:
+			{
+			int value = simple_strtoul(my_buf, NULL, 0);
+			if (value)
+				set_bit(proc_data->data.bit.bit, 
+					(proc_data->data.bit.bit_vector));
+			else
+				clear_bit(proc_data->data.bit.bit,
+					(proc_data->data.bit.bit_vector));
+			}
+			break;
+		case SUSPEND_PROC_DATA_INTEGER:
+			{
+				int * variable = proc_data->data.integer.variable;
+				int minimum = proc_data->data.integer.minimum;
+				int maximum = proc_data->data.integer.maximum;
+				*variable = simple_strtol(my_buf, NULL, 0);
+				if (((*variable) < minimum))
+					*variable = minimum;
+
+				if (((*variable) > maximum))
+					*variable = maximum;
+				break;
+			}
+		case SUSPEND_PROC_DATA_UL:
+			{
+				unsigned long * variable = proc_data->data.ul.variable;
+				unsigned long minimum = proc_data->data.ul.minimum;
+				unsigned long maximum = proc_data->data.ul.maximum;
+				*variable = simple_strtoul(my_buf, NULL, 0);
+				
+				if (minimum && ((*variable) < minimum))
+					*variable = minimum;
+
+				if (maximum && ((*variable) > maximum))
+					*variable = maximum;
+				break;
+			}
+			break;
+		case SUSPEND_PROC_DATA_STRING:
+			{
+				int copy_len = 
+					(count > 
+					 proc_data->data.string.max_length) ?
+					proc_data->data.string.max_length : 
+					count;
+				char * variable =
+					proc_data->data.string.variable;
+				strncpy(variable, my_buf, copy_len);
+				if ((copy_len) &&
+					 (my_buf[copy_len - 1] == '\n'))
+					variable[count - 1] = 0;
+				variable[count] = 0;
+			}
+			break;
+	}
+	free_pages((unsigned long) my_buf, 0);
+	/* Side effect routine? */
+	if (proc_data->write_proc) {
+		int routine_result = proc_data->write_proc();
+		if (routine_result < 0)
+			result = routine_result;
+	}
+	return result;
+}
+
+/*
+ * Non-plugin proc entries.
+ *
+ * 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			= "do_suspend",
+	  .permissions			= PROC_WRITEONLY,
+	  .type				= SUSPEND_PROC_DATA_CUSTOM,
+	  .data = {
+		  .special = {
+			.write_proc	= proc_try_suspend
+		  }
+	  }
+	},
+
+	{ .filename			= "do_resume",
+	  .permissions			= PROC_WRITEONLY,
+	  .type				= SUSPEND_PROC_DATA_CUSTOM,
+	  .data = {
+		  .special = {
+			.write_proc	= proc_try_resume
+		  }
+	  }
+	},
+
+
+	{ .filename			= "interface_version",
+	  .permissions			= PROC_READONLY,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		  .integer = {
+			  .variable	= &suspend_proc_version,
+		  }
+	  }
+	},
+};
+
+/*
+ * suspend_initialise_proc
+ *
+ * Initialise the /proc/suspend tree.
+ *
+ */
+
+static void suspend_initialise_proc(void)
+{
+	int i;
+	int numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+	
+	if (suspend_proc_initialised)
+		return;
+
+	suspend_dir = proc_mkdir("software_suspend", NULL);
+	
+	BUG_ON(!suspend_dir);
+
+	INIT_LIST_HEAD(&suspend_proc_entries);
+
+	suspend_proc_initialised = 1;
+
+	for (i=0; i< numfiles; i++)
+		suspend_register_procfile(&proc_params[i]);
+}
+
+/*
+ * suspend_register_procfile
+ *
+ * Helper for registering a new /proc/suspend entry.
+ */
+
+struct proc_dir_entry * suspend_register_procfile(
+		struct suspend_proc_data * suspend_proc_data)
+{
+	struct proc_dir_entry * new_entry;
+	
+	if (!suspend_proc_initialised)
+		suspend_initialise_proc();
+
+	new_entry = create_proc_entry(
+			suspend_proc_data->filename,
+			suspend_proc_data->permissions, 
+			suspend_dir);
+	if (new_entry) {
+		list_add_tail(&suspend_proc_data->proc_data_list, &suspend_proc_entries);
+		if (suspend_proc_data->type) {
+			new_entry->read_proc = generic_read_proc;
+			new_entry->write_proc = generic_write_proc;
+		} else {
+			new_entry->read_proc = suspend_proc_data->data.special.read_proc;
+			new_entry->write_proc = suspend_proc_data->data.special.write_proc;
+		}
+		new_entry->data = suspend_proc_data;
+	} else {
+		printk("Error! create_proc_entry returned NULL.\n");
+		INIT_LIST_HEAD(&suspend_proc_data->proc_data_list);
+	}
+	return new_entry;
+}
+
+/*
+ * suspend_unregister_procfile
+ *
+ * Helper for removing unwanted /proc/suspend entries.
+ *
+ */
+void suspend_unregister_procfile(struct suspend_proc_data * suspend_proc_data)
+{
+	if (list_empty(&suspend_proc_data->proc_data_list))
+		return;
+
+	remove_proc_entry(
+		suspend_proc_data->filename,
+		suspend_dir);
+	list_del(&suspend_proc_data->proc_data_list);
+}
+
+EXPORT_SYMBOL(suspend_register_procfile);
+EXPORT_SYMBOL(suspend_unregister_procfile);
diff -ruN 20-old/kernel/power/process.c 20-new/kernel/power/process.c
--- 20-old/kernel/power/process.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/process.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,524 @@
+/*
+ * kernel/power/process.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.
+ *
+ * Freeze_and_free contains the routines software suspend uses to freeze other
+ * processes during the suspend cycle and to (if necessary) free up memory in
+ * accordance with limitations on the image size.
+ *
+ * Ideally, the image saved to disk would be an atomic copy of the entire 
+ * contents of all RAM and related hardware state. One of the first 
+ * prerequisites for getting our approximation of this is stopping the activity
+ * of other processes. We can't stop all other processes, however, since some 
+ * are needed in doing the I/O to save the image. Freeze_and_free.c contains 
+ * the routines that control suspension and resuming of these processes.
+ * 
+ * Under high I/O load, we need to be careful about the order in which we
+ * freeze processes. If we freeze processes in the wrong order, we could
+ * deadlock others. The freeze_order array this specifies the order in which
+ * critical processes are frozen. All others are suspended after these have
+ * entered the refrigerator.
+ *
+ * Another complicating factor is that freeing memory requires the processes
+ * to not be frozen, but at the end of freeing memory, they need to be frozen
+ * so that we can be sure we actually have eaten enough memory. This is why
+ * freezing and freeing are in the one file. The freezer is not called from
+ * the main logic, but indirectly, via the code for eating memory. The eat
+ * memory logic is iterative, first freezing processes and checking the stats,
+ * then (if necessary) unfreezing them and eating more memory until it looks 
+ * like the criteria are met (at which point processes are frozen & stats
+ * checked again).
+ */
+
+#define SUSPEND_FREEZER_C
+
+#include <linux/module.h>
+#include <linux/suspend.h>
+//#include <asm/tlbflush.h>
+
+#include "suspend.h"
+
+volatile struct suspend2_core_ops * suspend2_core_ops = NULL;
+unsigned long suspend_action = 0;
+unsigned long suspend_result = 0;
+unsigned long suspend_debug_state = 0;
+unsigned long software_suspend_state = ((1 << SUSPEND_DISABLED) | (1 << SUSPEND_BOOT_TIME) |
+		(1 << SUSPEND_RESUME_NOT_DONE) | (1 << SUSPEND_IGNORE_LOGLEVEL));
+unsigned int suspend_task = 0;
+
+atomic_t __nosavedata suspend_cpu_counter = { 0 }; 
+
+/* Timeouts when freezing */
+#define FREEZER_TOTAL_TIMEOUT (5 * HZ)
+#define FREEZER_CHECK_TIMEOUT (HZ / 10)
+
+extern void suspend_relinquish_console(void);
+
+/* ------------------------------------------------------------------------ */
+
+/**
+ * refrigerator - idle routine for frozen processes
+ * @flag: unsigned long, non zero if signals to be flushed.
+ *
+ * A routine for kernel threads which should not do work during suspend
+ * to enter and spin in until the process is finished.
+ */
+
+void refrigerator(unsigned long flag)
+{
+	unsigned long flags;
+	long save;
+	
+	if (unlikely(current->flags & PF_NOFREEZE)) {
+		current->flags &= ~PF_FREEZE;
+		spin_lock_irqsave(&current->sigmask_lock, flags);
+		recalc_sigpending(current);
+		spin_unlock_irqrestore(&current->sigmask_lock, flags);
+		return;
+	}
+
+	/* You need correct to work with real-time processes.
+	   OTOH, this way one process may see (via /proc/) some other
+	   process in stopped state (and thereby discovered we were
+	   suspended. We probably do not care). 
+	 */
+	if ((flag) && (current->flags & PF_FREEZE)) {
+
+		suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0,
+			"\n%s (%d) refrigerated and sigpending recalculated.",
+			current->comm, current->pid);
+		spin_lock_irqsave(&current->sigmask_lock, flags);
+		recalc_sigpending(current);
+		spin_unlock_irqrestore(&current->sigmask_lock, flags);
+	} else
+		suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0,
+			"\n%s (%d) refrigerated.",
+			current->comm, current->pid);
+
+	if (test_suspend_state(SUSPEND_FREEZER_ON)) {
+		save = current->state;
+		current->flags |= PF_FROZEN;
+		while (current->flags & PF_FROZEN) {
+			current->state = TASK_STOPPED;
+			schedule();
+			if (flag) {
+				spin_lock_irqsave(
+					&current->sigmask_lock, flags);
+				recalc_sigpending(current);
+				spin_unlock_irqrestore(
+					&current->sigmask_lock, flags);
+			}
+		}
+		current->state = save;
+	} else
+		suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0,
+				"No longer freezing processes. Dropping out.\n");
+	current->flags &= ~PF_FREEZE;
+	spin_lock_irqsave(&current->sigmask_lock, flags);
+	recalc_sigpending(current);
+	spin_unlock_irqrestore(&current->sigmask_lock, flags);
+}
+
+
+#ifdef CONFIG_SMP
+static void __smp_pause(void * data)
+{
+	atomic_inc(&suspend_cpu_counter);
+	while(test_suspend_state(SUSPEND_FREEZE_SMP)) {
+		cpu_relax();
+		barrier();
+	}
+	local_flush_tlb();
+	atomic_dec(&suspend_cpu_counter);
+}
+
+void smp_pause(void)
+{
+	set_suspend_state(SUSPEND_FREEZE_SMP);
+	smp_call_function(__smp_pause, NULL, 0, 0);
+
+	while (atomic_read(&suspend_cpu_counter) < (num_online_cpus() - 1)) {
+		cpu_relax();
+		barrier();
+	}
+}
+
+void smp_continue(void)
+{
+	clear_suspend_state(SUSPEND_FREEZE_SMP);
+		
+	while (atomic_read(&suspend_cpu_counter)) {
+		cpu_relax();
+		barrier();
+	}
+}
+
+extern void __smp_suspend_lowlevel(void * info);
+
+void smp_suspend(void)
+{
+	set_suspend_state(SUSPEND_FREEZE_SMP);
+	smp_call_function(__smp_suspend_lowlevel, NULL, 0, 0);
+
+	while (atomic_read(&suspend_cpu_counter) < (num_online_cpus() - 1)) {
+		cpu_relax();
+		barrier();
+	}
+}
+#else
+#define smp_pause() do { } while(0)
+#define smp_continue() do { } while(0)
+#define smp_suspend() do { } while(0)
+#endif
+
+/*
+ * to_be_frozen
+ *
+ * Description:	Determine whether a process should be frozen yet.
+ * Parameters:	struct task_struct *	The process to consider.
+ * 		int			Which group of processes to consider.
+ * Returns:	int			0 if don't freeze yet, otherwise do.
+ */
+static int to_be_frozen(struct task_struct * p, int type_being_frozen) {
+
+	if ((p == current) ||
+	    (!(strcmp(p->comm, "dosexec"))) ||
+	    (p->flags & PF_NOFREEZE) ||
+	    (p->flags & PF_FROZEN) ||
+	    (p->state == TASK_ZOMBIE) ||
+	    (p->state == TASK_STOPPED))
+		return 0;
+	if ((!(p->mm)) && (type_being_frozen < 3))
+		return 0;
+	if ((p->flags & PF_SYNCTHREAD) && (type_being_frozen == 1))
+		return 0;
+	return 1;
+}
+
+/*
+ * num_to_be_frozen
+ *
+ * Description:	Determine how many processes of our type are still to be
+ * 		frozen. As a side effect, update the progress bar too.
+ * Parameters:	int	Which type we are trying to freeze.
+ * 		int	Whether we are displaying our progress.
+ */
+static int num_to_be_frozen(int type_being_frozen, int no_progress) {
+	
+	struct task_struct *p;
+	int todo_this_type = 0, total_todo = 0;
+	int total_threads = 0;
+
+	read_lock(&tasklist_lock);
+	for_each_task(p) {
+		if (to_be_frozen(p, type_being_frozen)) {
+			todo_this_type++;
+			total_todo++;
+		} else if (to_be_frozen(p, 3))
+			total_todo++;
+		total_threads++;
+	};
+	read_unlock(&tasklist_lock);
+
+	if ((!no_progress) && (suspend2_core_ops)) {
+		suspend2_core_ops->update_status(
+				total_threads - total_todo,
+				total_threads,
+				"%d/%d", 
+				total_threads - total_todo,
+				total_threads);
+	}
+	return todo_this_type;
+}
+
+/*
+ * freeze_threads
+ *
+ * Freeze a set of threads having particular attributes.
+ *
+ * Types:
+ * 1: User threads not syncing.
+ * 2: Remaining user threads.
+ * 3: Kernel threads.
+ */
+extern void show_task(struct task_struct * p);
+
+static int freeze_threads(int type, int no_progress)
+{
+	struct task_struct *p;
+	unsigned long start_time = jiffies;
+	int result = 0, still_to_do;
+
+	suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1,
+		"\n STARTING TO FREEZE TYPE %d THREADS.\n",
+		type);
+
+	do {
+		int numsignalled = 0;
+
+		/* 
+		 * Pause the other processors so we can safely
+		 * change threads' flags
+		 */
+		smp_pause();
+
+		if (TEST_RESULT_STATE(SUSPEND_ABORTED)) {
+			smp_continue();
+			return 1;
+		}
+
+		preempt_disable();
+
+		local_irq_disable();
+
+		read_lock(&tasklist_lock);
+
+		/*
+		 * Signal the processes.
+		 *
+		 * We signal them every time through. Otherwise pdflush -
+		 * and maybe other processes - might never enter the
+		 * fridge.
+		 *
+		 * NB: We're inside an SMP pause. Our printks are unsafe.
+		 * They're only here for debugging.
+		 *
+		 */
+		
+		for_each_task(p) {
+			unsigned long flags;
+			if (!to_be_frozen(p, type))
+				continue;
+			
+			numsignalled++;
+			suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, 
+				"\n   %s: pid %d",
+				p->comm, p->pid);
+			p->flags |= PF_FREEZE;
+			spin_lock_irqsave(&p->sigmask_lock, flags);
+			signal_wake_up(p, 0);
+			spin_unlock_irqrestore(&p->sigmask_lock, flags);
+		};
+
+		if (numsignalled)
+			suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0,
+				"\n Number of threads signalled this iteration is %d.\n",
+				numsignalled);
+
+		read_unlock(&tasklist_lock);
+
+		/* 
+		 * Let the processes run.
+		 */		
+		smp_continue();
+
+		preempt_enable();
+
+		local_irq_enable();
+		
+		/*
+		 * Sleep.
+		 */
+		set_task_state(current, TASK_INTERRUPTIBLE);
+		schedule_timeout(FREEZER_CHECK_TIMEOUT);
+
+		still_to_do = num_to_be_frozen(type, no_progress);
+	} while(still_to_do && (!TEST_RESULT_STATE(SUSPEND_ABORTED)) &&
+		!time_after(jiffies, start_time + FREEZER_TOTAL_TIMEOUT));
+
+	/*
+	 * Did we time out? See if we failed to freeze processes as well.
+	 *
+	 */
+	if ((time_after(jiffies, start_time + FREEZER_TOTAL_TIMEOUT)) && (still_to_do)) {
+		read_lock(&tasklist_lock);
+		for_each_task(p) {
+			if (!to_be_frozen(p, type)) 
+				continue;
+			
+			if (!result) {
+				printk(KERN_ERR name_suspend
+					"Stopping tasks failed.\n");
+				printk(KERN_ERR "Tasks that refused to be refrigerated"
+					" and haven't since exited:\n");
+				result = 1;
+			}
+			
+			if (p->flags & PF_FREEZE) {
+				printk(" - %s (#%d) signalled but "
+					"didn't enter refrigerator.\n",
+					p->comm, p->pid);
+				show_task(p);
+			} else
+				printk(" - %s (#%d) wasn't "
+					"signalled.\n",
+					p->comm, p->pid);
+		};
+		read_unlock(&tasklist_lock);
+	} else
+		suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1,
+			"\n\nSuccessfully froze processes of type %d.\n",
+			type);
+	return result;
+}
+
+/*
+ * freeze_processes - Freeze processes prior to saving an image of memory.
+ * 
+ * Return value: 0 = success, else # of processes that we failed to stop.
+ */
+extern asmlinkage long sys_sync(void);
+
+/* Freeze_processes.
+ * If the flag no_progress is non-zero, progress bars not be updated.
+ * Debugging output is still printed.
+ */
+int freeze_processes(int no_progress)
+{
+	int showidlelist, result = 0, num_type[3];
+	struct task_struct *p;
+
+	showidlelist = 1;
+
+	num_type[0] = num_type[1] = num_type[2] = 0;
+
+	suspend_task = current->pid;
+
+	set_suspend_state(SUSPEND_FREEZER_ON);
+
+	suspend_result = 0;	/* Might be called from pm_disk or suspend -
+				   ensure reset */
+
+	read_lock(&tasklist_lock);
+	for_each_task(p) {
+		if (p->mm) {
+			if (p->flags & PF_SYNCTHREAD) {
+				suspend_message(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0,
+					"%s (%d) is a syncthread at entrance to "
+					"fridge\n", p->comm, p->pid);
+				num_type[1]++;
+			} else
+				num_type[2]++;
+		} else {
+			if (p->flags & PF_NOFREEZE)
+				suspend_message(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0,
+					"%s (%d) is NO_FREEZE.\n",
+					p->comm, p->pid);
+			else
+				num_type[2]++;
+		}
+	};
+	read_unlock(&tasklist_lock);
+	suspend_message(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, "\n");
+
+	/* First, freeze all userspace,	non syncing threads. */
+	if (freeze_threads(1, no_progress) || (TEST_RESULT_STATE(SUSPEND_ABORTED)))
+		goto aborting;
+	
+	/* Now freeze processes that were syncing and are still running */
+	if (freeze_threads(2, no_progress) || (TEST_RESULT_STATE(SUSPEND_ABORTED)))
+		goto aborting;
+
+	/* Now do our own sync, just in case one wasn't running already */
+	if ((!no_progress) && (suspend2_core_ops))
+		suspend2_core_ops->prepare_status(1, 1,
+			"Freezing processes: Syncing remaining I/O.");
+
+	sys_sync();
+
+	set_suspend_state(SUSPEND_DISABLE_SYNCING);
+
+	/* Freeze kernel threads */
+	if (freeze_threads(3, no_progress) || (TEST_RESULT_STATE(SUSPEND_ABORTED)))
+		goto aborting;
+
+	if (TEST_ACTION_STATE(SUSPEND_FREEZE_TIMERS)) {
+		printk("Enabling timer freezer. If you get a hang, note " \
+			"the timer attempting to run, press T to disable " \
+			"the timer freezer. After resuming, please look up " \
+			"the address you recorded in System.map and report " \
+			"the routinue to Nigel.\n");
+		set_suspend_state(SUSPEND_TIMER_FREEZER_ON);
+	}
+
+out:
+	suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1,
+				"Left freezer loop.\n");
+
+	/* No more syncing now! */
+	
+	clear_suspend_state(SUSPEND_FREEZE_SMP);
+
+	while (atomic_read(&suspend_cpu_counter)) {
+		cpu_relax();
+		barrier();
+	}
+
+	return result;
+aborting:
+	result = -1;
+	goto out;
+}
+
+void thaw_processes(int which_threads)
+{
+	struct task_struct *p;
+	suspend_message(SUSPEND_FREEZER, SUSPEND_LOW, 1, "Thawing tasks\n");
+	
+	suspend_task = 0;
+	if (which_threads != FREEZER_KERNEL_THREADS)
+		clear_suspend_state(SUSPEND_FREEZER_ON);
+
+	clear_suspend_state(SUSPEND_DISABLE_SYNCING);
+	clear_suspend_state(SUSPEND_TIMER_FREEZER_ON);
+	
+	/* 
+	 * Pause the other processors so we can safely
+	 * change threads' flags
+	 */
+
+	smp_pause();
+
+	preempt_disable();
+	
+	local_irq_disable();
+
+	read_lock(&tasklist_lock);
+
+	for_each_task(p) {
+		if (p->flags & PF_FROZEN) {
+			if ((which_threads == FREEZER_KERNEL_THREADS) &&
+				(p->mm))
+				continue;
+			suspend_message(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0,
+					"Waking %5d: %s.\n", p->pid, p->comm);
+			p->flags &= ~PF_FROZEN;
+			wake_up_process(p);
+		}
+	};
+
+	read_unlock(&tasklist_lock);
+
+	smp_continue();
+	
+	preempt_enable();
+
+	local_irq_enable();
+}
+
+EXPORT_SYMBOL(suspend_task);
+EXPORT_SYMBOL(suspend_action);
+EXPORT_SYMBOL(software_suspend_state);
+EXPORT_SYMBOL(freeze_processes);
+EXPORT_SYMBOL(thaw_processes);
+#ifdef CONFIG_SMP
+EXPORT_SYMBOL(smp_suspend);
+EXPORT_SYMBOL(smp_continue);
+#endif
+EXPORT_SYMBOL(refrigerator);
diff -ruN 20-old/kernel/power/proc.h 20-new/kernel/power/proc.h
--- 20-old/kernel/power/proc.h	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/proc.h	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,64 @@
+/*
+ * kernel/power/proc.h
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * It provides declarations for suspend to use in managing
+ * /proc/software_suspend. When we switch to kobjects,
+ * this will become redundant.
+ *
+ */
+
+struct suspend_proc_data {
+	char * filename;
+	int permissions;
+	int type;
+	union {
+		struct {
+			unsigned long * bit_vector;
+			int bit;
+		} bit;
+		struct {
+			int * variable;
+			int minimum;
+			int maximum;
+		} integer;
+		struct {
+			unsigned long * variable;
+			unsigned long minimum;
+			unsigned long maximum;
+		} ul;
+		struct {
+			char * variable;
+			int max_length;
+		} string;
+		struct {
+			void * read_proc;
+			void * write_proc;
+			void * data;
+		} special;
+	} data;
+	
+	/* Side effects routines. Used, eg, for reparsing the
+	 * resume2 entry when it changes */
+	int (* read_proc) (void);
+	int (* write_proc) (void); 
+	struct list_head proc_data_list;
+};
+
+#define SUSPEND_PROC_DATA_CUSTOM	0
+#define SUSPEND_PROC_DATA_BIT		1
+#define SUSPEND_PROC_DATA_INTEGER	2
+#define SUSPEND_PROC_DATA_UL		3
+#define SUSPEND_PROC_DATA_STRING	4
+
+#define PROC_WRITEONLY 0200
+#define PROC_READONLY 0400
+#define PROC_RW 0600
+
+struct proc_dir_entry * suspend_register_procfile(
+		struct suspend_proc_data * suspend_proc_data);
+void suspend_unregister_procfile(struct suspend_proc_data * suspend_proc_data);
+
diff -ruN 20-old/kernel/power/range.c 20-new/kernel/power/range.c
--- 20-old/kernel/power/range.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/range.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,784 @@
+/* Suspend2 routines for manipulating ranges.
+ *
+ * (C) 2003-2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * Distributed under GPLv2.
+ * 
+ * These encapsulate the manipulation of ranges. I learnt after writing this
+ * code that ranges are more commonly called extents. They work like this:
+ *
+ * A lot of the data that suspend saves involves continguous ranges of memory
+ * or storage. Let's say that we're storing data on disk in blocks 1-32768 and
+ * 49152-49848 of a swap partition. Rather than recording 1, 2, 3... in arrays
+ * pointing to the locations, we simply use:
+ *
+ * struct range {
+ * 	unsigned long min;
+ * 	unsigned long max;
+ * 	struct range * next;
+ * }
+ *
+ * We can then store 1-32768 and 49152-49848 in 2 struct ranges, using 24 bytes
+ * instead of something like 133,860. This is of course inefficient where a range
+ * covers only one or two values, but the benefits gained by the much larger
+ * ranges more than outweight these instances.
+ *
+ * Whole pages are allocated to store ranges, with unused structs being chained
+ * together and linked into an unused_ranges list:
+ *
+ * struct range * unused_ranges; (just below).
+ *
+ * We can fit 341 ranges in a 4096 byte page (rangepage), with 4 bytes left over.
+ * These four bytes, referred to as the RangePageLink, are used to link the pages
+ * together. The RangePageLink is a pointer to the next page, or'd with the index
+ * number of the page.
+ *
+ * RangePages are stored in the header of the suspend image. For portability
+ * between suspend time and resume time, we 'relativise' the contents of each page
+ * before writing them to disk. That is, each .next and each RangePageLink is
+ * changed to point not to an absolute location, but to the relative location in
+ * the list of pages. This makes all the information valid and usable (after it
+ * has been absolutised again, of course) regardless of where it is reloaded to
+ * at resume time.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/suspend.h>
+#include <linux/mm.h>
+
+#include "pageflags.h"
+#include "suspend.h"
+
+struct range * unused_ranges = NULL;
+int nr_unused_ranges = 0;
+int max_ranges_used = 0;
+int num_range_pages = 0;
+static unsigned long ranges_allocated = 0;
+struct range * first_range_page = NULL, * last_range_page = NULL;
+
+/* Add_range_pages
+ *
+ * Allocates and initialises new pages for storing ranges.
+ * Returns 1 on failure to get a page.
+ * Otherwise adds the new pages to the unused_ranges pool and returns 0.
+ * During resuming, it ensures the page added doesn't collide with memory that
+ * will be overwritten when copying the original kernel back.
+ */
+
+static int add_range_pages(int number_requested)
+{
+	int i, j;
+	struct range * ranges;
+	void **eaten_memory = NULL, **this;
+
+	for (j = 0; j < number_requested; j++) {
+		if (test_suspend_state(SUSPEND_NOW_RESUMING)) {
+			struct page * pageaddr;
+			/* Make sure page doesn't collide when we're resuming */
+			while ((this = (void **) get_zeroed_page(GFP_ATOMIC))) {
+				pageaddr = virt_to_page(this);
+				if (!PageInUse(pageaddr))
+					break;
+				*this = eaten_memory;
+				eaten_memory = this;
+			}
+			// Free unwanted memory
+			while(eaten_memory) {
+				this = eaten_memory;
+				eaten_memory = *eaten_memory;
+				free_page((unsigned long) this);
+			}
+		} else
+			this = (void *) get_grabbed_pages(0);
+
+		if (!this)
+			return 1;
+
+		num_range_pages++;
+		if (!first_range_page)
+			first_range_page = (struct range *) this;
+		if (last_range_page)
+			*RANGEPAGELINK(last_range_page) |= (unsigned long) this;
+		*RANGEPAGELINK(this) = num_range_pages;
+		last_range_page = (struct range *) this;
+		ranges = (struct range *) this;
+		for (i = 0; i < RANGES_PER_PAGE; i++)
+			(ranges+i)->next = (ranges+i+1);
+		(ranges + i - 1)->next = unused_ranges;
+		unused_ranges = ranges;
+		nr_unused_ranges += i;
+	}
+	return 0;
+}
+
+
+/* 
+ * Free ranges.
+ *
+ * Frees pages allocated by add_range_pages()
+ *
+ * Checks that all ranges allocated have been freed and emits a warning if this
+ * is not true.
+ */
+
+int free_ranges(void)
+{
+	int i;
+	struct range * this_range_page = first_range_page, 
+		* next_range_page = NULL;
+
+	if (ranges_allocated)
+		printk(" *** Warning: %ld ranges still allocated when "
+				"free_ranges() called.\n", ranges_allocated);
+
+	for (i = 0; i < num_range_pages; i++) {
+		next_range_page = (struct range *) 
+			(((unsigned long)
+			  (*RANGEPAGELINK(this_range_page))) & PAGE_MASK);
+		free_pages((unsigned long) this_range_page, 0);
+		this_range_page = next_range_page;
+	}
+
+	nr_unused_ranges = num_range_pages = ranges_allocated = 0;
+	unused_ranges = last_range_page = first_range_page = NULL;
+
+	return 0;
+}
+
+/* get_range
+ *
+ * Returns a free range, having removed it from the unused list and having
+ * incremented the usage count. May imply allocating a new page and may
+ * therefore fail, returning NULL instead.
+ * 
+ * No locking. This is because we are only called from suspend, which is single
+ * threaded.
+ */
+
+static struct range * get_range(void)
+{
+	struct range * result;
+	
+	if ((!unused_ranges) && (add_range_pages(1)))
+		return NULL;
+
+	result = unused_ranges;
+	unused_ranges = unused_ranges->next;
+	nr_unused_ranges--;
+	ranges_allocated++;
+	if (ranges_allocated > max_ranges_used)
+		max_ranges_used++;
+	result->minimum = result->maximum = 0;
+	result->next = NULL;
+	return result;
+}
+
+/*
+ * put_range.
+ *
+ * Returns a range to the pool of unused pages and decrements the usage count.
+ *
+ * Assumes unlinking is done by the caller.
+ */
+void put_range(struct range * range)
+{
+	if (!range) {
+		printk("Error! put_range called with NULL range.\n");
+		return;
+	}
+	range->minimum = range->maximum = 0;
+	range->next = unused_ranges;
+	unused_ranges = range;
+	ranges_allocated--;
+	nr_unused_ranges++;
+}
+
+/*
+ * put_range_chain.
+ *
+ * Returns a whole chain of ranges to the unused pool.
+ */
+void put_range_chain(struct rangechain * chain)
+{
+	int count = 0;
+	struct range * this;
+
+	if (chain->first) {
+		this = chain->first;
+		while (this) {
+			this->minimum = this->maximum = 0;
+			this=this->next;
+		}
+		chain->last->next = unused_ranges;
+		unused_ranges = chain->first;
+		count = chain->allocs - chain->frees;
+		ranges_allocated -= count;
+		nr_unused_ranges += count;
+
+		chain->first = NULL;
+		chain->last = NULL;
+		chain->size = 0;
+		chain->allocs = 0;
+		chain->frees = 0;
+		chain->timesusedoptimisation = 0;
+		chain->lastaccessed = NULL; /* Invalidate optimisation info */
+		chain->prevtolastaccessed = NULL;
+		chain->prevtoprev = NULL;
+	}
+}
+
+/* print_chain.
+ *
+ * Displays the contents of a chain.
+ *
+ * printmethod:
+ * 0: integer
+ * 1: hex
+ * 2: page number
+ */
+void print_chain(int debuglevel, struct rangechain * chain, int printmethod)
+{
+	struct range * this = chain->first;
+	int count = 0, size = 0;
+	
+	if ((console_loglevel < debuglevel) || (!this) ||
+			(!TEST_DEBUG_STATE(SUSPEND_RANGES)))
+		return;
+
+	if (!chain->name)
+		suspend_message(SUSPEND_RANGES, debuglevel, 1, "Chain %p\n", chain);
+	else
+		suspend_message(SUSPEND_RANGES, debuglevel, 1, "%s\n", chain->name);
+	
+	while (this) {
+		/*
+		 * 'This' is printed separately so it is displayed if an oops
+		 * results.
+		 */
+		switch (printmethod) {
+			case 0:
+				suspend_message(SUSPEND_RANGES, debuglevel, 1, "(%p) ",
+					this);
+				suspend_message(SUSPEND_RANGES, debuglevel, 1, "%lx-%lx; ",
+					this->minimum, this->maximum);
+				break;
+			case 1:
+				suspend_message(SUSPEND_RANGES, debuglevel, 1, "(%p)",
+					this);
+				suspend_message(SUSPEND_RANGES, debuglevel, 1, "%lu-%lu; ",
+					this->minimum, this->maximum);
+				break;
+			case 2:
+				suspend_message(SUSPEND_RANGES, debuglevel, 1, "(%p)",
+					this);
+				suspend_message(SUSPEND_RANGES, debuglevel, 1, "%p-%p; ",
+					page_address(mem_map+this->minimum),
+					page_address(mem_map+this->maximum) +
+						PAGE_SIZE - 1);
+				break;
+		}
+		size+= this->maximum - this->minimum + 1;
+		this = this->next;
+		count++;
+		if (!(count%4))
+			suspend_message(SUSPEND_RANGES, debuglevel, 1, "\n");
+	}
+	
+	if ((count%4))
+		suspend_message(SUSPEND_RANGES, debuglevel, 1, "\n");
+
+	suspend_message(SUSPEND_RANGES, debuglevel, 1,"%d entries/%ld allocated. "
+			"Allocated %d and freed %d. Size %d.",
+			count, 
+			ranges_allocated,
+			chain->allocs,
+			chain->frees,
+			size);
+	if (count != (chain->allocs - chain->frees)) {
+		chain->debug = 1;
+		check_shift_keys(1, "Discrepancy in chain.");
+	}
+	suspend_message(SUSPEND_RANGES, debuglevel, 1, "\n");
+}
+
+/*
+ * add_to_range_chain.
+ *
+ * Takes a value to be stored and a pointer to a chain and adds the value to 
+ * the range chain, merging with an existing range or  adding a new entry as
+ * necessary. Ranges  are stored in increasing order.
+ *
+ * Values should be consecutive, and so may need to be transformed first. (eg
+ * for pages, would want to call with page-mem_map).
+ *
+ * Important optimisation:
+ * We store in the chain info the location of the last range accessed or added
+ * (and its previous). If the next value is outside this range by one, we start
+ * from the previous entry instead of the start of the chain. In cases of heavy
+ * fragmentation, this saves a lot of time searching.
+ * 
+ * Returns:
+ * 0 if successful
+ * 1 if the value is already included.
+ * 2 if unable to allocate memory.
+ * 3 if fall out bottom (shouldn't happen).
+ */
+
+int add_to_range_chain(struct rangechain * chain, unsigned long value)
+{
+	struct range * this, * prev = NULL, * prevtoprev = NULL;
+	int usedoptimisation = 0;
+	
+	if (!chain->first) {	/* Empty */
+		chain->last = chain->first = get_range();
+		if (!chain->first) {
+			printk("Error unable to allocate the first range for "
+					"the chain.\n");
+			return 2;
+		}
+		chain->allocs++;
+		chain->first->maximum = value;
+		chain->first->minimum = value;
+		chain->size++;
+		return 0;
+	}
+	
+	this = chain->first;
+
+	if (chain->lastaccessed && chain->prevtolastaccessed &&
+		       chain->prevtoprev) {
+		if ((value + 1) == chain->lastaccessed->minimum) {
+			prev = chain->prevtoprev;
+			this = chain->prevtolastaccessed;
+			usedoptimisation = 1;
+		} else if (((value - 1) == chain->lastaccessed->maximum)) {
+			prev = chain->prevtolastaccessed;
+			this = chain->lastaccessed;
+			usedoptimisation = 1;
+		}
+	}
+
+	while (this) {
+		/* Need new entry prior to this? */
+		if ((value + 1) < this->minimum) {
+			struct range * new = get_range();
+			if (!new)
+				return 2;
+			chain->allocs++;
+			new->minimum = value;
+			new->maximum = value;
+			new->next = this;
+			/* Prior to start of chain? */
+			if (!prev)
+				chain->first = new;
+			else
+				prev->next = new;
+			if (!usedoptimisation) {
+				chain->prevtoprev = prevtoprev;
+				chain->prevtolastaccessed = prev;
+				chain->lastaccessed = new;
+			}
+			chain->size++;
+			return 0;
+		}
+
+		if ((this->minimum <= value) && (this->maximum >= value)) {
+			if (chain->name)
+				printk("%s:", chain->name);
+			else
+				printk("%p:", chain);
+				printk("Trying to add a value (%ld/0x%lx) already "
+				"included in chain.\n",
+				value, value);
+			print_chain(SUSPEND_ERROR, chain, 0);
+			check_shift_keys(1, NULL);
+			return 1;
+		}
+		if ((value + 1) == this->minimum) {
+			this->minimum = value;
+			if (!usedoptimisation) {
+				chain->prevtoprev = prevtoprev;
+				chain->prevtolastaccessed = prev;
+				chain->lastaccessed = this;
+			}
+			chain->size++;
+			return 0;
+		}
+		if ((value - 1) == this->maximum) {
+			if ((this->next) && 
+					(this->next->minimum == value + 1)) {
+				struct range * oldnext = this->next;
+				this->maximum = this->next->maximum;
+				this->next = this->next->next;
+				if ((chain->last) == oldnext)
+					chain->last = this;
+				put_range(oldnext);
+				/* Invalidate optimisation info */
+				chain->lastaccessed = NULL;	
+				chain->frees++;
+				if (!usedoptimisation) {
+					chain->prevtoprev = prevtoprev;
+					chain->prevtolastaccessed = prev;
+					chain->lastaccessed = this;
+				}
+				chain->size++;
+				return 0;
+			} 
+			this->maximum = value;
+			if (!usedoptimisation) {
+				chain->prevtoprev = prevtoprev;
+				chain->prevtolastaccessed = prev;
+				chain->lastaccessed = this;
+			}
+			chain->size++;
+			return 0;
+		}
+		if (!this->next) {
+			struct range * new = get_range();
+			if (!new) {
+				printk("Error unable to append a new range to "
+						"the chain.\n");
+				return 2;
+			}
+			chain->allocs++;
+			new->minimum = value;
+			new->maximum = value;
+			new->next = NULL;
+			this->next = new;
+			chain->last = new;
+			if (!usedoptimisation) {
+				chain->prevtoprev = prev;
+				chain->prevtolastaccessed = this;
+				chain->lastaccessed = new;
+			}
+			chain->size++;
+			return 0;
+		}
+		prevtoprev = prev;
+		prev = this;
+		this = this->next;
+	}
+	printk("\nFell out the bottom of add_to_range_chain. This shouldn't "
+			"happen!\n");
+	SET_RESULT_STATE(SUSPEND_ABORTED);
+	return 3;
+}
+
+/* append_range
+ * Used where we know a range is to be added to the end of the list
+ * and does not need merging with the current last range.
+ * (count_data_pages only at the moment)
+ */
+
+int append_range_to_range_chain(struct rangechain * chain, 
+		unsigned long minimum, unsigned long maximum)
+{
+	struct range * newrange = NULL;
+
+	newrange = get_range();
+	if (!newrange) {
+		printk("Error unable to append a new range to the chain.\n");
+		return 2;
+	}
+
+	chain->allocs++;
+	chain->size+= (maximum - minimum + 1);
+	newrange->minimum = minimum;
+	newrange->maximum = maximum;
+	newrange->next = NULL;
+
+	if (chain->last) {
+		chain->last->next = newrange;
+		chain->last = newrange;
+	} else 
+		chain->last = chain->first = newrange;
+
+	/* No need to reset optimisation info since added to end */
+	return 0;
+}
+
+int append_to_range_chain(int chain, unsigned long min, unsigned long max)
+{
+	int result = 0;
+
+	switch (chain) {
+		case 0:
+			return 0;
+		case 1:
+			result = append_range_to_range_chain(
+					&pagedir1.origranges, min, max);
+			break;
+		case 2:
+			result = append_range_to_range_chain(
+					&pagedir2.origranges, min, max);
+			if (!result)
+				result = append_range_to_range_chain(
+					&pagedir1.destranges, min, max);
+	}
+	return result;
+}
+
+/* -------------- Routines for relativising and absoluting ranges -------------
+ *
+ * Prepare rangesets for save by translating addresses to relative indices.
+ */
+void relativise_ranges(void)
+{
+	struct range * this_range_page = first_range_page;
+	int i;
+	
+	while (this_range_page) {
+		struct range * this_range = this_range_page;
+		for (i = 0; i < RANGES_PER_PAGE; i++) {
+			if (this_range->next) {
+				struct range * orig = this_range->next;
+				this_range->next =
+					RANGE_RELATIVE(this_range->next);
+				suspend_message(SUSPEND_RANGES, SUSPEND_VERBOSE, 1,
+					"Relativised range %d on this page is %p. Absolutised range is %p.\n",
+					i, this_range->next, orig);
+			}
+			this_range++;
+		}
+		this_range_page = (struct range *)
+			((*RANGEPAGELINK(this_range_page)) & PAGE_MASK);
+	}
+}
+
+/* Convert ->next pointers for ranges back to absolute values.
+ * The issue is finding out what page the absolute value is now at.
+ * If we use an array of values, we gain speed, but then we need to
+ * be able to allocate contiguous pages. Fortunately, this is done
+ * prior to loading pagesets, so we can just allocate the pages
+ * needed, set up our array and use it and then discard the data
+ * before we exit.
+ */
+
+void absolutise_ranges()
+{
+	struct range * this_range_page = first_range_page;
+	int i;
+	
+	while (this_range_page) {
+		struct range * this_range = this_range_page;
+		for (i = 0; i < RANGES_PER_PAGE; i++) {
+			if (this_range->next) {
+				struct range * orig = this_range->next;
+				this_range->next = 
+					RANGE_ABSOLUTE(this_range->next);
+				suspend_message(SUSPEND_RANGES, SUSPEND_VERBOSE, 1,
+					"Relativised range %d on this page is %p. Absolutised range is %p.\n",
+					i, orig, this_range->next);
+			}
+			this_range++;
+		}
+		this_range_page = (struct range *)
+			((*RANGEPAGELINK(this_range_page)) & PAGE_MASK);
+	}
+}
+
+void absolutise_chain(struct rangechain * chain)
+{
+	if (chain->first)
+		chain->first = RANGE_ABSOLUTE(chain->first);
+	if (chain->last)
+		chain->last = RANGE_ABSOLUTE(chain->last);
+	if (chain->lastaccessed)
+		chain->lastaccessed = RANGE_ABSOLUTE(chain->lastaccessed);
+	if (chain->prevtolastaccessed)
+		chain->prevtolastaccessed =
+			RANGE_ABSOLUTE(chain->prevtolastaccessed);
+	if (chain->prevtoprev)
+		chain->prevtoprev =
+			RANGE_ABSOLUTE(chain->prevtoprev);
+}
+
+void relativise_chain(struct rangechain * chain)
+{
+	if (chain->first)
+		chain->first = RANGE_RELATIVE(chain->first);
+	if (chain->last)
+		chain->last = RANGE_RELATIVE(chain->last);
+	if (chain->lastaccessed)
+		chain->lastaccessed = RANGE_RELATIVE(chain->lastaccessed);
+	if (chain->prevtolastaccessed)
+		chain->prevtolastaccessed =
+			RANGE_RELATIVE(chain->prevtolastaccessed);
+	if (chain->prevtoprev)
+		chain->prevtoprev = RANGE_RELATIVE(chain->prevtoprev);
+}
+
+/*
+ * Each page in the rangepages lists starts with a pointer to the next page
+ * containing the list. This lets us only use order zero allocations.
+ */
+#define POINTERS_PER_PAGE ((PAGE_SIZE / sizeof(void *)) - 1)
+static unsigned long * range_pagelist = NULL;
+
+unsigned long * get_rangepages_list_entry(int index)
+{
+	int pagenum, offset, i;
+	unsigned long * current_list_page = range_pagelist;
+
+	BUG_ON(index > num_range_pages);
+
+	pagenum = index / POINTERS_PER_PAGE;
+	offset = index - (pagenum * POINTERS_PER_PAGE);
+
+	for (i = 0; i < pagenum; i++)
+		current_list_page = *((unsigned long **) current_list_page);
+
+	return (unsigned long *) current_list_page[offset];
+}
+
+int get_rangepages_list(void)
+{
+	struct range * this_range_page = first_range_page;
+	int i, j, pages_needed, num_in_this_page;
+	unsigned long * current_list_page = range_pagelist;
+	unsigned long * prev_list_page = NULL;
+
+	pages_needed =
+		((num_range_pages + POINTERS_PER_PAGE - 1) / POINTERS_PER_PAGE);
+	
+	for (i = 0; i < pages_needed; i++) {
+		int page_start = i * POINTERS_PER_PAGE;
+		
+		if (!current_list_page) {
+			current_list_page =
+				(unsigned long *) get_grabbed_pages(0);
+			if (!current_list_page)
+				current_list_page = (unsigned long *) get_zeroed_page(GFP_ATOMIC);
+			if (!current_list_page) {
+				abort_suspend("Unable to allocate memory for a range pages list.");
+				printk("Number of range pages is %d.\n", num_range_pages);
+				return -ENOMEM;
+			}
+
+			current_list_page[0] = 0;
+			if (!prev_list_page)
+				range_pagelist = current_list_page;
+			else {
+				*prev_list_page = (unsigned long) current_list_page;
+				prev_list_page = current_list_page;
+			}
+		}
+	
+		num_in_this_page = num_range_pages - page_start;
+		if (num_in_this_page > POINTERS_PER_PAGE)
+			num_in_this_page = POINTERS_PER_PAGE;
+		
+		for (j = 1; j <= num_in_this_page; j++) {
+			current_list_page[j] = (unsigned long) this_range_page;
+
+			this_range_page = (struct range *) (((unsigned long)
+				(*RANGEPAGELINK(this_range_page))) & PAGE_MASK);
+		}
+		
+		for (j = (num_in_this_page + 1); j <= POINTERS_PER_PAGE; j++)
+			current_list_page[j] = 0;
+
+		if ((num_range_pages - page_start) > POINTERS_PER_PAGE)
+			current_list_page = (unsigned long *) current_list_page[0];
+	}
+
+	return 0;
+}
+
+void put_rangepages_list(void)
+{
+	unsigned long * last;
+
+	while (range_pagelist) {
+		last = range_pagelist;
+		range_pagelist = *((unsigned long **) range_pagelist);
+		free_pages((unsigned long) last, 0);
+	}
+}
+
+int PageRangePage(char * seeking)
+{
+	int i;
+	
+	for (i = 1; i <= num_range_pages; i++)
+		if (get_rangepages_list_entry(i) == 
+			(unsigned long *) seeking)
+			return 1;
+
+	return 0;
+}
+/* relocate_rangepages
+ * 
+ * Called at the start of resuming. As well as absolutising pages, we need
+ * to ensure they won't be overwritten by the kernel we're restoring. 
+ */
+int relocate_rangepages()
+{
+	void **eaten_memory = NULL;
+	void **c = eaten_memory, *m = NULL, *f;
+	int oom = 0, i, numeaten = 0;
+	unsigned long * prev_page = NULL;
+
+	for (i = 1; i <= num_range_pages; i++) {
+		int this_collides = 0;
+		unsigned long * this_page = get_rangepages_list_entry(i);
+
+		this_collides = PageInUse(virt_to_page(this_page));
+
+		if (!this_collides) {
+			prev_page = this_page;
+			continue;
+		}
+
+		while ((m = (void *) get_zeroed_page(GFP_ATOMIC))) {
+			memset(m, 0, PAGE_SIZE);
+			if (!PageInUse(virt_to_page(m))) {
+				copy_page(m, (void *) this_page);
+				free_page((unsigned long) this_page);
+				if (i == 1)
+					first_range_page = m;
+				else
+					*RANGEPAGELINK(prev_page) =
+						(i | (unsigned long) m);
+				prev_page = m;
+				break;
+			}
+			numeaten++;
+			eaten_memory = m;
+			*eaten_memory = c;
+			c = eaten_memory;
+		}
+
+		if (!m) {
+			printk("\nRan out of memory trying to relocate "
+				"rangepages (tried %d pages).\n", numeaten);
+			oom = 1;
+			break;
+		}
+	}
+		
+	c = eaten_memory;
+	while(c) {
+		f = c;
+		c = *c;
+		if (f)
+			free_pages((unsigned long) f, 0);
+	}
+	eaten_memory = NULL;
+	
+	if (oom) 
+		return -ENOMEM;
+	else
+		return 0;
+}
+
+EXPORT_SYMBOL(put_rangepages_list);
+EXPORT_SYMBOL(get_rangepages_list);
+EXPORT_SYMBOL(get_rangepages_list_entry);
+EXPORT_SYMBOL(absolutise_chain);
+EXPORT_SYMBOL(relativise_chain);
+EXPORT_SYMBOL(put_range);
+EXPORT_SYMBOL(put_range_chain);
+EXPORT_SYMBOL(add_to_range_chain);
+EXPORT_SYMBOL(PageRangePage);
diff -ruN 20-old/kernel/power/range.h 20-new/kernel/power/range.h
--- 20-old/kernel/power/range.h	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/range.h	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,107 @@
+/*
+ * kernel/power/range.h
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * It contains declarations related to ranges. Ranges (otherwise
+ * known as an extent, I'm told), is suspend's method of storing
+ * all of the metadata for the image. See range.c for more info.
+ *
+ */
+
+struct rangechain {
+	struct range * first;
+	struct range * last;
+	int size; /* size of the range ie sum (max-min+1) */
+	int allocs;
+	int frees;
+	int debug;
+	int timesusedoptimisation;
+	char * name;
+	struct range * lastaccessed, *prevtolastaccessed, *prevtoprev;
+};
+
+/*
+ * We rely on ranges not fitting evenly into a page.
+ * The last four bytes are used to store the number
+ * of the page, to make saving & reloading pages simpler.
+ */
+struct range {
+	unsigned long minimum;
+	unsigned long maximum;
+	struct range * next;
+};
+
+
+#define RANGES_PER_PAGE (PAGE_SIZE / (sizeof(struct range)))
+#define RANGEPAGELINK(x) ((unsigned long *) \
+		((((unsigned long) x) & PAGE_MASK) + PAGE_SIZE - \
+		 sizeof(unsigned long)))
+
+#define range_for_each(rangechain, rangepointer, value) \
+if ((rangechain)->first) \
+	for ((rangepointer) = (rangechain)->first, (value) = \
+			(rangepointer)->minimum; \
+	     ((rangepointer) && ((rangepointer)->next || (value) <= \
+				 (rangepointer)->maximum)); \
+	     (((value) == (rangepointer)->maximum) ? \
+		((rangepointer) = (rangepointer)->next, (value) = \
+		 ((rangepointer) ? (rangepointer)->minimum : 0)) : \
+			(value)++))
+
+/*
+ * When using compression and expected_compression > 0,
+ * we allocate fewer swap entries, so GET_RANGE_NEXT can
+ * validly run out of data to return.
+ */
+#define GET_RANGE_NEXT(currentrange, currentval) \
+{ \
+	if (currentrange) { \
+		if ((currentval) == (currentrange)->maximum) { \
+			if ((currentrange)->next) { \
+				(currentrange) = (currentrange)->next; \
+				(currentval) = (currentrange)->minimum; \
+			} else { \
+				(currentrange) = NULL; \
+				(currentval) = 0; \
+			} \
+		} else \
+			currentval++; \
+	} \
+}
+
+extern int max_ranges_used;
+extern int num_range_pages;
+int add_to_range_chain(struct rangechain * chain, unsigned long value);
+void put_range_chain(struct rangechain * chain);
+void print_chain(int debuglevel, struct rangechain * chain, int printasswap);
+int free_ranges(void);
+int append_to_range_chain(int chain, unsigned long min, unsigned long max);
+void relativise_ranges(void);
+void relativise_chain(struct rangechain * chain);
+void absolutise_ranges(void);
+void absolutise_chain(struct rangechain * chain);
+int get_rangepages_list(void);
+void put_rangepages_list(void);
+unsigned long * get_rangepages_list_entry(int index);
+int relocate_rangepages(void);
+
+extern struct range * first_range_page, * last_range_page;
+
+#define RANGE_RELATIVE(x) (struct range *) ((((unsigned long) x) & \
+			(PAGE_SIZE - 1)) | \
+		((*RANGEPAGELINK(x) & (PAGE_SIZE - 1)) << PAGE_SHIFT))
+#define RANGE_ABSOLUTE(entry) (struct range *) \
+	((((unsigned long) (entry)) & (PAGE_SIZE - 1)) | \
+	 (unsigned long) get_rangepages_list_entry(((unsigned long) (entry)) >> PAGE_SHIFT))
+
+/* swap_entry_to_range_val & range_val_to_swap_entry: 
+ * We are putting offset in the low bits so consecutive swap entries
+ * make consecutive range values */
+#define swap_entry_to_range_val(swp_entry) ((swp_entry.val >> 8) | \
+		((swp_entry.val & 0x3f) << 24))
+#define range_val_to_swap_entry(val) (swp_entry_t) { ((val >> 24) | \
+		((val & 0xffffff) << 8)) }
+
diff -ruN 20-old/kernel/power/suspend_block_io.c 20-new/kernel/power/suspend_block_io.c
--- 20-old/kernel/power/suspend_block_io.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_block_io.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,914 @@
+/*
+ * block_io.c
+ *
+ * Copyright 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * Distributed under GPLv2.
+ * 
+ * This file contains block io functions for suspend2. These are
+ * used by the swapwriter and it is planned that they will also
+ * be used by the NFSwriter.
+ *
+ */
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/locks.h>
+
+#include "suspend.h"
+#include "block_io.h"
+#include "proc.h"
+#include "plugins.h"
+
+/* Bits in struct io_info->flags */
+#define IO_WRITING 1
+#define IO_RESTORE_PAGE_PROT 2
+#define IO_AWAITING_READ 3
+#define IO_AWAITING_WRITE 4
+#define IO_CLEANUP_IN_PROGRESS 5
+#define IO_HANDLE_PAGE_PROT 6
+
+//#define TUNE_BATCHING
+
+/*
+ * ---------------------------------------------------------------
+ *
+ *     IO in progress information storage and helpers
+ *
+ * ---------------------------------------------------------------
+ */
+
+struct io_info {
+	struct buffer_head * sys_struct;
+	int blocks[PAGE_SIZE/512];
+	struct page * buffer_page;
+	struct page * data_page;
+	unsigned long flags;
+	kdev_t dev;
+	int blocks_used;
+	int block_size;
+	struct list_head list;
+	int readahead_index;
+};
+
+static LIST_HEAD(ioinfo_free);
+static LIST_HEAD(ioinfo_ready_for_cleanup);
+static LIST_HEAD(ioinfo_busy);
+static LIST_HEAD(ioinfo_submit_batch);
+static spinlock_t ioinfo_lists_lock = SPIN_LOCK_UNLOCKED;
+
+static int submit_batch = 0, submit_batch_size = 32;
+static int submit_batched(void);
+
+struct task_struct * suspend_bio_task;
+
+/* [Max] number of I/O operations pending */
+static atomic_t outstanding_io;
+static int max_outstanding_io = 0;
+static int buffer_allocs, buffer_frees;
+
+/* [Max] number of pages used for above struct */
+static int infopages = 0;
+static int maxinfopages = 0;
+
+static volatile unsigned long suspend_readahead_flags[((MAX_READAHEAD + (8 * sizeof(unsigned long) - 1)) / (8 * sizeof(unsigned long)))];
+static spinlock_t suspend_readahead_flags_lock = SPIN_LOCK_UNLOCKED;
+static struct page * suspend_readahead_pages[MAX_READAHEAD];
+
+static unsigned long nr_schedule_calls[6];
+
+static char * sch_caller[] = {
+	"get_io_info_struct       ",
+	"suspend_finish_all_io    ",
+	"wait_on_one_page         ",
+	"submit                   ",
+	"start_one                ",
+	"suspend_wait_on_readahead",
+};
+
+static void suspend_io_cleanup(void * data);
+
+static void do_bio_wait(int caller)
+{
+	int num_cleaned = 0;
+	struct io_info * this, * next = NULL;
+	nr_schedule_calls[caller]++;
+	
+	/* Don't want to wait on I/O we haven't submitted! */
+	submit_batched();
+
+	if (!list_empty(&ioinfo_ready_for_cleanup))
+		list_for_each_entry_safe(this, next, &ioinfo_ready_for_cleanup, list) {
+			suspend_io_cleanup((void *) this);
+			num_cleaned++;
+			if (num_cleaned == 32)
+				break;
+		}
+	
+	run_task_queue(&tq_disk);
+	schedule();
+}
+
+/*
+ * cleanup_one
+ * 
+ * Description: Clean up after completing I/O on a page.
+ * Arguments:	struct io_info:	Data for I/O to be completed.
+ */
+static inline void cleanup_one(struct io_info * io_info)
+{
+	struct page * buffer_page;
+	struct page * data_page;
+	char *buffer_address, *data_address;
+	int reading;
+
+	buffer_page = io_info->buffer_page;
+	data_page = io_info->data_page;
+
+	/* 
+	 * Already being cleaned up? Can't happen while we're single
+	 * threaded, but a good check for later.
+	 */
+	
+	if (test_and_set_bit(IO_CLEANUP_IN_PROGRESS, &io_info->flags))
+		return;
+
+	reading = test_bit(IO_AWAITING_READ, &io_info->flags);
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		"Cleanup IO: [%p]\n", 
+		io_info);
+
+	TryLockPage(buffer_page); /* Ensure it's locked */
+
+	if (!block_flushpage(buffer_page, 0))
+		PAGE_BUG(buffer_page);
+	
+	if (buffer_page->mapping &&
+	    buffer_page->mapping->a_ops &&
+	    buffer_page->mapping->a_ops->sync_page)
+		buffer_page->mapping->a_ops->sync_page(buffer_page);
+
+	buffer_page->mapping = NULL;
+	buffer_page->index = 0;
+	UnlockPage(buffer_page);
+
+	if (reading && io_info->readahead_index == -1) {
+		/*
+		 * Copy the page we read into the buffer our caller provided.
+		 */
+		data_address = (char *) kmap(data_page);
+		buffer_address = (char *) kmap(buffer_page);
+		memcpy(data_address, buffer_address, PAGE_SIZE);
+		flush_dcache_page(data_page);
+		kunmap(data_page);
+		kunmap(buffer_page);
+	}
+
+	if (!reading || io_info->readahead_index == -1) {
+		/* Sanity check */
+		if (page_count(buffer_page) != 2)
+			printk(KERN_EMERG "Cleanup IO: Page count is %d. Not good!\n",
+					page_count(buffer_page));
+		put_page(buffer_page);
+		__free_pages(buffer_page, 0);
+		buffer_frees++;
+	} else
+		put_page(buffer_page);
+	
+	atomic_dec(&outstanding_io);
+	io_info->sys_struct = NULL;
+	io_info->flags = 0;
+}
+
+/*
+ * get_io_info_struct
+ *
+ * Description:	Get an I/O struct.
+ * Returns:	Pointer to the struct prepared for use.
+ */
+static struct io_info * get_io_info_struct(void)
+{
+	unsigned long newpage = 0, flags;
+	struct io_info * this = NULL;
+	int remaining = 0;
+
+	do {
+		/* Have we reached our number-of-IOs-activate-at-one limit? */
+		if ((max_async_ios) && (atomic_read(&outstanding_io) >= max_async_ios)) {
+			do_bio_wait(1);
+			continue;
+		}
+
+		/* Can start a new I/O. Is there a free one? */
+		if (!list_empty(&ioinfo_free)) {
+			/* Yes. Grab it. */
+			spin_lock_irqsave(&ioinfo_lists_lock, flags);
+			break;
+		}
+
+		/* No. Need to allocate a new page for I/O info structs. */
+		newpage = get_zeroed_page(GFP_ATOMIC);
+		if (!newpage)
+			continue;
+
+		suspend_message(SUSPEND_MEMORY, SUSPEND_VERBOSE, 0,
+				"[NewIOPage %lx]", newpage);
+		infopages++;
+		if (infopages > maxinfopages)
+			maxinfopages++;
+
+		/* Prepare the new page for use. */
+		this = (struct io_info *) newpage;
+		remaining = PAGE_SIZE;
+		spin_lock_irqsave(&ioinfo_lists_lock, flags);
+		while (remaining >= (sizeof(struct io_info))) {
+			list_add_tail(&this->list, &ioinfo_free);
+			this = (struct io_info *) (((char *) this) + 
+					sizeof(struct io_info));
+			remaining -= sizeof(struct io_info);
+		}
+		break;
+	} while (1);
+
+	/* We have an I/O info struct. Move it to the busy list. */
+	this = list_entry(ioinfo_free.next, struct io_info, list);
+	list_move_tail(&this->list, &ioinfo_busy);
+	spin_unlock_irqrestore(&ioinfo_lists_lock, flags);
+	return this;
+}
+
+/*
+ * suspend_finish_all_io
+ *
+ * Description:	Finishes all IO and frees all IO info struct pages.
+ */
+static void suspend_finish_all_io(void)
+{
+	struct io_info * this, * next = NULL;
+	unsigned long flags;
+
+	/* Submit any pending write batch */
+	submit_batched();
+
+	/* Wait for all I/O to complete. */
+	while (!list_empty(&ioinfo_busy))
+		do_bio_wait(2);
+
+	/* Wait for all I/O to complete. */
+ 	while (atomic_read(&outstanding_io))
+		do_bio_wait(2);
+
+	/*
+	 * We're single threaded and all I/O is completed, so we shouldn't
+	 * need to use the spinlock, but let's be safe.
+	 */
+	spin_lock_irqsave(&ioinfo_lists_lock, flags);
+	
+	/* 
+	 * Two stages, to avoid using freed pages.
+	 *
+	 * First free all io_info structs on a page except the first.
+	 */
+	list_for_each_entry_safe(this, next, &ioinfo_free, list) {
+		if (((unsigned long) this) & ~PAGE_MASK)
+			list_del(&this->list);
+	}
+
+	/* 
+	 * Now we have only one reference to each page, and can safely
+	 * free pages, knowing we're not going to be trying to access the
+	 * same page after freeing it.
+	 */
+	list_for_each_entry_safe(this, next, &ioinfo_free, list) {
+		list_del(&this->list);
+		free_pages((unsigned long) this, 0);
+		infopages--;
+		suspend_message(SUSPEND_MEMORY, SUSPEND_VERBOSE, 0,
+				"[FreedIOPage %lx]", this);
+	}
+	
+	spin_unlock_irqrestore(&ioinfo_lists_lock, flags);
+}
+
+/*
+ * wait_on_one_page
+ *
+ * Description:	Wait for a particular I/O to complete.
+ */
+static void wait_on_one_page(struct io_info * io_info)
+{
+	do {
+		do_bio_wait(3);
+	} while (io_info->flags);
+}
+
+/*
+ * suspend_reset_io_stats
+ *
+ * Description:	Reset all our sanity-checking statistics.
+ */
+static void suspend_reset_io_stats(void)
+{
+	int i;
+	
+	max_outstanding_io = 0;
+	maxinfopages = 0;
+	buffer_allocs = buffer_frees = 0;
+	
+	for (i = 0; i < 6; i++)
+		nr_schedule_calls[i] = 0;
+}
+
+/*
+ * suspend_check_io_stats
+ *
+ * Description:	Check that our statistics look right and print
+ * 		any debugging info wanted.
+ */
+static void suspend_check_io_stats(void)
+{
+	int i;
+
+	BUG_ON(atomic_read(&outstanding_io));
+	BUG_ON(infopages);
+	BUG_ON(buffer_allocs != buffer_frees);
+	BUG_ON(!list_empty(&ioinfo_busy));
+	BUG_ON(!list_empty(&ioinfo_ready_for_cleanup));
+	BUG_ON(!list_empty(&ioinfo_free));
+
+	if (atomic_read(&outstanding_io))
+		suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+			"Outstanding_io after writing is %d.\n",
+			atomic_read(&outstanding_io));
+	suspend_message(SUSPEND_WRITER, SUSPEND_LOW, 0,
+			"Maximum outstanding_io was %d.\n",
+			max_outstanding_io);
+	if (infopages)
+		suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+				"Info pages is %d.\n",
+				infopages);
+	suspend_message(SUSPEND_WRITER, SUSPEND_LOW, 0,
+			"Max info pages was %d.\n",
+			maxinfopages);
+	if (buffer_allocs != buffer_frees)
+		suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+			"Buffer allocs (%d) != buffer frees (%d)",
+				buffer_allocs,
+				buffer_frees);
+	for(i = 0; i < 6; i++)
+		suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+			"Nr schedule calls %s: %lu.\n", sch_caller[i], nr_schedule_calls[i]);
+}
+
+/* suspend_io_cleanup
+ */
+
+static void suspend_io_cleanup(void * data)
+{
+	struct io_info * io_info = (struct io_info *) data;
+	int readahead_index;
+	unsigned long flags;
+
+	/*
+	 * If this I/O was a readahead, remember its index.
+	 */
+	readahead_index = io_info->readahead_index;
+
+	/*
+	 * Do the cleanup.
+	 */
+	cleanup_one(io_info);
+
+	/*
+	 * Record the readahead as done.
+	 */
+	if (readahead_index > -1) {
+		int index = readahead_index/(8 * sizeof(unsigned long));
+		int bit = readahead_index - (index * 8 * sizeof(unsigned long));
+		spin_lock_irqsave(&suspend_readahead_flags_lock, flags);
+		set_bit(bit, &suspend_readahead_flags[index]);
+		spin_unlock_irqrestore(&suspend_readahead_flags_lock, flags);
+	}
+
+	/*
+	 * Add it to the free list.
+	 */
+	spin_lock_irqsave(&ioinfo_lists_lock, flags);
+	list_move_tail(&io_info->list, &ioinfo_free);
+	spin_unlock_irqrestore(&ioinfo_lists_lock, flags);
+}
+
+/*
+ * suspend_end_bio
+ *
+ * Description:	Function called by block driver from interrupt context when I/O
+ * 		is completed. This is the reason we use spinlocks in
+ * 		manipulating the io_info lists. 		
+ * 		Nearly the fs/buffer.c version, but we want to mark the page as 
+ * 		done in our own structures too.
+ */
+
+extern void end_buffer_io_async(struct buffer_head * bh, int uptodate);
+
+static void suspend_end_buffer_io_async(struct buffer_head * bh, int uptodate)
+{
+	struct page *page = bh->b_page;
+	struct io_info * io_info = (struct io_info *) page->index;
+	unsigned long flags;
+
+	end_buffer_io_async(bh, uptodate);
+
+	if (Page_Uptodate(page)) {
+		spin_lock_irqsave(&ioinfo_lists_lock, flags);
+		list_move_tail(&io_info->list, &ioinfo_ready_for_cleanup);
+		spin_unlock_irqrestore(&ioinfo_lists_lock, flags);
+	}
+}
+
+/**
+ *	submit - submit BIO request.
+ *	@rw:	READ or WRITE.
+ *	@io_info: IO info structure.
+ *
+ *	Straight from the textbook - allocate and initialize the bio.
+ *	If we're writing, make sure the page is marked as dirty.
+ *	Then submit it and carry on.
+ */
+
+static void suspend_brw_page(int rw, struct io_info * io_info)
+{
+	struct buffer_head *head, *bh;
+	struct page * page = io_info->buffer_page;
+	kdev_t dev = io_info->dev;
+	int *b = io_info->blocks;
+	int size = io_info->block_size;
+
+	BUG_ON(!PageLocked(page));
+
+	if (!page->buffers)
+ 		create_empty_buffers(page, dev, size);
+
+	io_info->sys_struct = head = bh = page->buffers;
+
+	do {
+		lock_buffer(bh);
+		bh->b_blocknr = *(b++);
+		set_bit(BH_Mapped, &bh->b_state);
+		if (rw == WRITE) {
+			set_bit(BH_Uptodate, &bh->b_state);
+			__mark_buffer_dirty(bh);
+		} else {
+			clear_bit(BH_Uptodate, &bh->b_state);
+			atomic_set_buffer_clean(bh);
+			__mark_buffer_clean(bh);
+		}
+		bh->b_flushtime = jiffies + 1;
+		bh->b_end_io = suspend_end_buffer_io_async;
+		mark_buffer_async(bh, 1);
+		bh = bh->b_this_page;
+	} while (bh != head);
+
+	do {
+		struct buffer_head *next = bh->b_this_page;
+		submit_bh(rw, bh);
+		bh = next;
+	} while (bh != head);
+}
+
+/*
+ * suspend_writepage
+ *
+ * Description:	Write the page function for the mapping.
+ * 		TODO: Check whether mappings are even needed
+ * 		anymore.
+ */
+static int suspend_writepage(struct page *page)
+{
+	struct io_info * io_info = (struct io_info *) page->index;
+	
+	suspend_message(SUSPEND_IO, SUSPEND_HIGH, 0, "Writepage %p.", page);
+	if (remove_exclusive_swap_page(page)) {
+		suspend_message(SUSPEND_IO, SUSPEND_VERBOSE, 0,
+				"Unlocking page %p. (1)",
+				page);
+		UnlockPage(page);
+		return 0;
+	}
+ 	suspend_brw_page(WRITE, io_info);
+	return 0;
+}
+
+
+static struct address_space_operations suspend_aops = {
+	writepage: suspend_writepage,
+	sync_page: block_sync_page,
+};
+
+struct address_space suspend_space = {
+	LIST_HEAD_INIT(suspend_space.clean_pages),
+	LIST_HEAD_INIT(suspend_space.dirty_pages),
+	LIST_HEAD_INIT(suspend_space.locked_pages),
+	0,				/* nrpages	*/
+	&suspend_aops,
+};
+
+/*
+ * suspend_set_block_size
+ *
+ * Description: Set the blocksize for a bdev. This is a separate function
+ * 		because we have different versions for 2.4 and 2.6.
+ */
+static int suspend_set_block_size(kdev_t bdev, int size)
+{
+	return set_blocksize(bdev, size);
+}
+
+static int suspend_get_block_size(kdev_t bdev)
+{
+	int blksize;
+
+	if (!blksize_size[MAJOR(bdev)])
+		blksize = 1024;
+	else
+		blksize = blksize_size[MAJOR(bdev)][MINOR(bdev)];
+
+	if (!blksize) {
+		printk(name_suspend "%x: Blocksize not set?", bdev);
+		blksize = PAGE_SIZE;
+	}
+
+	return blksize;
+}
+
+static int real_submit_one(int rw, struct io_info * io_info)
+{
+ 	suspend_brw_page(rw, io_info);
+	if (PageError(io_info->buffer_page)) {
+		check_shift_keys(1, "PageError trying to submit page.");
+		printk("IO_info is at %p.\n", io_info);
+		TryLockPage(io_info->buffer_page); /* Ensure it's locked */
+
+		if (!block_flushpage(io_info->buffer_page, 0))
+			PAGE_BUG(io_info->buffer_page);
+		io_info->buffer_page->mapping = NULL;
+		UnlockPage(io_info->buffer_page);
+		put_page(io_info->buffer_page);
+		__free_pages(io_info->buffer_page, 0);
+		return 1;
+	}
+	return 0;
+}
+/* 
+ * We don't need to worry about new requests being added to the list;
+ * we're called from process context 
+ */
+static int submit_batched(void)
+{
+	unsigned long flags;
+	struct io_info * this, * next = NULL;
+	int result = 0;
+	
+	list_for_each_entry_safe(this, next, &ioinfo_submit_batch, list) {
+		spin_lock_irqsave(&ioinfo_lists_lock, flags);
+		list_move_tail(&this->list, &ioinfo_busy);
+		spin_unlock_irqrestore(&ioinfo_lists_lock, flags);
+		if (test_bit(IO_AWAITING_READ, &this->flags))
+			result += real_submit_one(READ, this);
+		else
+			result += real_submit_one(WRITE, this);
+	}
+	submit_batch = 0;
+	return result;
+}
+static int add_to_batch(struct io_info * io_info)
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&ioinfo_lists_lock, flags);
+	/* We have an I/O info struct. Move it to the batch list. */
+	list_move_tail(&io_info->list, &ioinfo_submit_batch);
+	spin_unlock_irqrestore(&ioinfo_lists_lock, flags);
+
+	submit_batch++;
+
+	if (submit_batch == submit_batch_size)
+		return submit_batched();
+
+	return 0;
+}
+/*
+ * start_one
+ *
+ * Description:	Prepare and start a read or write operation.
+ * 		Note that we use our own buffer for reading or writing.
+ * 		This simplifies doing readahead and asynchronous writing.
+ * 		We can begin a read without knowing the location into which
+ * 		the data will eventually be placed, and the buffer passed
+ * 		for a write can be reused immediately (essential for the
+ * 		plugins system).
+ * 		Failure? What's that?
+ * Returns:	The io_info struct created.
+ */
+static struct io_info * start_one(int rw, struct submit_params * submit_info)
+{
+	struct io_info * io_info = get_io_info_struct();
+	unsigned long buffer_virt = 0;
+	char * to, * from;
+	struct page * buffer_page;
+	int i;
+
+	if (!io_info)
+		return NULL;
+
+	/* Get our local buffer */
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 1,
+			"Start_IO: [%p]", io_info);
+
+	/* Copy settings to the io_info struct */
+	io_info->data_page = submit_info->page;
+	io_info->readahead_index = submit_info->readahead_index;
+
+	if (io_info->readahead_index == -1) {
+		while (!(buffer_virt = get_zeroed_page(GFP_ATOMIC)))
+			do_bio_wait(4);
+
+		buffer_allocs++;
+		suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+				"[ALLOC BUFFER]->%d",
+				nr_free_pages());
+		buffer_page = virt_to_page(buffer_virt);
+		io_info->buffer_page = buffer_page;
+	} else {
+		unsigned long flags;
+		int index = io_info->readahead_index/(8 * sizeof(unsigned long));
+		int bit = io_info->readahead_index - index * 8 * sizeof(unsigned long);
+
+		spin_lock_irqsave(&suspend_readahead_flags_lock, flags);
+		clear_bit(bit, &suspend_readahead_flags[index]);
+		spin_unlock_irqrestore(&suspend_readahead_flags_lock, flags);
+
+		io_info->buffer_page = buffer_page = io_info->data_page;
+	}
+
+	/* If writing, copy our data. The data is probably in
+	 * lowmem, but we cannot be certain. If there is no
+	 * compression/encryption, we might be passed the
+	 * actual source page's address. */
+	if (rw == WRITE) {
+		set_bit(IO_WRITING, &io_info->flags);
+
+		to = (char *) buffer_virt;
+		from = kmap_atomic(io_info->data_page, KM_USER1);
+		memcpy(to, from, PAGE_SIZE);
+		flush_dcache_page(io_info->data_page);
+		flush_dcache_page(buffer_page);
+		kunmap_atomic(from, KM_USER1);
+	}
+
+	/* Submit the page */
+	get_page(buffer_page);
+	lock_page(buffer_page);
+
+	BUG_ON(buffer_page->mapping);
+	buffer_page->mapping = &suspend_space;
+	buffer_page->index = (unsigned long) io_info;
+
+	io_info->dev = submit_info->dev;
+	for (i = 0; i < submit_info->blocks_used; i++)
+		io_info->blocks[i] = submit_info->blocks[i];
+	io_info->blocks_used = submit_info->blocks_used;
+	io_info->block_size = PAGE_SIZE / submit_info->blocks_used;
+
+	if (rw == READ)
+		set_bit(IO_AWAITING_READ, &io_info->flags);
+	else
+		set_bit(IO_AWAITING_WRITE, &io_info->flags);
+
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 1,
+			"-> (PRE BRW) %d\n",
+			nr_free_pages());
+
+	if (submit_batch_size > 1)
+		add_to_batch(io_info);
+	else {
+		if (real_submit_one(rw, io_info))
+			return NULL;
+	}
+	
+	atomic_inc(&outstanding_io);
+	if (atomic_read(&outstanding_io) > max_outstanding_io)
+		max_outstanding_io++;
+	
+	return io_info;
+}
+
+static int suspend_do_io(int rw, 
+		struct submit_params * submit_info, int syncio)
+{
+	struct io_info * io_info = start_one(rw, submit_info);
+	if (!io_info)
+		return 1;
+	else if (syncio)
+		wait_on_one_page(io_info);
+
+	/* If we were the only one, clean everything up */
+	if (!atomic_read(&outstanding_io))
+		suspend_finish_all_io();
+	return 0;
+} 
+
+/* We used to use bread here, but it doesn't correctly handle
+ * blocksize != PAGE_SIZE. Now we create a submit_info to get the data we
+ * want and use our normal routines (synchronously).
+ */
+
+static int suspend_bdev_page_io(int rw, kdev_t bdev, long pos,
+		struct page * page)
+{
+	struct submit_params submit_info;
+	int block_size;
+	int i, j;
+
+	submit_info.page = page;
+	submit_info.dev = bdev;
+
+	block_size = blksize_size[MAJOR(bdev)][MINOR(bdev)];
+	for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size)
+		submit_info.blocks[i] = pos+i;
+	submit_info.blocks_used = i;
+	submit_info.readahead_index = -1;
+	return suspend_do_io(rw, &submit_info, 1);
+}
+
+/*
+ * wait_on_readahead
+ *
+ * Wait until a particular readahead is ready.
+ */
+static void suspend_wait_on_readahead(int readahead_index)
+{
+	int index = readahead_index/(8 * sizeof(unsigned long));
+	int bit = readahead_index - index * 8 * sizeof(unsigned long);
+
+	/* read_ahead_index is the one we want to return */
+	while (!test_bit(bit, &suspend_readahead_flags[index]))
+		do_bio_wait(5);
+}
+
+/*
+ * readahead_done
+ *
+ * Returns whether the readahead requested is ready.
+ */
+
+static int suspend_readahead_ready(int readahead_index)
+{
+	int index = readahead_index/(8 * sizeof(unsigned long));
+	int bit = readahead_index - (index * 8 * sizeof(unsigned long));
+
+	return test_bit(bit, &suspend_readahead_flags[index]);
+}
+
+/* suspend_readahead_prepare
+ * Set up for doing readahead on an image */
+static int suspend_prepare_readahead(int index)
+{
+	unsigned long new_page = get_zeroed_page(GFP_ATOMIC);
+
+	if(!new_page) {
+		printk("No page for readahead %d.\n", index);
+		return -ENOMEM;
+	}
+
+	suspend_bio_ops.readahead_pages[index] = virt_to_page(new_page);
+	return 0;
+}
+
+/* suspend_readahead_cleanup
+ * Clean up structures used for readahead */
+static void suspend_cleanup_readahead(int page)
+{
+	__free_pages(suspend_bio_ops.readahead_pages[page], 0);
+	suspend_bio_ops.readahead_pages[page] = 0;
+	return;
+}
+
+static unsigned long suspend_bio_memory_needed(void)
+{
+	return (REAL_MAX_ASYNC * (PAGE_SIZE + sizeof(struct request) +
+				8 * sizeof(struct buffer_head) + sizeof(struct io_info)));
+}
+
+#if 0
+static int suspend_bio_kthread(void * data)
+{
+	return 0;
+}
+
+static int start_suspend_bio_thread(void)
+{
+	
+	suspend_bio_task = kthread_run(suspend_bio_kthread, NULL,
+		PF_NOFREEZE, "suspend_bio");
+
+	if (IS_ERR(suspend_bio_task)) {
+		printk("suspend_bio thread could not be started.\n");
+		return -EPERM;
+	}
+
+	return 0;
+}
+
+static void end_suspend_bio_thread(void)
+{
+}
+#endif
+
+static struct suspend_proc_data proc_params[] = {
+	{ .filename			= "async_io_limit", 
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		  .integer = {
+			  .variable	= &max_async_ios,
+			  .minimum	= 1,
+			  .maximum	= MAX_READAHEAD,
+		  }
+	  }
+	},
+	
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+#ifdef TUNE_BATCHING
+	{ .filename			= "submit_batch_size", 
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		  .integer = {
+			  .variable	= &submit_batch_size,
+			  .minimum	= 1,
+			  .maximum	= 512,
+		  }
+	  }
+	},
+#endif
+#endif
+};
+
+struct suspend_bio_ops suspend_bio_ops = {
+	.set_block_size = suspend_set_block_size,
+	.get_block_size = suspend_get_block_size,
+	.submit_io = suspend_do_io,
+	.bdev_page_io = suspend_bdev_page_io,
+	.prepare_readahead = suspend_prepare_readahead,
+	.cleanup_readahead = suspend_cleanup_readahead,
+	.readahead_pages = suspend_readahead_pages,
+	.wait_on_readahead = suspend_wait_on_readahead,
+	.check_io_stats = suspend_check_io_stats,
+	.reset_io_stats = suspend_reset_io_stats,
+	.finish_all_io = suspend_finish_all_io,
+	.readahead_ready = suspend_readahead_ready,
+};
+
+EXPORT_SYMBOL(suspend_bio_ops);
+
+static struct suspend_plugin_ops suspend_blockwriter_ops = 
+{
+	.name					= "Block I/O",
+	.type					= MISC_PLUGIN,
+	//.initialise				= start_suspend_bio_thread,
+	//.cleanup				= end_suspend_bio_thread,
+	.memory_needed				= suspend_bio_memory_needed,
+};
+
+static __init int suspend_block_io_load(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+	int result;
+
+	if (!(result = suspend_register_plugin(&suspend_blockwriter_ops))) {
+		for (i=0; i< numfiles; i++)
+			suspend_register_procfile(&proc_params[i]);
+	}
+
+	return result;
+}
+
+#ifdef MODULE
+static __exit void suspend_block_io_unload(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+	for (i=0; i< numfiles; i++)
+		suspend_unregister_procfile(&proc_params[i]);
+	suspend_unregister_plugin(&suspend_blockwriter_ops);
+}
+
+module_init(suspend_block_io_load);
+module_exit(suspend_block_io_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Suspend2 block io functions");
+#else
+__initcall(suspend_block_io_load);
+#endif
diff -ruN 20-old/kernel/power/suspend_bootsplash.c 20-new/kernel/power/suspend_bootsplash.c
--- 20-old/kernel/power/suspend_bootsplash.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_bootsplash.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,325 @@
+/*
+ * kernel/power/suspend2_bootsplash.c
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * This file implements bootsplash support for suspend2.
+ */
+#define SUSPEND_CONSOLE_C
+
+#define __KERNEL_SYSCALLS__
+
+#include <linux/suspend.h>
+#include <linux/console.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <linux/vt_kern.h>
+#include <asm/hardirq.h>
+
+#include "plugins.h"
+#include "proc.h"
+#include "suspend.h"
+
+static int barwidth = 100, barposn = -1, newbarposn = 0;
+static int lastloglevel = -1;
+
+/* Your bootsplash progress bar may have a width of (eg) 1024 pixels. That
+ * doesn't necessarily mean you want the bar updated 1024 times when writing
+ * the image */
+static int bar_granularity_limit = 0;
+
+/* ------------------  Splash screen defines  -------------------------- */
+
+extern struct display fb_display[MAX_NR_CONSOLES];
+
+/* splash_is_on
+ * 
+ * Description: Determine whether a VT has a splash screen on.
+ * Arguments:	int consolenr. The VT number of a console to check.
+ * Returns:	Boolean indicating whether the splash screen for
+ *		that console is on right now.
+ */
+static int splash_is_on(int consolenr)
+{
+	struct splash_data *info = get_splash_data(consolenr);
+
+	if (info)
+		return ((info->splash_state & 1) == 1);
+	return 0;
+}
+
+/* splash_write_proc.
+ *
+ * Write to Bootsplash's proc entry. We need this to work when /proc
+ * hasn't been mounted yet and / can't be mounted. In addition, we
+ * want it to work despite the fact that bootsplash (under 2.4 at least)
+ * removes its proc entry when it shouldn't.  We therefore use
+ * our proc.c find_proc_dir_entry routine to get the location of the
+ * write routine once (boot time & at start of each resume), and keep it.
+ */
+
+extern struct proc_dir_entry * find_proc_dir_entry(const char *name,
+	struct proc_dir_entry *parent);
+
+static void splash_write_proc(const char *buffer, unsigned long count)
+{
+	static write_proc_t * write_routine;
+	struct proc_dir_entry * proc_entry;
+
+	if (in_interrupt())
+		return;
+
+	if (unlikely(!write_routine)) {
+		proc_entry = find_proc_dir_entry("splash", &proc_root);
+		if (proc_entry)
+			write_routine = proc_entry->write_proc;
+	}
+        
+	if (write_routine)
+                write_routine(NULL, buffer, count, NULL);
+}
+
+/* force_console_redraw
+ *
+ * Description:	Force a redraw of the console. Necessary after copying the
+ * 		original kernel back and when the progress bar is moved
+ * 		backwards.
+ */
+extern int console_blanked;
+
+static void force_console_redraw(void)
+{
+
+	console_blanked = fg_console + 1;
+	acquire_console_sem();
+	unblank_screen();
+	update_screen(fg_console);
+	release_console_sem();
+}
+
+/* fb_splash-set_progress
+ *
+ * Description:	Set the progress bar position for a splash screen.
+ * Arguments:	int consolenr. The VT number of a console to use.
+ * 		unsigned long value, unsigned long maximum:
+ * 		The proportion (value/maximum) of the bar to fill.
+ */
+
+static int fb_splash_set_progress(int consolenr, unsigned long value,
+	unsigned long maximum)
+{
+	char procstring[15];
+	int length, bitshift = generic_fls(maximum) - 16;
+	static unsigned long lastvalue = 0;
+	unsigned long thisvalue;
+
+	BUG_ON(consolenr >= MAX_NR_CONSOLES);
+	
+	if (in_interrupt())
+		return 0;
+
+	if (value > maximum)
+		value = maximum;
+
+	/* Avoid math problems - we can't do 64 bit math here
+	 * (and don't need it - anyone got screen resolution
+	 * of 65536 pixels or more?) */
+	if (bitshift > 0) {
+		maximum = maximum >> bitshift;
+		value = value >> bitshift;
+	}
+
+	thisvalue = value * 65534 / maximum;
+
+	length = sprintf(procstring, "show %lu", thisvalue);
+
+	splash_write_proc(procstring, length);
+	
+	/* Ensure redraw when the progress bar goes to a lower value */
+	if (thisvalue < lastvalue)
+		force_console_redraw();
+
+	lastvalue = thisvalue;
+	
+	return 0;
+}
+
+/* bootsplash_loglevel_change
+ *
+ * Description:	Update the display when the user changes the log level.
+ */
+
+static void bootsplash_loglevel_change(void)
+{
+	/* Calculate progress bar width. Note that whether the
+	 * splash screen is on might have changed (this might be
+	 * the first call in a new cycle), so we can't take it
+	 * for granted that the width should be the same as
+	 * last time we came in here */
+	if (!splash_is_on(fg_console))
+		return;
+	
+	/* proc interface ensures bar_granularity_limit >= 0 */
+	if (bar_granularity_limit)
+		barwidth = bar_granularity_limit;
+	else
+		barwidth = 100;
+
+	/* Only reset the display if we're switching between nice display
+	 * and displaying debugging output */
+	if (console_loglevel > 1) {
+		if (lastloglevel < 2)
+			splash_write_proc("verbose\n", 9);
+	} else if (lastloglevel > 1)
+			splash_write_proc("silent\n", 8);
+	
+	lastloglevel = console_loglevel;
+}
+
+static void bootsplash_prepare(void)
+{
+	if (!splash_is_on(fg_console))
+		return;
+
+	if (console_loglevel < 2)
+		splash_write_proc("silent\n", 8);
+	else
+		splash_write_proc("verbose\n", 9);
+	
+	lastloglevel = console_loglevel;
+}
+
+/* bootsplash_update_progress
+ *
+ * Description: Update the progress bar and (if on) in-bar message.
+ * Arguments:	UL value, maximum: Current progress percentage (value/max).
+ * 		const char *fmt, ...: Message to be displayed in the middle
+ * 		of the progress bar.
+ * 		Note that a NULL message does not mean that any previous
+ * 		message is erased! For that, you need message with
+ * 		clearbar on.
+ * Returns:	Unsigned long: The next value where status needs to be updated.
+ * 		This is to reduce unnecessary calls to update_progress.
+ *
+ * Note that for Bootsplash, we ignore the in-bar message
+ */
+static unsigned long bootsplash_update_progress(
+		unsigned long value, unsigned long maximum,
+		const char *fmt, va_list args)
+{
+	unsigned long next_update = 0;
+	int bitshift = generic_fls(maximum) - 16;
+
+	if ((!maximum) || (!barwidth))
+		return maximum;
+
+	if (value < 0)
+		value = 0;
+
+	if (value > maximum)
+		value = maximum;
+
+	/* Try to avoid math problems - we can't do 64 bit math here
+	 * (and shouldn't need it - anyone got screen resolution
+	 * of 65536 pixels or more?) */
+	if (bitshift > 0) {
+		unsigned long temp_maximum = maximum >> bitshift;
+		unsigned long temp_value = value >> bitshift;
+		newbarposn = (int) (temp_value * barwidth / temp_maximum);
+	} else
+		newbarposn = (int) (value * barwidth / maximum);
+	
+	if (newbarposn < barposn)
+		barposn = 0;
+
+	next_update = ((newbarposn + 1) * maximum / barwidth) + 1;
+
+	if ((splash_is_on(fg_console)) &&
+			(newbarposn != barposn)) {
+		fb_splash_set_progress(fg_console, value, maximum);
+		barposn = newbarposn;
+	}
+	return next_update;
+}
+
+/*
+ * User interface specific /proc/suspend entries.
+ */
+static struct suspend_plugin_ops bootsplash_ops;
+
+static struct suspend_proc_data proc_params[] = {
+	{ .filename			= "bootsplash_granularity_limit",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		  .integer = {
+			  .variable	= &bar_granularity_limit,
+			  .minimum	= 1,
+			  .maximum	= 2000,
+		  }
+	  }
+	},
+
+	{ .filename			= "disable_bootsplash_support",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		.integer = {
+			.variable	= &bootsplash_ops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	  }
+	}
+};
+
+static struct suspend_plugin_ops bootsplash_ops = {
+	.type					= UI_PLUGIN,
+	.name					= "Bootsplash Support",
+	.ops = {
+		.ui = {
+			.prepare		= bootsplash_prepare,
+			.log_level_change	= bootsplash_loglevel_change,
+			.update_progress	= bootsplash_update_progress,
+		}
+	}
+};
+
+/* ---- Registration ---- */
+
+static __init int bootsplash_load(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+	int result;
+
+	if (!(result = suspend_register_plugin(&bootsplash_ops))) {
+		printk("Software Suspend Bootsplash Support loaded.\n");
+		for (i=0; i< numfiles; i++)
+			suspend_register_procfile(&proc_params[i]);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void bootsplash_unload(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+	printk("Software Suspend Bootsplash support unloading.\n");
+
+	for (i=0; i< numfiles; i++)
+		suspend_unregister_procfile(&proc_params[i]);
+	
+	suspend_unregister_plugin(&bootsplash_ops);
+}
+
+module_init(bootsplash_load);
+module_exit(bootsplash_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Suspend2 Bootsplash support");
+#else
+__initcall(bootsplash_load);
+#endif
diff -ruN 20-old/kernel/power/suspend_builtin_24.c 20-new/kernel/power/suspend_builtin_24.c
--- 20-old/kernel/power/suspend_builtin_24.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_builtin_24.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,73 @@
+/*
+ * kernel/power/suspend2-version-specific.c
+ *
+ * Version specific routines for suspend2.
+ *
+ * #included in kernel/power/suspend2.c.
+ */
+
+/* ---------------- Support for a notifier chain ------------------------ */
+
+#include "suspend.h"
+#include <linux/notifier.h>
+#include <linux/module.h>
+
+/*
+ * The following code is based on the reboot notifier code in kernel/sys.c.
+ * It is used to notify drivers when a suspend cycle finishes, so that
+ * timers which have been stopped can be restarted.
+ */
+
+
+/*
+ *	Notifier list for kernel code which wants to be called
+ *	at resume. This is used to restart timers which were
+ *	stopped during suspend.
+ */
+
+static struct notifier_block *resume_notifier_list;
+rwlock_t suspend_notifier_lock = RW_LOCK_UNLOCKED;
+
+/**
+ *	register_resume_notifier - Register function to be called at resume time
+ *	@nb: Info about notifier function to be called
+ *
+ *	Registers a function with the list of functions
+ *	to be called at resume time.
+ *
+ *	Currently always returns zero, as notifier_chain_register
+ *	always returns zero.
+ */
+ 
+int register_resume_notifier(struct notifier_block * nb)
+{
+	return notifier_chain_register(&resume_notifier_list, nb);
+}
+
+/**
+ *	unregister_resume_notifier - Unregister previously registered resume 
+ *					notifier
+ *	@nb: Hook to be unregistered
+ *
+ *	Unregisters a previously registered resume
+ *	notifier function.
+ *
+ *	Returns zero on success, or %-ENOENT on failure.
+ */
+ 
+int unregister_resume_notifier(struct notifier_block * nb)
+{
+	return notifier_chain_unregister(&resume_notifier_list, nb);
+}
+
+int notify_resume(void)
+{
+	return notifier_call_chain(&resume_notifier_list, 0, NULL);
+}
+
+/* -------------------------------------------------------------------------- */
+EXPORT_SYMBOL(notify_resume);
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+EXPORT_SYMBOL(register_resume_notifier);
+EXPORT_SYMBOL(unregister_resume_notifier);
+#endif
diff -ruN 20-old/kernel/power/suspend_builtin.c 20-new/kernel/power/suspend_builtin.c
--- 20-old/kernel/power/suspend_builtin.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_builtin.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,510 @@
+/*
+ * kernel/power/suspend2-builtin.c
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * It contains the functions for suspend2 that are built into the kernel even if
+ * suspend is configured as modules.
+ */
+
+#include "suspend.h"
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+//#include <asm/highmem.h>
+#include <asm/uaccess.h>
+
+/* 
+ *---------------------  Variables ---------------------------
+ * 
+ * The following are used by the arch specific low level routines 
+ * and only needed if suspend2 is compiled in. Other variables,
+ * used by the freezer even if suspend2 is not compiled in are
+ * found in process.c
+ */
+volatile int suspend_io_time[2][2];
+struct pagedir __nosavedata pagedir_resume;
+struct pagedir pagedir1 = { 1, 0, 0}, pagedir2 = {2, 0, 0};
+static unsigned long avenrun_save[3];
+
+char suspend_print_buf[1024];
+EXPORT_SYMBOL(suspend_print_buf);
+
+/* Suspend2 variables used by built-in routines. */
+unsigned int nr_suspends = 0;
+int suspend_act_used = 0;
+int suspend_lvl_used = 0;
+int suspend_dbg_used = 0;
+int suspend_default_console_level = 0;
+
+/* 
+ * For resume2= kernel option. It's pointless to compile
+ * suspend2 without any writers, but compilation shouldn't
+ * fail if you do.
+ */
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEFAULT_RESUME2
+char resume2_file[256] = CONFIG_SOFTWARE_SUSPEND_DEFAULT_RESUME2;
+#else
+char resume2_file[256];
+#endif
+
+void (* exclusive_handler) (int) = NULL;
+
+/* --------------- Basic user interface functions --------------- 
+ * 
+ * These need to be available even if none of the core is
+ * loaded.
+ */
+
+DECLARE_WAIT_QUEUE_HEAD(suspend_wait_for_key);
+static int last_key;
+
+int suspend_wait_for_keypress(void)
+{
+	interruptible_sleep_on(&suspend_wait_for_key);
+	return last_key;
+}
+
+/* 
+ * Basic keypress handler for suspend. This is extensible
+ * via the user interface modules.
+ */
+
+/* For simplicity, we convert keyboard key codes to ascii,
+ * except in the case of function keys, which are mapped
+ * to 1-12. We can then use the same case statement for
+ * serial keyboards (and from a serial keyboard, you can
+ * press Control-A..L to toggle sections.
+ */
+static unsigned int kbd_keytable[] = {
+	  0,  27,  49,  50,  51,  52,  53,  54,  55,  56,
+	 57,  48,   0,   0,   0,   0,   0,   0,   0, 114,
+	116,   0,   0,   0,   0, 112,   0,   0,   0,   0,
+	  0, 115,   0,   0,   0,   0,   0,   0, 108,   0,
+	  0, 122,   0,   0,   0,   0,  99,   0,   0,   0,
+	  0,   0,   0,   0,   0,   0,   0,  32,   0,   1,
+	  2,   3,   4,   5,   6,   7,   8,   9,  10,   0,
+	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+	  0,   0,   0,   0,   0,   0,   0,  11,  12,   0,
+};
+
+/*
+ * keycode_to_action
+ *
+ * Convert a keycode (serial or keyboard) into our
+ * internal code (ascii, except for function keys).
+ */
+static unsigned int keycode_to_action(unsigned int keycode, int source)
+{
+	if (source == SUSPEND_KEY_SERIAL) {
+		if (keycode > 64)
+			return (keycode | 32);
+		else
+			return keycode;
+	}
+
+	/* Local keyboard - use table above */
+	if (keycode > sizeof(kbd_keytable))
+		return 0;
+
+	return kbd_keytable[keycode];
+}
+
+/* get_keyboard_exclusive
+ *
+ * Give a plugin exclusive access to the keyboard
+ * if it's not already claimed. Used for an encryption
+ * plugin to get the passphrase, for example.
+ */
+
+int suspend_get_keyboard_exclusive(void (* handler) (int))
+{
+	if (exclusive_handler)
+		return -EBUSY;
+
+	exclusive_handler = handler;
+
+	return 0;
+}
+
+/* release_keyboard_exclusive
+ *
+ * Release the exclusive access to the keyboard.
+ */
+
+void suspend_release_keyboard_exclusive(void)
+{
+	BUG_ON(!exclusive_handler);
+
+	exclusive_handler = NULL;
+}
+
+/*
+ * suspend_handle_keypress
+ *
+ * This is the basic routine for handling keystrokes.
+ * If it doesn't know what to do with a keypress, it
+ * passes it on to the plugins.
+ */
+void suspend_handle_keypress(unsigned int keycode, int source)
+{
+	keycode = keycode_to_action(keycode, source);
+
+	if (test_suspend_state(SUSPEND_SANITY_CHECK_PROMPT)) {
+		if (keycode == 32)
+			wake_up_interruptible(&suspend_wait_for_key);
+		else if (keycode == 99) {
+			set_suspend_state(SUSPEND_CONTINUE_REQ);
+			wake_up_interruptible(&suspend_wait_for_key);
+		}
+		return;
+	}
+
+	/* Do we have a plugin grabbing all the keys? */
+	if (exclusive_handler) {
+		exclusive_handler(keycode);
+		return;
+	}
+
+	last_key = keycode;
+
+	/* 
+	 * If the message was handled or is the space bar, we
+	 * wake our completion handler.
+	 */
+	if ((suspend2_core_ops->keypress(keycode)) ||
+		(keycode == 32))
+		wake_up_interruptible(&suspend_wait_for_key);
+}
+
+/* suspend_early_boot_message()
+ * Description:	Handle errors early in the process of booting.
+ * 		The user may press C to continue booting, perhaps
+ * 		invalidating the image,  or space to reboot. 
+ * 		This works from either the serial console or normally 
+ * 		attached keyboard.
+ *
+ * 		Note that we come in here from init, while the kernel is
+ * 		locked. If we want to get events from the serial console,
+ * 		we need to temporarily unlock the kernel.
+ *
+ * Arguments:	Char *. Pointer to a string explaining why we're moaning.
+ */
+
+#define say(message, a...) printk(KERN_EMERG message, ##a)
+
+int suspend_early_boot_message(int can_erase_image, char *warning_reason, ...)
+{
+	unsigned long orig_state = get_suspend_state(), continue_req;
+	int orig_loglevel = console_loglevel;
+	va_list args;
+	int printed_len;
+
+	set_suspend_state(SUSPEND_RUNNING);
+
+#ifdef CONFIG_BOOTSPLASH
+/* 
+ * So we can make any error visible if necessary. The core might
+ * not be loaded and the bootsplash module might not be loaded
+ * or even have been compiled.
+ */
+	{
+		extern int splash_verbose(void);
+		splash_verbose();
+	}
+#endif
+
+#if defined(CONFIG_VT) || defined(CONFIG_SERIAL_CONSOLE)
+	console_loglevel = 7;
+
+	if (suspend2_core_ops)
+		suspend2_core_ops->early_boot_plugins();
+
+	say("=== Software Suspend ===\n\n");
+	if (warning_reason) {
+		va_start(args, warning_reason);
+		printed_len = vsnprintf(suspend_print_buf, 
+				sizeof(suspend_print_buf), 
+				warning_reason,
+				args);
+		va_end(args);
+		say("BIG FAT WARNING!! %s\n\n", suspend_print_buf);
+		if (can_erase_image) {
+			say("If you want to use the current suspend image, reboot and try\n");
+			say("again with the same kernel that you suspended from. If you want\n");
+			say("to forget that image, continue and the image will be erased.\n");
+		} else {
+			say("If you continue booting, note that any image WILL NOT BE REMOVED.\n");
+			say("Suspend is unable to do so because the appropriate modules aren't\n");
+			say("loaded. You should manually remove the image to avoid any\n");
+			say("possibility of corrupting your filesystem(s) later.\n");
+		}
+		say("Press SPACE to reboot or C to continue booting with this kernel\n");
+	} else {
+		say("BIG FAT WARNING!!\n\n");
+		say("You have tried to resume from this image before.\n");
+		say("If it failed once, it will probably fail again.\n");
+		say("Would you like to remove the image and boot normally?\n");
+		say("This will be equivalent to entering noresume2 on the\n");
+		say("kernel command line.\n\n");
+		say("Press SPACE to remove the image or C to continue resuming.\n");
+	}
+	
+	set_suspend_state(SUSPEND_SANITY_CHECK_PROMPT);
+
+	interruptible_sleep_on(&suspend_wait_for_key);
+
+	continue_req = test_suspend_state(SUSPEND_CONTINUE_REQ);
+	
+	if ((warning_reason) && (!continue_req))
+		machine_restart(NULL);
+	
+	restore_suspend_state(orig_state);
+	if (continue_req)
+		set_suspend_state(SUSPEND_CONTINUE_REQ);
+
+	console_loglevel = orig_loglevel;
+#endif // CONFIG_VT or CONFIG_SERIAL_CONSOLE
+	return -EPERM;
+}
+#undef say
+
+/* --------------- Registration of the core code -----------------------------
+ *
+ * We don't need to do anything more. The writers determine
+ * whether suspending is disabled and they're not loaded yet.
+ */
+int suspend2_register_core(struct suspend2_core_ops * ops_pointer)
+{
+	if (suspend2_core_ops)
+		return -EBUSY;
+
+	suspend2_core_ops = ops_pointer;
+	return 0;
+}
+
+void suspend2_unregister_core(void)
+{
+	suspend2_core_ops = NULL;
+}
+
+/* ------------ Functions for kickstarting a suspend or resume ----------- */
+
+static int can_suspend(void)
+{
+	if (test_suspend_state(SUSPEND_RUNNING)) {
+		printk(name_suspend "Software suspend is already running.\n");
+		return 0;
+	}
+
+	if (!suspend2_core_ops) {
+		printk(name_suspend "Software suspend is disabled.\n"
+			"You do not appear to have inserted the core module.\n");
+		SET_RESULT_STATE(SUSPEND_ABORTED);
+		return 0;
+	}
+
+	if (test_suspend_state(SUSPEND_DISABLED)) {
+		printk(name_suspend "Software suspend is disabled.\n"
+			"This may be because you haven't put something along the "
+			"lines of\n\nresume2=swap:/dev/hda1\n\n"
+			"in lilo.conf or equivalent. (Where /dev/hda1 is your "
+			"swap partition).\n");
+		SET_RESULT_STATE(SUSPEND_ABORTED);
+		return 0;
+	}
+	
+	return 1;
+}
+
+/*
+ * Check if we have an image and if so try to resume.
+ */
+
+void software_suspend_try_resume(void)
+{
+	mm_segment_t oldfs;
+	oldfs = get_fs(); set_fs(KERNEL_DS);
+
+	clear_suspend_state(SUSPEND_RESUME_NOT_DONE);
+
+	if (!suspend2_core_ops) {
+		/*
+		 * We can only get here at boot time. It is really dangerous
+		 * to suspend, boot without resuming and then boot with
+		 * resuming. Since the core (and writers) isn't loaded, we
+		 * can't fix this. We can however print a big fat warning
+		 * and give the user the option of rebooting.
+		 *
+		 * We don't do this if no resume2= parameter was specified.
+		 */
+
+		if (resume2_file[0])
+			suspend_early_boot_message(0,
+			 "Can't check whether to resume. Suspend's core module isn't loaded.");
+		goto out;
+	}
+	suspend2_core_ops->do_resume();
+out:
+	set_fs(oldfs);
+	clear_suspend_state(SUSPEND_IGNORE_LOGLEVEL);
+	return;
+}
+
+/*
+ * suspend_try_suspend
+ * Functionality   : First level of code for software suspend invocations.
+ * 		     Performs the basic checking as to whether suspend is
+ * 		     enabled before invoking the high level routine.
+ * Called From     : 
+ */
+void suspend_try_suspend(void)
+{
+	mm_segment_t oldfs;
+
+	suspend_result = 0;
+	
+	if (!can_suspend())
+		return;
+	
+	oldfs = get_fs(); set_fs(KERNEL_DS);
+	suspend2_core_ops->do_suspend();
+	set_fs(oldfs);
+}
+
+/* -------------------  Commandline Parameter Handling -----------------
+ *
+ * Resume setup: obtain the storage device.
+ */
+
+static int __init resume_setup(char *str)
+{
+	if (str == NULL)
+		return 1;
+	
+	strncpy(resume2_file, str, 255);
+	return 0;
+}
+
+/*
+ * Allow the user to set the action parameter from lilo, prior to resuming.
+ */
+static int __init suspend_act_setup(char *str)
+{
+	if(str)
+		suspend_action=simple_strtol(str,NULL,0);
+	suspend_act_used = 1;
+	return 0;
+}
+
+/*
+ * Allow the user to set the debug parameter from lilo, prior to resuming.
+ */
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+static int __init suspend_dbg_setup(char *str)
+{
+	if(str)
+		suspend_debug_state=simple_strtol(str,NULL,0);
+	suspend_dbg_used = 1;
+	return 0;
+}
+
+/*
+ * Allow the user to set the debug level parameter from lilo, prior to
+ * resuming.
+ */
+static int __init suspend_lvl_setup(char *str)
+{
+	if(str)
+		console_loglevel =
+		suspend_default_console_level = 
+			simple_strtol(str,NULL,0);
+	suspend_lvl_used = 1;
+	clear_suspend_state(SUSPEND_IGNORE_LOGLEVEL);
+	return 0;
+}
+#endif
+
+/*
+ * Allow the user to specify that we should ignore any image found and
+ * invalidate the image if necesssary. This is equivalent to running
+ * the task queue and a sync and then turning off the power. The same
+ * precautions should be taken: fsck if you're not journalled.
+ */
+static int __init noresume_setup(char *str)
+{
+	set_suspend_state(SUSPEND_NORESUME_SPECIFIED);
+	/* Message printed later */
+	return 0;
+}
+
+#ifdef CONFIG_HIGHMEM
+/* In leiu of exporting variables, some get_ functions for suspend2 */
+unsigned long get_highstart_pfn(void)
+{
+	return highstart_pfn;
+}
+EXPORT_SYMBOL(get_highstart_pfn);
+#endif
+
+/*
+ * Running suspend makes for a very high load average. I'm told that
+ * sendmail and crond check the load average, so in order for them
+ * not to be unnecessarily affected by the operation of suspend, we
+ * store the avenrun values prior to suspending and restore them
+ * at the end of the resume cycle. Thus, the operation of suspend
+ * should be invisible to them. Thanks to Marcus Gaugusch and Bernard
+ * Blackham for noticing the problem and suggesting the solution.
+ */
+void suspend_save_avenrun(void)
+{
+	int i;
+
+	for (i = 0; i < 3; i++)
+		avenrun_save[i] = avenrun[i];
+}
+
+void suspend_restore_avenrun(void)
+{
+	int i;
+
+	for (i = 0; i < 3; i++)
+		avenrun[i] = avenrun_save[i];
+}
+
+__setup("resume2=", resume_setup);
+__setup("suspend_act=", suspend_act_setup);
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+__setup("suspend_dbg=", suspend_dbg_setup);
+__setup("suspend_lvl=", suspend_lvl_setup);
+#endif
+__setup("noresume2", noresume_setup);
+
+EXPORT_SYMBOL(name_to_kdev_t);
+
+EXPORT_SYMBOL(suspend_save_avenrun);
+EXPORT_SYMBOL(suspend_restore_avenrun);
+
+EXPORT_SYMBOL(nr_suspends);
+EXPORT_SYMBOL(pagedir1);
+EXPORT_SYMBOL(pagedir2);
+EXPORT_SYMBOL(suspend2_register_core);
+EXPORT_SYMBOL(suspend2_unregister_core);
+EXPORT_SYMBOL(resume2_file);
+EXPORT_SYMBOL(suspend_act_used);
+EXPORT_SYMBOL(suspend_lvl_used);
+EXPORT_SYMBOL(suspend_dbg_used);
+EXPORT_SYMBOL(suspend_try_suspend);
+EXPORT_SYMBOL(suspend_debug_state);
+EXPORT_SYMBOL(suspend_result);
+
+/* Symnols exported for Suspend plugins */
+EXPORT_SYMBOL(suspend_default_console_level);
+EXPORT_SYMBOL(pagedir_resume);
+EXPORT_SYMBOL(suspend_io_time);
+EXPORT_SYMBOL(suspend2_core_ops);
+EXPORT_SYMBOL(suspend_early_boot_message);
+EXPORT_SYMBOL(suspend_wait_for_keypress);
diff -ruN 20-old/kernel/power/suspend.c 20-new/kernel/power/suspend.c
--- 20-old/kernel/power/suspend.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,1778 @@
+/*
+ * 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
+
+#include <linux/suspend.h>
+#include <linux/reboot.h>
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/version.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
+
+#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
+
+/*	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());
+			break;
+
+		case SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED:
+			BUG_ON(irqs_disabled());
+			break;
+
+		case SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_DISABLED:
+			BUG_ON(!irqs_disabled());
+			break;
+
+		case SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED:
+			BUG_ON(irqs_disabled());
+			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());
+			break;
+
+		case SUSPEND_DRIVERS_UNUSED_DEVICES_IRQS_ENABLED:
+			BUG_ON(irqs_disabled());
+			break;
+
+		case SUSPEND_DRIVERS_USED_DEVICES_IRQS_DISABLED:
+			BUG_ON(!irqs_disabled());
+			break;
+
+		case SUSPEND_DRIVERS_USED_DEVICES_IRQS_ENABLED:
+			BUG_ON(irqs_disabled());
+			break;
+
+		case SUSPEND_DRIVERS_PRE_POWERDOWN: /* Power down system */
+			BUG_ON(irqs_disabled());
+			break;
+	}
+	return result;
+}
+
+/* -------------------------------------------------------------------------- */
+
+static int suspend_version_specific_initialise(void)
+{
+	suspend_save_avenrun();
+
+	return 0;
+}
+
+static void suspend_version_specific_cleanup(void)
+{
+	suspend_restore_avenrun();
+}
+/* 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 = 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_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!!! -----
+	 *  
+	 */
+	
+	/* 
+	 * 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;		
+
+}
+
+extern asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd,
+	       void * arg);
+
+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 */
+}
+
+/*
+ * 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 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);
+	}
+
+	prepare_status(1, 0, "Ready to power down.");
+	suspend_drivers_suspend(SUSPEND_DRIVERS_PRE_POWERDOWN);
+
+	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();
+
+	/* 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);
+
+#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))
+		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\n",
+			suspend_result,
+			suspend_action,
+			suspend_debug_state,
+			suspend_default_console_level,
+			image_size_limit,
+			max_async_ios);
+#else
+	SNPRINTF("- Parameters     : %ld %ld %d %d\n",
+			suspend_result,
+			suspend_action,
+			image_size_limit,
+			max_async_ios);
+#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;
+				}
+
+				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\n",
+			ALL_SETTINGS_VERSION,
+			suspend_result,
+			suspend_action,
+			suspend_debug_state,
+			suspend_default_console_level,
+			image_size_limit,
+			max_async_ios);
+
+	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,
+		  }
+	  }
+	},
+
+#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 = 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,
+};
+
+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();
+
+	return 0;
+}
+
+#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
+__initcall(core_load);
+#endif
+EXPORT_SYMBOL(checksum_map);
+EXPORT_SYMBOL(attempt_to_parse_resume_device);
diff -ruN 20-old/kernel/power/suspend_checksums.c 20-new/kernel/power/suspend_checksums.c
--- 20-old/kernel/power/suspend_checksums.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_checksums.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,610 @@
+#include <linux/suspend.h>
+#include <linux/highmem.h>
+#ifdef CONFIG_KDB
+#include <linux/kdb.h>
+#include <linux/kdbprivate.h>
+#endif
+#include <linux/module.h>
+
+#include "suspend.h"
+#include "plugins.h"
+#include "pageflags.h"
+#include "proc.h"
+
+#define CHECKSUMS_PER_PAGE ((PAGE_SIZE - sizeof(void *)) / sizeof(unsigned long))
+#define NEXT_CHECKSUM_PAGE(page) *((unsigned long *) (((char *) (page)) + PAGE_SIZE - sizeof(void *)))
+static int checksum_pages;
+static unsigned long * first_checksum_page, *last_checksum_page;
+static int num_reload_pages = 0;
+
+struct reload_data
+{
+	int pageset;
+	int pagenumber;
+	struct page * page_address;
+	char * base_version;
+	char * compared_version;
+	struct reload_data * next;
+};
+
+static struct reload_data * first_reload_data, * last_reload_data;
+
+static unsigned long suspend_page_checksum(struct page * page)
+{
+	unsigned long * virt;
+	int i;
+	unsigned long value = 0;
+
+	virt = (unsigned long *) kmap_atomic(page, KM_USER0);
+	for (i = 0; i < (PAGE_SIZE / sizeof(unsigned long)); i++)
+		value += *(virt + i);
+	kunmap_atomic(virt, KM_USER0);
+	return value;
+}
+
+extern void get_first_pbe(struct pbe2 * pbe, struct pagedir * pagedir);
+extern void get_next_pbe(struct pbe2 * pbe);
+
+static void suspend_calculate_checksums(void)
+{
+	struct pbe2 pbe;
+	int i = 0, page_index = 0, whichpagedir = 1;
+	unsigned long * current_checksum_page = first_checksum_page;
+	
+	if (!first_checksum_page) {
+		prepare_status(1, 0, "Unable to checksum at this point.");
+		return;
+	}
+
+	prepare_status(1, 0, "Calculating checksums... ");
+	//printk("First checksum page is %p.\n", current_checksum_page);
+	
+	get_first_pbe(&pbe, &pagedir1);
+	
+	do {
+		//printk("Page number %d... Orig address %p.", i, pbe.origaddress);
+		*(current_checksum_page + page_index) =
+			suspend_page_checksum(pbe.origaddress);
+		//printk("Checksum calculated as %lx.\n", *(current_checksum_page + page_index));
+		i++;
+		page_index++;
+		if (page_index == CHECKSUMS_PER_PAGE) {
+			page_index = 0;
+			current_checksum_page = (unsigned long *)
+				NEXT_CHECKSUM_PAGE(current_checksum_page);
+			//printk("Moving to new checksum page %p.\n", current_checksum_page);
+		}
+		if (whichpagedir == 1) {
+			if (pagedir1.pageset_size == i) {
+				//if (test_suspend_state(SUSPEND_PAGESET2_NOT_LOADED))
+					goto out;
+				get_first_pbe(&pbe, &pagedir2);
+				whichpagedir = 2;
+				i = 0;
+			}
+		} else {
+			if (pagedir2.pageset_size == i)
+				goto out;
+		}
+		get_next_pbe(&pbe);
+	} while(1);
+	
+out:
+	prepare_status(1, 0, "Checksums done.");
+}
+
+void suspend_check_checksums(void)
+{
+	struct pbe2 pbe;
+	int i = 0, page_index = 0, whichpagedir = 1, num_differences = 0;
+	unsigned long * current_checksum_page = first_checksum_page;
+	unsigned long sum_now;
+	struct reload_data * next_reload_data = first_reload_data;
+
+	if (!first_checksum_page) {
+		prepare_status(1, 0, "Unable to checksum at this point.");
+		return;
+	}
+
+	//prepare_status(1, 0, "Checking checksums... ");
+	
+	get_first_pbe(&pbe, &pagedir1);
+	
+	do {
+		/* Also ignore the page containing our variables */
+		if (PageChecksumIgnore(pbe.origaddress) || (pbe.origaddress == virt_to_page(&i)))
+			goto skip;
+				
+		sum_now = suspend_page_checksum(pbe.origaddress);
+		if (sum_now != *(current_checksum_page + page_index)) {
+			num_differences++;
+			if (next_reload_data) {
+				char * virt;
+				next_reload_data->pageset = whichpagedir;
+				next_reload_data->pagenumber = i;
+				next_reload_data->page_address = pbe.origaddress;
+				virt = kmap_atomic(pbe.origaddress, KM_USER0);
+				memcpy(next_reload_data->compared_version, 
+						virt, PAGE_SIZE);
+				kunmap_atomic(virt, KM_USER0);
+				next_reload_data = next_reload_data->next;
+			}
+		}
+skip:
+		i++;
+		page_index++;
+		if (page_index == CHECKSUMS_PER_PAGE) {
+			page_index = 0;
+			current_checksum_page =	(unsigned long *) 
+				NEXT_CHECKSUM_PAGE(current_checksum_page);
+		}
+		if (whichpagedir == 1) {
+			if (pagedir1.pageset_size == i) {
+				//if (test_suspend_state(SUSPEND_PAGESET2_NOT_LOADED))
+					goto out;
+				get_first_pbe(&pbe, &pagedir2);
+				whichpagedir = 2;
+				i = 0;
+			}
+		} else {
+			if (pagedir2.pageset_size == i)
+				goto out;
+		}
+		get_next_pbe(&pbe);
+	
+	} while(1);
+	
+out:
+	//printk("%d/%d different.\n", num_differences, i);
+	//prepare_status(1, 0, "Differencing done.");
+	return;
+}
+
+/*
+ * free_reload_data.
+ *
+ * Reload data begins on a page boundary.
+ */
+static void suspend_free_reload_data(void)
+{
+	struct reload_data * this_data = first_reload_data;
+	struct reload_data *prev_reload_data = this_data;
+	
+	while (this_data) {
+		if (this_data->compared_version) {
+		  ClearPageNosave(virt_to_page(this_data->compared_version));
+		  free_pages((unsigned long) this_data->compared_version, 0);
+		}
+
+		if (this_data->base_version) {
+			ClearPageNosave(virt_to_page(this_data->base_version));
+			free_pages((unsigned long) this_data->base_version, 0);
+		}
+
+		this_data = this_data->next;
+
+		if (!(((unsigned long) this_data) & ~PAGE_MASK)) {
+			//printk("Linking %p to %p.\n", prev_reload_data, this_data);
+			prev_reload_data->next = this_data;
+			prev_reload_data = this_data;
+		}
+	}
+
+	this_data = first_reload_data;
+	while (this_data) {
+		prev_reload_data = this_data;
+		this_data = this_data->next;
+		//printk("Freeing reload page %p.\n", prev_reload_data);
+		free_pages((unsigned long) prev_reload_data, 0);
+		num_reload_pages--;
+	}
+
+	first_reload_data = last_reload_data = NULL;
+
+}
+
+/* suspend_reread_pages()
+ *
+ * Description:	Reread pages from an image for diagnosing differences.
+ * Arguments:	page_list:	A list containing information on pages
+ *                              to be reloaded, sorted by pageset and
+ *                              page index.
+ * Returns:	Zero on success or -1 on failure.
+ */
+
+static int suspend_reread_pages(struct reload_data * page_list)
+{
+	int result = 0, whichtoread;
+	long i;
+	struct pbe2 pbe;
+	struct list_head *filter;
+	struct suspend_plugin_ops * this_filter, * first_filter = get_next_filter(NULL);
+
+	if (!page_list)
+		return 0;
+
+	PRINTFREEMEM("at start of read pageset");
+
+	for (whichtoread = page_list->pageset; whichtoread <= 2; whichtoread++) {
+		struct pagedir * pagedir;
+
+		switch (whichtoread) {
+			case 1:
+				pagedir = &pagedir1;
+				break;
+			case 2:
+				pagedir = &pagedir2;
+				break;
+			default:
+				goto out;
+		}
+
+		suspend_message(SUSPEND_IO, SUSPEND_LOW, 0,
+			"Reread pages from pagedir %d.\n", whichtoread);
+
+		/* Initialise page transformers */
+		list_for_each(filter, &suspend_filters) {
+			this_filter = list_entry(filter, struct suspend_plugin_ops,
+				ops.filter.filter_list);
+			if (this_filter->disabled)
+				continue;
+			if (this_filter->ops.filter.read_init && 
+				this_filter->ops.filter.read_init(whichtoread)) {
+				abort_suspend("Failed to initialise a filter.");
+				return 1;
+			}
+		}
+
+		/* Initialise writer */
+		if (active_writer->ops.writer.read_init(whichtoread)) {
+			abort_suspend("Failed to initialise the writer."); 
+			result = 1;
+			goto reread_free_buffers;
+		}
+
+		get_first_pbe(&pbe, pagedir);
+
+		/* Read the pages */
+		for (i=0; i< pagedir->pageset_size; i++) {
+			/* Read */
+			result = first_filter->ops.filter.read_chunk(
+					virt_to_page(page_list->base_version),
+					SUSPEND_SYNC);
+
+			if (result) {
+				abort_suspend("Failed to read a chunk of the image.");
+				goto reread_free_buffers;
+			}
+
+			/* Interactivity*/
+			check_shift_keys(0, NULL);
+
+			/* Prepare next */
+			get_next_pbe(&pbe);
+
+			/* Got the one we're after? */
+			if (i == page_list->pagenumber)
+				page_list = page_list->next;
+
+			if (page_list->pageset != whichtoread)
+				break;
+		}
+		
+reread_free_buffers:
+
+		/* Cleanup reads from this pageset. */
+		list_for_each_entry(this_filter, &suspend_filters, ops.filter.filter_list) {
+			if (this_filter->disabled)
+				continue;
+			if (this_filter->ops.filter.read_cleanup &&
+				this_filter->ops.filter.read_cleanup()) {
+				abort_suspend("Failed to cleanup a filter.");
+				result = 1;
+			}
+		}
+
+		if (active_writer->ops.writer.read_cleanup()) {
+			abort_suspend("Failed to cleanup the writer.");
+			result = 1;
+		}
+	}
+out:
+	printk("\n");
+
+	return result;
+}
+static void suspend_free_checksum_pages(void)
+{
+	unsigned long * next_checksum_page;
+
+	while(first_checksum_page) {
+		next_checksum_page =
+		  (unsigned long *) NEXT_CHECKSUM_PAGE(first_checksum_page);
+		free_pages((unsigned long) first_checksum_page, 0);
+		first_checksum_page = next_checksum_page;
+	}
+	last_checksum_page = NULL;
+	checksum_pages = 0;
+	suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_PAGES, 1);
+}
+
+#define PRINTABLE(a) (((a) < 32 || (a) > 122) ? '.' : (a))
+extern int PageRangePage(char * seeking);
+
+static void local_print_location(
+		unsigned char * real,
+		unsigned char * original,
+		unsigned char * resumetime)
+{
+	int i;
+
+	for (i = 0; i < 8; i++)
+		if (*(original + i) != *(resumetime + i))
+			break;
+	if (i == 8)
+		return;
+	
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "%p", real);
+	if (PageNosave(virt_to_page(real)))
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			" [NoSave]");
+	if (PageRangePage(real))
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			" [RangePage]");
+	if (PageSlab(virt_to_page(real)))
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			" [Slab]");
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "\n");
+
+#ifdef CONFIG_KDB
+	for (i = 0; i < 8; i++) {
+		static const char *last_sym = NULL;
+		if (*(original + i) != *(resumetime + i)) {
+			kdb_symtab_t symtab;
+
+			kdbnearsym((unsigned long) real + i,
+					&symtab);
+
+			if ((!symtab.sym_name) ||
+					(symtab.sym_name == last_sym))
+				continue;
+
+			last_sym = symtab.sym_name;
+
+			suspend_message(SUSPEND_INTEGRITY, SUSPEND_LOW, 1,
+				"%p = %s\n",
+				symtab.sym_start,
+				symtab.sym_name);
+		}
+	}
+#endif
+
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%2x ", *(original + i));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "    ");
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%c", PRINTABLE(*(original + i)));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "    ");
+	
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%2x ", *(resumetime + i));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "    ");
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%c", PRINTABLE(*(resumetime + i)));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "\n\n");
+}
+
+static int suspend_allocate_reload_data(int pages)
+{
+	struct reload_data * this_data;
+	unsigned long data_start;
+	int i;
+
+	if (num_reload_pages >= pages)
+		return 0;
+
+	for (i = 1; i <= pages; i++) {
+		data_start = suspend2_get_grabbed_pages(0);
+
+		if (!data_start)
+			return -ENOMEM;
+
+		SetPageChecksumIgnore(virt_to_page(data_start));
+		this_data = (struct reload_data *) data_start; 
+		num_reload_pages++;
+
+		while (data_start == 
+		  ((((unsigned long) (this_data + 1)) - 1) & PAGE_MASK)) {
+			struct page * page;
+			unsigned long virt;
+
+			virt = suspend2_get_grabbed_pages(0);
+			if (!virt) {
+				printk("Couldn't get a page in which to store "
+					"a changed page.\n");
+				return -ENOMEM;
+			}
+			page = virt_to_page(virt);
+
+			this_data->compared_version = (char *) virt;
+			SetPageNosave(page);
+			SetPageChecksumIgnore(page);
+			
+			virt = suspend2_get_grabbed_pages(0);
+			if (!virt) {
+				printk("Couldn't get a page in which to store "
+					"a baseline page.\n");
+				return -ENOMEM;
+			}
+			page = virt_to_page(virt);
+
+			this_data->base_version = (char *) virt;
+			SetPageNosave(page);
+			SetPageChecksumIgnore(page);
+
+			if (last_reload_data)
+				last_reload_data->next = this_data;
+			else
+				first_reload_data = this_data;
+			
+			last_reload_data = this_data;
+
+			this_data++;
+		}
+
+		check_shift_keys(0, NULL);
+	}
+
+	return 0;
+}
+
+static void suspend_print_differences(void)
+{
+	struct reload_data * this_data = first_reload_data;
+	int i;
+
+	suspend_reread_pages(first_reload_data);
+	
+	if (get_rangepages_list())
+		return;
+
+	while (this_data) {
+		if (this_data->pageset && 
+		    this_data->pagenumber) {
+			suspend_message(SUSPEND_INTEGRITY, SUSPEND_MEDIUM, 1,
+				"Pagedir %d. Page %d. Address %p."
+				" Base %p. Copy %p.\n",
+				this_data->pageset,
+				this_data->pagenumber,
+				page_address(this_data->page_address),
+				this_data->base_version,
+				this_data->compared_version);
+			for (i= 0; i < (PAGE_SIZE / 8); i++) {
+				local_print_location(
+					page_address(this_data->page_address) + i * 8,
+					this_data->base_version + i * 8,
+					this_data->compared_version + i * 8);
+				check_shift_keys(0, NULL);
+			}
+			check_shift_keys(1, NULL);
+		} else
+			return;
+		this_data = this_data->next;
+	}
+
+	put_rangepages_list();
+}
+
+int __suspend_allocate_checksum_pages(void)
+{
+	int pages_required =
+		(pageset1_size + pageset2_size) / CHECKSUMS_PER_PAGE;
+	unsigned long this_page;
+
+	while (checksum_pages <= pages_required) {
+		this_page = suspend2_get_grabbed_pages(0);
+		if (!this_page)
+			return -ENOMEM;
+
+		if (!first_checksum_page)
+			first_checksum_page = 
+				(unsigned long *) this_page;
+		else
+			NEXT_CHECKSUM_PAGE(last_checksum_page) = this_page;
+		
+		last_checksum_page = (unsigned long *) this_page;
+		SetPageChecksumIgnore(virt_to_page(this_page));
+		checksum_pages++;
+	}
+	suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_PAGES, 0);
+
+	return suspend_allocate_reload_data(2);
+}
+
+static int suspend_checksum_init(void)
+{
+	if (allocate_local_pageflags(&checksum_map, 0))
+		return 1;
+	PRINTFREEMEM("after allocating checksum map");
+	suspend_store_free_mem(5, 0);
+	return 0;
+}
+
+
+static void suspend_checksum_cleanup(void)
+{
+	suspend_free_reload_data();
+	suspend_free_checksum_pages();
+	
+	free_local_pageflags(&checksum_map);
+	PRINTFREEMEM("after freeing checksum map");
+	suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_MAP, 1);
+}
+
+static struct suspend_plugin_ops checksum_ops = 
+{
+	.name					= "Checksum",
+	.type					= CHECKSUM_PLUGIN,
+	.initialise				= suspend_checksum_init,
+	.cleanup				= suspend_checksum_cleanup,
+	.ops = {
+		.checksum = {
+			.calculate_checksums	= suspend_calculate_checksums,
+			.check_checksums	= suspend_check_checksums,
+			.print_differences	= suspend_print_differences,
+			.allocate_pages		= __suspend_allocate_checksum_pages,
+		}
+	}
+};
+
+static struct suspend_proc_data proc_params[] = {
+	{ .filename			= "disable_checksumming",
+	.permissions			= PROC_RW,
+	.type				= SUSPEND_PROC_DATA_INTEGER,
+	.data = {
+		.integer = {
+			.variable	= &checksum_ops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	 }
+	},
+};
+
+static __init int checksum_load(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+	int result;
+
+	if (!(result = suspend_register_plugin(&checksum_ops))) {
+		printk("Software Suspend Checksum Module\n");
+		for (i=0; i< numfiles; i++)
+			suspend_register_procfile(&proc_params[i]);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void checksum_unload(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+	printk("Software Suspend Checksum module unloading.\n");
+
+	for (i=0; i< numfiles; i++)
+		suspend_unregister_procfile(&proc_params[i]);
+	suspend_unregister_plugin(&checksum_ops);
+}
+
+module_init(checksum_load);
+module_exit(checksum_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Suspend2 checksum module");
+#else
+__initcall(checksum_load);
+#endif
diff -ruN 20-old/kernel/power/suspend_dm.c 20-new/kernel/power/suspend_dm.c
--- 20-old/kernel/power/suspend_dm.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_dm.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,136 @@
+/*
+ * kernel/power/suspend_dm.c
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * This file contains support for interfacing with the device mapper
+ * to allocate memory for its work.
+ */
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+
+/* For calculating how much memory it needs */
+#include "../../drivers/md/dm-io.h"
+
+#include "suspend.h"
+#include "plugins.h"
+#include "proc.h"
+
+static struct suspend_plugin_ops suspend_dm_ops;
+static int io_get_result;
+
+/* ---- Exported functions ---- */
+
+/* suspend_dm_init()
+ *
+ * Description:	Allocate buffers for device mapper use.
+ * Returns:	Zero on success, -ENOMEM if unable to vmalloc.
+ */
+
+static int suspend_dm_init(void)
+{
+	io_get_result = dm_io_get(max_async_ios);
+	return io_get_result;
+}
+
+/* suspend_dm_cleanup()
+ *
+ * Description: Tell DM to release the memory we allocated.
+ * Returns:	Zero. Always works!
+ */
+
+static void suspend_dm_cleanup(void)
+{
+	if (!io_get_result)
+		dm_io_put(max_async_ios);
+}
+
+/* suspend_dm_save_config_info
+ *
+ * Description:	Save informaton needed when reloading the image at resume time.
+ * Arguments:	Buffer:		Pointer to a buffer of size PAGE_SIZE.
+ * Returns:	Number of bytes used for saving our data.
+ */
+
+static int suspend_dm_save_config_info(char * buffer)
+{
+	return 0;
+}
+
+/* suspend_dm_load_config_info
+ *
+ * Description:	Reload information needed for decompressing the image at 
+ * 		resume time.
+ * Arguments:	Buffer:		Pointer to the start of the data.
+ *		Size:		Number of bytes that were saved.
+ */
+
+static void suspend_dm_load_config_info(char * buffer, int size)
+{
+	BUG_ON(size);
+	return;
+}
+
+/*
+ * data for our proc entries.
+ */
+
+static struct suspend_proc_data disable_dm_support_proc_data = {
+	.filename			= "disable_device_mapper_support",
+	.permissions			= PROC_RW,
+	.type				= SUSPEND_PROC_DATA_INTEGER,
+	.data = {
+		.integer = {
+			.variable	= &suspend_dm_ops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	}
+};
+
+/*
+ * Ops structure.
+ */
+
+static struct suspend_plugin_ops suspend_dm_ops = {
+	.type			= MISC_PLUGIN,
+	.name			= "Device Mapper Support",
+	.initialise		= suspend_dm_init,
+	.cleanup		= suspend_dm_cleanup,
+	.save_config_info	= suspend_dm_save_config_info,
+	.load_config_info	= suspend_dm_load_config_info,
+};
+
+/* ---- Registration ---- */
+
+static __init int suspend_dm_load(void)
+{
+	int result;
+
+	if (!(result = suspend_register_plugin(&suspend_dm_ops))) {
+		printk("Software Suspend Device Mapper support registering.\n");
+		suspend_register_procfile(&disable_dm_support_proc_data);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void suspend_dm_unload(void)
+{
+	printk("Software Suspend Device Mapper support unloading.\n");
+	suspend_unregister_procfile(&disable_dm_support_proc_data);
+	suspend_unregister_plugin(&suspend_dm_ops);
+}
+
+
+module_init(suspend_dm_load);
+module_exit(suspend_dm_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Device Mapper support for Suspend2");
+#else
+__initcall(suspend_dm_load);
+#endif
diff -ruN 20-old/kernel/power/suspend_gzip.c 20-new/kernel/power/suspend_gzip.c
--- 20-old/kernel/power/suspend_gzip.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_gzip.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,560 @@
+/*
+ * kernel/power/gzip_compression.c
+ *
+ * Copyright (C) 2003,2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * This file contains data compression routines for suspend.
+ * Compression is implemented using the zlib library.
+ *
+ */
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/highmem.h>
+#include <linux/zlib.h>
+
+#include "plugins.h"
+#include "proc.h"
+#include "suspend.h"
+
+/* Forward declaration for the ops structure we export */
+struct suspend_plugin_ops gzip_compression_ops;
+
+/* The next driver in the pipeline */
+static struct suspend_plugin_ops * next_driver;
+
+/* Zlib routines we use to compress/decompress the data */
+extern int zlib_compress(unsigned char *data_in, unsigned char *cpage_out,
+		u32 *sourcelen, u32 *dstlen);
+extern void zlib_decompress(unsigned char *data_in, unsigned char *cpage_out,
+	       u32 srclen, u32 destlen);
+
+/* Buffers */
+static void *compression_workspace = NULL;
+static char *local_buffer = NULL;
+
+/* Configuration data we pass to zlib */
+static z_stream strm;
+
+/* Stats we save */
+static __nosavedata unsigned long bytes_in = 0, bytes_out = 0;
+
+/* Expected compression is used to reduce the amount of storage allocated */
+static int expected_gzip_compression = 0;
+
+/* ---- Zlib memory management ---- */
+
+/* allocate_zlib_compression_space
+ *
+ * Description:	Allocate space for zlib to use in compressing our data.
+ *		Each call must have a matching call to free_zlib_memory.
+ * Returns:	Int: Zero if successful, -ENONEM otherwise.
+ */
+static inline int allocate_zlib_compression_space(void)
+{
+	BUG_ON(compression_workspace);
+
+	compression_workspace = vmalloc_32(zlib_deflate_workspacesize());
+	if (!compression_workspace) {
+		printk(KERN_WARNING
+			"Failed to allocate %d bytes for deflate workspace\n",
+			zlib_deflate_workspacesize());
+		return -ENOMEM;
+	}
+	
+	return 0;
+}
+
+/* allocate_zlib_decompression_space
+ *
+ * Description:	Allocate space for zlib to use in decompressing our data.
+ *		Each call must have a matching call to free_zlib_memory.
+ * Returns:	Int: Zero if successful, -ENONEM otherwise.
+ */
+static inline int allocate_zlib_decompression_space(void)
+{
+	BUG_ON(compression_workspace);
+
+	compression_workspace = vmalloc_32(zlib_inflate_workspacesize());
+	if (!compression_workspace) {
+		printk(KERN_WARNING
+			"Failed to allocate %d bytes for inflate workspace\n",
+			zlib_inflate_workspacesize());
+		return -ENOMEM;
+	}
+	
+	return 0;
+}
+
+/* free_zlib_memory
+ *
+ * Description:	Frees memory allocated by either allocation routine (above).
+ */
+static inline void free_zlib_memory(void)
+{
+	if (!compression_workspace)
+		return;
+
+	vfree(compression_workspace);
+	compression_workspace = NULL;
+}
+
+/* ---- Local buffer management ---- */
+
+/* allocate_local_buffer
+ *
+ * Description:	Allocates a page of memory for buffering output.
+ * Returns:	Int: Zero if successful, -ENONEM otherwise.
+ */
+static int allocate_local_buffer(void)
+{
+	if (local_buffer)
+		return 0;
+
+	local_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	
+	if (!local_buffer) {
+		printk(KERN_ERR
+			"Failed to allocate a page for compression "
+			"driver's buffer.\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/* free_local_buffer
+ *
+ * Description:	Frees memory allocated for buffering output.
+ */
+static inline void free_local_buffer(void)
+{
+	if (local_buffer)
+		free_pages((unsigned long) local_buffer, 0);
+	local_buffer = NULL;
+}
+
+/* ---- Functions exported via operations struct ---- */
+
+/* gzip_write_init
+ *
+ * Description:	Allocate buffers and prepares zlib for deflating a new stream
+ * 		of data.
+ * Arguments:	Stream_number:	Ignored.
+ * Returns: 	Int. Zero if successful, otherwise an appropriate error number.
+ */
+
+static int gzip_write_init(int stream_number)
+{
+	int result;
+	
+	next_driver = get_next_filter(&gzip_compression_ops);
+
+	if (!next_driver) {
+		printk("GZip Compression Driver: Argh! No one wants my output!");
+		return -ECHILD;
+	}
+
+	if ((result = allocate_zlib_compression_space()))
+		return result;
+	
+	if ((result = allocate_local_buffer()))
+		return result;
+
+	strm.total_in = 0;
+	strm.total_out = 0;
+	strm.workspace = compression_workspace;
+	strm.next_out = (char *) local_buffer;
+	strm.avail_out = PAGE_SIZE;
+	result = zlib_deflateInit(&strm, Z_BEST_SPEED);
+		
+	if (Z_OK != result) {
+		printk(KERN_ERR name_suspend "Failed to initialise zlib.\n");
+		return -EPERM;
+	}
+
+	/* 
+	 * Reset the statistics iif we are about to write the first part of
+	 * the image
+	 */
+	if (stream_number == 2)
+		bytes_in = bytes_out = 0;
+
+	return 0;
+}
+
+/* gzip_write_chunk()
+ *
+ * Description:	Compress a page of data, buffering output and passing on
+ * 		filled pages to the next plugin in the pipeline.
+ * Arguments:	Buffer_start:	Pointer to a buffer of size PAGE_SIZE, 
+ * 				containing data to be compressed.
+ * Returns:	0 on success. Otherwise the error is that returned by later
+ * 		plugins, -ECHILD if we have a broken pipeline or -EPERM if
+ * 		zlib errs.
+ */
+
+static int gzip_write_chunk(struct page * buffer_page)
+{
+	int ret; 
+	char * buffer_start = kmap(buffer_page);
+	
+	/* Work to do */
+	strm.next_in = buffer_start;
+	strm.avail_in = PAGE_SIZE;
+	while (strm.avail_in) {
+		ret = zlib_deflate(&strm, Z_PARTIAL_FLUSH);
+		if (ret != Z_OK) {
+			printk("Zlib failed to compress our data. "
+				"Result code was %d.\n", ret);
+			kunmap(buffer_page);
+			return -EPERM;
+		}
+
+		if (!strm.avail_out) {
+
+			if ((ret = next_driver->ops.filter.write_chunk(
+					virt_to_page(local_buffer)))) {
+				kunmap(buffer_page);
+				return ret;
+			}
+			strm.next_out = local_buffer;
+			strm.avail_out = PAGE_SIZE;
+		}
+	}
+	kunmap(buffer_page);
+
+	return 0;
+}
+
+/* gzip_write_cleanup()
+ *
+ * Description:	Flush remaining data, update statistics and free allocated
+ * 		space.
+ * Returns:	Zero. Never fails. Okay. Zlib might fail... but it shouldn't.
+ */
+
+static int gzip_write_cleanup(void)
+{
+	int ret = 0, finished = 0;
+	
+	while (!finished) {
+		if (strm.avail_out) {
+			ret = zlib_deflate(&strm, Z_FINISH);
+
+			if (ret == Z_STREAM_END) {
+				ret = zlib_deflateEnd(&strm);
+				finished = 1;
+			}
+
+			if ((ret != Z_OK) && (ret != Z_STREAM_END)) {
+				zlib_deflateEnd(&strm);
+				printk("Failed to finish compressing data. "
+					"Result %d received.\n", ret);
+				return -EPERM;
+			}
+		}
+
+		if ((!strm.avail_out) || (finished)) {
+			if ((ret = next_driver->ops.filter.write_chunk(
+					virt_to_page(local_buffer))))
+				return ret;
+			strm.next_out = local_buffer;
+			strm.avail_out = PAGE_SIZE;
+		}
+	}
+
+	bytes_in+= strm.total_in;
+	bytes_out+= strm.total_out;
+
+	free_zlib_memory();
+	free_local_buffer();
+
+	return 0;
+}
+
+/* gzip_read_init
+ *
+ * Description:	Prepare to read a new stream of data.
+ * Arguments:	Stream_number:	Not used.
+ * Returns: 	Int. Zero if successful, otherwise an appropriate error number.
+ */
+
+static int gzip_read_init(int stream_number)
+{
+	int result;
+
+	next_driver = get_next_filter(&gzip_compression_ops);
+
+	if (!next_driver) {
+		printk("GZip Compression Driver: Argh! "
+			"No one wants to feed me data!");
+		return -ECHILD;
+	}
+	
+	if ((result = allocate_zlib_decompression_space()))
+		return result;
+	
+	if ((result = allocate_local_buffer()))
+		return result;
+
+	strm.total_in = 0;
+	strm.total_out = 0;
+	strm.workspace = compression_workspace;
+	strm.avail_in = 0;
+	if ((result = zlib_inflateInit(&strm)) != Z_OK) {
+		printk(KERN_ERR name_suspend "Failed to initialise zlib.\n");
+		return -EPERM;
+	}
+
+	return 0;
+}
+
+/* gzip_read_chunk()
+ *
+ * Description:	Retrieve data from later plugins and decompress it until the
+ * 		input buffer is filled.
+ * Arguments:	Buffer_start: 	Pointer to a buffer of size PAGE_SIZE.
+ * 		Sync:		Whether the previous plugin (or core) wants its
+ * 				data synchronously.
+ * Returns:	Zero if successful. Error condition from me or from downstream
+ * 		on failure.
+ */
+
+static int gzip_read_chunk(struct page * buffer_page, int sync)
+{
+	int ret; 
+	char * buffer_start = kmap(buffer_page);
+
+	/* 
+	 * All our reads must be synchronous - we can't decompress
+	 * data that hasn't been read yet.
+	 */
+
+	/* Work to do */
+	strm.next_out = buffer_start;
+	strm.avail_out = PAGE_SIZE;
+	while (strm.avail_out) {
+		if (!strm.avail_in) {
+			if ((ret = next_driver->ops.filter.read_chunk(
+					virt_to_page(local_buffer), 
+					SUSPEND_SYNC)) < 0) {
+				kunmap(buffer_page);
+				return ret;
+			}
+			strm.next_in = local_buffer;
+			strm.avail_in = PAGE_SIZE;
+		}
+			
+		ret = zlib_inflate(&strm, Z_PARTIAL_FLUSH);
+
+		if ((ret == Z_BUF_ERROR) && (!strm.avail_in)) {
+			continue;
+		}
+			
+		if ((ret != Z_OK) && (ret != Z_STREAM_END)) {
+			printk("Zlib failed to decompress our data. "
+					"Result code was %d.\n", ret);
+			kunmap(buffer_page);
+			return -EPERM;
+		}
+	}
+	kunmap(buffer_page);
+
+	return 0;
+}
+
+/* read_cleanup()
+ *
+ * Description:	Clean up after reading part or all of a stream of data.
+ * Returns:	int: Always zero. Never fails.
+ */
+
+static int gzip_read_cleanup(void)
+{
+	zlib_inflateEnd(&strm);
+	
+	free_zlib_memory();
+	free_local_buffer();
+	return 0;
+}
+
+/* gzip_print_debug_stats
+ *
+ * Description:	Print information to be recorded for debugging purposes into a
+ * 		buffer.
+ * Arguments:	buffer: Pointer to a buffer into which the debug info will be
+ * 			printed.
+ * 		size:	Size of the buffer.
+ * Returns:	Number of characters written to the buffer.
+ */
+
+static int gzip_print_debug_stats(char * buffer, int size)
+{
+	int pages_in = bytes_in >> PAGE_SHIFT;
+	int pages_out = bytes_out >> PAGE_SHIFT;
+	int len;
+	
+	//Output the compression ratio achieved.
+	len = suspend_snprintf(buffer, size, "- GZIP compressor enabled.\n");
+	if (pages_in)
+		len+= suspend_snprintf(buffer+len, size - len,
+		  "  Compressed %ld bytes into %ld.\n  "
+		  "Image compressed by %d percent.\n",
+		  bytes_in, bytes_out, (pages_in - pages_out) * 100 / pages_in);
+	return len;
+}
+
+/* compression_memory_needed
+ *
+ * Description:	Tell the caller how much memory we need to operate during
+ * 		suspend/resume.
+ * Returns:	Unsigned long. Maximum number of bytes of memory required for
+ * 		operation.
+ */
+
+static unsigned long gzip_memory_needed(void)
+{
+	return PAGE_SIZE + max( zlib_deflate_workspacesize(),
+				zlib_inflate_workspacesize());
+}
+
+static unsigned long gzip_storage_needed(void)
+{
+	return 2 * sizeof(unsigned long);
+}
+
+/* gzip_save_config_info
+ *
+ * Description:	Save information needed when reloading the image at resume time.
+ * Arguments:	Buffer:		Pointer to a buffer of size PAGE_SIZE.
+ * Returns:	Number of bytes used for saving our data.
+ */
+
+static int gzip_save_config_info(char * buffer)
+{
+	*((unsigned long *) buffer) = bytes_in;
+	*((unsigned long *) (buffer + sizeof(unsigned long))) = bytes_out;
+	*((int *) (buffer + 2 * sizeof(unsigned long))) = expected_gzip_compression;
+	return 2 * sizeof(unsigned long) + sizeof(int);
+}
+
+/* gzip_load_config_info
+ *
+ * Description:	Reload information needed for decompressing the image at 
+ * 		resume time.
+ * Arguments:	Buffer:		Pointer to the start of the data.
+ *		Size:		Number of bytes that were saved.
+ */
+
+static void gzip_load_config_info(char * buffer, int size)
+{
+	if(size == 2 * sizeof(unsigned long) + sizeof(int)) {
+		bytes_in = *((unsigned long *) buffer);
+		bytes_out = *((unsigned long *) (buffer + sizeof(unsigned long)));
+		expected_gzip_compression = *((int *) (buffer + 2 * sizeof(unsigned long)));
+	} else
+		printk("Suspend GZIP config info size mismatch: settings ignored.\n");
+	return;
+}
+
+/* gzip_get_expected_compression
+ * 
+ * Description:	Returns the expected ratio between data passed into this plugin
+ * 		and the amount of data output when writing.
+ * Returns:	The value set by the user via our proc entry.
+ */
+
+static int gzip_get_expected_compression(void)
+{
+	return 100 - expected_gzip_compression;
+}
+
+/*
+ * data for our proc entries.
+ */
+
+struct suspend_proc_data expected_compression_proc_data = {
+	.filename			= "expected_gzip_compression",
+	.permissions			= PROC_RW,
+	.type				= SUSPEND_PROC_DATA_INTEGER,
+	.data = {
+		.integer = {
+			.variable	= &expected_gzip_compression,
+			.minimum	= 0,
+			.maximum	= 99,
+		}
+	}
+};
+
+struct suspend_proc_data disable_compression_proc_data = {
+	.filename			= "disable_gzip_compression",
+	.permissions			= PROC_RW,
+	.type				= SUSPEND_PROC_DATA_INTEGER,
+	.data = {
+		.integer = {
+			.variable	= &gzip_compression_ops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	}
+};
+
+/*
+ * Ops structure.
+ */
+
+struct suspend_plugin_ops gzip_compression_ops = {
+	.type			= FILTER_PLUGIN,
+	.name			= "Zlib Page Compressor",
+	.memory_needed 		= gzip_memory_needed,
+	.print_debug_info	= gzip_print_debug_stats,
+	.save_config_info	= gzip_save_config_info,
+	.load_config_info	= gzip_load_config_info,
+	.storage_needed		= gzip_storage_needed,
+	.ops = {
+		.filter = {
+		 .write_init		= gzip_write_init,
+		 .write_chunk		= gzip_write_chunk,
+		 .write_cleanup		= gzip_write_cleanup,
+		 .read_init		= gzip_read_init,
+		 .read_chunk		= gzip_read_chunk,
+		 .read_cleanup		= gzip_read_cleanup,
+		 .expected_compression	= gzip_get_expected_compression,
+		}
+	}
+};
+
+/* ---- Registration ---- */
+
+static __init int gzip_load(void)
+{
+	int result;
+
+	if (!(result = suspend_register_plugin(&gzip_compression_ops))) {
+		printk("Software Suspend Gzip Compression Driver registered.\n");
+		suspend_register_procfile(&expected_compression_proc_data);
+		suspend_register_procfile(&disable_compression_proc_data);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void gzip_unload(void)
+{
+	printk("Software Suspend Gzip Compression Driver unloading.\n");
+	suspend_unregister_procfile(&expected_compression_proc_data);
+	suspend_unregister_procfile(&disable_compression_proc_data);
+	suspend_unregister_plugin(&gzip_compression_ops);
+}
+
+module_init(gzip_load);
+module_exit(gzip_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Gzip Compression support for Suspend2");
+#else
+__initcall(gzip_load);
+#endif
diff -ruN 20-old/kernel/power/suspend.h 20-new/kernel/power/suspend.h
--- 20-old/kernel/power/suspend.h	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend.h	2004-11-24 15:35:52.000000000 +1100
@@ -0,0 +1,296 @@
+/*
+ * kernel/power/suspend2.h
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * It contains declarations used throughout swsusp and suspend2.
+ *
+ */
+#ifndef KERNEL_POWER_SUSPEND_H
+#define KERNEL_POWER_SUSPEND_H
+
+#include <linux/sched.h>
+#include <linux/kdev_t.h>
+#include <linux/mmzone.h>
+#include <linux/shmem_fs.h>
+#include <linux/wait.h>
+#include <linux/swap.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include "range.h"
+#include <asm/fixmap.h>
+/* ---------------------------- swsusp only ----------------------------- */
+
+typedef struct pbe {
+	unsigned long address;		/* address of the copy */
+	unsigned long orig_address;	/* original address of page */
+	swp_entry_t swap_address;	
+	swp_entry_t dummy;		/* we need scratch space at 
+					 * end of page (see link, diskpage)
+					 */
+} suspend_pagedir_t;
+
+#define SUSPEND_PD_PAGES(x)     (((x)*sizeof(struct pbe))/PAGE_SIZE+1)
+   
+extern struct page *highmem_start_page;
+
+/* mm/page_alloc.c */
+extern void drain_local_pages(void);
+
+void save_processor_state(void);
+void restore_processor_state(void);
+struct saved_context;
+void __save_processor_state(struct saved_context *ctxt);
+void __restore_processor_state(struct saved_context *ctxt);
+
+/* ---------------------------- Suspend2 -------------------------------- */
+
+/* Page Backup Entry.
+ *
+ * This is an abstraction which contains the data for one
+ * page of the image. (The data is really stored in ranges).
+ */
+
+struct pbe2 {
+	struct page * origaddress;	/* Original address of page */
+	struct page * address;		/* Address of copy of page */
+	struct range * currentorigrange;
+	struct range * currentdestrange;
+
+	struct pagedir * pagedir;
+};
+
+/* Pagedir
+ *
+ * Contains the metadata for a set of pages saved in the image.
+ */
+struct pagedir {
+	int pagedir_num;
+	int pageset_size;
+	int lastpageset_size;
+	struct rangechain origranges;
+	struct rangechain destranges;
+	struct rangechain allocdranges;
+};
+
+/* Function for setting the chain names for a pagedir (used
+ * for debugging */
+void set_chain_names(struct pagedir * p);
+
+#define pageset1_size (pagedir1.pageset_size)
+#define pageset2_size (pagedir2.pageset_size)
+
+/* Pagedir_nosave is pagedir1, loaded back in at the beginning
+ * of resuming and relocated so we can do our atomic restoration
+ * of the original kernel.
+ * Pagedir1 is the metadata for pageset 1 pages. Ditto for pageset 2.
+ */
+extern suspend_pagedir_t *pagedir_nosave __nosavedata;
+extern struct pagedir pagedir1, pagedir2;
+
+/* Non-plugin data saved in our image header */
+struct suspend_header {
+	u32 version_code;
+	unsigned long num_physpages;
+	char machine[65];
+	char version[65];
+	int num_cpus;
+	int page_size;
+	unsigned long orig_mem_free;
+	int num_range_pages;
+	struct range * unused_ranges;
+	int pageset_2_size;
+	int param0;
+	int param1;
+	int param2;
+	int param3;
+	int param4;
+	int progress0;
+	int progress1;
+	int progress2;
+	int progress3;
+	int io_time[2][2];
+	
+	/* Implementation specific variables */
+#ifdef KERNEL_POWER_SWSUSP_C
+	suspend_pagedir_t *suspend_pagedir;
+	unsigned int num_pbes;
+#else
+	struct pagedir pagedir;
+#endif
+};
+
+/* Suspend memory pool functions */
+struct page * get_suspend_pool_pages(unsigned int gfp_mask, unsigned int order);
+void free_suspend_pool_pages(struct page *page, unsigned int order);
+
+extern void schedule_suspend_message(int message_number);
+extern int suspend_min_free;
+
+extern void suspend_restore_avenrun(void);
+extern void suspend_save_avenrun(void);
+
+extern unsigned long get_highstart_pfn(void);
+
+#define SWAP_FILENAME_MAXLENGTH	32
+
+extern int suspend_default_console_level;
+extern int max_async_ios;
+extern int image_size_limit;
+
+struct pageset_sizes_result {
+	int size1; /* Can't be unsigned - breaks MAX function */
+	int size1low;
+	int size2;
+	int size2low;
+	int needmorespace;
+};
+
+#define MB(x) ((x) >> (20 - PAGE_SHIFT))
+
+extern int suspend_amount_grabbed;
+
+/*
+ * XXX: We try to keep some more pages free so that I/O operations succeed
+ * without paging. Might this be more?
+ */
+#ifdef CONFIG_HIGHMEM
+#define MIN_FREE_RAM (get_highstart_pfn() >> 7)
+#else
+#define MIN_FREE_RAM (max_mapnr >> 7)
+#endif
+
+extern void prepare_status(int printalways, int clearbar, const char *fmt, ...);
+extern void abort_suspend(const char *fmt, ...);
+
+extern int suspend_snprintf(char * buffer, int buffer_size,
+		const char *fmt, ...);
+
+/* ------ prepare_image.c ------ */
+extern unsigned long get_grabbed_pages(int order);
+
+/* ------ io.c ------ */
+int suspend_early_boot_message(int can_erase_image, char *reason, ...);
+
+/* ------ console.c ------ */
+void check_shift_keys(int pause, char * message);
+unsigned long update_status(unsigned long value, unsigned long maximum,
+		const char *fmt, ...);
+
+extern int expected_compression_ratio(void);
+
+#define MAIN_STORAGE_NEEDED(USE_ECR) \
+	((pageset1_size + pageset2_size) * \
+	 (USE_ECR ? expected_compression_ratio() : 100) / 100)
+
+#define HEADER_BYTES_NEEDED \
+	((num_range_pages << PAGE_SHIFT) + \
+	 sizeof(struct suspend_header) + \
+	 sizeof(struct plugin_header) + \
+	 (int) header_storage_for_plugins() + \
+	 num_plugins * \
+	 	(sizeof(struct plugin_header) + sizeof(int)))
+	
+#define HEADER_STORAGE_NEEDED ((HEADER_BYTES_NEEDED + (int) PAGE_SIZE - 1) >> PAGE_SHIFT)
+
+#define STORAGE_NEEDED(USE_ECR) \
+	(MAIN_STORAGE_NEEDED(USE_ECR) + HEADER_STORAGE_NEEDED)
+
+#define RAM_TO_SUSPEND (1 + max((pageset1_size - pageset2_sizelow), 0) + \
+		MIN_FREE_RAM + memory_for_plugins())
+
+#ifndef KERNEL_POWER_SWSUSP_C
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+#define cond_show_pcp_lists() \
+do { \
+	if (TEST_DEBUG_STATE(SUSPEND_FREEZER)) \
+		show_pcp_lists(); \
+} while(0)
+
+#define MDELAY(a) do { if (TEST_ACTION_STATE(SUSPEND_SLOW)) mdelay(a); } \
+	while (0)
+#define MAX_FREEMEM_SLOTS 25
+enum {
+	SUSPEND_FREE_BASE,
+	SUSPEND_FREE_CONSOLE_ALLOC,
+	SUSPEND_FREE_DRAIN_PCP,
+	SUSPEND_FREE_IN_USE_MAP,
+	SUSPEND_FREE_PS2_MAP,
+	SUSPEND_FREE_CHECKSUM_MAP,
+	SUSPEND_FREE_UNMAP_MAP,
+	SUSPEND_FREE_RELOAD_PAGES,
+	SUSPEND_FREE_INIT_PLUGINS,
+	SUSPEND_FREE_MEM_POOL,
+	SUSPEND_FREE_FREEZER,
+	SUSPEND_FREE_EAT_MEMORY,
+	SUSPEND_FREE_SYNC,
+	SUSPEND_FREE_GRABBED_MEMORY,
+	SUSPEND_FREE_RANGE_PAGES,
+	SUSPEND_FREE_EXTRA_PD1,
+	SUSPEND_FREE_WRITER_STORAGE,
+	SUSPEND_FREE_HEADER_STORAGE,
+	SUSPEND_FREE_CHECKSUM_PAGES,
+	SUSPEND_FREE_KSTAT,
+	SUSPEND_FREE_DEBUG_INFO,
+	SUSPEND_FREE_INVALIDATE_IMAGE,
+	SUSPEND_FREE_IO,
+	SUSPEND_FREE_IO_INFO,
+	SUSPEND_FREE_START_ONE
+};
+extern void suspend_store_free_mem(int slot, int side);
+extern int suspend_free_mem_values[MAX_FREEMEM_SLOTS][2];
+#else
+#define suspend_store_free_mem(a, b) do { } while(0)
+#define MDELAY(a) do { } while (0)
+#define cond_show_pcp_lists() do { } while(0)
+#endif
+#endif /* Not swsusp */
+
+extern int expected_compression_ratio(void);
+int print_module_list_to_buffer(char * buffer, int size);
+
+extern unsigned int nr_suspends;
+extern char resume2_file[];
+
+extern int suspend_wait_for_keypress(void);
+
+#ifdef CONFIG_SMP
+extern void smp_suspend(void);
+extern void smp_continue(void);
+#else
+#define smp_suspend() do { } while(0)
+#define smp_continue() do { } while(0)
+#endif
+
+/* For user interface */
+//#include <linux/syscalls.h>
+extern asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, 
+	size_t count);
+
+#ifdef CONFIG_FBCON_SPLASHSCREEN
+#include <linux/console.h>
+#include <linux/console_struct.h>
+#include <linux/fb.h>
+#include <video/fbcon.h>
+static inline struct splash_data * get_splash_data(int consolenr)
+{
+	extern struct display fb_display[MAX_NR_CONSOLES];
+	
+	BUG_ON(consolenr >= MAX_NR_CONSOLES);
+
+	return fb_display[consolenr].splash_data;
+}
+#endif
+
+extern asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count);
+
+extern struct pm_ops * pm_ops;
+extern dev_t name_to_kdev_t(char *line);
+//extern char _text[], _etext[], _edata[], __bss_start[], _end[];
+extern void signal_wake_up(struct task_struct *t, int resume);
+
+extern struct partial_device_tree * suspend_device_tree;
+
+#endif
diff -ruN 20-old/kernel/power/suspend_lzf.c 20-new/kernel/power/suspend_lzf.c
--- 20-old/kernel/power/suspend_lzf.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_lzf.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,553 @@
+/*
+ * kernel/power/lzf_compress.c
+ *
+ * Copyright (C) 2003 Marc Lehmann <pcg@goof.com>
+ * Copyright (C) 2003,2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * This file contains data compression routines for suspend,
+ * using LZH compression.
+ *
+ */
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+
+#include "plugins.h"
+#include "proc.h"
+#include "suspend.h"
+
+static int expected_lzf_compression = 0;
+
+/*
+ * size of hashtable is (1 << HLOG) * sizeof (char *)
+ * decompression is independent of the hash table size
+ * the difference between 15 and 14 is very small
+ * for small blocks (and 14 is also faster).
+ * For a low-memory configuration, use HLOG == 13;
+ * For best compression, use 15 or 16.
+ */
+#ifndef HLOG
+# define HLOG 14
+#endif
+
+/*
+ * sacrifice some compression quality in favour of compression speed.
+ * (roughly 1-2% worse compression for large blocks and
+ * 9-10% for small, redundant, blocks and >>20% better speed in both cases)
+ * In short: enable this for binary data, disable this for text data.
+ */
+#ifndef ULTRA_FAST
+# define ULTRA_FAST 1
+#endif
+
+#define STRICT_ALIGN 0
+#define USE_MEMCPY 1
+#define INIT_HTAB 0
+
+#include "lzf/lzf_c.c"
+#include "lzf/lzf_d.c"
+
+static struct suspend_plugin_ops lzf_compression_ops;
+static struct suspend_plugin_ops * next_driver;
+
+static void *compression_workspace = NULL;
+static u8 *local_buffer = NULL;
+static struct page * local_buffer_page = NULL;
+static u8 *page_buffer = NULL;
+static struct page * page_buffer_page = NULL;
+static unsigned int bufofs;
+
+static __nosavedata unsigned long bytes_in = 0, bytes_out = 0;
+
+/* allocate_compression_space
+ *
+ * Description:	Allocate space for use in [de]compressing our data.
+ *		Each call must have a matching call to free_memory.
+ * Returns:	Int: Zero if successful, -ENONEM otherwise.
+ */
+
+static inline int allocate_compression_space(void)
+{
+	BUG_ON(compression_workspace);
+
+	compression_workspace = vmalloc_32((1<<HLOG)*sizeof(char *));
+	if (!compression_workspace) {
+		printk(KERN_WARNING
+			"Failed to allocate %d bytes for lzf workspace\n",
+			(1<<HLOG)*sizeof(char *));
+		return -ENOMEM;
+	}
+	
+	return 0;
+}
+
+/* free_zlib_memory
+ *
+ * Description:	Frees memory allocated by the allocation routine (above).
+ */
+
+static inline void free_memory(void)
+{
+	if (!compression_workspace)
+		return;
+
+	vfree(compression_workspace);
+	compression_workspace = NULL;
+}
+
+/* ---- Local buffer management ---- */
+
+/* allocate_local_buffer
+ *
+ * Description:	Allocates a page of memory for buffering output.
+ * Returns:	Int: Zero if successful, -ENONEM otherwise.
+ */
+
+static int allocate_local_buffer(void)
+{
+	if (!local_buffer) {
+		local_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	
+		if (!local_buffer) {
+			printk(KERN_ERR
+				"Failed to allocate the local buffer for "
+				"lzf compression driver.\n");
+			return -ENOMEM;
+		}
+		local_buffer_page = virt_to_page(local_buffer);
+	}
+
+	if (!page_buffer) {
+		page_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	
+		if (!page_buffer) {
+			printk(KERN_ERR
+				"Failed to allocate the page buffer for "
+				"lzf compression driver.\n");
+			return -ENOMEM;
+		}
+		page_buffer_page = virt_to_page(page_buffer);
+	}
+
+	return 0;
+}
+
+/* free_local_buffer
+ *
+ * Description:	Frees memory allocated for buffering output.
+ */
+
+static inline void free_local_buffer(void)
+{
+	if (local_buffer)
+		free_pages((unsigned long) local_buffer, 0);
+
+	local_buffer = NULL;
+	local_buffer_page = NULL;
+
+	if (page_buffer)
+		free_pages((unsigned long) page_buffer, 0);
+
+	page_buffer = NULL;
+	page_buffer_page = NULL;
+}
+
+/* ---- Exported functions ---- */
+
+/* write_init()
+ *
+ * Description:	Allocate buffers and prepare to compress data.
+ * Arguments:	Stream_number:	Ignored.
+ * Returns:	Zero on success, -ENOMEM if unable to vmalloc.
+ */
+
+static int lzf_write_init(int stream_number)
+{
+	int result;
+	
+	next_driver = get_next_filter(&lzf_compression_ops);
+
+	if (!next_driver) {
+		printk("LZF Compression Driver: Argh! No one wants my output!");
+		return -ECHILD;
+	}
+
+	if ((result = allocate_compression_space()))
+		return result;
+	
+	if ((result = allocate_local_buffer()))
+		return result;
+
+	/* Only reset the stats if starting to write an image */
+	if (stream_number == 2)
+		bytes_in = bytes_out = 0;
+	
+	bufofs = 0;
+
+	return 0;
+}
+
+/* lzf_write()
+ *
+ * Description:	Helper function for write_chunk. Write the compressed data.
+ * Arguments:	u8*:		Output buffer to be written.
+ * 		unsigned int:	Length of buffer.
+ * Return:	int:		Result to be passed back to caller.
+ */
+
+static int lzf_write (u8 *buffer, unsigned int len)
+{
+	int ret;
+
+	bytes_out += len;
+
+	while (len + bufofs > PAGE_SIZE) {
+		unsigned int chunk = PAGE_SIZE - bufofs;
+		memcpy (local_buffer + bufofs, buffer, chunk);
+		buffer += chunk;
+		len -= chunk;
+		bufofs = 0;
+		if ((ret = next_driver->ops.filter.write_chunk(local_buffer_page)) < 0)
+			return ret;
+	}
+	memcpy (local_buffer + bufofs, buffer, len);
+	bufofs += len;
+	return 0;
+}
+
+/* lzf_write_chunk()
+ *
+ * Description:	Compress a page of data, buffering output and passing on
+ * 		filled pages to the next plugin in the pipeline.
+ * Arguments:	Buffer_page:	Pointer to a buffer of size PAGE_SIZE, 
+ * 				containing data to be compressed.
+ * Returns:	0 on success. Otherwise the error is that returned by later
+ * 		plugins, -ECHILD if we have a broken pipeline or -EPERM if
+ * 		zlib errs.
+ */
+
+static int lzf_write_chunk(struct page * buffer_page)
+{
+	int ret; 
+	u16 len;
+	char * buffer_start = kmap(buffer_page);
+	
+	bytes_in += PAGE_SIZE;
+
+	len = lzf_compress(buffer_start, PAGE_SIZE, page_buffer,
+			PAGE_SIZE - 3, compression_workspace);
+
+	if ((ret = lzf_write((u8 *)&len, 2)) >= 0) {
+		if (len) // some compression
+			ret = lzf_write(page_buffer, len);
+		else
+			ret = lzf_write(buffer_start, PAGE_SIZE);
+	}
+	kunmap(buffer_page);
+	return ret;
+}
+
+/* write_cleanup()
+ *
+ * Description: Write unflushed data and free workspace.
+ * Returns:	Result of writing last page.
+ */
+
+static int lzf_write_cleanup(void)
+{
+	int ret;
+	
+	ret = next_driver->ops.filter.write_chunk(local_buffer_page);
+
+	free_memory();
+	free_local_buffer();
+
+	return ret;
+}
+
+/* read_init()
+ *
+ * Description:	Prepare to read a new stream of data.
+ * Arguments:	int: Section of image about to be read.
+ * Returns:	int: Zero on success, error number otherwise.
+ */
+
+static int lzf_read_init(int stream_number)
+{
+	int result;
+
+	next_driver = get_next_filter(&lzf_compression_ops);
+
+	if (!next_driver) {
+		printk("LZF Compression Driver: Argh! No one wants "
+				"to feed me data!");
+		return -ECHILD;
+	}
+	
+	if ((result = allocate_local_buffer()))
+		return result;
+
+	bufofs = PAGE_SIZE;
+
+	return 0;
+}
+
+/* lzf_read()
+ *
+ * Description:	Read data into compression buffer.
+ * Arguments:	u8 *:		Address of the buffer.
+ * 		unsigned int:	Length
+ * Returns:	int:		Result of reading the image chunk.
+ */
+
+static int lzf_read (u8 * buffer, unsigned int len)
+{
+	int ret;
+
+	while (len + bufofs > PAGE_SIZE) {
+		unsigned int chunk = PAGE_SIZE - bufofs;
+		memcpy(buffer, local_buffer + bufofs, chunk);
+		buffer += chunk;
+		len -= chunk;
+		bufofs = 0;
+		if ((ret = next_driver->ops.filter.read_chunk(
+					local_buffer_page, SUSPEND_SYNC)) < 0) {
+			return ret;
+		}
+	}
+	memcpy (buffer, local_buffer + bufofs, len);
+	bufofs += len;
+	return 0;
+}
+
+/* lzf_read_chunk()
+ *
+ * Description:	Retrieve data from later plugins and decompress it until the
+ * 		input buffer is filled.
+ * Arguments:	Buffer_start: 	Pointer to a buffer of size PAGE_SIZE.
+ * 		Sync:		Whether the previous plugin (or core) wants its
+ * 				data synchronously.
+ * Returns:	Zero if successful. Error condition from me or from downstream
+ * 		on failure.
+ */
+
+static int lzf_read_chunk(struct page * buffer_page, int sync)
+{
+	int ret; 
+	u16 len;
+	char * buffer_start = kmap(buffer_page);
+
+	/* 
+	 * All our reads must be synchronous - we can't decompress
+	 * data that hasn't been read yet.
+	 */
+
+	if ((ret = lzf_read ((u8 *)&len, 2)) >= 0) {
+		if (len == 0) { // uncompressed
+			ret = lzf_read(buffer_start, PAGE_SIZE);
+		} else { // compressed
+			if ((ret = lzf_read(page_buffer, len)) >= 0) {
+				ret = lzf_decompress(page_buffer, len, buffer_start, PAGE_SIZE);
+				if (ret != PAGE_SIZE)
+					ret = -EPERM; // why EPERM??
+				else
+					ret = 0;
+			}
+		}
+	}
+	kunmap(buffer_page);
+	return ret;
+}
+
+/* read_cleanup()
+ *
+ * Description:	Clean up after reading part or all of a stream of data.
+ * Returns:	int: Always zero. Never fails.
+ */
+
+static int lzf_read_cleanup(void)
+{
+	free_local_buffer();
+	return 0;
+}
+
+/* lzf_print_debug_stats
+ *
+ * Description:	Print information to be recorded for debugging purposes into a
+ * 		buffer.
+ * Arguments:	buffer: Pointer to a buffer into which the debug info will be
+ * 			printed.
+ * 		size:	Size of the buffer.
+ * Returns:	Number of characters written to the buffer.
+ */
+
+static int lzf_print_debug_stats(char * buffer, int size)
+{
+	int pages_in = bytes_in >> PAGE_SHIFT, 
+		pages_out = bytes_out >> PAGE_SHIFT;
+	int len;
+	
+	/* Output the compression ratio achieved. */
+	len = suspend_snprintf(buffer, size, "- LZF Compressor enabled.\n");
+	if (pages_in)
+		len+= suspend_snprintf(buffer+len, size - len,
+		  "  Compressed %ld bytes into %ld (%d percent compression).\n",
+		  bytes_in, bytes_out, (pages_in - pages_out) * 100 / pages_in);
+	return len;
+}
+
+/* compression_memory_needed
+ *
+ * Description:	Tell the caller how much memory we need to operate during
+ * 		suspend/resume.
+ * Returns:	Unsigned long. Maximum number of bytes of memory required for
+ * 		operation.
+ */
+
+static unsigned long lzf_memory_needed(void)
+{
+	return PAGE_SIZE * 2 + (1<<HLOG)*sizeof(char *);
+}
+
+static unsigned long lzf_storage_needed(void)
+{
+	return 2 * sizeof(unsigned long);
+}
+
+/* lzf_save_config_info
+ *
+ * Description:	Save informaton needed when reloading the image at resume time.
+ * Arguments:	Buffer:		Pointer to a buffer of size PAGE_SIZE.
+ * Returns:	Number of bytes used for saving our data.
+ */
+
+static int lzf_save_config_info(char * buffer)
+{
+	*((unsigned long *) buffer) = bytes_in;
+	*((unsigned long *) (buffer + sizeof(unsigned long))) = bytes_out;
+	*((int *) (buffer + 2 * sizeof(unsigned long))) = expected_lzf_compression;
+	return 2 * sizeof(unsigned long) + sizeof(int);
+}
+
+/* lzf_load_config_info
+ *
+ * Description:	Reload information needed for decompressing the image at 
+ * 		resume time.
+ * Arguments:	Buffer:		Pointer to the start of the data.
+ *		Size:		Number of bytes that were saved.
+ */
+
+static void lzf_load_config_info(char * buffer, int size)
+{
+	if(size == 2 * sizeof(unsigned long) + sizeof(int)) {
+		bytes_in = *((unsigned long *) buffer);
+		bytes_out = *((unsigned long *) (buffer + sizeof(unsigned long)));
+		expected_lzf_compression = *((int *) (buffer + 2 * sizeof(unsigned long)));
+	} else
+		printk("Suspend LZF config info size mismatch: settings ignored.\n");
+	return;
+}
+
+/* lzf_get_expected_compression
+ * 
+ * Description:	Returns the expected ratio between data passed into this plugin
+ * 		and the amount of data output when writing.
+ * Returns:	100 if the plugin is disabled. Otherwise the value set by the
+ * 		user via our proc entry.
+ */
+
+static int lzf_get_expected_compression(void)
+{
+	return 100 - expected_lzf_compression;
+}
+
+/*
+ * data for our proc entries.
+ */
+
+static struct suspend_proc_data expected_compression_proc_data = {
+	.filename			= "expected_lzf_compression",
+	.permissions			= PROC_RW,
+	.type				= SUSPEND_PROC_DATA_INTEGER,
+	.data = {
+		.integer = {
+			.variable	= &expected_lzf_compression,
+			.minimum	= 0,
+			.maximum	= 99,
+		}
+	}
+};
+
+static struct suspend_proc_data disable_compression_proc_data = {
+	.filename			= "disable_lzf_compression",
+	.permissions			= PROC_RW,
+	.type				= SUSPEND_PROC_DATA_INTEGER,
+	.data = {
+		.integer = {
+			.variable	= &lzf_compression_ops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	}
+};
+
+/*
+ * Ops structure.
+ */
+
+static struct suspend_plugin_ops lzf_compression_ops = {
+	.type			= FILTER_PLUGIN,
+	.name			= "LZF Page Compressor",
+	.memory_needed 		= lzf_memory_needed,
+	.print_debug_info	= lzf_print_debug_stats,
+	.save_config_info	= lzf_save_config_info,
+	.load_config_info	= lzf_load_config_info,
+	.storage_needed		= lzf_storage_needed,
+	.ops = {
+		.filter = {
+			.write_init		= lzf_write_init,
+			.write_chunk		= lzf_write_chunk,
+			.write_cleanup		= lzf_write_cleanup,
+			.read_init		= lzf_read_init,
+			.read_chunk		= lzf_read_chunk,
+			.read_cleanup		= lzf_read_cleanup,
+			.expected_compression	= lzf_get_expected_compression,
+		}
+	}
+};
+
+/* ---- Registration ---- */
+
+static __init int lzf_load(void)
+{
+	int result;
+
+	if (!(result = suspend_register_plugin(&lzf_compression_ops))) {
+		printk("Software Suspend LZF Compression Driver registered.\n");
+		suspend_register_procfile(&expected_compression_proc_data);
+		suspend_register_procfile(&disable_compression_proc_data);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void lzf_unload(void)
+{
+	printk("Software Suspend LZF Compression Driver unloading.\n");
+	suspend_unregister_procfile(&expected_compression_proc_data);
+	suspend_unregister_procfile(&disable_compression_proc_data);
+	suspend_unregister_plugin(&lzf_compression_ops);
+}
+
+
+module_init(lzf_load);
+module_exit(lzf_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marc Lehmann");
+MODULE_DESCRIPTION("LZF Compression support for Suspend2");
+#else
+__initcall(lzf_load);
+#endif
diff -ruN 20-old/kernel/power/suspend_swap.c 20-new/kernel/power/suspend_swap.c
--- 20-old/kernel/power/suspend_swap.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_swap.c	2004-11-24 15:24:21.000000000 +1100
@@ -0,0 +1,2061 @@
+/*
+ * Swapwriter.c
+ *
+ * Copyright 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * Distributed under GPLv2.
+ * 
+ * This file encapsulates functions for usage of swap space as a
+ * backing store.
+ */
+
+#include <linux/suspend.h>
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/swap.h>
+#include <linux/slab.h>
+#include <asm/uaccess.h>
+
+#include "suspend.h"
+#include "block_io.h"
+#include "proc.h"
+#include "plugins.h"
+
+#define SIGNATURE_VER 6
+
+/* --- Struct of pages stored on disk */
+
+static struct suspend_plugin_ops swapwriterops;
+
+struct swaplink {
+	char dummy[PAGE_SIZE - sizeof(swp_entry_t)];
+	swp_entry_t next;
+};
+
+union diskpage {
+	union swap_header swh;	/* swh.magic is the only member used */
+	struct swaplink link;
+	struct suspend_header sh;
+};
+
+union p_diskpage {
+	union diskpage *pointer;
+	char *ptr;
+        unsigned long address;
+};
+
+#define SIGNATURE_LENGTH 10
+
+// - Manage swap signature.
+static int prepare_signature(struct submit_params * first_header_page,
+		char * current_header);
+static int parse_signature(char * signature, int restore);
+
+// Higher Level
+static int readahead_index = 0, readahead_submit_index = 0;
+static int readahead_allocs = 0, readahead_frees = 0;
+
+static char * swapwriter_buffer = NULL;
+static int swapwriter_buffer_posn = 0;
+static int swapwriter_page_index = 0;
+static unsigned long * header_link = NULL;
+#define BYTES_PER_HEADER_PAGE (PAGE_SIZE - sizeof(swp_entry_t))
+
+/*
+ * ---------------------------------------------------------------
+ *
+ *     Internal Data Structures
+ *
+ * ---------------------------------------------------------------
+ */
+
+/* header_data contains data that is needed to reload pagedir1, and
+ * is therefore saved in the suspend header.
+ *
+ * Pagedir2 swap comes before pagedir1 swap (save order), and the first swap
+ * entry for pagedir1 to use is set when pagedir2 is written (when we know how
+ * much swap it used). Since this first entry is almost certainly not at the
+ * start of a range, the firstoffset variable below tells us where to start in
+ * the range. All of this means we don't have to worry about getting different
+ * compression ratios for the kernel and cache (when compressing the image).
+ * We can simply allocate one pool of swap (size determined using expected
+ * compression ratio) and use it without worrying whether one pageset
+ * compresses better and the other worse (this is what happens). As long as the
+ * user gets the expected compression right, it will work.
+ */
+
+struct {
+	/* Range chains for swap & blocks */
+	struct rangechain swapranges;
+	struct rangechain block_chain[MAX_SWAPFILES];
+	
+	/* Location of start of pagedir 1 */
+	struct range * pd1start_block_range;
+	unsigned long pd1start_block_offset;
+	int pd1start_chain;
+
+	/* Devices used for swap */
+	kdev_t swapdevs[MAX_SWAPFILES];
+	char blocksizes[MAX_SWAPFILES];
+
+	/* Asynchronous I/O limit */
+	int max_async_ios;
+} header_data;
+
+
+kdev_t header_device = 0;
+kdev_t header_block_device = 0;
+struct range * this_range_page = NULL, * next_range_page = NULL;
+int headerblocksize = PAGE_SIZE;
+int headerblock;
+
+/* For swapfile automatically swapon/off'd. */
+static char swapfilename[256] = "";
+extern asmlinkage long sys_swapon(const char * specialfile, int swap_flags);
+
+int suspend_swapon_status = 0;
+
+/* Must be silent - might be called from cat /proc/suspend/debug_info
+ * Returns 0 if was off, -EBUSY if was on, error value otherwise.
+ */
+static int enable_swapfile(void)
+{
+	int activateswapresult = -EINVAL;
+
+	if (suspend_swapon_status)
+		return 0;
+
+	if (swapfilename[0]) {
+		/* Attempt to swap on with maximum priority */
+		activateswapresult = sys_swapon(swapfilename, 0xFFFF);
+		if ((activateswapresult) && (activateswapresult != -EBUSY))
+			printk(name_suspend
+				"The swapfile/partition specified by "
+				"/proc/suspend/swapfile (%s) could not"
+				" be turned on (error %d). Attempting "
+				"to continue.\n",
+				swapfilename, activateswapresult);
+		if (!activateswapresult)
+			suspend_swapon_status = 1;
+	}
+	return activateswapresult;
+}
+
+extern asmlinkage long sys_swapoff(const char * specialfile);
+/* Returns 0 if was on, -EINVAL if was off, error value otherwise */
+static int disable_swapfile(void)
+{
+	int result = -EINVAL;
+	
+	if (!suspend_swapon_status)
+		return 0;
+
+	if (swapfilename[0]) {
+		result = sys_swapoff(swapfilename);
+		if (result == -EINVAL)
+	 		return 0;	/* Wasn't on */
+		if (!result)
+			suspend_swapon_status = 0;
+	}
+
+	return result;
+}
+
+static int manage_swapfile(int enable)
+{
+	static int result;
+	mm_segment_t	oldfs;
+
+	oldfs = get_fs(); set_fs(KERNEL_DS);
+	if (enable)
+		result = enable_swapfile();
+	else
+		result = disable_swapfile();
+	set_fs(oldfs);
+
+	return result;
+}
+
+/*
+ * ---------------------------------------------------------------
+ *
+ *     Current state.
+ *
+ * ---------------------------------------------------------------
+ */
+
+/* Which pagedir are we saving/reloading? Needed so we can know whether to
+ * remember the last swap entry used at the end of writing pageset2, and
+ * get that location when saving or reloading pageset1.*/
+static int current_stream = 0;
+
+/* Pointer to current swap entry being loaded/saved. */
+static struct range * currentblockrange = NULL;
+static unsigned long currentblockoffset = 0;
+static int currentblockchain = 0;
+static int currentblocksperpage = 0;
+
+/* Header Page Information */
+static int header_pages_allocated = 0;
+static struct submit_params * first_header_submit_info = NULL,
+ * last_header_submit_info = NULL, * current_header_submit_info = NULL;
+
+/*
+ * ---------------------------------------------------------------
+ *
+ *     User Specified Parameters
+ *
+ * ---------------------------------------------------------------
+ */
+
+static int resume_firstblock = 0;
+static int resume_firstblocksize = PAGE_SIZE;
+static kdev_t resume_device = 0;
+static kdev_t resume_block_device = 0;
+
+/*
+ * ---------------------------------------------------------------
+ *
+ *     Disk I/O routines
+ *
+ * ---------------------------------------------------------------
+ */
+extern char swapfilename[];
+
+extern int expected_compression;
+
+struct sysinfo swapinfo;
+
+#define MARK_SWAP_SUSPEND 0
+#define MARK_SWAP_RESUME 1
+
+static int swapwriter_invalidate_image(void);
+
+static int get_phys_params(swp_entry_t entry)
+{
+	int swapfilenum = SWP_TYPE(entry);
+	unsigned long offset;
+	int blocks[PAGE_SIZE/512];
+	kdev_t dev = 0;
+	int block_size;
+	struct inode *swapf = 0;
+	get_swaphandle_info(entry, &offset, &dev, &swapf);
+	if (dev) {
+		/* We are assuming this.
+		blocks_used = 1;
+		block_size = PAGE_SIZE;
+		*/
+		suspend_message(SUSPEND_BMAP,
+			SUSPEND_VERBOSE,
+			0,
+			"Add swap partition entry to chain: Swap address %lx,"
+			" chain %d, swapfile %x, block %d.\n",
+			entry.val,
+			swapfilenum,
+			header_data.swapdevs[swapfilenum],
+			offset);
+		add_to_range_chain(&header_data.block_chain[swapfilenum],
+				offset);
+	} else if (swapf) {
+		int i, j;
+		unsigned int block_num = offset
+			<< (PAGE_SHIFT - swapf->i_sb->s_blocksize_bits);
+		block_size = swapf->i_sb->s_blocksize;
+		for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size)
+			if (!(blocks[i] = bmap(swapf,block_num++))) {
+				printk("Invalid block for swap file. "
+					"Trying to map block %x on device %x.\n",
+					(swap_info[i].swap_device ?
+					 swap_info[i].swap_device :
+					 swap_info[i].swap_file->d_inode->i_dev),
+					block_num);
+				abort_suspend("get_phys_params: bad swap file");
+				return 0;
+			} else {
+				suspend_message(SUSPEND_BMAP,
+					SUSPEND_VERBOSE,
+					0,
+					"Add swapfile to chain: Swap address "
+					"%lx, chain %d, bdev %x, block "
+					"%d/%ld-> block %d.\n",
+					entry.val,
+					swapfilenum,
+					header_data.swapdevs[swapfilenum],
+					i+1,
+					PAGE_SIZE / block_size,
+					blocks[i]);
+				add_to_range_chain(
+					&header_data.block_chain[swapfilenum],
+					blocks[i]);
+			}
+	} else {
+		printk("Warning! Trying to get invalid physical parameters!"
+				" (Entry %lx).\n", entry.val);
+		return 0;
+	}
+	return 1;
+#if 0
+	int result = PAGE_SIZE /
+		suspend_bio_ops.get_block_size(swap_info[chain].bdev);
+	printk("Block size for chain %d is %d,\n",
+			chain, result);
+	return result;
+#endif
+}
+
+static int get_header_params(struct submit_params * headerpage)
+{
+	swp_entry_t entry = headerpage->swap_address;
+	int swapfilenum = SWP_TYPE(entry);
+	unsigned long offset;
+	int blocks[PAGE_SIZE/512];
+	kdev_t dev = 0;
+	int block_size;
+	struct inode *swapf = 0;
+	int i, j;
+	get_swaphandle_info(headerpage->swap_address, &offset, &dev, &swapf);
+	if (dev) {
+		headerpage->dev = dev;
+		headerpage->blocks[0] = offset;
+		headerpage->blocks_used = 1;
+		suspend_message(SUSPEND_BMAP, SUSPEND_VERBOSE, 1,
+			"Header entry: %lx -> %x:%x.",
+			headerpage->swap_address.val,
+			headerpage->dev,
+			headerpage->blocks[0]);
+		/* We are assuming this.
+		blocks_used = 1;
+		block_size = PAGE_SIZE;
+		*/
+	} else if (swapf) {
+		unsigned int block_num = offset
+			<< (PAGE_SHIFT - swapf->i_sb->s_blocksize_bits);
+		block_size = swapf->i_sb->s_blocksize;
+		headerpage->dev = swap_info[swapfilenum].swap_device ?
+					swap_info[swapfilenum].swap_device :
+					swap_info[swapfilenum].swap_file->d_inode->i_dev;
+		//printk("Swapfilenum is %d -> %d.\n", swapfilenum,
+		//	headerpage->dev);
+		headerpage->blocks_used = PAGE_SIZE / block_size;
+		for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size)
+			if (!(blocks[i] = bmap(swapf,block_num++))) {
+				abort_suspend("get_header_params: "
+						"bad swap file");
+				return -EFAULT;
+			} else {
+				headerpage->blocks[i] = blocks[i];
+				suspend_message(SUSPEND_BMAP, SUSPEND_VERBOSE, 1,
+					"Header entry: %lx -> %x:%x (%d/%d).",
+					headerpage->swap_address.val,
+					headerpage->dev,
+					blocks[i],
+					i+1,
+					headerpage->blocks_used);
+			}
+	} else {
+		printk("Warning! Trying to get invalid header params! "
+				"(Entry %lx).\n", headerpage->swap_address.val);
+		return -EFAULT;
+	}
+	return 0;
+}
+
+static inline int get_blocks_per_page(int chain)
+{
+	return PAGE_SIZE /
+	    suspend_bio_ops.get_block_size(swap_info[currentblockchain].swap_device);
+}
+
+static int try_to_parse_resume_device(char * commandline)
+{
+	resume_block_device = resume_device = name_to_kdev_t(commandline);
+
+	if (!resume_block_device) {
+		if (test_suspend_state(SUSPEND_BOOT_TIME))
+			suspend_early_boot_message(1, "Failed to translate the device name into a device id.\n");
+		else
+			printk(name_suspend "Failed to translate \"%s\" into a device id.\n", commandline);
+		return 1;
+	}
+	return 0;
+}
+
+static int try_to_parse_header_device(void)
+{
+	int blksize = 0;
+	header_block_device = header_device;
+
+	if (!blksize_size[MAJOR(header_block_device)]) {
+		printk(name_suspend "%x: Blocksize not known?\n",
+				header_block_device);
+	} else blksize = blksize_size[MAJOR(header_block_device)]
+		[MINOR(header_block_device)];
+	if (!blksize) {
+		printk(name_suspend "%x: Blocksize not set?\n",
+				header_block_device);
+		blksize = PAGE_SIZE;
+	}
+	suspend_message(SUSPEND_IO, SUSPEND_HIGH, 0,
+		"Header blocksize was %d.\n", blksize);
+	if (set_blocksize(header_block_device, headerblocksize)) {
+		if (suspend_early_boot_message(1, "Failed to get access to the "
+			"resume header device.\nYou could be booting "
+			"with a 2.6 kernel when you suspended a 2.4 "
+			"kernel."))
+			swapwriter_invalidate_image();
+
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void open_other_swap_device(int which, kdev_t thisdevice)
+{
+	int blksize = 512 * (int) header_data.blocksizes[which];
+	swap_info[which].swap_device = thisdevice;
+	
+	set_blocksize(thisdevice, blksize);
+}
+
+static inline char * get_path_for_swapfile(int which, char * path_page)
+{
+	return d_path(	swap_info[which].swap_file,
+			swap_info[which].swap_vfsmnt,
+			path_page,
+			PAGE_SIZE);
+}
+
+static void swapwriter_noresume_reset(void)
+{
+	int i;
+
+ 	/* 
+	 * If we have read part of the image, we might have filled header_data with
+	 * data that should be zeroed out.
+	 */
+
+	memset((char *) &header_data, 0, sizeof(header_data));
+	for (i = 0; i < MAX_SWAPFILES; i++) {
+		swap_info[i].swap_device = 0;
+	}
+	 
+}
+
+/*
+ *
+ */
+
+int parse_signature(char * header, int restore)
+{
+	int type = -1;
+	
+	if (!memcmp("SWAP-SPACE",header,10))
+		return 0;
+	else if (!memcmp("SWAPSPACE2",header,10))
+		return 1;
+
+	else if (!memcmp("pmdisk", header,6))
+		type = 2;
+	
+	else if (!memcmp("S1SUSP",header,6))
+		type = 4;
+	else if (!memcmp("S2SUSP",header,6))
+		type = 5;
+	
+	else if (!memcmp("1R",header,2))
+		type = 6;
+	else if (!memcmp("2R",header,2))
+		type = 7;
+	
+	else if (!memcmp("std",header,3))
+		type = 8;
+	else if (!memcmp("STD",header,3))
+		type = 9;
+	
+	else if (!memcmp("sd",header,2))
+		type = 10;
+	else if (!memcmp("SD",header,2))
+		type = 11;
+	
+	else if (!memcmp("z",header,1))
+		type = 12;
+	else if (!memcmp("Z",header,1))
+		type = 13;
+	
+	/* 
+	 * Put bdev of suspend header in last byte of swap header
+	 * (unsigned short)
+	 */
+	if (type > 11) {
+		kdev_t * header_ptr = (kdev_t *) &header[1];
+		unsigned char * headerblocksize_ptr =
+			(unsigned char *) &header[5];
+		unsigned long * headerblock_ptr = (unsigned long *) &header[6];
+		header_device = *header_ptr;
+		/* 
+		 * We are now using the highest bit of the char to indicate
+		 * whether we have attempted to resume from this image before.
+		 */
+		clear_suspend_state(SUSPEND_RESUMED_BEFORE);
+		if (((int) *headerblocksize_ptr) & 0x80)
+			set_suspend_state(SUSPEND_RESUMED_BEFORE);
+		headerblocksize = 512 * (((int) *headerblocksize_ptr) & 0xf);
+		headerblock = *headerblock_ptr;
+	}
+
+	if ((restore) && (type > 5)) {
+		/* We only reset our own signatures */
+		if (type & 1)
+			memcpy(header,"SWAPSPACE2",10);
+		else
+			memcpy(header,"SWAP-SPACE",10);
+	}
+
+	return type;
+}
+
+/*
+ * prepare_signature
+ */
+
+static int prepare_signature(struct submit_params * header_page_info,
+		char * current_header)
+{
+	int current_type = parse_signature(current_header, 0);
+	kdev_t * header_ptr = (kdev_t *) (&current_header[1]);
+	unsigned char * headerblocksize_ptr =
+		(unsigned char *) (&current_header[5]);
+	unsigned long * headerblock_ptr =
+		(unsigned long *) (&current_header[6]);
+
+	if ((current_type > 1) && (current_type < 6))
+		return 1;
+
+	if (current_type & 1)
+		current_header[0] = 'Z';
+	else
+		current_header[0] = 'z';
+	*header_ptr = header_page_info->dev;
+	*headerblocksize_ptr =
+		(unsigned char) (PAGE_SIZE / 512 / 
+				 header_page_info->blocks_used);
+	/* prev is the first/last swap page of the resume area */
+	*headerblock_ptr = (unsigned long) header_page_info->blocks[0]; 
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 1,
+			"Saving header block size of %ld (%ld 512 "
+			"byte blocks per page).\n",
+			PAGE_SIZE / header_page_info->blocks_used,
+			PAGE_SIZE / 512 / header_page_info->blocks_used);
+	return 0;
+}
+
+extern int signature_check(char * header, int fix);
+
+static int free_swap_pages_for_header(void)
+{
+	if (!first_header_submit_info)
+		return 1;
+
+	PRINTFREEMEM("at start of free_swap_pages_for_header");
+
+	while (first_header_submit_info) {
+		struct submit_params * next = first_header_submit_info->next;
+		if (first_header_submit_info->swap_address.val)
+			swap_free(first_header_submit_info->swap_address);
+		kfree(first_header_submit_info);
+		first_header_submit_info = next;
+	}
+	
+	suspend_message(SUSPEND_WRITER, SUSPEND_LOW, 1,
+			" Freed %d swap pages in free_swap_pages_for_header.\n",
+			header_pages_allocated);
+	first_header_submit_info = last_header_submit_info = NULL;
+	header_pages_allocated = 0;
+	PRINTFREEMEM("at end of free_swap_pages_for_header");
+	suspend_store_free_mem(SUSPEND_FREE_HEADER_STORAGE, 1);
+	return 0;
+}
+
+static void get_main_pool_phys_params(void)
+{
+	struct range * rangepointer = NULL;
+	unsigned long address;
+	int i;
+	
+	for (i = 0; i < MAX_SWAPFILES; i++)
+		if (header_data.block_chain[i].first)
+			put_range_chain(&header_data.block_chain[i]);
+
+	range_for_each(&header_data.swapranges, rangepointer, address)
+		get_phys_params(range_val_to_swap_entry(address));
+}
+
+extern void put_range(struct range * range);
+
+static unsigned long swapwriter_storage_allocated(void)
+{
+	return (header_data.swapranges.size + header_pages_allocated);
+}
+
+static long swapwriter_storage_available(void)
+{
+	si_swapinfo(&swapinfo);
+	return (swapinfo.freeswap + (long) swapwriter_storage_allocated());
+}
+
+static int swapwriter_initialise(void)
+{
+	manage_swapfile(1);
+	return 0;
+}
+
+static void swapwriter_cleanup(void)
+{
+	manage_swapfile(0);
+}
+
+static int swapwriter_release_storage(void)
+{
+	int i = 0, swapcount = 0;
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE
+	if ((TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) && test_suspend_state(SUSPEND_NOW_RESUMING))
+		return 0;
+#endif
+
+	free_swap_pages_for_header();
+	
+	if (header_data.swapranges.first) {
+		/* Free swap entries */
+		struct range * rangepointer;
+		unsigned long rangevalue;
+		swp_entry_t entry;
+		range_for_each(&header_data.swapranges, rangepointer, 
+				rangevalue) {
+			entry = range_val_to_swap_entry(rangevalue);
+			swap_free(entry);
+
+			swapcount++;
+			check_shift_keys(0, NULL);
+		}
+		put_range_chain(&header_data.swapranges);
+		
+		for (i = 0; i < MAX_SWAPFILES; i++)
+			if (header_data.block_chain[i].first)
+				put_range_chain(&header_data.block_chain[i]);
+	}
+	
+	suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+			"Freed %d swap pages in free_swap.\n", swapcount);
+
+	return 0;
+}
+
+static long swapwriter_allocate_header_space(unsigned long space_really_requested)
+{
+	/* space_requested was going to be in bytes... not yet */
+	int i, ret = 0;
+	unsigned long space_requested;
+
+	/* 
+	 * Up to here in the process, we haven't taken account of the fact
+	 * that we need an extra four bytes per 4092 bytes written for link
+	 * to the next page on which the header will be written. We factor
+	 * that in here.
+	 */
+	space_requested = ((4096 * space_really_requested + 4091) / 4092);
+	space_requested = (space_requested * 4 + 4091) / 4092;
+	space_requested += space_really_requested;
+	
+	PRINTFREEMEM("at start of allocate_header_space");
+	
+	for (i=(header_pages_allocated+1); i<=space_requested; i++) {
+		struct submit_params * new_submit_param;
+		
+		/* Get a submit structure */
+		new_submit_param = kmalloc(sizeof(struct submit_params), GFP_ATOMIC);
+		
+		if (!new_submit_param) {
+			header_pages_allocated = i - 1;
+			printk("Failed to kmalloc a struct submit param.\n");
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		memset(new_submit_param, 0, sizeof(struct submit_params));
+
+		if (last_header_submit_info) {
+			last_header_submit_info->next = new_submit_param;
+			last_header_submit_info = new_submit_param;
+		} else
+			last_header_submit_info = first_header_submit_info =
+				new_submit_param;
+
+		/* Get swap entry */
+		new_submit_param->swap_address = get_swap_page();
+		new_submit_param->readahead_index = -1;
+		
+		if ((!new_submit_param->swap_address.val) &&
+			       (header_data.swapranges.first)) {
+			/*
+			 *  Steal one from pageset swap chain. If, as a result,
+			 *  it is too small, more swap will be allocated or
+			 *  memory eaten.
+			 */
+
+			new_submit_param->swap_address =
+				range_val_to_swap_entry(
+					header_data.swapranges.first->minimum);
+			if (header_data.swapranges.first->minimum <
+					header_data.swapranges.first->maximum)
+				header_data.swapranges.first->minimum++;
+			else {
+				struct range * oldfirst =
+					header_data.swapranges.first;
+				header_data.swapranges.first = oldfirst->next;
+				header_data.swapranges.frees++;
+				header_data.swapranges.prevtoprev =
+				 header_data.swapranges.prevtolastaccessed =
+				 header_data.swapranges.lastaccessed = NULL;
+				if (header_data.swapranges.last == oldfirst)
+					header_data.swapranges.last = NULL;
+				put_range(oldfirst);
+			}
+			
+			header_data.swapranges.size--;
+
+			/*
+			 * Recalculate block chains for main pool.
+			 * We don't assume blocks are at start of a chain and
+			 * don't know how many blocks per swap entry.
+			 */
+			get_main_pool_phys_params();
+		}
+		if (!new_submit_param->swap_address.val) {
+			free_swap_pages_for_header();
+			printk("Unable to allocate swap page for header.\n");
+			ret = -ENOMEM;
+			goto out;
+		}
+		if (get_header_params(new_submit_param)) {
+			printk("Failed to get header parameters.\n");
+			ret = -EFAULT;
+			goto out;
+		}
+		suspend_message(SUSPEND_HEADER, SUSPEND_MEDIUM, 0,
+			" Got header page %d/%d. Dev is %x. Block is %lu. "
+			"Blocksperpage is %d.\n",
+			i, space_requested,
+			new_submit_param->dev,
+			new_submit_param->blocks[0],
+			new_submit_param->blocks_used);
+	}
+	header_pages_allocated = space_requested;
+	suspend_message(SUSPEND_HEADER, SUSPEND_LOW, 1,
+			" Have %d swap pages in swapwriter::"
+			"allocate_header_space.\n",
+			header_pages_allocated);
+out:
+	PRINTFREEMEM("at end of swapwriter::allocate_header_space");
+	suspend_store_free_mem(SUSPEND_FREE_HEADER_STORAGE, 0);
+	return ret;
+}
+
+static int swapwriter_allocate_storage(unsigned long space_requested)
+{
+	int i, swapcount = 0, result = 0;
+	int lastsize = header_data.swapranges.size;
+	int numwanted = (int) (space_requested);
+	int pages_to_get = numwanted - header_data.swapranges.size;
+	
+	if (numwanted < 1)
+		return 0;
+
+	suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+		"Started with swapranges.size == %d. "
+		"Seeking to allocate %d more.\n",
+		header_data.swapranges.size,
+		pages_to_get);
+
+	for(i=0; i < pages_to_get; i++) {
+		swp_entry_t entry;
+		suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 1, "");
+		entry = get_swap_page();
+		if (!entry.val) {
+			suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+				"Allocated %d/%d swap pages for main pool "
+				"in allocate_swap.\n",
+				swapcount, numwanted);
+			printk("Unable to allocate enough swap."
+				" Got %d pages of %d wanted.\n",
+				i, pages_to_get);
+			result = -ENOSPC;
+			goto out;
+		}
+		swapcount++;
+		{
+			int result =
+				add_to_range_chain(&header_data.swapranges,
+					       swap_entry_to_range_val(entry));
+			if (result)
+				printk("add_to_range_chain returned %d.\n",
+						result);
+		}
+		if (header_data.swapranges.size != (lastsize + 1))
+			printk("swapranges.size == %d.\n",
+					header_data.swapranges.size);
+		lastsize = header_data.swapranges.size;
+		check_shift_keys(0, NULL);
+		if (TEST_RESULT_STATE(SUSPEND_ABORTED))
+			break;
+	}
+	suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+		" Allocated %d/%d swap pages in allocate_swap.\n",
+		swapcount, numwanted);
+	
+	suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+		"Finished with swapranges.size == %d.\n",
+		header_data.swapranges.size);
+
+out:
+	get_main_pool_phys_params();
+	
+	/* Any memory we allocate will be for range pages */
+	suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0);
+	return result;
+}
+
+static int swapwriter_write_header_chunk(char * buffer, int buffer_size);
+static int header_bytes_written;
+
+static int swapwriter_write_header_init(void)
+{
+	int i;
+
+	header_bytes_written = 0;
+
+	for (i = 0; i < MAX_SWAPFILES; i++)
+		if (swap_info[i].swap_file) {
+			header_data.swapdevs[i] = swap_info[i].swap_device;
+			header_data.blocksizes[i] =
+				block_size(swap_info[i].swap_device);
+		}
+
+	header_data.max_async_ios = max_async_ios;
+
+	swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+	header_link =
+		(unsigned long *) (swapwriter_buffer + BYTES_PER_HEADER_PAGE);
+	swapwriter_page_index = 1;
+
+	current_header_submit_info = first_header_submit_info;
+	
+	/* Info needed to bootstrap goes at the start of the header.
+	 * First we save the 'header_data' struct, including the number
+	 * of header pages. Then we save the structs containing data needed
+	 * for reading the header pages back.
+	 * Note that even if header pages take more than one page, when we
+	 * read back the info, we will have restored the location of the
+	 * next header page by the time we go to use it.
+	 */
+	swapwriter_write_header_chunk((char *) &header_data, 
+			sizeof(header_data));
+
+	return 0;
+}
+
+static int swapwriter_write_header_chunk(char * buffer, int buffer_size)
+{
+	int bytes_left = buffer_size;
+	
+	/* 
+	 * We buffer the writes until a page is full and to use the last
+	 * sizeof(swp_entry_t) bytes for links between pages. This is 
+	 * totally transparent to the caller.
+	 *
+	 * Note also that buffer_size can be > PAGE_SIZE.
+	 */
+
+	header_bytes_written += buffer_size;
+	
+	suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+		"\nStart of write_header_chunk loop with %d bytes to store.\n",
+		buffer_size);
+
+	while (bytes_left) {
+		char * source_start = buffer + buffer_size - bytes_left;
+		char * dest_start = swapwriter_buffer + swapwriter_buffer_posn;
+		int dest_capacity = BYTES_PER_HEADER_PAGE - swapwriter_buffer_posn;
+		swp_entry_t next_header_page;
+		if (bytes_left <= dest_capacity) {
+			suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+				"Storing %d bytes from %p-%p in page %d, %p-%p.\n",
+				bytes_left,
+				source_start, source_start + bytes_left - 1,
+				swapwriter_page_index,
+				dest_start, dest_start + bytes_left - 1);
+			memcpy(dest_start, source_start, bytes_left);
+			swapwriter_buffer_posn += bytes_left;
+			return 0;
+		}
+	
+		/* A page is full */
+		suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+			"Storing %d bytes from %p-%p in page %d, %p-%p.\n",
+			dest_capacity,
+			source_start, source_start + dest_capacity - 1,
+			swapwriter_page_index,
+			dest_start, dest_start + dest_capacity - 1);
+		memcpy(dest_start, source_start, dest_capacity);
+		bytes_left -= dest_capacity;
+
+		BUG_ON(!current_header_submit_info);
+
+		if (!current_header_submit_info->next) {
+			suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+				"This submit_info is the last one. Link zeroed.\n");
+			*header_link = 0;
+		} else {
+			next_header_page =
+				SWP_ENTRY(SWP_TYPE(
+				current_header_submit_info->next->swap_address),
+				current_header_submit_info->next->blocks[0]);
+
+			*header_link = next_header_page.val;
+		
+			suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+				"Header link is at %p. "
+				"Contents set to swap device #%ld, block %ld.\n",
+				header_link,
+				(long) SWP_TYPE(next_header_page),
+				SWP_OFFSET(next_header_page));
+		}
+
+		suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+			"Writing header page %d/%d. "
+			"Dev is %x. Block is %lu. Blocksperpage is %d.\n",
+			swapwriter_page_index, header_pages_allocated,
+			current_header_submit_info->dev,
+			current_header_submit_info->blocks[0],
+			current_header_submit_info->blocks_used);
+		
+		current_header_submit_info->page =
+			virt_to_page(swapwriter_buffer);
+		check_shift_keys(0, NULL);
+		suspend_bio_ops.submit_io(WRITE, current_header_submit_info, 0);
+
+		swapwriter_buffer_posn = 0;
+		swapwriter_page_index++;
+		current_header_submit_info = current_header_submit_info->next;
+	}
+
+	return 0;
+}
+
+static int swapwriter_write_header_cleanup(void)
+{
+	suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+		"Write header cleanup.\n");
+
+	/* Write any unsaved data */
+	if (swapwriter_buffer_posn) {
+		*header_link = 0;
+
+		suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+			"Writing header page %d/%d. "
+			"Dev is %x. Block is %lu. Blocksperpage is %d.\n",
+			swapwriter_page_index, header_pages_allocated,
+			current_header_submit_info->dev,
+			current_header_submit_info->blocks[0],
+			current_header_submit_info->blocks_used);
+		
+		current_header_submit_info->page =
+			virt_to_page(swapwriter_buffer);
+		suspend_bio_ops.submit_io(WRITE, 
+				current_header_submit_info, 0);
+	}
+
+	suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+		"Read swap header. Device %lx. Block %lx\n",
+		resume_block_device, resume_firstblock);
+	
+	/* Adjust swap header */
+	suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock,
+			virt_to_page(swapwriter_buffer));
+
+	prepare_signature(first_header_submit_info,
+		((union swap_header *) swapwriter_buffer)->magic.magic);
+		
+	suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+		"Write swap header.\n");
+
+	suspend_bio_ops.bdev_page_io(WRITE, resume_block_device, resume_firstblock,
+			virt_to_page(swapwriter_buffer));
+
+	free_pages((unsigned long) swapwriter_buffer, 0);
+	swapwriter_buffer = NULL;
+	header_link = NULL;
+	
+	suspend_message(SUSPEND_HEADER, SUSPEND_HIGH, 0,
+		"Finish all I/O.\n");
+
+	suspend_bio_ops.finish_all_io();
+
+	return 0;
+}
+
+/* ------------------------- HEADER READING ------------------------- */
+
+/*
+ * read_header_init()
+ * 
+ * Description:
+ * 1. Attempt to read the device specified with resume2=.
+ * 2. Check the contents of the swap header for our signature.
+ * 3. Warn, ignore, reset and/or continue as appropriate.
+ * 4. If continuing, read the swapwriter configuration section
+ *    of the header and set up block device info so we can read
+ *    the rest of the header & image.
+ *
+ * Returns:
+ * May not return if user choose to reboot at a warning.
+ * -EINVAL if cannot resume at this time. Booting should continue
+ * normally.
+ */
+
+static int swapwriter_read_header_init(void)
+{
+	int i;
+	
+	swapwriter_page_index = 1;
+
+	swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC);
+
+	if (!header_device) {
+		printk("read_header_init called when we haven't "
+				"verified there is an image!\n");
+		return -EINVAL;
+	}
+
+	/* 
+	 * If the header is not on the resume_device, get the resume device first.
+	 */
+	if (header_device != resume_device) {
+		int result = try_to_parse_header_device();
+
+		if (result)
+			return result;
+	} else
+		header_block_device = resume_block_device;
+
+	/* Read swapwriter configuration */
+	suspend_bio_ops.bdev_page_io(READ, header_block_device, headerblock,
+			virt_to_page((unsigned long) swapwriter_buffer));
+	//FIXME Remember location of next page to be read.
+	
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		"Retrieving %d bytes from %x:%x to page %d, %p-%p.\n",
+		header_block_device, headerblock,
+		sizeof(header_data),
+		swapwriter_page_index,
+		swapwriter_buffer, swapwriter_buffer + sizeof(header_data) - 1);
+	memcpy(&header_data, swapwriter_buffer, sizeof(header_data));
+	
+	/* Restore device info */
+	for (i = 0; i < MAX_SWAPFILES; i++) {
+		kdev_t thisdevice = header_data.swapdevs[i];
+		
+		suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 1,
+				"Swap device %d is %x.", i, thisdevice);
+		
+		if (!thisdevice)
+			continue;
+
+		if (thisdevice == resume_device) {
+			suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 0,
+					"Resume root device %x", thisdevice);
+			swap_info[i].swap_device = resume_block_device;
+			/* Mark as used so the device doesn't get suspended. */
+			swap_info[i].swap_file = (struct dentry *) 0xffffff;
+			continue;
+		}
+
+		if (thisdevice == header_device) {
+			suspend_message(SUSPEND_WRITER, SUSPEND_VERBOSE, 0,
+					"Resume header device %x", thisdevice);
+			swap_info[i].swap_device = header_block_device;
+			/* Mark as used so the device doesn't get suspended. */
+			swap_info[i].swap_file = (struct dentry *) 0xffffff;
+			continue;
+		}
+
+		open_other_swap_device(i, thisdevice);
+		swap_info[i].swap_file = (struct dentry *) 0xffffff;
+	}
+
+	max_async_ios =	header_data.max_async_ios;
+
+	swapwriter_buffer_posn = sizeof(header_data);
+
+	return 0;
+}
+
+static int swapwriter_read_header_chunk(char * buffer, int buffer_size)
+{
+	int bytes_left = buffer_size, ret = 0;
+	
+	check_shift_keys(0, "");
+
+	/* Read a chunk of the header */
+	while ((bytes_left) && (!ret)) {
+		swp_entry_t next =
+		   ((union p_diskpage) swapwriter_buffer).pointer->link.next;
+		kdev_t dev = swap_info[SWP_TYPE(next)].swap_device;
+		int pos = SWP_OFFSET(next);
+		char * dest_start = buffer + buffer_size - bytes_left;
+		char * source_start =
+			swapwriter_buffer + swapwriter_buffer_posn;
+		int source_capacity =
+			BYTES_PER_HEADER_PAGE - swapwriter_buffer_posn;
+
+		if (bytes_left <= source_capacity) {
+			suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+				"Retrieving %d bytes from page %d, "
+				"%p-%p into %p-%p.\n",
+				bytes_left,
+				swapwriter_page_index,
+				source_start, source_start + bytes_left - 1,
+				dest_start, dest_start + bytes_left - 1);
+			memcpy(dest_start, source_start, bytes_left);
+			swapwriter_buffer_posn += bytes_left;
+			return buffer_size;
+		}
+
+		/* Next to read the next page */
+		suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+			"Retrieving %d bytes from page %d, %p-%p to %p-%p.\n",
+			source_capacity,
+			swapwriter_page_index,
+			source_start, source_start + source_capacity - 1,
+			dest_start, dest_start + source_capacity - 1);
+		memcpy(dest_start, source_start, source_capacity);
+		bytes_left -= source_capacity;
+
+		suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		 "Header link is at %p. Contents set to %lx = "
+		 "swap device #%x, block %d.\n",
+		 &((union p_diskpage) swapwriter_buffer).pointer->link.next,
+		 ((union p_diskpage) swapwriter_buffer).pointer->link.next.val,
+		 dev, pos);
+
+		swapwriter_page_index++;
+
+		suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+			"Reading header page %d. Dev is %x. Block is %lu.\n",
+			swapwriter_page_index, dev, pos);
+		
+		suspend_bio_ops.bdev_page_io(READ, dev, pos, virt_to_page(swapwriter_buffer));
+
+		swapwriter_buffer_posn = 0;
+	}
+
+	return buffer_size - bytes_left;
+}
+
+static int swapwriter_read_header_cleanup(void)
+{
+	free_pages((unsigned long) swapwriter_buffer, 0);
+	return 0;
+}
+
+static int swapwriter_prepare_save_ranges(void)
+{
+	int i;
+
+	relativise_chain(&header_data.swapranges);
+	
+	for (i = 0; i < MAX_SWAPFILES; i++)
+		relativise_chain(&header_data.block_chain[i]);
+	
+	header_data.pd1start_block_range =
+		RANGE_RELATIVE(header_data.pd1start_block_range);
+
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		"Pagedir1 firstblockrange is %p.\n",
+		header_data.pd1start_block_range);
+
+	return 0;
+}
+
+static int swapwriter_post_load_ranges(void)
+{
+	int i;
+	
+	if (get_rangepages_list())
+		return -ENOMEM;
+	
+	absolutise_chain(&header_data.swapranges);
+
+	for (i = 0; i < MAX_SWAPFILES; i++)
+		absolutise_chain(&header_data.block_chain[i]);
+
+	header_data.pd1start_block_range =
+		RANGE_ABSOLUTE(header_data.pd1start_block_range);
+	
+	return 0;
+}
+
+static int swapwriter_write_init(int stream_number)
+{
+	if (stream_number == 1) {
+		currentblockrange = header_data.pd1start_block_range;
+		currentblockoffset = header_data.pd1start_block_offset;
+		currentblockchain = header_data.pd1start_chain;
+	} else
+		for (currentblockchain = 0; currentblockchain < MAX_SWAPFILES;
+				currentblockchain++)
+			if (header_data.block_chain[currentblockchain].first) {
+				currentblockrange =
+					header_data.
+					 block_chain[currentblockchain].first;
+				currentblockoffset = currentblockrange->minimum;
+				break;
+			}
+
+	BUG_ON(!currentblockrange);
+
+	currentblocksperpage = PAGE_SIZE / 
+		suspend_bio_ops.get_block_size(swap_info[currentblockchain].swap_device);
+
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		"Stream %d beginning from position: chain %d. "
+		"range %p, block %ld.\n",
+		stream_number,
+		currentblockchain, currentblockrange, currentblockoffset);
+	
+	swapwriter_page_index = 1;
+	current_stream = stream_number;
+
+	suspend_bio_ops.reset_io_stats();
+
+	return 0;
+}
+
+static int swapwriter_write_chunk(struct page * buffer_page)
+{
+	int i;
+	struct submit_params submit_params;
+
+	if (TEST_ACTION_STATE(SUSPEND_TEST_FILTER_SPEED))
+		return 0;
+		
+	if (currentblockchain == MAX_SWAPFILES) {
+		printk("Error! We have run out of blocks for writing data.\n");
+		for (i = 0; i < MAX_SWAPFILES; i++) {
+			if (!swap_info[i].swap_file)
+				printk("Swap slot %d is unused.\n", i);
+			else
+				printk("Swap slot %d is device %x.\n",
+						i, swap_info[i].swap_device);
+			if (header_data.block_chain[i].size)
+				printk("Chain size for device %d is %d.\n", i,
+					header_data.block_chain[i].size);
+		}
+		return -ENOSPC;
+	}
+	
+	if (!currentblockrange) {
+		do {
+			currentblockchain++;
+		} while ((currentblockchain < MAX_SWAPFILES) &&
+		   (!header_data.block_chain[currentblockchain].first));
+
+		/* We can validly not have a new blockrange. We
+		 * might be compressing data and the user was
+		 * too optimistic in setting the compression
+		 * ratio or we're just copying the pageset. */
+
+		if (currentblockchain == MAX_SWAPFILES) {
+			printk("Argh. Ran out of block chains.\n");
+			return -ENOSPC;
+		}
+				
+		currentblockrange = 
+		 header_data.block_chain[currentblockchain].first;
+		currentblockoffset = currentblockrange->minimum;
+		currentblocksperpage = PAGE_SIZE /
+		  suspend_bio_ops.get_block_size(swap_info[currentblockchain].swap_device);
+	}
+
+	submit_params.readahead_index = -1;
+	submit_params.page = buffer_page;
+	submit_params.dev = swap_info[currentblockchain].swap_device;
+	submit_params.blocks_used = currentblocksperpage;
+		
+	/* Get the blocks */
+	for (i = 0; i < currentblocksperpage; i++) {
+		submit_params.blocks[i] = currentblockoffset;
+		GET_RANGE_NEXT(currentblockrange, currentblockoffset);
+	}
+
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		"Writing page %d. Dev is %x. Block is %lu. "
+		"Blocksperpage is %d.\n",
+		swapwriter_page_index,
+		submit_params.dev,
+		submit_params.blocks[0],
+		currentblocksperpage);
+		
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+		"page:%d. bdev:%x. blocks (%d):",
+		swapwriter_page_index,
+		submit_params.dev,
+		submit_params.blocks_used);
+
+	for (i = 0; i < currentblocksperpage; i++)
+		suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0,
+			"0x%lx%s",
+			submit_params.blocks[i],
+			((i+1) < currentblocksperpage) ? "," : "\n");
+
+	check_shift_keys(0, NULL);
+
+	suspend_bio_ops.submit_io(WRITE, &submit_params, 0);
+
+	swapwriter_page_index++;
+
+	return 0;
+}
+
+static int swapwriter_write_cleanup(void)
+{
+	if (current_stream == 2) {
+		header_data.pd1start_block_range = currentblockrange;
+		header_data.pd1start_block_offset = currentblockoffset;
+		header_data.pd1start_chain = currentblockchain;
+	}
+	
+	suspend_bio_ops.finish_all_io();
+	
+	suspend_bio_ops.check_io_stats();
+
+	return 0;
+}
+
+static int swapwriter_read_init(int stream_number)
+{
+	int i;
+
+	if (stream_number == 1) {
+		currentblockrange = header_data.pd1start_block_range;
+		currentblockoffset = header_data.pd1start_block_offset;
+		currentblockchain = header_data.pd1start_chain;
+	} else {
+		currentblockrange = NULL;
+		currentblockoffset = 0;
+		currentblockchain = 0;
+		for (currentblockchain = 0; currentblockchain < MAX_SWAPFILES;
+			       currentblockchain++)
+			if (header_data.block_chain[currentblockchain].first) {
+				currentblockrange =
+					header_data.block_chain[currentblockchain].first;
+				currentblockoffset = currentblockrange->minimum;
+				break;
+			}
+
+		if (!currentblockrange){
+			printk("Error! Can't find any block chain data.\n");
+			for (i = 0; i < MAX_SWAPFILES; i++) {
+				if (!swap_info[i].swap_file)
+					printk("Swap slot %d is unused.\n", i);
+				else
+					printk("Swap slot %d is device %x.\n",
+							i, swap_info[i].swap_device);
+				if (header_data.block_chain[i].size)
+					printk("Chain size for device %d"
+							" is %d.\n", i,
+					 header_data.block_chain[i].size);
+				printk("First entry in chain at %p.\n",
+					header_data.block_chain[i].first);
+			}
+			BUG_ON(1);
+		}
+	}
+	suspend_message(SUSPEND_WRITER, SUSPEND_MEDIUM, 0,
+		"Stream %d beginning from position: chain %d. "
+			"range %p, block %ld.\n",
+		stream_number,
+		currentblockchain, currentblockrange, currentblockoffset);
+	
+	currentblocksperpage = get_blocks_per_page(currentblockchain);
+
+	swapwriter_page_index = 1;
+
+	suspend_bio_ops.reset_io_stats();
+
+	readahead_index = readahead_submit_index = -1;
+	readahead_allocs = readahead_frees = 0;
+
+	return 0;
+}
+
+static int swapwriter_begin_read_chunk(struct page * page, 
+		int readahead_index, int sync)
+{
+	int i;
+	struct submit_params submit_params;
+
+	if (currentblockchain == MAX_SWAPFILES) {
+		/* Readahead might ask us to read too many blocks */
+		printk("Currentblockchain == MAX_SWAPFILES. Begin_read_chunk returning -ENODATA.\n");
+		return -ENODATA;
+	}
+
+	if (!currentblockrange) {
+		do {
+			currentblockchain++;
+		} while ((!header_data.block_chain[currentblockchain].first) &&
+				(currentblockchain < MAX_SWAPFILES));
+
+		/* We can validly not have a new blockrange. We
+		 * might have allocated exactly the right amount
+		 * of swap for the image and be reading the last
+		 * block now.
+		 */
+
+		if (currentblockchain == MAX_SWAPFILES) {
+			prepare_status(1, 0,
+			  "Currentblockchain == MAX_SWAPFILES and "
+			  "more data to be read. "
+			  "Begin_read_chunk returning -ENOSPC.");
+			return -ENOSPC;
+		}
+				
+		currentblockrange =
+		  header_data.block_chain[currentblockchain].first;
+		currentblockoffset = currentblockrange->minimum;
+		currentblocksperpage = get_blocks_per_page(currentblockchain);
+	}
+	
+	submit_params.readahead_index = readahead_index;
+	submit_params.page = page;
+	submit_params.dev = swap_info[currentblockchain].swap_device;
+	submit_params.blocks_used = currentblocksperpage;
+		
+	/* Get the blocks. There is no chance that they span chains. */
+	for (i = 0; i < currentblocksperpage; i++) {
+		submit_params.blocks[i] = currentblockoffset;
+		GET_RANGE_NEXT(currentblockrange, currentblockoffset);
+	}
+
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		"Reading page %d. Dev is %x. Block is %lu. "
+		"Blocksperpage is %d. Page is %p(%lx). Readahead index is %d.",
+		swapwriter_page_index,
+		submit_params.dev,
+		submit_params.blocks[0],
+		currentblocksperpage, 
+		page, page_address(page),
+		readahead_index);
+		
+	suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1,
+		"page:%d. bdev:%x. blocks (%d):",
+		swapwriter_page_index,
+		submit_params.dev,
+		submit_params.blocks_used);
+
+	for (i = 0; i < currentblocksperpage; i++)
+		suspend_message(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0,
+			"0x%lx%s",
+			submit_params.blocks[i],
+			((i+1) < currentblocksperpage) ? "," : "\n");
+
+	check_shift_keys(0, NULL);
+
+	if ((i = suspend_bio_ops.submit_io(READ, &submit_params, sync)))
+		return -EPERM;
+
+	swapwriter_page_index++;
+
+	check_shift_keys(0, NULL);
+
+	return 0;
+}
+
+/* Note that we ignore the sync parameter. We are implementing
+ * read ahead, and will always wait until our readhead buffer has
+ * been read before returning.
+ */
+
+static int swapwriter_read_chunk(struct page * buffer_page, int sync)
+{
+	static int last_result;
+	unsigned long * virt;
+
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+			"At entrance to swapwriter_read_chunk.\n");
+
+	if (sync == SUSPEND_ASYNC)
+		return swapwriter_begin_read_chunk(buffer_page, -1, sync);
+
+	/* Start new readahead while we wait for our page */
+	if (readahead_index == -1) {
+		last_result = 0;
+		readahead_index = readahead_submit_index = 0;
+	}
+
+	/* Start a new readahead? */
+	if (last_result) {
+		/* We failed to submit a read, and have cleaned up
+		 * all the readahead previously submitted */
+		if (readahead_submit_index == readahead_index)
+			return -EPERM;
+		goto wait;
+	}
+	
+	do {
+		if ((test_suspend_state(SUSPEND_USE_MEMORY_POOL)) && (suspend_memory_pool_level(1) < 50))
+			break;
+
+		if (suspend_bio_ops.prepare_readahead(readahead_submit_index))
+			break;
+
+ 		readahead_allocs++;
+
+		suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+			"\nBeginning new readahead %d.\n",
+			readahead_submit_index);
+
+		last_result = swapwriter_begin_read_chunk(
+			suspend_bio_ops.readahead_pages[readahead_submit_index], 
+			readahead_submit_index, SUSPEND_ASYNC);
+		if (last_result) {
+			printk("Begin read chunk for page %d returned %d.\n",
+				readahead_submit_index, last_result);
+			suspend_bio_ops.cleanup_readahead(readahead_submit_index);
+			break;
+		}
+
+		readahead_submit_index++;
+
+		if (readahead_submit_index == max_async_ios)
+			readahead_submit_index = 0;
+
+	} while((!last_result) && (readahead_submit_index != readahead_index) &&
+			(!suspend_bio_ops.readahead_ready(readahead_index)));
+
+wait:
+	suspend_bio_ops.wait_on_readahead(readahead_index);
+
+	virt = kmap_atomic(buffer_page, KM_USER1);
+	suspend_message(SUSPEND_WRITER, SUSPEND_HIGH, 0,
+		"Returned result of readahead %d,"
+		" Copying data from %p to %p.\n", readahead_index,
+		page_address(suspend_bio_ops.readahead_pages[readahead_index]),
+		virt);
+
+	memcpy(virt, page_address(suspend_bio_ops.readahead_pages[readahead_index]),
+			PAGE_SIZE);
+	kunmap_atomic(virt, KM_USER1);
+
+	suspend_bio_ops.cleanup_readahead(readahead_index);
+
+	readahead_frees++;
+
+	readahead_index++;
+	if (readahead_index == max_async_ios)
+		readahead_index = 0;
+
+	return 0;
+}
+
+static int swapwriter_read_cleanup(void)
+{
+	suspend_bio_ops.finish_all_io();
+	while (readahead_index != readahead_submit_index) {
+		suspend_bio_ops.cleanup_readahead(readahead_index);
+		readahead_frees++;
+		readahead_index++;
+		if (readahead_index == max_async_ios)
+			readahead_index = 0;
+	}
+	suspend_bio_ops.check_io_stats();
+	BUG_ON(readahead_allocs != readahead_frees);
+	return 0;
+}
+
+extern unsigned int nr_suspends;
+
+/* swapwriter_invalidate_image
+ * 
+ */
+static int swapwriter_invalidate_image(void)
+{
+	union p_diskpage cur;
+	int result = 0;
+	char newsig[11];
+	
+	cur.address = get_zeroed_page(GFP_ATOMIC);
+	if (!cur.address) {
+		printk("Unable to allocate a page for restoring the swap signature.\n");
+		return -ENOMEM;
+	}
+
+	suspend_store_free_mem(SUSPEND_FREE_INVALIDATE_IMAGE, 0);
+
+	/*
+	 * If nr_suspends == 0, we must be booting, so no swap pages
+	 * will be recorded as used yet.
+	 */
+
+	if (nr_suspends > 0)
+		swapwriter_release_storage();
+
+	/* 
+	 * We don't do a sanity check here: we want to restore the swap 
+	 * whatever version of kernel made the suspend image.
+	 * 
+	 * We need to write swap, but swap may not be enabled so
+	 * we write the device directly
+	 */
+	
+	suspend_bio_ops.bdev_page_io(READ, resume_block_device,
+			resume_firstblock, virt_to_page(cur.pointer));
+
+	result = parse_signature(cur.pointer->swh.magic.magic, 1);
+		
+	if (result < 4)
+		goto out;
+
+	strncpy(newsig, cur.pointer->swh.magic.magic, 10);
+	newsig[10] = 0;
+	suspend_message(SUSPEND_ANY_SECTION, SUSPEND_VERBOSE, 0,
+			"Swap signature will be set to %s.\n", newsig);
+
+	suspend_bio_ops.bdev_page_io(WRITE, resume_block_device, resume_firstblock,
+			virt_to_page(cur.pointer));
+
+	if (!nr_suspends)
+		printk(KERN_WARNING name_suspend "Image invalidated.\n");
+out:
+	suspend_bio_ops.finish_all_io();
+	free_pages(cur.address, 0);
+	suspend_store_free_mem(SUSPEND_FREE_INVALIDATE_IMAGE, 1);
+	return 0;
+}
+
+/*
+ * workspace_size
+ *
+ * Description:
+ * Returns the number of bytes of RAM needed for this
+ * code to do its work. (Used when calculating whether
+ * we have enough memory to be able to suspend & resume).
+ *
+ */
+static unsigned long swapwriter_memory_needed(void)
+{
+	return 1;
+}
+
+/* Print debug info
+ *
+ * Description:
+ */
+
+static int swapwriter_print_debug_stats(char * buffer, int size)
+{
+	int len = 0;
+	struct sysinfo sysinfo;
+	
+	if (active_writer != &swapwriterops) {
+		len = suspend_snprintf(buffer, size, "- Swapwriter inactive.\n");
+		return len;
+	}
+
+	len = suspend_snprintf(buffer, size, "- Swapwriter active.\n");
+	if (swapfilename[0])
+		len+= suspend_snprintf(buffer+len, size-len,
+			"  Attempting to automatically swapon: %s.\n", swapfilename);
+
+	si_swapinfo(&sysinfo);
+	
+	len+= suspend_snprintf(buffer+len, size-len, "  Swap available for image: %ld pages.\n",
+			sysinfo.freeswap + swapwriter_storage_allocated());
+
+	return len;
+}
+
+/*
+ * Storage needed
+ *
+ * Returns amount of space in the swap header required
+ * for the swapwriter's data. This ignores the links between
+ * pages, which we factor in when allocating the space.
+ *
+ * We ensure the space is allocated, but actually save the
+ * data from write_header_init and therefore don't also define a
+ * save_config_info routine.
+ */
+static unsigned long swapwriter_storage_needed(void)
+{
+	return sizeof(header_data);
+}
+
+/*
+ * Image_exists
+ *
+ */
+
+static int swapwriter_image_exists(void)
+{
+	int signature_found;
+	union p_diskpage diskpage;
+	
+	if (!resume_device) {
+		printk("Not even trying to read header because resume_device is not set.\n");
+		return 0;
+	}
+	
+	//PRINTFREEMEM("at start of swapwriter_image_exists.");
+
+	diskpage.address = get_zeroed_page(GFP_ATOMIC);
+
+	/* FIXME: Make sure bdev_page_io handles wrong parameters */
+	suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr));
+	suspend_bio_ops.finish_all_io();
+	signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0);
+
+	if (signature_found < 2) {
+		printk(KERN_ERR name_suspend "This is normal swap space.\n" );
+		return 0;	/* non fatal error */
+	} else if (signature_found == -1) {
+		printk(KERN_ERR name_suspend "Unable to find a signature. Could you have moved a swap file?\n");
+		return 0;
+	} else if (signature_found < 6) {
+		if ((!(test_suspend_state(SUSPEND_NORESUME_SPECIFIED)))
+				&& suspend_early_boot_message(1, "Detected the signature of an alternate implementation.\n"))
+			set_suspend_state(SUSPEND_NORESUME_SPECIFIED);
+		return 0;
+	} else if ((signature_found >> 1) != SIGNATURE_VER) {
+		if ((!(test_suspend_state(SUSPEND_NORESUME_SPECIFIED))) &&
+				suspend_early_boot_message(1, "Found a different style suspend image signature."))
+			set_suspend_state(SUSPEND_NORESUME_SPECIFIED);
+	}
+
+	return 1;
+}
+
+/*
+ * Mark resume attempted.
+ *
+ * Record that we tried to resume from this image.
+ */
+
+static void swapwriter_mark_resume_attempted(void)
+{
+	union p_diskpage diskpage;
+	int signature_found;
+	
+	if (!resume_device) {
+		printk("Not even trying to record attempt at resuming"
+				" because resume_device is not set.\n");
+		return;
+	}
+	
+	diskpage.address = get_zeroed_page(GFP_ATOMIC);
+
+	/* FIXME: Make sure bdev_page_io handles wrong parameters */
+	suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr));
+	signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0);
+
+	switch (signature_found) {
+		case 12:
+		case 13:
+			diskpage.pointer->swh.magic.magic[5] |= 0x80;
+			break;
+	}
+	
+	suspend_bio_ops.bdev_page_io(WRITE, resume_block_device, resume_firstblock,
+			virt_to_page(diskpage.ptr));
+	suspend_bio_ops.finish_all_io();
+	free_pages(diskpage.address, 0);
+	return;
+}
+
+/*
+ * Parse Image Location
+ *
+ * Attempt to parse a resume2= parameter.
+ * Swap Writer accepts:
+ * resume2=swap:DEVNAME[:FIRSTBLOCK][@BLOCKSIZE]
+ *
+ * Where:
+ * DEVNAME is convertable to a kdev_t by name_to_kdev_t
+ * FIRSTBLOCK is the location of the first block in the swap file
+ * (specifying for a swap partition is nonsensical but not prohibited).
+ * BLOCKSIZE is the logical blocksize >= 512 & <= PAGE_SIZE, 
+ * mod 512 == 0 of the device.
+ * Data is validated by attempting to read a swap header from the
+ * location given. Failure will result in swapwriter refusing to
+ * save an image, and a reboot with correct parameters will be
+ * necessary.
+ */
+
+static int swapwriter_parse_image_location(char * commandline, int only_writer)
+{
+	char *thischar, *devstart = NULL, *colon = NULL, *at_symbol = NULL;
+	union p_diskpage diskpage;
+	int signature_found;
+
+	CLEAR_RESULT_STATE(SUSPEND_ABORTED);
+
+	if (strncmp(commandline, "swap:", 5)) {
+		if (!only_writer) {
+			printk(name_suspend "Swapwriter: Image location doesn't begin with 'swap:'\n");
+			return 1;
+		}
+	} else
+		commandline += 5;
+
+	devstart = thischar = commandline;
+	while ((*thischar != ':') && ((thischar - commandline) < 250) && (*thischar))
+		thischar++;
+
+	if (*thischar == ':') {
+		colon = thischar;
+		*colon = 0;
+		thischar++;
+	}
+
+	while ((*thischar != '@') && ((thischar - commandline) < 250) && (*thischar))
+		thischar++;
+
+	if (*thischar == '@') {
+		at_symbol = thischar;
+		*at_symbol = 0;
+	}
+	
+	if (colon)
+		resume_firstblock = (int) simple_strtoul(colon + 1, NULL, 0);
+	else
+		resume_firstblock = 0;
+	printk("Looking for first block of swap header at block %x.\n", resume_firstblock);
+
+	if (at_symbol) {
+		resume_firstblocksize = (int) simple_strtoul(at_symbol + 1, NULL, 0);
+		if (resume_firstblocksize & 0x1FF)
+			printk("Blocksizes are usually a multiple of 512. Don't expect this to work!\n");
+	} else
+		resume_firstblocksize = 4096;
+	printk("Setting logical block size of resume device to %d.\n", resume_firstblocksize);
+	
+	if (try_to_parse_resume_device(devstart))
+		goto invalid;
+
+	if (colon)
+		*colon = ':';
+	if (at_symbol)
+		*at_symbol = '@';
+
+	if ((suspend_bio_ops.get_block_size(resume_block_device) 
+				!= resume_firstblocksize) &&
+	    (suspend_bio_ops.set_block_size(resume_block_device, resume_firstblocksize)
+	    			 == -EINVAL))
+		goto invalid;
+
+	diskpage.address = get_zeroed_page(GFP_ATOMIC);
+	if (suspend_bio_ops.bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr))) {
+		printk(KERN_ERR name_suspend "Failed to submit I/O.\n");
+		return -EINVAL;
+	}
+	suspend_bio_ops.finish_all_io();
+	signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0);
+	free_page((unsigned long) diskpage.address);
+
+	if (signature_found != -1) {
+		printk(KERN_ERR name_suspend "Swap space signature found.\n");
+		return 0;
+	}
+
+	printk(KERN_ERR name_suspend "Sorry. No swap signature found at specified location.\n");
+	return -EINVAL;
+
+invalid:
+	if (colon)
+		*colon = ':';
+	if (at_symbol)
+		*at_symbol = '@';
+	printk(KERN_ERR name_suspend "Sorry. Location looks invalid.\n");
+	return -EINVAL;
+}
+
+int header_locations_read_proc(char * page, char ** start, off_t off, int count,
+		int *eof, void *data)
+{
+	int i, printedpartitionsmessage = 0, len = 0, haveswap = 0, device_block_size;
+	struct inode *swapf = 0;
+	int zone;
+	char * path_page = (char *) __get_free_page(GFP_KERNEL);
+	char * path;
+	int path_len;
+	
+	*eof = 1;
+	if (!page)
+		return 0;
+
+	for (i = 0; i < MAX_SWAPFILES; i++) {
+		if (!swap_info[i].swap_file)
+			continue;
+		
+		if (swap_info[i].swap_device) {
+			haveswap = 1;
+			if (!printedpartitionsmessage) {
+				len += sprintf(page + len, 
+					"For swap partitions, simply use the format: resume2=swap:/dev/hda1.\n");
+				printedpartitionsmessage = 1;
+			}
+		} else {
+			path_len = 0;
+			
+			path = get_path_for_swapfile(i, path_page);
+			path_len = sprintf(path_page, "%-31s ", path);
+			
+			haveswap = 1;
+			swapf = swap_info[i].swap_file->d_inode;
+			device_block_size = block_size(swap_info[i].swap_device ?
+					swap_info[i].swap_device :
+					swap_info[i].swap_file->d_inode->i_dev);
+			if (!(zone = bmap(swapf,0))) {
+				len+= sprintf(page + len, 
+					"Swapfile %-31s has been corrupted. Reuse mkswap on it and try again.\n",
+					path_page);
+			} else {
+				len+= sprintf(page + len, "For swapfile `%s`, use resume2=swap:/dev/<partition name>:0x%x@%d.\n",
+						path_page,
+						zone, device_block_size);
+			}
+
+		}
+	}
+	
+	if (!haveswap)
+		len = sprintf(page, "You need to turn on swap partitions before examining this file.\n");
+
+	free_pages((unsigned long) path_page, 0);
+	return len;
+}
+
+extern int attempt_to_parse_resume_device(void);
+
+static struct suspend_proc_data swapwriter_proc_data[] = {
+	{
+	 .filename			= "swapfilename",
+	 .permissions			= PROC_RW,
+	 .type				= SUSPEND_PROC_DATA_STRING,
+	 .data = {
+		.string = {
+			.variable	= swapfilename,
+			.max_length	= 255,
+		}
+	 }
+	},
+
+	{
+	 .filename			= "headerlocations",
+	 .permissions			= PROC_READONLY,
+	 .type				= SUSPEND_PROC_DATA_CUSTOM,
+	 .data = {
+		 .special = {
+			.read_proc 	= header_locations_read_proc,
+		}
+	 }
+	},
+
+	{ .filename			= "disable_swapwriter",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		.integer = {
+			.variable	= &swapwriterops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	  },
+	  .write_proc = attempt_to_parse_resume_device,
+	}
+};
+
+static struct suspend_plugin_ops swapwriterops = {
+	.type					= WRITER_PLUGIN,
+	.name					= "Swap Writer",
+	.memory_needed				= swapwriter_memory_needed,
+	.print_debug_info			= swapwriter_print_debug_stats,
+	.storage_needed				= swapwriter_storage_needed,
+	.initialise				= swapwriter_initialise,
+	.cleanup				= swapwriter_cleanup,
+	.ops = {
+		.writer = {
+		 .write_init		= swapwriter_write_init,
+		 .write_chunk		= swapwriter_write_chunk,
+		 .write_cleanup		= swapwriter_write_cleanup,
+		 .read_init		= swapwriter_read_init,
+		 .read_chunk		= swapwriter_read_chunk,
+		 .read_cleanup		= swapwriter_read_cleanup,
+		 .noresume_reset	= swapwriter_noresume_reset,
+		 .storage_available 	= swapwriter_storage_available,
+		 .storage_allocated	= swapwriter_storage_allocated,
+		 .release_storage	= swapwriter_release_storage,
+		 .allocate_header_space	= swapwriter_allocate_header_space,
+		 .allocate_storage	= swapwriter_allocate_storage,
+		 .image_exists		= swapwriter_image_exists,
+		 .mark_resume_attempted	= swapwriter_mark_resume_attempted,
+		 .write_header_init	= swapwriter_write_header_init,
+		 .write_header_chunk	= swapwriter_write_header_chunk,
+		 .write_header_cleanup	= swapwriter_write_header_cleanup,
+		 .read_header_init	= swapwriter_read_header_init,
+		 .read_header_chunk	= swapwriter_read_header_chunk,
+		 .read_header_cleanup	= swapwriter_read_header_cleanup,
+		 .prepare_save_ranges	= swapwriter_prepare_save_ranges,
+		 .post_load_ranges	= swapwriter_post_load_ranges,
+		 .invalidate_image	= swapwriter_invalidate_image,
+		 .parse_image_location	= swapwriter_parse_image_location,
+		}
+	}
+};
+
+/* ---- Registration ---- */
+static __init int swapwriter_load(void)
+{
+	int result;
+	int i, numfiles = sizeof(swapwriter_proc_data) / sizeof(struct suspend_proc_data);
+	
+	if (!(result = suspend_register_plugin(&swapwriterops))) {
+		printk("Software Suspend Swap Writer registered.\n");
+
+		for (i=0; i< numfiles; i++)
+			suspend_register_procfile(&swapwriter_proc_data[i]);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void swapwriter_unload(void)
+{
+	int i, numfiles = sizeof(swapwriter_proc_data) / sizeof(struct suspend_proc_data);
+
+	printk("Software Suspend Swap Writer unloading.\n");
+
+	for (i=0; i< numfiles; i++)
+		suspend_unregister_procfile(&swapwriter_proc_data[i]);
+	suspend_unregister_plugin(&swapwriterops);
+}
+
+module_init(swapwriter_load);
+module_exit(swapwriter_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Suspend2 swap writer");
+#else
+__initcall(swapwriter_load);
+#endif
diff -ruN 20-old/kernel/power/suspend_text.c 20-new/kernel/power/suspend_text.c
--- 20-old/kernel/power/suspend_text.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_text.c	2004-11-24 15:24:22.000000000 +1100
@@ -0,0 +1,625 @@
+/*
+ * kernel/power/suspend2_text_display.c
+ *
+ * Copyright (C) 2002-2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ *
+ * This file is released under the GPLv2.
+ *
+ * Routines for Software Suspend's user interface.
+ * 
+ * The user interface includes support for a text mode 'nice display'.
+ *
+ * The 'nice display' is text based and implements a progress bar and
+ * (optional) textual progress, as well as an overall description of
+ * the current action and the display of a header and the code version.
+ *
+ * It uses /dev/console, and thus also works on a serial console.
+ */
+#define SUSPEND_TEXT_MODE_C
+
+//#define __KERNEL_SYSCALLS__
+
+#include <linux/suspend.h>
+#include <linux/console.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <linux/module.h>
+ 
+#include "plugins.h"
+#include "proc.h"
+#include "suspend.h"
+
+extern asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type,
+	unsigned long flags, void * data);
+extern asmlinkage long sys_umount(char * name, int flags);
+extern asmlinkage long sys_mkdir(const char *name, int mode);
+/*
+ * The original macros use currcons, which we don't have access to.
+ */
+#undef video_num_columns
+#define video_num_columns	(vc_cons[fg_console].d->vc_cols)
+#undef video_num_lines
+#define video_num_lines		(vc_cons[fg_console].d->vc_rows)
+
+static int barwidth = 0, barposn = -1, newbarposn = 0;
+static int draw_progress_bar = 1;
+static char print_buf[1024];	/* Same as printk - should be safe */
+
+/* We remember the last header that was (or could have been) displayed for
+ * use during log level switches */
+static char lastheader[512];
+static int lastheader_message_len = 0;
+
+static void hide_cursor(void);
+static void unblank_screen_via_file(void);
+
+static int suspend_console_fd = -1;
+static struct termios termios;
+static int lastloglevel = -1;
+
+#ifdef CONFIG_DEVFS_FS
+static int mounted_devfs = 0;
+#endif
+
+#define cond_console_print(chars) \
+	if (suspend_console_fd > -1) { \
+		int count = strlen(chars); \
+		sys_write(suspend_console_fd, chars, count); \
+		hide_cursor(); \
+		unblank_screen_via_file();  \
+	}
+
+static void move_cursor_to(unsigned char * xy)
+{
+	char buf[10];
+	
+	snprintf(buf, 10, "\233%d;%dH", xy[1], xy[0]);
+	cond_console_print(buf);
+}
+
+static void clear_display(void)
+{
+	char buf[4] = "\2332J";
+	unsigned char home[2] = { 0, 0 };
+	
+	cond_console_print(buf);
+	move_cursor_to(home);
+}
+
+static void hide_cursor(void)
+{
+	char buf[6] = "\033[?1c";
+	if (suspend_console_fd > -1)
+		sys_write(suspend_console_fd, buf, 5);
+}
+
+static void restore_cursor(void)
+{
+	char buf[6] = "\033[?0c";
+	if (suspend_console_fd > -1)
+		sys_write(suspend_console_fd, buf, 5);
+}
+
+static void unblank_screen_via_file(void)
+{
+	char buf[6] = "\033[13]";
+	if (suspend_console_fd > -1)
+		sys_write(suspend_console_fd, buf, 5);
+}
+
+/* prepare_status
+ * Description:	Prepare the 'nice display', drawing the header and version,
+ * 		along with the current action and perhaps also resetting the
+ * 		progress bar.
+ * Arguments:	int printalways: Whether to print the action when debugging
+ * 		is on.
+ * 		int clearbar: Whether to reset the progress bar.
+ * 		const char *fmt, ...: The action to be displayed.
+ */
+static void text_prepare_status(int printalways, int clearbar, const char *fmt, va_list args)
+{
+	unsigned char posn[2];
+
+	if (fmt)
+		lastheader_message_len = vsnprintf(lastheader, 512, fmt, args);
+
+	if (console_loglevel >= SUSPEND_ERROR) {
+
+		if (printalways)
+			printk("\n** %s\n", lastheader);
+		return;
+	}
+	
+	barwidth = (video_num_columns - 2 * (video_num_columns / 4) - 2);
+
+	/* Print version */
+	posn[0] = (unsigned char) (0);
+	posn[1] = (unsigned char) (video_num_lines);
+	move_cursor_to(posn);
+	cond_console_print(SUSPEND_CORE_VERSION);
+
+	/* Print header */
+	posn[0] = (unsigned char) ((video_num_columns - 31) / 2);
+	posn[1] = (unsigned char) ((video_num_lines / 3) - 3);
+	move_cursor_to(posn);
+
+	cond_console_print("S O F T W A R E   S U S P E N D");
+		
+	/* Print action */
+	posn[1] = (unsigned char) (video_num_lines / 3);
+	posn[0] = (unsigned char) 0;
+	move_cursor_to(posn);
+	
+	/* Clear old message */
+	for (barposn = 0; barposn < video_num_columns; barposn++) 
+		cond_console_print(" ");
+
+	posn[0] = (unsigned char)
+		((video_num_columns - lastheader_message_len) / 2);
+	move_cursor_to(posn);
+	cond_console_print(lastheader);
+	
+	if (draw_progress_bar) {
+		/* Draw left bracket of progress bar. */
+		posn[0] = (unsigned char) (video_num_columns / 4);
+		posn[1]++;
+		move_cursor_to(posn);
+		cond_console_print("[");
+
+		/* Draw right bracket of progress bar. */
+		posn[0] = (unsigned char) 
+			(video_num_columns - (video_num_columns / 4) - 1);
+		move_cursor_to(posn);
+		cond_console_print("]");
+
+		if (clearbar) {
+			/* Position at start of progress */
+			posn[0] = (unsigned char) (video_num_columns / 4 + 1);
+			move_cursor_to(posn);
+
+			/* Clear bar */
+			for (barposn = 0; barposn < barwidth; barposn++)
+				cond_console_print(" ");
+			move_cursor_to(posn);
+		}
+	}
+	
+	hide_cursor();
+
+	barposn = 0;
+}
+
+/* text_loglevel_change
+ *
+ * Description:	Update the display when the user changes the log level.
+ * Returns:	Boolean indicating whether the level was changed.
+ */
+
+static void text_loglevel_change(void)
+{
+	/* Calculate progress bar width. Note that whether the
+	 * splash screen is on might have changed (this might be
+	 * the first call in a new cycle), so we can't take it
+	 * for granted that the width is the same as last time
+	 * we came in here */
+	barwidth = (video_num_columns - 2 * (video_num_columns / 4) - 2);
+	barposn = 0;
+
+	/* Only reset the display if we're switching between nice display
+	 * and displaying debugging output */
+	
+	if (console_loglevel >= SUSPEND_ERROR) {
+		char message[35];
+		if (lastloglevel < SUSPEND_ERROR)
+			clear_display();
+
+		snprintf(message, 35,
+			"Switched to console loglevel %d.\n", 
+			console_loglevel);
+		cond_console_print(message);
+
+		if (lastloglevel < SUSPEND_ERROR) {
+			cond_console_print(lastheader);
+			cond_console_print("\n");
+		}
+	
+	} else if (lastloglevel >= SUSPEND_ERROR) {
+		clear_display();
+	
+		/* Get the nice display or last action [re]drawn */
+		text_prepare_status(1, 0, NULL, NULL);
+	}
+	
+	lastloglevel = console_loglevel;
+}
+/* text_update_progress
+ *
+ * Description: Update the progress bar and (if on) in-bar message.
+ * Arguments:	UL value, maximum: Current progress percentage (value/max).
+ * 		const char *fmt, ...: Message to be displayed in the middle
+ * 		of the progress bar.
+ * 		Note that a NULL message does not mean that any previous
+ * 		message is erased! For that, you need prepare_status with
+ * 		clearbar on.
+ * Returns:	Unsigned long: The next value where status needs to be updated.
+ * 		This is to reduce unnecessary calls to text_update_progress.
+ */
+unsigned long text_update_progress(unsigned long value, unsigned long maximum,
+		const char *fmt, va_list args)
+{
+	unsigned long next_update = 0;
+	int bitshift = generic_fls(maximum) - 16;
+	unsigned char posn[2];
+	int message_len = 0;
+
+	if (!barwidth)
+		barwidth = (video_num_columns - 2 * (video_num_columns / 4) - 2);
+
+	if (!maximum)
+		return maximum;
+
+	if (value < 0)
+		value = 0;
+
+	if (value > maximum)
+		value = maximum;
+
+	/* Try to avoid math problems - we can't do 64 bit math here
+	 * (and shouldn't need it - anyone got screen resolution
+	 * of 65536 pixels or more?) */
+	if (bitshift > 0) {
+		unsigned long temp_maximum = maximum >> bitshift;
+		unsigned long temp_value = value >> bitshift;
+		newbarposn = (int) (temp_value * barwidth / temp_maximum);
+	} else
+		newbarposn = (int) (value * barwidth / maximum);
+	
+	if (newbarposn < barposn)
+		barposn = 0;
+
+	next_update = ((newbarposn + 1) * maximum / barwidth) + 1;
+
+	if ((console_loglevel >= SUSPEND_ERROR) || (!draw_progress_bar))
+		return next_update;
+
+	/* Update bar */
+	if (draw_progress_bar) {
+		posn[1] = (unsigned char) ((video_num_lines / 3) + 1);
+
+		/* Clear bar if at start */
+		if (!barposn) {
+			posn[0] = (unsigned char) (video_num_columns / 4 + 1);
+			move_cursor_to(posn);
+			for (; barposn < barwidth; barposn++)
+				cond_console_print(" ");
+			barposn = 0;
+		}
+		posn[0] = (unsigned char) (video_num_columns / 4 + 1 + barposn);
+		move_cursor_to(posn);
+
+		for (; barposn < newbarposn; barposn++)
+			cond_console_print("-");
+	}
+
+	/* Print string in progress bar on loglevel 1 */
+	if ((fmt) && (console_loglevel)) {
+		message_len = vsnprintf(print_buf, sizeof(print_buf), " ", NULL);
+		message_len += vsnprintf(print_buf + message_len,
+				sizeof(print_buf) - message_len, fmt, args);
+		message_len += vsnprintf(print_buf + message_len,
+				sizeof(print_buf) - message_len, " ", NULL);
+
+		if (message_len) {
+			posn[0] = (unsigned char)
+				((video_num_columns - message_len) / 2);
+			posn[1] = (unsigned char)
+				((video_num_lines / 3) + 1);
+			move_cursor_to(posn);
+			cond_console_print(print_buf);
+		}
+	}
+
+	barposn = newbarposn;
+	hide_cursor();
+	
+	return next_update;
+}
+
+extern asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, 
+		unsigned long arg);
+
+static void text_message(unsigned long section, unsigned long level,
+		int normally_logged,
+		const char *fmt, va_list args)
+{
+	int printed_len = 0;
+
+	if ((section) && (!TEST_DEBUG_STATE(section)))
+		return;
+
+	if (level == SUSPEND_STATUS) {
+		text_prepare_status(1, 0, fmt, args);
+		return;
+	}
+	
+	if (level > console_loglevel)
+		return;
+
+	printed_len = vsnprintf(print_buf + printed_len,
+			sizeof(print_buf) - printed_len, fmt, args);
+
+
+	if ((TEST_ACTION_STATE(SUSPEND_LOGALL)) ||
+	    (normally_logged)) {
+		/* If we didn't print anything, don't do the \n anyway! */
+		if (!printed_len)
+			return;
+		printk(print_buf);
+	} else
+		cond_console_print(print_buf);
+}
+/*
+ *
+ */
+
+static void suspend_get_dev_console(void)
+{
+	if (suspend_console_fd > -1)
+		return;
+
+	suspend_console_fd = sys_open("/dev/console", O_RDWR | O_NONBLOCK, 0);
+	if (suspend_console_fd < 0) {
+		sys_mkdir("/dev", 0700);
+#ifdef CONFIG_DEVFS_FS
+		sys_mount("devfs", "/dev", "devfs", 0, NULL);
+		mounted_devfs = 1;
+#endif
+		suspend_console_fd = sys_open("/dev/console", O_RDWR | O_NONBLOCK, 0);
+	}
+	if (suspend_console_fd < 0) {
+		printk("Can't open /dev/console. Error value was %d.\n",
+				suspend_console_fd);
+		suspend_console_fd = -1;
+		return;
+	}
+
+	sys_ioctl(suspend_console_fd, TCGETS, (long)&termios);
+	termios.c_lflag &= ~ICANON;
+	sys_ioctl(suspend_console_fd, TCSETSF, (long)&termios);
+}
+
+/* prepare_console
+ *
+ */
+static void text_prepare_console(void)
+{
+	suspend_get_dev_console();
+
+	if (console_loglevel < 2)
+		clear_display();
+
+	lastloglevel = console_loglevel;
+}
+
+/* cleanup_console
+ *
+ * Description: Close our handle on /dev/console. Must be done
+ * earlier than pm_restore_console to avoid problems with other
+ * processes trying to grab it when thawed.
+ */
+
+static void cleanup_console(void)
+{
+	if (console_loglevel < 2)
+		clear_display();
+	restore_cursor();
+	termios.c_lflag |= ICANON;
+	sys_ioctl(suspend_console_fd, TCSETSF, (long)&termios);
+	sys_close(suspend_console_fd);
+	suspend_console_fd = -1;
+
+#ifdef CONFIG_DEVFS_FS
+	if (mounted_devfs)
+		sys_umount("/dev", 0);
+#endif
+	
+	lastloglevel = -1;
+	return;
+}
+
+static int text_keypress(unsigned int key)
+{
+	switch (key) {
+		case 48:
+			console_loglevel = 0;
+			break;
+		case 49:
+			console_loglevel = 1;
+			break;
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+		case 122:
+			/* `: Toggle slow */
+			suspend_action ^= (1 << SUSPEND_SLOW);
+			suspend2_core_ops->schedule_message(7);
+			break;
+		case 1:
+			/* F1: Toggle any section debugging. */
+			suspend_debug_state ^= (1 << SUSPEND_ANY_SECTION);
+			suspend2_core_ops->schedule_message(20);
+			break;
+		case 2:
+			/* F2: Freeze. */
+			suspend_debug_state ^= (1 << SUSPEND_FREEZER);
+			suspend2_core_ops->schedule_message(21);
+			break;
+		case 3:
+			/* F3: Eat Memory */
+			suspend_debug_state ^= (1 << SUSPEND_EAT_MEMORY);
+			suspend2_core_ops->schedule_message(22);
+			break;
+		case 4:
+			/* F4: Pagesets. */
+			suspend_debug_state ^= (1 << SUSPEND_PAGESETS);
+			suspend2_core_ops->schedule_message(23);
+			break;
+		case 5:
+			/* F5: IO. */
+			suspend_debug_state ^= (1 << SUSPEND_IO);
+			suspend2_core_ops->schedule_message(24);
+			break;
+		case 6:
+			/* F6: Bmapping of pages */
+			suspend_debug_state ^= (1 << SUSPEND_BMAP);
+			suspend2_core_ops->schedule_message(25);
+			break;
+		case 7:
+			/* F7: Writer */
+			suspend_debug_state ^= (1 << SUSPEND_WRITER);
+			suspend2_core_ops->schedule_message(26);
+			break;
+		case 8:
+			/* F8: Memory */
+			suspend_debug_state ^= (1 << SUSPEND_MEMORY);
+			suspend2_core_ops->schedule_message(27);
+			break;
+		case 9:
+			/* F9: Ranges */
+			suspend_debug_state ^= (1 << SUSPEND_RANGES);
+			suspend2_core_ops->schedule_message(28);
+			break;
+		case 10:
+			/* F10: Memory Pool */
+			suspend_debug_state ^= (1 << SUSPEND_MEM_POOL);
+			suspend2_core_ops->schedule_message(29);
+			break;
+		case 11:
+			/* F11: Nosave */
+			suspend_debug_state ^= (1 << SUSPEND_NOSAVE);
+			suspend2_core_ops->schedule_message(30);
+			break;
+		case 12:
+			/* F12: Integrity */
+			suspend_debug_state ^= (1 << SUSPEND_INTEGRITY);
+			suspend2_core_ops->schedule_message(31);
+			break;
+		case 112:
+			/* During suspend, toggle pausing with P */
+			suspend_action ^= (1 << SUSPEND_PAUSE);
+			suspend2_core_ops->schedule_message(1);
+			break;
+		case 115:
+			/* Otherwise, if S pressed, toggle single step */
+			suspend_action ^= (1 << SUSPEND_SINGLESTEP);
+			suspend2_core_ops->schedule_message(3);
+			break;
+		case 108:
+			/* Otherwise, if L pressed, toggle logging everything */
+			suspend_action ^= (1 << SUSPEND_LOGALL);
+			suspend2_core_ops->schedule_message(4);
+			break;
+		case 116:
+			/* T: Toggle freezing timers */
+			clear_suspend_state(SUSPEND_TIMER_FREEZER_ON);
+			suspend2_core_ops->schedule_message(99);
+			break;
+		case 50:
+		case 51:
+		case 52:
+		case 53:
+		case 54:
+		case 55:
+		case 56:
+		case 57:
+			console_loglevel = ((key - 48));
+			break;
+#endif
+		default:
+			return 0;
+	}
+	return 1;
+}
+
+/*
+ * User interface specific /proc/suspend entries.
+ */
+
+static struct suspend_plugin_ops text_mode_ops;
+
+static struct suspend_proc_data proc_params[] = {
+	{ .filename			= "text_mode_progress_bar",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		  .integer = {
+			  .variable	= &draw_progress_bar,
+			  .minimum	= 0,
+			  .maximum	= 1,
+
+		  }
+	  }
+	},
+	
+	{ .filename			= "disable_textmode_support",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		.integer = {
+			.variable	= &text_mode_ops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	  }
+	}
+};
+
+static struct suspend_plugin_ops text_mode_ops = {
+	.type					= UI_PLUGIN,
+	.name					= "Text Mode Support",
+	.ops = {
+		.ui = {
+			.prepare		= text_prepare_console,	
+			.log_level_change	= text_loglevel_change,
+			.message		= text_message,
+			.update_progress	= text_update_progress,
+			.cleanup		= cleanup_console,
+			.keypress		= text_keypress,
+		}
+	}
+};
+
+/* ---- Registration ---- */
+
+static __init int text_mode_load(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+	int result;
+
+	if (!(result = suspend_register_plugin(&text_mode_ops))) {
+		printk("Software Suspend text mode support loaded.\n");
+		for (i=0; i< numfiles; i++)
+			suspend_register_procfile(&proc_params[i]);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void text_mode_unload(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+	printk("Software Suspend text mode support unloading.\n");
+
+	for (i=0; i< numfiles; i++)
+		suspend_unregister_procfile(&proc_params[i]);
+	
+	suspend_unregister_plugin(&text_mode_ops);
+}
+
+module_init(text_mode_load);
+module_exit(text_mode_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Suspend2 Text Mode support");
+#else
+__initcall(text_mode_load);
+#endif
diff -ruN 20-old/kernel/power/suspend_ui.c 20-new/kernel/power/suspend_ui.c
--- 20-old/kernel/power/suspend_ui.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/suspend_ui.c	2004-11-24 15:24:22.000000000 +1100
@@ -0,0 +1,677 @@
+/*
+ * kernel/power/ui.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.
+ *
+ * Routines for Software Suspend's user interface.
+ * 
+ * The user interface is modular, providing support for setting a general
+ * status message, a progress bar, an in-progress-bar message and
+ * displaying debugging messages instead when the console log level is
+ * switched > 1.
+ *
+ * As well as displaying status information, the user has some control
+ * over the software while suspending. Hooks in drivers/char/console.c
+ * and drivers/char/serial.c allow a user on the keyboard or serial
+ * console to...
+ *
+ * 					 	  Key
+ * Toggle rebooting			 	   R
+ * Toggle logging all output		 	   L
+ * Toggle pausing between major steps (1)	   P
+ * Toggle pausing at minor steps		   S
+ * Switch log levels (2)			  0-7
+ * Cancel the suspend (3)			 Escape
+ * Continue when paused				 Space
+ *
+ * (1) Pausing only occurs when the log level is > 1.
+ * (2) When debugging is not compiled in, only levels 0 & 1 work.
+ * (3) Can be disabled using /proc/suspend/enable_escape.
+ *
+ * (The following assumes debugging is compiled in)...
+ * 
+ * Fundamental to the debugging code is the idea that each debug 
+ * message which suspend can display has both a section of code to
+ * which it belongs and a level of verbosity. When suspend is running,
+ * it only displays a message if debugging for the section has been
+ * enabled prior to beginning the cycle and the current loglevel is
+ * greater than or equal to the level of the message.
+ * 
+ * Prior to starting a suspend cycle, a user can select which sections
+ * should display debugging information by setting
+ * /proc/suspend/debug_sections. This is a bit vector (values in
+ * include/linux/suspend-debug.h). The initial log level can be also
+ * be set using /proc/suspend/default_console_level.
+ * The debug sections and level can be changed prior to resuming using
+ * the kernel command line parameters suspend_dbg and suspend_lvl.
+ * 
+ * In addition to the above ability to control whether messages are
+ * displayed, messages can be displayed in two ways. The printlog call
+ * displays a message using printk, with the result that it is also
+ * logged to syslog in the normal way.
+ *
+ * A call to message usually gets the text to the screen using
+ * vt_console_print and is thus not logged. This is the preferred
+ * means of displaying highlevel debugging information, because it
+ * reduces clutter in the logs. (You can easily end up with 1/5645.^M
+ * 2/5645.^M 3/5645.^M... otherwise).
+ * If loggging of this output is wanted, the log all output toggle
+ * can be activated and printk will be used instead of printing to
+ * /dev/console (assuming the text mode module is used).
+ */
+#define SUSPEND_CONSOLE_C
+
+#define __KERNEL_SYSCALLS__
+
+#include <linux/suspend.h>
+#include <linux/console.h>
+#include <linux/tty.h>
+#include <linux/vt_kern.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+ 
+#include "proc.h"
+#include "plugins.h"
+#include "suspend.h"
+
+extern char suspend_print_buf[1024];	/* Same as printk - should be safe */
+static char lastheader[512];
+static int lastheader_message_len = 0;
+
+static int lastloglevel = -1;
+static int orig_loglevel = 0;
+static int orig_default_message_loglevel;
+static int orig_kmsg;
+
+/* abort_suspend
+ *
+ * Description: Begin to abort a cycle. If this wasn't at the user's request
+ * 		(and we're displaying output), tell the user why and wait for
+ * 		them to acknowledge the message.
+ * Arguments:	A parameterised string (imagine this is printk) to display,
+ *	 	telling the user why we're aborting.
+ */
+
+void abort_suspend(const char *fmt, ...)
+{
+	va_list args;
+	int printed_len = 0;
+
+	if (!TEST_RESULT_STATE(SUSPEND_ABORTED)) {
+		if (!TEST_RESULT_STATE(SUSPEND_ABORT_REQUESTED)) {
+			va_start(args, fmt);
+			printed_len = vsnprintf(suspend_print_buf, 
+					sizeof(suspend_print_buf), fmt, args);
+			va_end(args);
+			printed_len = sprintf(suspend_print_buf + printed_len,
+					" (Press SPACE to continue)");
+			prepare_status(1, 1, suspend_print_buf);
+
+			/* 
+			 * Make sure message seen - wait for shift to be
+			 * released if being pressed 
+			 */
+			suspend_wait_for_keypress();
+		}
+		/* Turn on aborting flag */
+		SET_RESULT_STATE(SUSPEND_ABORTED);
+	}
+}
+
+/* handle_loglevel_change
+ *
+ * Description:	Update the display when the user changes the log level.
+ * Returns:	Boolean indicating whether the level was changed.
+ */
+
+static int handle_loglevel_change(void)
+{
+	static int recursive = 0;
+	struct suspend_plugin_ops * this_plugin;
+	
+	if ((console_loglevel == lastloglevel) || recursive ||
+		(test_suspend_state(SUSPEND_IGNORE_LOGLEVEL)))
+		return 0;
+
+	recursive = 1;
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.log_level_change)
+			this_plugin->ops.ui.log_level_change();
+	}
+	
+	lastloglevel = console_loglevel;
+
+	recursive = 0;
+	
+	return 1;
+}
+
+/* suspend_message.
+ *
+ * Description:	This function is intended to do the same job as printk, but
+ * 		without normally logging what is printed. The point is to be
+ * 		able to get debugging info on screen without filling the logs
+ * 		with "1/534. ^M 2/534^M. 3/534^M"
+ *
+ * 		It may be called from an interrupt context - can't sleep!
+ *
+ * Arguments:	int mask: The debugging section(s) this message belongs to.
+ * 		int level: The level of verbosity of this message.
+ * 		int restartline: Whether to output a \r or \n with this line
+ * 			(\n if we're logging all output).
+ * 		const char *fmt, ...: Message to be displayed a la printk.
+ */
+void __suspend_message(unsigned long section, unsigned long level,
+		int normally_logged,
+		const char *fmt, ...)
+{
+	va_list args;
+	struct suspend_plugin_ops * this_plugin;
+
+	if ((level) && (level > console_loglevel))
+		return;
+
+	/* Don't do this if not printing anything - suspend_message gets called
+	 * before we prepare the console at resume time */
+	if (console_loglevel != lastloglevel)
+		handle_loglevel_change();
+
+	va_start(args, fmt);
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.message)
+			this_plugin->ops.ui.message(
+					section, level, normally_logged,
+					fmt, args);
+	}
+
+	va_end(args);
+}
+
+
+/* prepare_status
+ * Description:	Prepare the 'nice display', drawing the header and version,
+ * 		along with the current action and perhaps also resetting the
+ * 		progress bar.
+ * Arguments:	int printalways: Whether to print the action when debugging
+ * 		is on.
+ * 		int clearbar: Whether to reset the progress bar.
+ * 		const char *fmt, ...: The action to be displayed.
+ */
+void prepare_status(int printalways, int clearbar, const char *fmt, ...)
+{
+	va_list args;
+	struct suspend_plugin_ops * this_plugin;
+
+	if (console_loglevel != lastloglevel)
+		handle_loglevel_change();
+
+	if (fmt) {
+		va_start(args, fmt);
+		lastheader_message_len = vsnprintf(lastheader, 512, fmt, args);
+		va_end(args);
+	}
+
+	if (clearbar)
+		list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+			if (this_plugin->disabled)
+				continue;
+			if (this_plugin->ops.ui.update_progress)
+				this_plugin->ops.ui.update_progress(0, 1, NULL, NULL);
+		}
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.message)
+			this_plugin->ops.ui.message(
+				0, SUSPEND_STATUS, 1, lastheader, NULL);
+	}
+}
+
+/* update_status
+ *
+ * Description: Update the progress bar and (if on) in-bar message.
+ * Arguments:	UL value, maximum: Current progress percentage (value/max).
+ * 		const char *fmt, ...: Message to be displayed in the middle
+ * 		of the progress bar.
+ * 		Note that a NULL message does not mean that any previous
+ * 		message is erased! For that, you need prepare_status with
+ * 		clearbar on.
+ * Returns:	Unsigned long: The next value where status needs to be updated.
+ * 		This is to reduce unnecessary calls to update_status.
+ */
+unsigned long update_status(unsigned long value, unsigned long maximum,
+		const char *fmt, ...)
+{
+	unsigned long next_update = maximum, this = 0;
+	va_list args;
+	struct suspend_plugin_ops * this_plugin;
+
+	if (!maximum)
+		return maximum;
+
+	if (value < 0)
+		value = 0;
+
+	if (value > maximum)
+		value = maximum;
+
+	if (console_loglevel != lastloglevel)
+		handle_loglevel_change();
+
+	va_start(args, fmt);
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.update_progress) {
+			this = this_plugin->ops.ui.update_progress(
+					value, maximum, fmt, args);
+			if ((this) && (this < next_update))
+				next_update = this;
+		}
+	}
+
+	va_end(args);
+
+	return next_update;
+}
+
+static struct waiting_message
+{
+	int message;
+	struct waiting_message * next;
+} * waiting_messages = NULL;
+
+/* display_suspend_message
+ *
+ * Description:	Display a message as a result of the user pressing a key
+ * 		and the key being processed in an interrupt handler.
+ */
+
+int display_suspend_messages(void)
+{
+	int did_work;
+	
+	if (console_loglevel != lastloglevel)
+		handle_loglevel_change();
+
+	/* Loglevel change might add a message */
+	did_work = (waiting_messages != NULL);
+
+	while (waiting_messages) {
+		struct waiting_message * this_message = waiting_messages;
+
+		switch(waiting_messages->message) {
+			case 1:
+				prepare_status(1, 0, "Pausing %s.", 
+					TEST_ACTION_STATE(SUSPEND_PAUSE) ?
+					"enabled" : "disabled");
+				break;
+			case 2:
+				prepare_status(1, 0, "Rebooting %s.", 
+					TEST_ACTION_STATE(SUSPEND_REBOOT) ?
+					"enabled" : "disabled");
+				break;
+			case 3:
+				prepare_status(1, 0, "Single step %s.", 
+					TEST_ACTION_STATE(SUSPEND_SINGLESTEP) ?
+					"enabled" : "disabled");
+				break;
+			case 4:
+				prepare_status(1, 0, "Logging all output %s.",
+					TEST_ACTION_STATE(SUSPEND_LOGALL) ?
+					"enabled" : "disabled");
+				break;
+			case 5:
+				prepare_status(1, 1,
+					"--- ESCAPE PRESSED AGAIN :"
+					" TRYING HARDER TO ABORT ---");
+				break;
+			case 6:
+				prepare_status(1, 1, "--- ESCAPE PRESSED :"
+						" ABORTING PROCESS ---");
+				break;
+			case 7:
+				prepare_status(1, 0, "Slowing down %s.",
+					TEST_ACTION_STATE(SUSPEND_SLOW) ?
+					"enabled" : "disabled");
+				break;
+			case 8:
+				prepare_status(1, 0, "Log level changed to %d.",
+					console_loglevel);
+				break;
+			case 20:
+				prepare_status(1, 0, "General messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_ANY_SECTION) ?
+					"enabled" : "disabled");
+				break;
+			case 21:
+				prepare_status(1, 0, "Freezer messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_FREEZER) ?
+					"enabled" : "disabled");
+				break;
+			case 22:
+				prepare_status(1, 0, "Eat memory messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_EAT_MEMORY) ?
+					"enabled" : "disabled");
+				break;
+			case 23:
+				prepare_status(1, 0, "Pageset messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_PAGESETS) ?
+					"enabled" : "disabled");
+				break;
+			case 24:
+				prepare_status(1, 0, "IO messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_IO) ?
+					"enabled" : "disabled");
+				break;
+			case 25:
+				prepare_status(1, 0, "Bmap messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_BMAP) ?
+					"enabled" : "disabled");
+				break;
+			case 26:
+				prepare_status(1, 0, "Writer messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_WRITER) ?
+					"enabled" : "disabled");
+				break;
+			case 27:
+				prepare_status(1, 0, "Memory messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_MEMORY) ?
+					"enabled" : "disabled");
+				break;
+			case 28:
+				prepare_status(1, 0, "Range messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_RANGES) ?
+					"enabled" : "disabled");
+				break;
+			case 29:
+				prepare_status(1, 0, "Memory pool messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_MEM_POOL) ?
+					"enabled" : "disabled");
+				break;
+			case 30:
+				prepare_status(1, 0, "Nosave messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_NOSAVE) ?
+					"enabled" : "disabled");
+				break;
+			case 31:
+				prepare_status(1, 0, "Integrity messages %s.",
+					TEST_DEBUG_STATE(SUSPEND_INTEGRITY) ?
+					"enabled" : "disabled");
+				break;
+			case 99:
+				prepare_status(1, 0, "Timer freezer disabled.");
+		}
+
+		waiting_messages = this_message->next;
+		kfree(this_message);
+	}
+	return did_work;
+}
+
+/* schedule_suspend_message
+ *
+ * Description:
+ * 
+ */
+
+void schedule_suspend_message(int message_number)
+{
+	struct waiting_message * new_message =
+		kmalloc(sizeof(struct waiting_message), GFP_ATOMIC);
+
+	if (!new_message) {
+		printk("Argh. Unable to allocate memory for "
+				"scheduling the display of a message.\n");
+		return;
+	}
+
+	new_message->message = message_number;
+	new_message->next = waiting_messages;
+
+	waiting_messages = new_message;
+	return;
+}
+
+/* request_abort_suspend
+ *
+ * Description:	Handle the user requesting the cancellation of a suspend by
+ * 		pressing escape. Note that on a second press, we try a little
+ * 		harder, attempting to forcefully thaw processes. This shouldn't
+ * 		been needed, and may result in an oops (if we've overwritten
+ * 		memory), but has been useful on ocassion.
+ * Callers:	Called from drivers/char/keyboard.c or drivers/char/serial.c
+ * 		when the user presses escape.
+ */
+void request_abort_suspend(void)
+{
+	if (test_suspend_state(SUSPEND_NOW_RESUMING) || (TEST_RESULT_STATE(SUSPEND_ABORT_REQUESTED)))
+		return;
+
+	if (TEST_RESULT_STATE(SUSPEND_ABORTED)) {
+		schedule_suspend_message(5);
+		show_state();
+		thaw_processes(FREEZER_ALL_THREADS);
+	} else {
+		schedule_suspend_message(6);
+		SET_RESULT_STATE(SUSPEND_ABORTED);
+		SET_RESULT_STATE(SUSPEND_ABORT_REQUESTED);
+	}
+}
+
+/* check_shift_keys
+ * 
+ * Description:	Potentially pause and wait for the user to tell us to continue.
+ * 		We normally only pause when @pause is set.
+ * Arguments:	int pause: Whether we normally pause.
+ * 		char * message: The message to display. Not parameterised
+ * 		 because it's normally a constant.
+ */
+
+void check_shift_keys(int pause, char * message)
+{
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+	int displayed_message = 0, last_key = 0;
+	
+	display_suspend_messages();
+	while (last_key != 32 &&
+		((TEST_ACTION_STATE(SUSPEND_PAUSE) && pause) || 
+		 (TEST_ACTION_STATE(SUSPEND_SINGLESTEP)))) {
+		if (!displayed_message) {
+			prepare_status(1, 0, 
+			   "%s Press SPACE to continue.%s",
+			   message ? message : "",
+			   (TEST_ACTION_STATE(SUSPEND_SINGLESTEP)) ? 
+			   " Single step on." : "");
+			displayed_message = 1;
+		}
+		last_key = suspend_wait_for_keypress();
+		display_suspend_messages();
+	}
+#else
+	display_suspend_messages();
+#endif
+}
+
+extern asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, 
+		unsigned long arg);
+
+/* suspend2_prepare_console
+ *
+ * Description:	Prepare a console for use, save current settings.
+ * Returns:	Boolean: Whether an error occured. Errors aren't
+ * 		treated as fatal, but a warning is printed.
+ */
+int suspend2_prepare_console(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+
+	orig_loglevel = console_loglevel;
+	orig_default_message_loglevel = default_message_loglevel;
+	orig_kmsg = kmsg_redirect;
+
+	kmsg_redirect = fg_console + 1;
+	default_message_loglevel = 1;
+
+	if (!test_suspend_state(SUSPEND_NOW_RESUMING))
+		lastloglevel = console_loglevel = suspend_default_console_level;
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.prepare)
+			this_plugin->ops.ui.prepare();
+	}
+
+	return 0;
+}
+
+/* suspend2_restore_console
+ *
+ * Description: Restore the settings we saved above.
+ */
+
+void suspend2_cleanup_console(void)
+{
+	struct suspend_plugin_ops * this_plugin;
+
+	list_for_each_entry(this_plugin, &suspend_ui, ops.ui.ui_list) {
+		if (this_plugin->disabled)
+			continue;
+		if (this_plugin->ops.ui.cleanup)
+			this_plugin->ops.ui.cleanup();
+	}
+
+	suspend_default_console_level = console_loglevel;
+	console_loglevel = orig_loglevel;
+	lastloglevel = -1;
+	kmsg_redirect = orig_kmsg;
+	default_message_loglevel = orig_default_message_loglevel;
+	return;
+}
+
+/* post_resume_console_redraw(void)
+ *
+ * Description:	Redraw the console after copying the original kernel back.
+ */
+extern int console_blanked;
+
+void post_resume_console_redraw(void)
+{
+	console_blanked = fg_console + 1;
+	acquire_console_sem();
+	unblank_screen();
+	update_screen(fg_console);
+	release_console_sem();
+}
+
+#if defined(CONFIG_SOFTWARE_SUSPEND2) && defined(CONFIG_PROC_FS)
+/*
+ * User interface specific /proc/suspend entries.
+ */
+
+static struct suspend_proc_data proc_params[] = {
+	{ .filename			= "default_console_level",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_INTEGER,
+	  .data = {
+		  .integer = {
+			  .variable	= &suspend_default_console_level,
+			  .minimum	= 0,
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+			  .maximum	= 7,
+#else
+			  .maximum	= 1,
+#endif
+
+		  }
+	  }
+	},
+
+	{ .filename			= "enable_escape",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_BIT,
+	  .data = {
+		  .bit = {
+			  .bit_vector	= &suspend_action,
+			  .bit		= SUSPEND_CAN_CANCEL,
+		  }
+	  }
+	},
+
+#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG
+	{ .filename			= "debug_sections",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_UL,
+	  .data = {
+		  .ul = {
+			  .variable	= &suspend_debug_state,
+			  .minimum	= 0,
+			  .maximum	= 2 << 30,
+		  }
+	  }
+	},
+
+	{ .filename			= "log_everything",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_BIT,
+	  .data = {
+		  .bit = {
+			  .bit_vector	= &suspend_action,
+			  .bit		= SUSPEND_LOGALL,
+		  }
+	  }
+	},
+	  
+	{ .filename			= "pause_between_steps",
+	  .permissions			= PROC_RW,
+	  .type				= SUSPEND_PROC_DATA_BIT,
+	  .data = {
+		  .bit = {
+			  .bit_vector	= &suspend_action,
+			  .bit		= SUSPEND_PAUSE,
+		  }
+	  }
+	},
+#endif
+};
+
+/* suspend_console_proc_init
+ * Description: Boot time initialisation for user interface.
+ */
+void suspend_console_proc_init(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+	for (i=0; i< numfiles; i++)
+		suspend_register_procfile(&proc_params[i]);
+}
+
+void suspend_console_proc_exit(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+	for (i=0; i< numfiles; i++)
+		suspend_unregister_procfile(&proc_params[i]);
+}
+
+#endif
+EXPORT_SYMBOL(__suspend_message);
+EXPORT_SYMBOL(request_abort_suspend);
+EXPORT_SYMBOL(prepare_status);
+EXPORT_SYMBOL(update_status);
+EXPORT_SYMBOL(check_shift_keys);
+EXPORT_SYMBOL(abort_suspend);
diff -ruN 20-old/kernel/power/utility.c 20-new/kernel/power/utility.c
--- 20-old/kernel/power/utility.c	1970-01-01 10:00:00.000000000 +1000
+++ 20-new/kernel/power/utility.c	2004-11-24 15:24:22.000000000 +1100
@@ -0,0 +1,152 @@
+/*
+ * kernel/power/utility.c
+ *
+ * Copyright (C) 2004 Nigel Cunningham <ncunningham@linuxmail.org>
+ * 
+ * This file is released under the GPLv2.
+ *
+ * Routines that only suspend uses at the moment, but which might move
+ * when we merge because they're generic.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/proc_fs.h>
+#include <asm/string.h>
+
+#include "pageflags.h"
+
+extern int suspend_snprintf(char * buffer, int buffer_size, const char *fmt, ...);
+extern struct proc_dir_entry * find_proc_dir_entry(const char *name, struct proc_dir_entry *parent);
+
+/*
+ * suspend_snprintf
+ *
+ * Functionality    : Print a string with parameters to a buffer of a 
+ *                    limited size. Unlike vsnprintf, we return the number
+ *                    of bytes actually put in the buffer, not the number
+ *                    that would have been put in if it was big enough.
+ */
+int suspend_snprintf(char * buffer, int buffer_size, const char *fmt, ...)
+{
+	int result;
+	va_list args;
+
+	if (!buffer_size) {
+		return 0;
+	}
+
+	va_start(args, fmt);
+	result = vsnprintf(buffer, buffer_size, fmt, args);
+	va_end(args);
+
+	if (result > buffer_size) {
+		return buffer_size;
+	}
+
+	return result;
+}
+
+/* 
+ * find_proc_dir_entry.
+ *
+ * Based on remove_proc_entry.
+ * This will go shortly, once user space utilities
+ * are updated to look at /proc/suspend/all_settings.
+ */
+
+struct proc_dir_entry * find_proc_dir_entry(const char *name, struct proc_dir_entry *parent)
+{
+	struct proc_dir_entry **p;
+	int len;
+
+	len = strlen(name);
+	for (p = &parent->subdir; *p; p=&(*p)->next ) {
+		if (proc_match(len, name, *p)) {
+			return *p;
+		}
+	}
+	return NULL;
+}
+
+/* ------------- Dynamically Allocated Page Flags --------------- */
+
+#define BITS_PER_PAGE (PAGE_SIZE * 8)
+#define PAGES_PER_BITMAP ((max_mapnr + BITS_PER_PAGE - 1) / BITS_PER_PAGE)
+#define BITMAP_ORDER (get_bitmask_order((PAGES_PER_BITMAP) - 1))
+
+/* clear_map
+ *
+ * Description:	Clear an array used to store local page flags.
+ * Arguments:	unsigned long *:	The pagemap to be cleared.
+ */
+
+void clear_map(unsigned long * pagemap)
+{
+	int size = (1 << BITMAP_ORDER) * PAGE_SIZE;
+	
+	memset(pagemap, 0, size);
+}
+
+/* allocate_local_pageflags
+ *
+ * Description:	Allocate a bitmap for local page flags.
+ * Arguments:	unsigned long **:	Pointer to the bitmap.
+ * 		int:			Whether to set nosave flags for the
+ * 					newly allocated pages.
+ * Note:	This looks suboptimal, but remember that we might be allocating
+ * 		the Nosave bitmap here.
+ */
+int allocate_local_pageflags(unsigned long ** pagemap, int setnosave)
+{
+	unsigned long * check;
+	int i;
+	if (*pagemap) {
+		printk("Error. Local pageflags map already allocated.\n");
+		clear_map(*pagemap);
+	} else {
+		check = (unsigned long *) __get_free_pages(GFP_ATOMIC,
+				BITMAP_ORDER);
+		if (!check) {
+			printk("Error. Unable to allocate memory for local page flags.");
+			return 1;
+		}
+		clear_map(check);
+		*pagemap = check;
+		if (setnosave) {
+			struct page * firstpage = 
+				virt_to_page((unsigned long) check);
+			for (i = 0; i < (1 << BITMAP_ORDER); i++)
+				SetPageNosave(firstpage + i);
+		}
+	}
+	return 0;
+}
+
+/* freemap
+ *
+ * Description:	Free a local pageflags bitmap.
+ * Arguments:	unsigned long **: Pointer to the bitmap being freed.
+ * Note:	Map being freed might be Nosave.
+ */
+int free_local_pageflags(unsigned long ** pagemap)
+{
+	int i;
+	if (!*pagemap)
+		return 1;
+	else {
+		struct page * firstpage =
+			virt_to_page((unsigned long) *pagemap);
+		for (i = 0; i < (1 << BITMAP_ORDER); i++)
+			ClearPageNosave(firstpage + i);
+		free_pages((unsigned long) *pagemap, BITMAP_ORDER);
+		*pagemap = NULL;
+		return 0;
+	}
+}
+
+EXPORT_SYMBOL(suspend_snprintf);
+EXPORT_SYMBOL(allocate_local_pageflags);
+EXPORT_SYMBOL(free_local_pageflags);
+EXPORT_SYMBOL(find_proc_dir_entry);
diff -ruN 20-old/kernel/printk.c 20-new/kernel/printk.c
--- 20-old/kernel/printk.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/kernel/printk.c	2004-11-24 15:24:22.000000000 +1100
@@ -64,6 +64,7 @@
 	MINIMUM_CONSOLE_LOGLEVEL,	/* minimum_console_loglevel */
 	DEFAULT_CONSOLE_LOGLEVEL,	/* default_console_loglevel */
 };
+EXPORT_SYMBOL(console_printk);
 
 int oops_in_progress;
 
@@ -548,6 +549,7 @@
 	if (must_wake_klogd && !oops_in_progress)
 		wake_up_interruptible(&log_wait);
 }
+EXPORT_SYMBOL(release_console_sem);
 
 /** console_conditional_schedule - yield the CPU if required
  *
@@ -714,3 +716,4 @@
 		tty->driver.write(tty, 0, msg, strlen(msg));
 	return;
 }
+
diff -ruN 20-old/kernel/sched.c 20-new/kernel/sched.c
--- 20-old/kernel/sched.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/kernel/sched.c	2004-11-24 15:24:22.000000000 +1100
@@ -23,12 +23,14 @@
 #include <linux/mm.h>
 #include <linux/init.h>
 #include <linux/smp_lock.h>
+#include <linux/suspend.h>
 #include <linux/nmi.h>
 #include <linux/interrupt.h>
 #include <linux/kernel_stat.h>
 #include <linux/completion.h>
 #include <linux/prefetch.h>
 #include <linux/compiler.h>
+#include <linux/module.h>
 
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
@@ -115,16 +117,28 @@
 struct kernel_stat kstat;
 extern struct task_struct *child_reaper;
 
+#ifdef CONFIG_PM
+#define TASK_FROZEN(p) ((p->flags & PF_FROZEN) || \
+			(test_suspend_state(SUSPEND_FREEZER_ON) && \
+	    		 (!(strcmp(p->comm, "dosexec")))))
+
+#else
+#define TASK_FROZEN(p) (0)
+#endif
+
+			 //(!(p->flags & PF_FREEZE)) &&
+			 //(p->pid) && \
+			 //(suspend_task != p->pid)))
 #ifdef CONFIG_SMP
 
 #define idle_task(cpu) (init_tasks[cpu_number_map(cpu)])
 #define can_schedule(p,cpu) \
-	((p)->cpus_runnable & (p)->cpus_allowed & (1UL << cpu))
+	((!TASK_FROZEN(p)) && ((p)->cpus_runnable & (p)->cpus_allowed & (1UL << cpu)))
 
 #else
 
 #define idle_task(cpu) (&init_task)
-#define can_schedule(p,cpu) (1)
+#define can_schedule(p,cpu) (!TASK_FROZEN(p))
 
 #endif
 
@@ -650,6 +664,9 @@
 	task_set_cpu(next, this_cpu);
 	spin_unlock_irq(&runqueue_lock);
 
+	if (unlikely(TASK_FROZEN(next)))
+		printk(KERN_EMERG "Scheduling suspended task %s!\n", next->comm);
+
 	if (unlikely(prev == next)) {
 		/* We won't go through the normal tail, so do this by hand */
 		prev->policy &= ~SCHED_YIELD;
@@ -1361,7 +1378,7 @@
 	return retval;
 }
 
-static void show_task(struct task_struct * p)
+void show_task(struct task_struct * p)
 {
 	unsigned long free = 0;
 	int state;
@@ -1453,6 +1470,7 @@
 	}
 	read_unlock(&tasklist_lock);
 }
+EXPORT_SYMBOL(show_state);
 
 /**
  * reparent_to_init() - Reparent the calling kernel thread to the init task.
diff -ruN 20-old/kernel/signal.c 20-new/kernel/signal.c
--- 20-old/kernel/signal.c	2004-03-05 14:35:19.000000000 +1100
+++ 20-new/kernel/signal.c	2004-11-24 15:24:22.000000000 +1100
@@ -492,7 +492,7 @@
  * No need to set need_resched since signal event passing
  * goes through ->blocked
  */
-static inline void signal_wake_up(struct task_struct *t)
+inline void signal_wake_up(struct task_struct *t)
 {
 	t->sigpending = 1;
 
diff -ruN 20-old/kernel/softirq.c 20-new/kernel/softirq.c
--- 20-old/kernel/softirq.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/kernel/softirq.c	2004-11-24 15:24:22.000000000 +1100
@@ -370,6 +370,7 @@
 
 	daemonize();
 	current->nice = 19;
+	current->flags |= PF_SYNCTHREAD;
 	sigfillset(&current->blocked);
 
 	/* Migrate to the right CPU */
@@ -387,6 +388,8 @@
 	for (;;) {
 		if (!softirq_pending(cpu))
 			schedule();
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 
 		__set_current_state(TASK_RUNNING);
 
@@ -394,6 +397,8 @@
 			do_softirq();
 			if (current->need_resched)
 				schedule();
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 		}
 
 		__set_current_state(TASK_INTERRUPTIBLE);
diff -ruN 20-old/kernel/sys.c 20-new/kernel/sys.c
--- 20-old/kernel/sys.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/kernel/sys.c	2004-11-24 15:24:22.000000000 +1100
@@ -4,6 +4,7 @@
  *  Copyright (C) 1991, 1992  Linus Torvalds
  */
 
+#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/mm.h>
 #include <linux/utsname.h>
@@ -14,6 +15,7 @@
 #include <linux/prctl.h>
 #include <linux/init.h>
 #include <linux/highuid.h>
+#include <linux/suspend.h>
 
 #include <asm/uaccess.h>
 #include <asm/io.h>
@@ -344,6 +346,17 @@
 		machine_restart(buffer);
 		break;
 
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	case LINUX_REBOOT_CMD_SW_SUSPEND:
+		if((test_suspend_state(SUSPEND_DISABLED)) ||
+		   (test_suspend_state(SUSPEND_RUNNING)))
+			return -EAGAIN;
+		
+		suspend_try_suspend();
+		do_exit(0);
+		break;
+#endif
+
 	default:
 		unlock_kernel();
 		return -EINVAL;
@@ -1292,3 +1305,5 @@
 EXPORT_SYMBOL(unregister_reboot_notifier);
 EXPORT_SYMBOL(in_group_p);
 EXPORT_SYMBOL(in_egroup_p);
+EXPORT_SYMBOL(sys_reboot);
+EXPORT_SYMBOL(C_A_D);
diff -ruN 20-old/kernel/timer.c 20-new/kernel/timer.c
--- 20-old/kernel/timer.c	2003-01-18 10:45:30.000000000 +1100
+++ 20-new/kernel/timer.c	2004-11-24 15:24:22.000000000 +1100
@@ -22,6 +22,7 @@
 #include <linux/smp_lock.h>
 #include <linux/interrupt.h>
 #include <linux/kernel_stat.h>
+#include <linux/module.h>
 
 #include <asm/uaccess.h>
 
@@ -874,3 +875,4 @@
 	return 0;
 }
 
+EXPORT_SYMBOL(avenrun);
diff -ruN 20-old/lib/Config.in 20-new/lib/Config.in
--- 20-old/lib/Config.in	2003-12-17 08:48:34.000000000 +1100
+++ 20-new/lib/Config.in	2004-11-24 15:24:22.000000000 +1100
@@ -11,6 +11,7 @@
 #
 if [ "$CONFIG_CRAMFS" = "y" -o \
      "$CONFIG_PPP_DEFLATE" = "y" -o \
+     "$CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION" = "y" -o \
      "$CONFIG_CRYPTO_DEFLATE" = "y" -o \
      "$CONFIG_JFFS2_FS" = "y" -o \
      "$CONFIG_ZISOFS_FS" = "y" ]; then
@@ -28,6 +29,7 @@
 fi
 
 if [ "$CONFIG_PPP_DEFLATE" = "y" -o \
+     "$CONFIG_SOFTWARE_SUSPEND_GZIP_COMPRESSION" = "y" -o \
      "$CONFIG_CRYPTO_DEFLATE" = "y" -o \
      "$CONFIG_JFFS2_FS" = "y" ]; then
    define_tristate CONFIG_ZLIB_DEFLATE y
diff -ruN 20-old/MAINTAINERS 20-new/MAINTAINERS
--- 20-old/MAINTAINERS	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/MAINTAINERS	2004-11-24 15:24:22.000000000 +1100
@@ -1733,6 +1733,13 @@
 L:	linux-raid@vger.kernel.org
 S:	Maintained
 
+SOFTWARE SUSPEND:
+P:	Nigel Cunningham
+M:	ncunningham@linuxmail.org
+L:	http://lists.sourceforge.net/lists/listinfo/swsusp-devel
+W:	http://swsusp.sf.net/
+S:	Maintained
+
 SONIC NETWORK DRIVER
 P:	Thomas Bogendoerfer
 M:	tsbogend@alpha.franken.de
diff -ruN 20-old/mm/filemap.c 20-new/mm/filemap.c
--- 20-old/mm/filemap.c	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/mm/filemap.c	2004-11-24 15:24:22.000000000 +1100
@@ -64,6 +64,8 @@
  */
 spinlock_cacheline_t pagemap_lru_lock_cacheline = {SPIN_LOCK_UNLOCKED};
 
+EXPORT_SYMBOL(pagemap_lru_lock_cacheline);
+
 #define CLUSTER_PAGES		(1 << page_cluster)
 #define CLUSTER_OFFSET(x)	(((x) >> page_cluster) << page_cluster)
 
diff -ruN 20-old/mm/highmem.c 20-new/mm/highmem.c
--- 20-old/mm/highmem.c	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/mm/highmem.c	2004-11-24 15:24:22.000000000 +1100
@@ -21,6 +21,7 @@
 #include <linux/highmem.h>
 #include <linux/swap.h>
 #include <linux/slab.h>
+#include <linux/suspend.h>
 
 /*
  * Virtual_count is not a pure "count".
@@ -74,7 +75,13 @@
 
 		page->virtual = NULL;
 	}
-	flush_tlb_all();
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	/* While suspending, we delay flushing tlbs on other processors. */
+	if (test_suspend_state(SUSPEND_FREEZE_SMP))
+		__flush_tlb();
+	else
+#endif
+		flush_tlb_all();
 }
 
 static inline unsigned long map_new_virtual(struct page *page, int nonblocking)
diff -ruN 20-old/mm/Makefile 20-new/mm/Makefile
--- 20-old/mm/Makefile	2003-01-18 10:44:27.000000000 +1100
+++ 20-new/mm/Makefile	2004-11-24 15:24:22.000000000 +1100
@@ -9,7 +9,7 @@
 
 O_TARGET := mm.o
 
-export-objs := shmem.o filemap.o memory.o page_alloc.o
+export-objs := shmem.o filemap.o memory.o page_alloc.o swapfile.o vmscan.o
 
 obj-y	 := memory.o mmap.o filemap.o mprotect.o mlock.o mremap.o \
 	    vmalloc.o slab.o bootmem.o swap.o vmscan.o page_io.o \
diff -ruN 20-old/mm/oom_kill.c 20-new/mm/oom_kill.c
--- 20-old/mm/oom_kill.c	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/mm/oom_kill.c	2004-11-24 15:24:22.000000000 +1100
@@ -20,6 +20,7 @@
 #include <linux/swap.h>
 #include <linux/swapctl.h>
 #include <linux/timex.h>
+#include <linux/suspend.h>
 
 /* #define DEBUG */
 
@@ -241,6 +242,9 @@
 	if (nr_swap_pages > 0)
 		return;
 
+	if (test_suspend_state(SUSPEND_RUNNING))
+		return;
+
 	spin_lock(&oom_lock);
 	now = jiffies;
 	since = now - last;
diff -ruN 20-old/mm/page_alloc.c 20-new/mm/page_alloc.c
--- 20-old/mm/page_alloc.c	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/mm/page_alloc.c	2004-11-24 15:24:22.000000000 +1100
@@ -20,6 +20,7 @@
 #include <linux/pagemap.h>
 #include <linux/bootmem.h>
 #include <linux/slab.h>
+#include <linux/suspend.h>
 #include <linux/module.h>
 
 int nr_swap_pages;
@@ -151,6 +152,12 @@
 	ClearPageReferenced(page);
 	ClearPageDirty(page);
 
+	if (!PageHighMem(page) &&
+		unlikely(test_suspend_state(SUSPEND_USE_MEMORY_POOL))) {
+		suspend2_free_pool_pages(page, order);
+		return;
+	}
+	
 	if (current->flags & PF_FREE_PAGES)
 		goto local_freelist;
  back_local_freelist:
@@ -377,6 +384,20 @@
 	struct page * page;
 	int freed, class_idx;
 
+	if (unlikely(test_suspend_state(SUSPEND_USE_MEMORY_POOL))) {
+		/*
+		 * When pool enabled, processes get allocations
+		 * from a special pool so the image size doesn't
+		 * vary (all the pages in the pool are saved, 
+		 * used or not).
+		 *
+		 * The only process that should be running is
+		 * suspend, so the demand should be very
+		 * predicatable.
+		 */
+		return suspend2_get_pool_pages(gfp_mask, order);
+	}
+	
 	zone = zonelist->zones;
 	classzone = *zone;
 	class_idx = zone_idx(classzone);
@@ -474,10 +495,15 @@
 	}
 
  out:
-	printk(KERN_NOTICE "__alloc_pages: %u-order allocation failed (gfp=0x%x/%i)\n",
-	       order, gfp_mask, !!(current->flags & PF_MEMALLOC));
-	if (unlikely(vm_gfp_debug))
-		dump_stack();
+#if CONFIG_PM
+	if (!test_suspend_state(SUSPEND_RUNNING))
+#endif
+	{
+		printk(KERN_NOTICE "__alloc_pages: %u-order allocation failed (gfp=0x%x/%i)\n",
+		       order, gfp_mask, !!(current->flags & PF_MEMALLOC));
+		if (unlikely(vm_gfp_debug))
+			dump_stack();
+	}
 	return NULL;
 }
 
@@ -590,6 +616,7 @@
 
 	return pages;
 }
+EXPORT_SYMBOL(nr_free_highpages);
 #endif
 
 #define K(x) ((x) << (PAGE_SHIFT-10))
@@ -967,3 +994,10 @@
 }
 
 __setup("lower_zone_reserve=", setup_lower_zone_reserve);
+
+EXPORT_SYMBOL(pgdat_list);
+EXPORT_SYMBOL(nr_active_pages);
+EXPORT_SYMBOL(nr_inactive_pages);
+EXPORT_SYMBOL(active_list);
+EXPORT_SYMBOL(inactive_list);
+EXPORT_SYMBOL(nr_free_pages);
diff -ruN 20-old/mm/slab.c 20-new/mm/slab.c
--- 20-old/mm/slab.c	2004-11-24 15:50:27.000000000 +1100
+++ 20-new/mm/slab.c	2004-11-24 15:24:22.000000000 +1100
@@ -75,6 +75,7 @@
 #include	<linux/init.h>
 #include	<linux/compiler.h>
 #include	<linux/seq_file.h>
+#include	<linux/suspend.h>
 #include	<asm/uaccess.h>
 
 /*
@@ -495,6 +496,12 @@
 	 */
 	flags |= cachep->gfpflags;
 	addr = (void*) __get_free_pages(flags, cachep->gfporder);
+#ifdef CONFIG_SOFTWARE_SUSPEND2
+	if (unlikely((!addr) && (current->pid == suspend_task) &&
+		    test_suspend_state(SUSPEND_SLAB_ALLOC_FALLBACK))) {
+		addr = (void *) suspend2_core_ops->get_grabbed_pages(0);
+	}
+#endif
 	/* Assume that now we have the pages no one else can legally
 	 * messes with the 'struct page's.
 	 * However vm_scan() might try to test the structure to see if
diff -ruN 20-old/mm/swapfile.c 20-new/mm/swapfile.c
--- 20-old/mm/swapfile.c	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/mm/swapfile.c	2004-11-24 15:24:22.000000000 +1100
@@ -14,6 +14,8 @@
 #include <linux/vmalloc.h>
 #include <linux/pagemap.h>
 #include <linux/shm.h>
+#include <linux/suspend.h>
+#include <linux/module.h>
 
 #include <asm/pgtable.h>
 
@@ -1264,3 +1266,13 @@
 	swap_device_unlock(swapdev);
 	return ret;
 }
+
+EXPORT_SYMBOL(remove_exclusive_swap_page);
+EXPORT_SYMBOL(swap_info);
+EXPORT_SYMBOL(get_swap_page);
+EXPORT_SYMBOL(swap_free);
+EXPORT_SYMBOL(sys_swapon);
+EXPORT_SYMBOL(sys_swapoff);
+EXPORT_SYMBOL(get_swaphandle_info);
+EXPORT_SYMBOL(si_swapinfo);
+EXPORT_SYMBOL(swaplock);
diff -ruN 20-old/mm/vmscan.c 20-new/mm/vmscan.c
--- 20-old/mm/vmscan.c	2004-11-24 15:50:28.000000000 +1100
+++ 20-new/mm/vmscan.c	2004-11-24 15:24:22.000000000 +1100
@@ -23,6 +23,7 @@
 #include <linux/init.h>
 #include <linux/highmem.h>
 #include <linux/file.h>
+#include <linux/module.h>
 
 #include <asm/pgalloc.h>
 
@@ -391,6 +392,9 @@
 	while (max_scan && classzone->nr_inactive_pages && (entry = inactive_list.prev) != &inactive_list) {
 		struct page * page;
 
+		if (unlikely(current->flags & PF_FREEZE))
+			break;
+
 		if (unlikely(current->need_resched)) {
 			spin_unlock(&pagemap_lru_lock);
 			__set_current_state(TASK_RUNNING);
@@ -643,6 +647,59 @@
 
 static int check_classzone_need_balance(zone_t * classzone);
 
+int try_to_free_pages_zone_suspend(zone_t *classzone, unsigned int gfp_mask, int nr_pages)
+{
+	gfp_mask = pf_gfp_mask(gfp_mask);
+	
+	for (;;) {
+		int tries = vm_passes;
+		int failed_swapout = !(gfp_mask & __GFP_IO);
+
+		do {
+			nr_pages = shrink_caches(classzone, gfp_mask, nr_pages, &failed_swapout);
+			if (nr_pages <= 0)
+				return 1;
+			shrink_dcache_memory(vm_vfs_scan_ratio, gfp_mask);
+			shrink_icache_memory(vm_vfs_scan_ratio, gfp_mask);
+#ifdef CONFIG_QUOTA
+			shrink_dqcache_memory(vm_vfs_scan_ratio, gfp_mask);
+#endif
+			if (!failed_swapout)
+				failed_swapout = !swap_out(classzone);
+		} while (--tries);
+
+		if (likely(current->pid != 1))
+			break;
+		if (!check_classzone_need_balance(classzone))
+			break;
+
+		__set_current_state(TASK_RUNNING);
+		yield();
+	}
+	return 0;
+}
+
+int try_to_free_pages_suspend(int amount_needed)
+{
+	pg_data_t *pgdat;
+	zonelist_t *zonelist;
+	unsigned long pf_free_pages;
+	int error = 0;
+	unsigned int gfp_mask = GFP_KSWAPD;
+
+	pf_free_pages = current->flags & PF_FREE_PAGES;
+	current->flags &= ~PF_FREE_PAGES;
+
+	for_each_pgdat(pgdat) {
+		zonelist = pgdat->node_zonelists + (gfp_mask & GFP_ZONEMASK);
+		error |= try_to_free_pages_zone_suspend(zonelist->zones[0], gfp_mask, amount_needed);
+	}
+
+	current->flags |= pf_free_pages;
+	return error;
+}
+EXPORT_SYMBOL(try_to_free_pages_suspend);
+
 int fastcall try_to_free_pages_zone(zone_t *classzone, unsigned int gfp_mask)
 {
 	gfp_mask = pf_gfp_mask(gfp_mask);
@@ -752,7 +809,7 @@
 
 		for_each_pgdat(pgdat)
 			need_more_balance |= kswapd_balance_pgdat(pgdat);
-	} while (need_more_balance);
+	} while ((need_more_balance) && (!(current->flags & PF_FREEZE)));
 }
 
 static int kswapd_can_sleep_pgdat(pg_data_t * pgdat)
@@ -828,7 +885,9 @@
 		mb();
 		if (kswapd_can_sleep())
 			schedule();
-
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+		
 		__set_current_state(TASK_RUNNING);
 		remove_wait_queue(&kswapd_wait, &wait);
 
diff -ruN 20-old/net/bluetooth/bnep/core.c 20-new/net/bluetooth/bnep/core.c
--- 20-old/net/bluetooth/bnep/core.c	2004-08-20 12:40:01.000000000 +1000
+++ 20-new/net/bluetooth/bnep/core.c	2004-11-24 15:24:22.000000000 +1100
@@ -504,6 +504,8 @@
 		netif_wake_queue(dev);
 
 		schedule();
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 	}
 	set_current_state(TASK_RUNNING);
 	remove_wait_queue(sk->sleep, &wait);
diff -ruN 20-old/net/bluetooth/cmtp/core.c 20-new/net/bluetooth/cmtp/core.c
--- 20-old/net/bluetooth/cmtp/core.c	2003-08-26 07:00:28.000000000 +1000
+++ 20-new/net/bluetooth/cmtp/core.c	2004-11-24 15:24:22.000000000 +1100
@@ -318,6 +318,8 @@
 		cmtp_process_transmit(session);
 
 		schedule();
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 	}
 	set_current_state(TASK_RUNNING);
 	remove_wait_queue(sk->sleep, &wait);
diff -ruN 20-old/net/bluetooth/rfcomm/core.c 20-new/net/bluetooth/rfcomm/core.c
--- 20-old/net/bluetooth/rfcomm/core.c	2004-11-24 15:05:27.000000000 +1100
+++ 20-new/net/bluetooth/rfcomm/core.c	2004-11-24 15:24:22.000000000 +1100
@@ -1735,6 +1735,8 @@
 			 * Incomming connections and data will wake us up. */
 			set_current_state(TASK_INTERRUPTIBLE);
 			schedule();
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 		}
 
 		/* Process stuff */
diff -ruN 20-old/net/khttpd/main.c 20-new/net/khttpd/main.c
--- 20-old/net/khttpd/main.c	2003-01-18 10:45:34.000000000 +1100
+++ 20-new/net/khttpd/main.c	2004-11-24 15:24:22.000000000 +1100
@@ -156,6 +156,9 @@
 				UpdateCurrentDate();
 		}
 			
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+
 		if (signal_pending(current)!=0)
 		{
 			(void)printk(KERN_NOTICE "kHTTPd: Ring Ring - signal received\n");
@@ -220,6 +223,9 @@
 			current->state = TASK_INTERRUPTIBLE;
 			interruptible_sleep_on_timeout(&WQ,HZ); 
 		}
+
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if ( (signal_pending(current)) || (sysctl_khttpd_unload!=0) )
 		 	break;
 		sysctl_khttpd_stop = 0;
@@ -277,11 +283,16 @@
 		{
 			/* Used to restart dead threads here, but it was buggy*/
 			interruptible_sleep_on_timeout(&WQ,HZ);
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 		}
 		
 		/* Wait for the daemons to stop, one second per iteration */
-		while (atomic_read(&DaemonCount)>0)
+		while (atomic_read(&DaemonCount)>0) {
 			interruptible_sleep_on_timeout(&WQ,HZ);
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
+		}
 		StopListening();
 		sysctl_khttpd_start = 0;
 		/* reap the zombie-daemons */
@@ -294,8 +305,11 @@
 	atomic_inc(&khttpd_stopCount);
 
 	/* Wait for the daemons to stop, one second per iteration */
-	while (atomic_read(&DaemonCount)>0)
+	while (atomic_read(&DaemonCount)>0) {
  		interruptible_sleep_on_timeout(&WQ,HZ);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
+	}
 	StopListening();
 	/* reap the zombie-daemons */
 	do
diff -ruN 20-old/net/sunrpc/sched.c 20-new/net/sunrpc/sched.c
--- 20-old/net/sunrpc/sched.c	2003-06-21 08:16:56.000000000 +1000
+++ 20-new/net/sunrpc/sched.c	2004-11-24 15:24:22.000000000 +1100
@@ -997,12 +997,22 @@
 
 	strcpy(current->comm, "rpciod");
 
+	current->flags |= PF_SYNCTHREAD;
+	
 	dprintk("RPC: rpciod starting (pid %d)\n", rpciod_pid);
 	while (rpciod_users) {
+#ifdef CONFIG_PREEMPT
+		preempt_disable();
+#endif
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 		if (signalled()) {
 			rpciod_killall();
 			flush_signals(current);
 		}
+#ifdef CONFIG_PREEMPT
+		preempt_enable();
+#endif
 		__rpc_schedule();
 
 		if (++rounds >= 64) {	/* safeguard */
@@ -1013,6 +1023,8 @@
 		if (!rpciod_task_pending()) {
 			dprintk("RPC: rpciod back to sleep\n");
 			wait_event_interruptible(rpciod_idle, rpciod_task_pending());
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
 			dprintk("RPC: switch to rpciod\n");
 			rounds = 0;
 		}
diff -ruN 20-old/net/sunrpc/svcsock.c 20-new/net/sunrpc/svcsock.c
--- 20-old/net/sunrpc/svcsock.c	2003-12-17 08:48:36.000000000 +1100
+++ 20-new/net/sunrpc/svcsock.c	2004-11-24 15:24:22.000000000 +1100
@@ -1090,6 +1090,8 @@
 		spin_unlock_bh(&serv->sv_lock);
 
 		schedule_timeout(timeout);
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_FREEZE);
 
 		spin_lock_bh(&serv->sv_lock);
 		remove_wait_queue(&rqstp->rq_wait, &wait);
