Index: linuxx/CREDITS
diff -u linuxx/CREDITS:1.1.1.1.6.3 linuxx/CREDITS:1.1.1.1.10.1.6.3
--- linuxx/CREDITS:1.1.1.1.6.3	Thu Feb  6 07:39:22 2003
+++ linuxx/CREDITS	Thu Feb  6 13:18:14 2003
@@ -499,6 +499,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/
@@ -990,6 +998,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
@@ -1722,6 +1737,11 @@
 S: Schlehenweg 9
 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
Index: linuxx/MAINTAINERS
diff -u linuxx/MAINTAINERS:1.1.1.1.6.5 linuxx/MAINTAINERS:1.1.1.1.6.1.2.1.6.4
--- linuxx/MAINTAINERS:1.1.1.1.6.5	Fri Feb 28 22:08:33 2003
+++ linuxx/MAINTAINERS	Fri Feb 28 22:43:41 2003
@@ -1498,6 +1498,14 @@
 L:	linux-raid@vger.kernel.org
 S:	Maintained
 
+SOFTWARE SUSPEND:
+P:	Pavel Machek
+M:	pavel@suse.cz
+M:	pavel@ucw.cz
+L:	http://lister.fornax.hu/mailman/listinfo/swsusp
+W:	http://swsusp.sf.net/
+S:	Maintained
+
 SONIC NETWORK DRIVER
 P:	Thomas Bogendoerfer
 M:	tsbogend@alpha.franken.de
Index: linuxx/Documentation/Configure.help
diff -u linuxx/Documentation/Configure.help:1.1.1.1.6.4 linuxx/Documentation/Configure.help:1.1.1.1.6.1.2.1.6.3
--- linuxx/Documentation/Configure.help:1.1.1.1.6.4	Fri Feb 28 22:08:33 2003
+++ linuxx/Documentation/Configure.help	Fri Feb 28 22:43:43 2003
@@ -109,6 +109,31 @@
   like MGA monitors that you are very unlikely to see on today's
   systems.
 
+Software Suspend
+CONFIG_SOFTWARE_SUSPEND
+  Enable the possibilty of suspendig machine. It doesn't need APM.
+  You may suspend your machine by either pressing Sysrq-d or with
+  'swsusp' or 'shutdown -z <time>' (patch for sysvinit needed). It
+  creates an image which is saved in your active swaps. By the next
+  booting the kernel detects the saved image, restores the memory from
+  it and then it continues to run as before you've suspended.
+  If you don't want the previous state to continue use the 'noresume'
+  kernel option. However note that your partitions will be fsck'd and
+  you must re-mkswap your swap partitions/files.
+
+  Right now you may boot without resuming and then later resume but
+  in meantime you cannot use those swap partitions/files which were
+  involved in suspending. Also in this case there is a risk that buffers
+  on disk won't match with saved ones.
+
+  SMP is supported ``as-is''. There's a code for it but doesn't work.
+  There have been problems reported relating SCSI.
+  
+  This option is about getting stable. However there is still some
+  absence of features.
+
+  For more information take a look at Documentation/swsusp.txt.
+
 Symmetric Multi-Processing support
 CONFIG_SMP
   This enables support for systems with more than one CPU. If you have
Index: linuxx/Documentation/kernel-parameters.txt
diff -u linuxx/Documentation/kernel-parameters.txt:1.1.1.1.6.2 linuxx/Documentation/kernel-parameters.txt:1.1.1.1.10.1.6.2
--- linuxx/Documentation/kernel-parameters.txt:1.1.1.1.6.2	Fri Feb 28 22:08:34 2003
+++ linuxx/Documentation/kernel-parameters.txt	Fri Feb 28 22:43:45 2003
@@ -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.
@@ -413,6 +414,8 @@
 			initial RAM disk.
 
 	nointroute	[IA-64]
+
+	noresume	[SWSUSP] Disables resume and restore original swap space.
  
 	no-scroll	[VGA]
 
@@ -531,6 +534,8 @@
 	reboot=		[BUGS=IA-32]
 
 	reserve=	[KNL,BUGS] force the kernel to ignore some iomem area.
+
+	resume=		[SWSUSP] specify the partition device for software suspension.
 
 	riscom8=	[HW,SERIAL]
 
Index: linuxx/Documentation/swsusp.txt
diff -u /dev/null linuxx/Documentation/swsusp.txt:1.1.6.1
--- /dev/null	Fri Feb 28 23:45:45 2003
+++ linuxx/Documentation/swsusp.txt	Thu Nov 28 20:22:42 2002
@@ -0,0 +1,191 @@
+From kernel/suspend.c:
+
+ * BIG FAT WARNING *********************************************************
+ *
+ * If you have unsupported (*) devices using DMA...
+ *				...say goodbye to your data.
+ *
+ * If you touch anything on disk between suspend and resume...
+ *				...kiss your data goodbye.
+ *
+ * If your disk driver does not support suspend... (IDE does)
+ *				...you'd better find out how to get along
+ *				   without your data.
+ *
+ * (*) pm interface support is needed to make it safe.
+
+You need to append resume=/dev/your_swap_partition to kernel command
+line. Then you suspend by echo "1 0 0" > /proc/sys/kernel/swsusp.
+
+
+The three integers obtained by cat /proc/sys/kernel/swsusp have the
+following meanings:
+State:
+-----
+Always zero on reading. If set to non zero value, cause the
+machine to software suspend (if possible).
+		   bit 0: off = normal state, on = suspend required
+		   bit 1: reserved for future use
+Action:
+------
+The value is an OR of the following flags:
+		   bit 0: off = halt, on = reboot 
+		   bit 1: off = eatmem, on = freemem
+		   bit 2: reserved for future use
+		   bit 3: reserved for future use
+Debug:
+-----
+If compiled with debug enabled in kernel/suspend.c the value is
+an OR of following flags:
+		   bit 0: debugging messages
+		   bit 1: display progression
+		   bit 2: very verbose mode
+		   bit 3: slow down process
+
+		      
+[Notice. Rest docs is pretty outdated (see date!) It should be safe to
+use swsusp on ext3/reiserfs these days.]
+
+
+Article about goals and implementation of Software Suspend for Linux
+Author: Gábor Kuti
+Last revised: 2002-04-08
+
+Idea and goals to achieve
+
+Nowadays it is common in several laptops that they have a suspend button. It
+saves the state of the machine to a filesystem or to a partition and switches
+to standby mode. Later resuming the machine the saved state is loaded back to
+ram and the machine can continue its work. It has two real benefits. First we
+save ourselves the time machine goes down and later boots up, energy costs
+real high when running from batteries. The other gain is that we don't have to
+interrupt our programs so processes that are calculating something for a long
+time shouldn't need to be written interruptible.
+
+On desk machines the power saving function isn't as important as it is in
+laptops but we really may benefit from the second one. Nowadays the number of
+desk machines supporting suspend function in their APM is going up but there
+are (and there will still be for a long time) machines that don't even support
+APM of any kind. On the other hand it is reported that using APM's suspend
+some irqs (e.g. ATA disk irq) is lost and it is annoying for the user until
+the Linux kernel resets the device.
+
+So I started thinking about implementing Software Suspend which doesn't need
+any APM support and - since it uses pretty near only high-level routines - is
+supposed to be architecture independent code.
+
+Using the code
+
+The code is experimental right now - testers, extra eyes are welcome. To
+compile this support into the kernel, you need CONFIG_EXPERIMENTAL, 
+and then CONFIG_SOFTWARE_SUSPEND in menu General Setup to be  enabled. It
+cannot be used as a module and I don't think it will ever be needed.
+
+You have two ways to use this code. The first one is if you've compiled in
+sysrq support then you may press Sysrq-D to request suspend. The other way
+is with a patched SysVinit (my patch is against 2.76 and available at my
+home page). You might call 'swsusp' or 'shutdown -z <time>'. Next way is to
+echo 4 > /proc/acpi/sleep.
+
+Either way it saves the state of the machine into active swaps and then
+reboots. You must explicitly specify the swap partition to resume from with ``resume=''
+kernel option. If signature is found it loads and restores saved state. If the
+option ``noresume'' is specified as a boot parameter, it skips the resuming.
+Warning! Look at section ``Things to implement'' to see what isn't yet
+implemented.  Also I strongly suggest you to list all active swaps in
+/etc/fstab. Firstly because you don't have to specify anything to resume and
+secondly if you have more than one swap area you can't decide which one has the
+'root' signature. 
+
+In the meantime while the system is suspended you should not touch any of the
+hardware!
+
+About the code
+Goals reached
+
+The code can be downloaded from
+http://falcon.sch.bme.hu/~seasons/linux/. It mainly works but there are still
+some of XXXs, TODOs, FIXMEs in the code which seem not to be too important. It
+should work all right except for the problems listed in ``Things to
+implement''. Notes about the code are really welcome.
+
+How the code works
+
+When suspending is triggered it immediately wakes up process bdflush. Bdflush
+checks whether we have anything in our run queue tq_bdflush. Since we queued up
+function do_software_suspend, it is called. Here we shrink everything including
+dcache, inodes, buffers and memory (here mainly processes are swapped out). We
+count how many pages we need to duplicate (we have to be atomical!) then we
+create an appropiate sized page directory. It will point to the original and
+the new (copied) address of the page. We get the free pages by
+__get_free_pages() but since it changes state we have to be able to track it
+later so it also flips in a bit in page's flags (a new Nosave flag). We
+duplicate pages and then mark them as used (so atomicity is ensured). After
+this we write out the image to swaps, do another sync and the machine may
+reboot. We also save registers to stack.
+
+By resuming an ``inverse'' method is executed. The image if exists is loaded,
+loadling is either triggered by ``resume='' kernel option.  We
+change our task to bdflush (it is needed because if we don't do this init does
+an oops when it is waken up later) and then pages are copied back to their
+original location. We restore registers, free previously allocated memory,
+activate memory context and task information. Here we should restore hardware
+state but even without this the machine is restored and processes are continued
+to work. I think hardware state should be restored by some list (using
+notify_chain) and probably by some userland program (run-parts?) for users'
+pleasure. Check out my patch at the same location for the sysvinit patch.
+
+WARNINGS!
+- It does not like pcmcia cards. And this is logical: pcmcia cards need cardmgr to be
+  initialized. they are not initialized during singleuser boot, but "resumed" kernel does
+  expect them to be initialized. That leads to armagedon. You should eject any pcmcia cards
+  before suspending.
+
+Things to implement
+- SMP support. I've done an SMP support but since I don't have access to a kind
+  of this one I cannot test it. Please SMP people test it.  .. Tested it,
+  doesn't work. Had no time to figure out why. There is some mess with
+  interrupts AFAIK..
+- We should only make a copy of data related to kernel segment, since any
+  process data won't be changed.
+- Hardware state restoring.  Now there's support for notifying via the notify
+  chain, event handlers are welcome. Some devices may have microcodes loaded
+  into them. We should have event handlers for them as well.
+- We should support other architectures (There are really only some arch
+  related functions..)
+- We should also restore original state of swaps if the ``noresume'' kernel
+  option is specified.. Or do we need such a feature to save state for some
+  other time? Do we need some kind of ``several saved states''?  (Linux-HA
+  people?). There's been some discussion about checkpointing on linux-future.
+- Should make more sanity checks. Or are these enough?
+
+Not so important ideas for implementing
+
+- If a real time process is running then don't suspend the machine.
+- Support for power.conf file as in Solaris, autoshutdown, special
+  devicetypes support, maybe in sysctl.
+- Introduce timeout for SMP locking. But first locking ought to work :O
+- Pre-detect if we don't have enough swap space or free it instead of
+  calling panic.
+- Support for adding/removing hardware while suspended?
+- We should not free pages at the beginning so aggressively, most of them
+  go there anyway..
+- If X is active while suspending then by resuming calling svgatextmode
+  corrupts the virtual console of X.. (Maybe this has been fixed AFAIK).
+
+Drivers we support
+- IDE disks are okay
+- vesafb
+
+Drivers that need support
+- pc_keyb -- perhaps we can wait for vojtech's input patches
+- do IDE cdroms need some kind of support?
+- IDE CD-RW -- how to deal with that?
+
+Any other idea you might have tell me!
+
+Contacting the author
+If you have any question or any patch that solves the above or detected
+problems please contact me at seasons@falcon.sch.bme.hu. I might delay
+answering, sorry about that.
+
Index: linuxx/Documentation/sysctl/kernel.txt
diff -u linuxx/Documentation/sysctl/kernel.txt:1.1.1.1 linuxx/Documentation/sysctl/kernel.txt:1.1.1.1.12.1
--- linuxx/Documentation/sysctl/kernel.txt:1.1.1.1	Thu Nov 28 14:06:38 2002
+++ linuxx/Documentation/sysctl/kernel.txt	Thu Nov 28 20:22:42 2002
@@ -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 ]
@@ -219,6 +220,29 @@
 Shared memory segments up to 1Gb are now supported in the 
 kernel.  This value defaults to SHMMAX.
 
+==============================================================
+
+swsusp:
+Three integers:
+State:
+-----
+If set to non zero value, cause the machine to software
+suspend (if possible).
+Action:
+------
+The value is an OR of the following flags:
+		   1: reboot machine after suspension, instead of
+		      halt it.
+		   2: just try to free memory instead of eating
+		      pages to force them to swap.
+Debug:
+-----
+If compiled with debug enabled in kernel/suspend.c the value is
+an OR of the following flags:
+		   1: debugging messages,
+		   2: adding delays to
+		      slow down suspension and be able to read
+		      all messages.
 ==============================================================
 
 tainted: 
Index: linuxx/arch/i386/config.in
diff -u linuxx/arch/i386/config.in:1.1.1.1.6.4 linuxx/arch/i386/config.in:1.1.1.1.6.1.2.1.6.3
--- linuxx/arch/i386/config.in:1.1.1.1.6.4	Fri Feb 28 22:08:40 2003
+++ linuxx/arch/i386/config.in	Fri Feb 28 22:43:59 2003
@@ -317,6 +317,10 @@
 
 bool 'Power Management support' CONFIG_PM
 
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+   dep_bool 'Software Suspend' CONFIG_SOFTWARE_SUSPEND $CONFIG_PM
+fi
+
 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
Index: linuxx/arch/i386/defconfig
diff -u linuxx/arch/i386/defconfig:1.1.1.1.6.1 linuxx/arch/i386/defconfig:1.1.1.1.10.1.6.1
--- linuxx/arch/i386/defconfig:1.1.1.1.6.1	Fri Nov 29 13:50:58 2002
+++ linuxx/arch/i386/defconfig	Tue Dec  3 18:32:02 2002
@@ -109,6 +109,7 @@
 CONFIG_BINFMT_MISC=y
 CONFIG_PM=y
 # CONFIG_APM is not set
+# CONFIG_SOFTWARE_SUSPEND is not set
 
 #
 # Memory Technology Devices (MTD)
Index: linuxx/arch/i386/vmlinux.lds
diff -u linuxx/arch/i386/vmlinux.lds:1.1.1.1 linuxx/arch/i386/vmlinux.lds:1.1.1.1.12.1
--- linuxx/arch/i386/vmlinux.lds:1.1.1.1	Thu Nov 28 14:06:40 2002
+++ linuxx/arch/i386/vmlinux.lds	Thu Nov 28 20:22:43 2002
@@ -53,6 +53,12 @@
   __init_end = .;
 
   . = ALIGN(4096);
+  __nosave_begin = .;
+  .data_nosave : { *(.data.nosave) }
+  . = ALIGN(4096);
+  __nosave_end = .;
+
+  . = ALIGN(4096);
   .data.page_aligned : { *(.data.idt) }
 
   . = ALIGN(32);
Index: linuxx/arch/i386/kernel/apm.c
diff -u linuxx/arch/i386/kernel/apm.c:1.1.1.1.6.2 linuxx/arch/i386/kernel/apm.c:1.1.1.1.10.1.6.2
--- linuxx/arch/i386/kernel/apm.c:1.1.1.1.6.2	Thu Feb  6 07:39:43 2003
+++ linuxx/arch/i386/kernel/apm.c	Thu Feb  6 13:18:28 2003
@@ -1708,6 +1708,7 @@
 	daemonize();
 
 	strcpy(current->comm, "kapmd");
+	current->flags |= PF_IOTHREAD;
 	sigfillset(&current->blocked);
 
 #ifdef CONFIG_SMP
Index: linuxx/arch/i386/kernel/mtrr.c
diff -u linuxx/arch/i386/kernel/mtrr.c:1.1.1.1.6.1 linuxx/arch/i386/kernel/mtrr.c:1.1.1.1.12.1.6.5
--- linuxx/arch/i386/kernel/mtrr.c:1.1.1.1.6.1	Fri Feb 28 22:08:40 2003
+++ linuxx/arch/i386/kernel/mtrr.c	Fri Feb 28 22:44:01 2003
@@ -270,6 +270,7 @@
 #include <linux/init.h>
 #include <linux/smp.h>
 #include <linux/smp_lock.h>
+#include <linux/suspend.h>
 
 #include <asm/uaccess.h>
 #include <asm/io.h>
@@ -2296,6 +2297,68 @@
     init_table ();
     return 0;
 }   /*  End Function mtrr_init  */
+
+#ifdef SOFTWARE_SUSPEND_MTRR
+struct mtrr_suspend_state
+{
+     mtrr_type ltype;
+     unsigned long lbase, lsize;
+};
+/* We return a pointer ptr on an area of *ptr bytes
+   beginning at ptr+sizeof(int)
+   This buffer has to be saved in some way during suspension */
+int *mtrr_suspend(void)
+{
+     int i, max, len;
+     int *ptr;
+     static struct mtrr_suspend_state *mtrr_suspend_buffer=NULL;
+     
+     max = get_num_var_ranges ();
+     if(!mtrr_suspend_buffer)
+     {
+	  len = max * sizeof (struct mtrr_suspend_state) + sizeof(int);
+	  ptr = kmalloc (len, GFP_KERNEL);
+	  if (ptr == NULL)
+	       return(NULL);
+	  *ptr = len;
+	  ptr++;
+	  mtrr_suspend_buffer = (struct mtrr_suspend_state *)ptr;
+	  ptr--;
+     }
+     for (i = 0; i < max; ++i,mtrr_suspend_buffer++)
+	  (*get_mtrr) (i,
+		       &(mtrr_suspend_buffer->lbase),
+		       &(mtrr_suspend_buffer->lsize),
+		       &(mtrr_suspend_buffer->ltype));
+     return(ptr);
+}
+
+/* We restore mtrrs from buffer ptr */
+int mtrr_resume(int *ptr)
+{
+     int i, max, len;
+     struct mtrr_suspend_state *mtrr_suspend_buffer;
+     
+     max = get_num_var_ranges ();
+     len = max * sizeof (struct mtrr_suspend_state) + sizeof(int);
+     if(*ptr != len)
+     {
+	  printk ("mtrr: Resuming failed due to different number of MTRRs\n");
+	  return (-1);
+     }
+     ptr++;
+     mtrr_suspend_buffer=(struct mtrr_suspend_state *)ptr;
+     for (i = 0; i < max; ++i,mtrr_suspend_buffer++)     
+	  if (mtrr_suspend_buffer->lsize)	  
+	       set_mtrr(i,
+			mtrr_suspend_buffer->lbase,
+			mtrr_suspend_buffer->lsize,
+			mtrr_suspend_buffer->ltype);
+     return(0);
+}
+EXPORT_SYMBOL(mtrr_suspend);
+EXPORT_SYMBOL(mtrr_resume);
+#endif
 
 /*
  * Local Variables:
Index: linuxx/arch/i386/kernel/process.c
diff -u linuxx/arch/i386/kernel/process.c:1.1.1.1 linuxx/arch/i386/kernel/process.c:1.1.1.1.12.1
--- linuxx/arch/i386/kernel/process.c:1.1.1.1	Thu Nov 28 14:06:40 2002
+++ linuxx/arch/i386/kernel/process.c	Thu Nov 28 20:22:43 2002
@@ -739,6 +739,36 @@
 	}
 }
 
+#ifdef CONFIG_SOFTWARE_SUSPEND
+/**
+ *	swsusp_power_off	-	ask the BIOS to power off
+ *
+ *	Handle the power off sequence. This is the one piece of code we
+ *	will execute even on SMP machines. In order to deal with BIOS
+ *	bugs we support real mode APM BIOS power off calls. We also make
+ *	the SMP call on CPU0 as some systems will only honour this call
+ *	on their first cpu.
+ *
+ *	This piece of code is extracted from apm.c. We want to be able
+ *	to do this even if apm isn't activated.
+ */
+ 
+void swsusp_power_off(void)
+{
+	unsigned char	po_bios_call[] = {
+		0xb8, 0x00, 0x10,	/* movw  $0x1000,ax  */
+		0x8e, 0xd0,		/* movw  ax,ss       */
+		0xbc, 0x00, 0xf0,	/* movw  $0xf000,sp  */
+		0xb8, 0x07, 0x53,	/* movw  $0x5307,ax  */
+		0xbb, 0x01, 0x00,	/* movw  $0x0001,bx  */
+		0xb9, 0x03, 0x00,	/* movw  $0x0003,cx  */
+		0xcd, 0x15		/* int   $0x15       */
+	};
+
+	machine_real_restart(po_bios_call, sizeof(po_bios_call));
+}
+#endif
+
 asmlinkage int sys_fork(struct pt_regs regs)
 {
 	return do_fork(SIGCHLD, regs.esp, &regs, 0);
Index: linuxx/arch/i386/kernel/signal.c
diff -u linuxx/arch/i386/kernel/signal.c:1.1.1.1 linuxx/arch/i386/kernel/signal.c:1.1.1.1.12.1
--- linuxx/arch/i386/kernel/signal.c:1.1.1.1	Thu Nov 28 14:06:40 2002
+++ linuxx/arch/i386/kernel/signal.c	Thu Nov 28 20:22:43 2002
@@ -20,6 +20,7 @@
 #include <linux/stddef.h>
 #include <linux/tty.h>
 #include <linux/personality.h>
+#include <linux/suspend.h>
 #include <asm/ucontext.h>
 #include <asm/uaccess.h>
 #include <asm/i387.h>
@@ -595,6 +596,11 @@
 	if ((regs->xcs & 3) != 3)
 		return 1;
 
+	if (current->flags & PF_FREEZE) {
+		refrigerator(0);
+		goto no_signal;
+	}
+
 	if (!oldset)
 		oldset = &current->blocked;
 
@@ -702,6 +708,7 @@
 		return 1;
 	}
 
+ no_signal:
 	/* Did we come from a system call? */
 	if (regs->orig_eax >= 0) {
 		/* Restart the system call - no handlers present */
Index: linuxx/arch/i386/mm/pageattr.c
diff -u linuxx/arch/i386/mm/pageattr.c:1.1.6.1 linuxx/arch/i386/mm/pageattr.c:1.1.4.1.6.2
--- linuxx/arch/i386/mm/pageattr.c:1.1.6.1	Fri Nov 29 13:51:01 2002
+++ linuxx/arch/i386/mm/pageattr.c	Wed Feb  5 22:53:49 2003
@@ -15,7 +15,8 @@
 #define LARGE_PAGE_MASK (~(LARGE_PAGE_SIZE-1))
 #define LARGE_PAGE_SIZE (1UL << PMD_SHIFT)
 
-static inline pte_t *lookup_address(unsigned long address) 
+// Also used in kernel/suspend.c
+inline pte_t *lookup_address(unsigned long address) 
 { 
 	pmd_t *pmd;	
 	pgd_t *pgd = pgd_offset(&init_mm, address); 
Index: linuxx/drivers/acpi/system.c
diff -u linuxx/drivers/acpi/system.c:1.1.2.3 linuxx/drivers/acpi/system.c:1.1.2.1.2.1.6.3
--- linuxx/drivers/acpi/system.c:1.1.2.3	Fri Feb 28 22:08:56 2003
+++ linuxx/drivers/acpi/system.c	Fri Feb 28 22:44:44 2003
@@ -45,6 +45,7 @@
 #include <linux/mc146818rtc.h>
 #endif
 #endif
+#include <linux/suspend.h>
 
 
 #define _COMPONENT		ACPI_SYSTEM_COMPONENT
@@ -291,7 +292,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)
@@ -306,8 +307,12 @@
 				return AE_ERROR;
 			acpi_set_firmware_waking_vector((acpi_physical_address) acpi_wakeup_address);
 		} else
+#ifdef CONFIG_SOFTWARE_SUSPEND
+			swsusp=1;
+#else
 			/* We don't support S4 under 2.4.  Give up */
 			return AE_ERROR;
+#endif
 	}
 
 	acpi_enter_sleep_state_prep(state);
@@ -321,6 +326,10 @@
 	ACPI_FLUSH_CPU_CACHE();
 
 	/* perform OS-specific sleep actions */
+	if(swsusp) { /* we just ignore acpi architecture for the moment */
+		software_suspend_pending(); /* 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
@@ -328,6 +337,7 @@
 	 * no matter what.
 	 */
 	acpi_system_restore_state(state);
+	}
 	acpi_leave_sleep_state(state);
 
 	/* make sure interrupts are enabled */
@@ -371,10 +381,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) {
+			  	if(acpi_gbl_FACS->S4bios_f &&
+				   0 != acpi_gbl_FADT->smi_cmd)
+				  	p += sprintf(p, "(Bios) ");
+#ifdef CONFIG_SOFTWARE_SUSPEND
+				else
+					p += sprintf(p, "(swsusp) ");
+#endif
+			}
 		}
 	}
 	p += sprintf(p, "\n");
@@ -677,9 +692,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) {
+				if(acpi_gbl_FACS->S4bios_f &&
+				   acpi_gbl_FADT->smi_cmd != 0)
+					p += sprintf(p, "(Bios) ");
+#ifdef CONFIG_SOFTWARE_SUSPEND
+				else
+					p += sprintf(p, "(swsusp) ");
+#endif
+			}
 		}
 	}
 
@@ -1231,9 +1252,15 @@
 		case ACPI_STATE_S4:
 			if (acpi_gbl_FACS->S4bios_f &&
 			    0 != acpi_gbl_FADT->smi_cmd) {
-				printk(" S4bios");
+				printk(" (bios)");
 				system->states[i] = 1;
 			}
+#ifdef CONFIG_SOFTWARE_SUSPEND
+			else {
+				printk(" (swsusp)");
+				system->states[i] = 1;
+			}
+#endif
 			/* no break */
 		default: 
 			if (ACPI_SUCCESS(status)) {
Index: linuxx/drivers/block/ll_rw_blk.c
diff -u linuxx/drivers/block/ll_rw_blk.c:1.1.1.1.6.2 linuxx/drivers/block/ll_rw_blk.c:1.1.1.1.10.1.6.3
--- linuxx/drivers/block/ll_rw_blk.c:1.1.1.1.6.2	Thu Feb  6 07:40:21 2003
+++ linuxx/drivers/block/ll_rw_blk.c	Thu Feb  6 13:19:03 2003
@@ -1119,6 +1119,10 @@
 	if (!bh->b_end_io)
 		BUG();
 
+#if 0
+	if (suspend_device && (bh->b_rdev != suspend_device))
+		panic("Attempted to corrupt disk.");
+#endif
 	/* Test device size, when known. */
 	if (blk_size[major])
 		minorsize = blk_size[major][MINOR(bh->b_rdev)];
Index: linuxx/drivers/block/loop.c
diff -u linuxx/drivers/block/loop.c:1.1.1.1.6.2 linuxx/drivers/block/loop.c:1.1.1.1.10.1.6.2
--- linuxx/drivers/block/loop.c:1.1.1.1.6.2	Thu Feb  6 07:40:21 2003
+++ linuxx/drivers/block/loop.c	Thu Feb  6 13:19:03 2003
@@ -71,6 +71,7 @@
 #include <linux/smp_lock.h>
 #include <linux/swap.h>
 #include <linux/slab.h>
+#include <linux/suspend.h>
 
 #include <asm/uaccess.h>
 
@@ -581,7 +582,9 @@
 	atomic_inc(&lo->lo_pending);
 	spin_unlock_irq(&lo->lo_lock);
 
-	current->flags |= PF_NOIO;
+	current->flags |= PF_NOIO | PF_IOTHREAD; /* 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
Index: linuxx/drivers/char/console.c
diff -u linuxx/drivers/char/console.c:1.1.1.1.6.1 linuxx/drivers/char/console.c:1.1.1.1.10.1.6.1
--- linuxx/drivers/char/console.c:1.1.1.1.6.1	Fri Nov 29 13:52:30 2002
+++ linuxx/drivers/char/console.c	Tue Dec  3 18:33:28 2002
@@ -147,11 +147,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);
 
@@ -522,7 +522,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();
@@ -1389,7 +1389,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;
@@ -3020,6 +3020,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
Index: linuxx/drivers/char/agp/agpgart_be.c
diff -u linuxx/drivers/char/agp/agpgart_be.c:1.1.1.1.6.2 linuxx/drivers/char/agp/agpgart_be.c:1.1.1.1.10.1.6.3
--- linuxx/drivers/char/agp/agpgart_be.c:1.1.1.1.6.2	Thu Feb  6 07:40:26 2003
+++ linuxx/drivers/char/agp/agpgart_be.c	Thu Feb  6 13:19:08 2003
@@ -579,7 +579,7 @@
 
 	agp_bridge.gatt_table_real = (unsigned long *) table;
 	agp_gatt_table = (void *)table;
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND)
 	err = change_page_attr(virt_to_page(table), 1<<page_order, PAGE_KERNEL_NOCACHE);
 #endif
 	if (!err) 
@@ -612,6 +612,7 @@
 
 static void agp_generic_resume(void)
 {
+  	agp_bridge.configure();
 	return;
 }
 
@@ -651,7 +652,7 @@
 	 * from the table.
 	 */
 
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND)
 	change_page_attr(virt_to_page(agp_bridge.gatt_table_real), 1<<page_order, 
 			 PAGE_KERNEL);
 #endif
@@ -777,7 +778,7 @@
 	if (page == NULL) {
 		return 0;
 	}
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND)
 	if (change_page_attr(page, 1, PAGE_KERNEL_NOCACHE) < 0) {
 		__free_page(page); 
 		return 0;
@@ -799,7 +800,7 @@
 	}
 	
 	page = virt_to_page(pt);
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND)
 	change_page_attr(page, 1, PAGE_KERNEL); 
 #endif	
 	put_page(page);
@@ -1778,11 +1779,6 @@
 	return addr | agp_bridge.masks[0].mask;
 }
 
-static void intel_resume(void)
-{
-	intel_configure();
-}
-
 /* Setup function */
 static gatt_mask intel_generic_masks[] =
 {
@@ -1849,7 +1845,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;
@@ -2336,7 +2332,7 @@
 	}
 	SetPageReserved(virt_to_page(page_map->real));
 	CACHE_FLUSH();
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND)
 	err = change_page_attr(virt_to_page(page_map->real), 1, PAGE_KERNEL_NOCACHE);
 #endif
 	if (!err) 
@@ -2360,7 +2356,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_SUSPEND)
 	change_page_attr(virt_to_page(page_map->real), 1, PAGE_KERNEL);
 #endif
 	ClearPageReserved(virt_to_page(page_map->real));
@@ -3427,7 +3423,7 @@
 		return -ENOMEM;
 	}
 	SetPageReserved(virt_to_page(page_map->real));
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND)
 	err = change_page_attr(virt_to_page(page_map->real), 1, PAGE_KERNEL_NOCACHE);
 #endif
 	CACHE_FLUSH();
@@ -3451,7 +3447,7 @@
 
 static void serverworks_free_page_map(serverworks_page_map *page_map)
 {
-#ifdef CONFIG_X86
+#if defined(CONFIG_X86) && !defined(CONFIG_SOFTWARE_SUSPEND)
 	change_page_attr(virt_to_page(page_map->real),1,PAGE_KERNEL); 
 #endif
 	iounmap(page_map->remapped);
Index: linuxx/drivers/ide/ide-disk.c
diff -u linuxx/drivers/ide/ide-disk.c:1.1.1.1.6.2 linuxx/drivers/ide/ide-disk.c:1.1.1.1.10.1.6.7
--- linuxx/drivers/ide/ide-disk.c:1.1.1.1.6.2	Thu Feb  6 07:40:31 2003
+++ linuxx/drivers/ide/ide-disk.c	Thu Feb  6 13:19:12 2003
@@ -1830,64 +1830,69 @@
 	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("*");
-		schedule();
-	}
-	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;
+	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++)
+		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;
+		}
 	driver_blocked = 1;
-	if (ide_disks_busy())
+	if (ide_disks_busy(0))
 		panic("How did you get that request through?!");
 }
 
-/* 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 (!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;
+#if 0				/* CBD: not working on my configs. Necessary for others? */
+			if(resume) {
+			  	printk("ide_disk_unsuspend: %6s: reinit drive %p",
+				       ide_hwifs[i].name,hwgroup->drive);
+				ide_reinit_drive(hwgroup->drive);
+			}
+#endif
+		}
 	driver_blocked = 0;
 }
 
Index: linuxx/drivers/md/md.c
diff -u linuxx/drivers/md/md.c:1.1.1.1.6.3 linuxx/drivers/md/md.c:1.1.1.1.10.1.6.3
--- linuxx/drivers/md/md.c:1.1.1.1.6.3	Fri Feb 28 22:09:07 2003
+++ linuxx/drivers/md/md.c	Fri Feb 28 22:45:39 2003
@@ -3345,7 +3345,6 @@
 	struct md_list_head *tmp;
 	unsigned long last_check;
 
-
 	err = down_interruptible(&mddev->resync_sem);
 	if (err)
 		goto out_nolock;
@@ -3508,6 +3507,8 @@
 	mdp_disk_t *spare;
 	struct md_list_head *tmp;
 
+	current->flags |= PF_IOTHREAD;
+
 	printk(KERN_INFO "md: recovery thread got woken up ...\n");
 restart:
 	ITERATE_MDDEV(mddev,tmp) {
@@ -3714,6 +3715,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)
 {
@@ -4044,4 +4054,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");
Index: linuxx/drivers/md/raid1.c
diff -u linuxx/drivers/md/raid1.c:1.1.1.1.6.1 linuxx/drivers/md/raid1.c:1.1.1.1.10.1.6.2
--- linuxx/drivers/md/raid1.c:1.1.1.1.6.1	Fri Nov 29 13:52:47 2002
+++ linuxx/drivers/md/raid1.c	Wed Feb  5 22:53:49 2003
@@ -1163,6 +1163,7 @@
 
 	if (mddev->sb_dirty)
 		md_update_sb(mddev);
+	current->flags |= PF_IOTHREAD;
 
 	for (;;) {
 		md_spin_lock_irqsave(&retry_list_lock, flags);
@@ -1286,6 +1287,8 @@
 {
 	raid1_conf_t *conf = data;
 	mddev_t *mddev = conf->mddev;
+
+	current->flags |= PF_IOTHREAD;
 
 	if (!conf->resync_mirrors)
 		return;
Index: linuxx/drivers/md/raid5.c
diff -u linuxx/drivers/md/raid5.c:1.1.1.1 linuxx/drivers/md/raid5.c:1.1.1.1.12.1
--- linuxx/drivers/md/raid5.c:1.1.1.1	Thu Nov 28 14:07:09 2002
+++ linuxx/drivers/md/raid5.c	Thu Nov 28 20:22:50 2002
@@ -1291,6 +1291,8 @@
 
 	PRINTK("+++ raid5d active\n");
 
+	current->flags |= PF_IOTHREAD;
+
 	handled = 0;
 
 	if (mddev->sb_dirty)
@@ -1339,6 +1341,8 @@
 {
 	raid5_conf_t *conf = data;
 	mddev_t *mddev = conf->mddev;
+
+	current->flags |= PF_IOTHREAD;
 
 	if (!conf->resync_parity)
 		return;
Index: linuxx/drivers/net/8139too.c
diff -u linuxx/drivers/net/8139too.c:1.1.1.1.6.3 linuxx/drivers/net/8139too.c:1.1.1.1.10.1.6.4
--- linuxx/drivers/net/8139too.c:1.1.1.1.6.3	Fri Feb 28 22:09:14 2003
+++ linuxx/drivers/net/8139too.c	Fri Feb 28 22:45:54 2003
@@ -110,6 +110,7 @@
 #include <linux/mii.h>
 #include <linux/completion.h>
 #include <linux/crc32.h>
+#include <linux/suspend.h>
 #include <asm/io.h>
 #include <asm/uaccess.h>
 
@@ -1597,10 +1598,13 @@
 
 	strncpy (current->comm, dev->name, sizeof(current->comm) - 1);
 	current->comm[sizeof(current->comm) - 1] = '\0';
+	current->flags |= PF_REFRIGERATE;
 
 	while (1) {
 		timeout = next_tick;
 		do {
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_IOTHREAD);
 			timeout = interruptible_sleep_on_timeout (&tp->thr_wait, timeout);
 		} while (!signal_pending (current) && (timeout > 0));
 
Index: linuxx/drivers/net/eepro100.c
diff -u linuxx/drivers/net/eepro100.c:1.1.1.1.6.2 linuxx/drivers/net/eepro100.c:1.1.1.1.16.3
--- linuxx/drivers/net/eepro100.c:1.1.1.1.6.2	Thu Feb  6 07:40:59 2003
+++ linuxx/drivers/net/eepro100.c	Thu Feb  6 13:19:36 2003
@@ -524,6 +524,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);
Index: linuxx/drivers/net/pcnet32.c
diff -u linuxx/drivers/net/pcnet32.c:1.1.1.1.6.1 linuxx/drivers/net/pcnet32.c:1.1.1.1.12.1.6.1
--- linuxx/drivers/net/pcnet32.c:1.1.1.1.6.1	Fri Nov 29 13:52:54 2002
+++ linuxx/drivers/net/pcnet32.c	Tue Dec  3 18:34:12 2002
@@ -323,6 +323,9 @@
 	mii:1;				/* mii port available */
     struct net_device	*next;
     struct mii_if_info mii_if;
+#ifdef CONFIG_PM
+    u32 pm_state[16];
+#endif
     struct timer_list	watchdog_timer;
 };
 
@@ -1673,6 +1676,42 @@
     return -EOPNOTSUPP;
 }
 
+#ifdef CONFIG_PM
+static int pcnet32_suspend(struct pci_dev *pdev, u32 state)
+{
+	struct net_device *dev = pci_get_drvdata (pdev);
+	struct pcnet32_private *pc = (struct pcnet32_private *)dev->priv;
+
+	//printk("Suspending pcnet32...\n");
+#if 0
+	pci_save_state(pdev, pc->pm_state);
+	if (!netif_running(dev))
+		return 0;
+	netif_device_detach(dev);
+#endif
+	return 0;
+}
+
+static int pcnet32_resume(struct pci_dev *pdev)
+{
+	struct net_device *dev = pci_get_drvdata (pdev);
+	struct pcnet32_private *pc = (struct pcnet32_private *)dev->priv;
+
+	//printk("Resuming pcnet32...\n");
+#if 0
+	pci_restore_state(pdev, pc->pm_state);
+
+	if (!netif_running(dev))
+		return 0;
+
+	pcnet32_restart(dev,0x0042); /* based upon pcnet32_tx_timeout */
+	netif_device_attach(dev);
+	/*set_rx_mode(dev);  does that come from eepro100 ? */
+#endif
+	return 0;
+}
+#endif
+					    
 static void pcnet32_watchdog(struct net_device *dev)
 {
     struct pcnet32_private *lp = dev->priv;
@@ -1688,6 +1727,10 @@
     name:	DRV_NAME,
     probe:	pcnet32_probe_pci,
     id_table:	pcnet32_pci_tbl,
+#ifdef CONFIG_PM
+    suspend:	pcnet32_suspend,
+    resume:	pcnet32_resume
+#endif
 };
 
 MODULE_PARM(debug, "i");
Index: linuxx/drivers/net/via-rhine.c
diff -u linuxx/drivers/net/via-rhine.c:1.1.1.1.6.2 linuxx/drivers/net/via-rhine.c:1.1.1.1.10.1.6.2
--- linuxx/drivers/net/via-rhine.c:1.1.1.1.6.2	Thu Feb  6 07:41:08 2003
+++ linuxx/drivers/net/via-rhine.c	Thu Feb  6 13:19:37 2003
@@ -1795,12 +1795,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
 };
 
 
Index: linuxx/drivers/usb/hub.c
diff -u linuxx/drivers/usb/hub.c:1.1.1.1.6.1 linuxx/drivers/usb/hub.c:1.1.1.1.10.1.6.3
--- linuxx/drivers/usb/hub.c:1.1.1.1.6.1	Fri Nov 29 13:53:27 2002
+++ linuxx/drivers/usb/hub.c	Wed Feb  5 22:53:50 2003
@@ -16,6 +16,7 @@
 #include <linux/list.h>
 #include <linux/slab.h>
 #include <linux/smp_lock.h>
+#include <linux/suspend.h>
 #ifdef CONFIG_USB_DEBUG
 	#define DEBUG
 #else
@@ -915,11 +916,14 @@
 
 	/* Setup a nice name */
 	strcpy(current->comm, "khubd");
+	current->flags |= PF_REFRIGERATE;
 
 	/* Send me a signal to get me die (for debugging) */
 	do {
 		usb_hub_events();
 		wait_event_interruptible(khubd_wait, !list_empty(&hub_event_list)); 
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_IOTHREAD);
 	} while (!signal_pending(current));
 
 	dbg("usb_hub_thread exiting");
Index: linuxx/drivers/usb/storage/usb.c
diff -u linuxx/drivers/usb/storage/usb.c:1.1.1.1.6.1 linuxx/drivers/usb/storage/usb.c:1.1.1.1.10.1.6.1
--- linuxx/drivers/usb/storage/usb.c:1.1.1.1.6.1	Fri Nov 29 13:53:34 2002
+++ linuxx/drivers/usb/storage/usb.c	Tue Dec  3 18:35:23 2002
@@ -319,6 +319,7 @@
 	 */
 	exit_files(current);
 	current->files = init_task.files;
+	current->flags |= PF_IOTHREAD;
 	atomic_inc(&current->files->count);
 	daemonize();
 	reparent_to_init();
Index: linuxx/fs/buffer.c
diff -u linuxx/fs/buffer.c:1.1.1.1.6.3 linuxx/fs/buffer.c:1.1.1.1.10.1.6.5
--- linuxx/fs/buffer.c:1.1.1.1.6.3	Thu Feb  6 07:42:01 2003
+++ linuxx/fs/buffer.c	Thu Feb  6 13:20:06 2003
@@ -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>
@@ -2354,12 +2355,19 @@
 int brw_page(int rw, struct page *page, kdev_t dev, int b[], int size)
 {
 	struct buffer_head *head, *bh;
+#ifdef CONFIG_SUSPEND
+	int created_buffers = 0;
+#endif
 
 	if (!PageLocked(page))
 		panic("brw_page: page not locked for I/O");
 
-	if (!page->buffers)
-		create_empty_buffers(page, dev, size);
+	if (!page->buffers) {
+ 		create_empty_buffers(page, dev, size);
+#ifdef CONFIG_SUSPEND
+		created_buffers = 1;
+#endif
+}
 	head = bh = page->buffers;
 
 	/* Stage 1: lock all the buffers */
@@ -2377,6 +2385,12 @@
 		submit_bh(rw, bh);
 		bh = next;
 	} while (bh != head);
+
+#ifdef CONFIG_SUSPEND
+	if unlikely(suspend_task && created_buffers)	// We are in suspend-to-disk and want to free a new buffer once done.
+		suspend_swap_bh = head;
+#endif
+
 	return 0;
 }
 
@@ -2922,6 +2936,7 @@
 	tsk->session = 1;
 	tsk->pgrp = 1;
 	strcpy(tsk->comm, "bdflush");
+	tsk->flags |= PF_REFRIGERATE;
 
 	/* avoid getting signals */
 	spin_lock_irq(&tsk->sigmask_lock);
@@ -2953,6 +2968,8 @@
 				break;
 			ndirty -= NRSYNC;
 		}
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_IOTHREAD);
 		if (ndirty > 0 || bdflush_stop())
 			interruptible_sleep_on(&bdflush_wait);
 	}
@@ -2972,6 +2989,7 @@
 	tsk->session = 1;
 	tsk->pgrp = 1;
 	strcpy(tsk->comm, "kupdated");
+	tsk->flags |= PF_REFRIGERATE;
 
 	/* sigstop and sigcont will stop and wakeup kupdate */
 	spin_lock_irq(&tsk->sigmask_lock);
@@ -2985,6 +3003,8 @@
 	for (;;) {
 		/* update interval */
 		interval = bdf_prm.b_un.interval;
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_IOTHREAD);
 		if (interval) {
 			tsk->state = TASK_INTERRUPTIBLE;
 			schedule_timeout(interval);
Index: linuxx/fs/jbd/journal.c
diff -u linuxx/fs/jbd/journal.c:1.1.1.1.6.2 linuxx/fs/jbd/journal.c:1.1.1.1.10.1.6.3
--- linuxx/fs/jbd/journal.c:1.1.1.1.6.2	Thu Feb  6 07:42:05 2003
+++ linuxx/fs/jbd/journal.c	Thu Feb  6 13:20:08 2003
@@ -34,6 +34,7 @@
 #include <linux/init.h>
 #include <linux/mm.h>
 #include <linux/slab.h>
+#include <linux/suspend.h>
 #include <asm/uaccess.h>
 #include <linux/proc_fs.h>
 
@@ -212,6 +213,7 @@
 	spin_unlock_irq(&current->sigmask_lock);
 
 	sprintf(current->comm, "kjournald");
+	current->flags |= PF_REFRIGERATE;
 
 	/* Set up an interval timer which can be used to trigger a
            commit wakeup after the commit interval expires */
@@ -248,7 +250,15 @@
 		}
 
 		wake_up(&journal->j_wait_done_commit);
-		interruptible_sleep_on(&journal->j_wait_commit);
+		if (current->flags & PF_FREEZE) { /* The simpler the better. Flushing journal isn't a
+						     good idea, because that depends on threads that
+						     may be already stopped. */
+			jbd_debug(1, "Now suspending kjournald\n");
+			refrigerator(PF_IOTHREAD);
+			jbd_debug(1, "Resuming kjournald\n");						
+		} else		/* we assume on resume that commits are already there,
+				   so we don't sleep */
+			interruptible_sleep_on(&journal->j_wait_commit);
 
 		jbd_debug(1, "kjournald wakes\n");
 
Index: linuxx/fs/lockd/svc.c
diff -u linuxx/fs/lockd/svc.c:1.1.1.1.6.1 linuxx/fs/lockd/svc.c:1.1.1.1.12.1.6.2
--- linuxx/fs/lockd/svc.c:1.1.1.1.6.1	Thu Feb  6 07:42:08 2003
+++ linuxx/fs/lockd/svc.c	Thu Feb  6 13:20:12 2003
@@ -26,6 +26,7 @@
 #include <linux/slab.h>
 #include <linux/smp.h>
 #include <linux/smp_lock.h>
+#include <linux/suspend.h>
 
 #include <linux/sunrpc/types.h>
 #include <linux/sunrpc/stats.h>
@@ -95,6 +96,7 @@
 	daemonize();
 	reparent_to_init();
 	sprintf(current->comm, "lockd");
+	current->flags |= PF_REFRIGERATE;
 
 	/* Process request with signals blocked.  */
 	spin_lock_irq(&current->sigmask_lock);
@@ -121,7 +123,11 @@
 	while ((nlmsvc_users || !signalled()) && nlmsvc_pid == current->pid)
 	{
 		long timeout = MAX_SCHEDULE_TIMEOUT;
-		if (signalled()) {
+		if (current->flags & PF_FREEZE) { 
+			dprintk("Now suspending lockd\n");
+			refrigerator(PF_IOTHREAD);
+			dprintk("Now resuming lockd\n");
+		} else if (signalled()) {
 			spin_lock_irq(&current->sigmask_lock);
 			flush_signals(current);
 			spin_unlock_irq(&current->sigmask_lock);
Index: linuxx/fs/reiserfs/journal.c
diff -u linuxx/fs/reiserfs/journal.c:1.1.1.1.6.2 linuxx/fs/reiserfs/journal.c:1.1.1.1.10.1.6.4
--- linuxx/fs/reiserfs/journal.c:1.1.1.1.6.2	Thu Feb  6 07:42:11 2003
+++ linuxx/fs/reiserfs/journal.c	Thu Feb  6 13:20:13 2003
@@ -58,6 +58,7 @@
 #include <linux/stat.h>
 #include <linux/string.h>
 #include <linux/smp_lock.h>
+#include <linux/suspend.h>
 
 /* the number of mounted filesystems.  This is used to decide when to
 ** start and kill the commit thread
@@ -1871,6 +1872,7 @@
   spin_unlock_irq(&current->sigmask_lock);
 
   sprintf(current->comm, "kreiserfsd") ;
+  current->flags |= PF_REFRIGERATE;
   lock_kernel() ;
   while(1) {
 
@@ -1884,6 +1886,8 @@
       break ;
     }
     wake_up(&reiserfs_commit_thread_done) ;
+    if (current->flags & PF_FREEZE)
+      refrigerator(PF_IOTHREAD);
     interruptible_sleep_on_timeout(&reiserfs_commit_thread_wait, 5 * HZ) ;
   }
   unlock_kernel() ;
Index: linuxx/include/asm-generic/bitops.h
diff -u linuxx/include/asm-generic/bitops.h:1.1.1.1 linuxx/include/asm-generic/bitops.h:1.1.1.1.12.1
--- linuxx/include/asm-generic/bitops.h:1.1.1.1	Thu Nov 28 14:07:46 2002
+++ linuxx/include/asm-generic/bitops.h	Thu Nov 28 20:22:54 2002
@@ -51,6 +51,12 @@
 	return ((mask & *addr) != 0);
 }
 
+/*
+ * fls: find last bit set.
+ */
+
+#define fls(x) generic_fls(x)
+
 #ifdef __KERNEL__
 
 /*
Index: linuxx/include/asm-i386/bitops.h
diff -u linuxx/include/asm-i386/bitops.h:1.1.1.1.6.1 linuxx/include/asm-i386/bitops.h:1.1.1.1.10.1.6.1
--- linuxx/include/asm-i386/bitops.h:1.1.1.1.6.1	Fri Nov 29 13:54:13 2002
+++ linuxx/include/asm-i386/bitops.h	Tue Dec  3 18:36:07 2002
@@ -330,6 +330,12 @@
 	return word;
 }
 
+/*
+ * fls: find last bit set.
+ */
+
+#define fls(x) generic_fls(x)
+
 #ifdef __KERNEL__
 
 /**
Index: linuxx/include/asm-i386/mtrr.h
diff -u linuxx/include/asm-i386/mtrr.h:1.1.1.1 linuxx/include/asm-i386/mtrr.h:1.1.1.1.12.1
--- linuxx/include/asm-i386/mtrr.h:1.1.1.1	Thu Nov 28 14:07:46 2002
+++ linuxx/include/asm-i386/mtrr.h	Thu Nov 28 20:22:54 2002
@@ -89,6 +89,10 @@
 extern int mtrr_del (int reg, unsigned long base, unsigned long size);
 extern int mtrr_del_page (int reg, unsigned long base, unsigned long size);
 extern void mtrr_centaur_report_mcr(int mcr, u32 lo, u32 hi);
+#  ifdef CONFIG_SOFTWARE_SUSPEND
+extern int *mtrr_suspend(void);
+extern int mtrr_resume(int *ptr);
+#  endif
 #  else
 static __inline__ int mtrr_add (unsigned long base, unsigned long size,
 				unsigned int type, char increment)
Index: linuxx/include/asm-i386/processor.h
diff -u linuxx/include/asm-i386/processor.h:1.1.1.1 linuxx/include/asm-i386/processor.h:1.1.1.1.12.1
--- linuxx/include/asm-i386/processor.h:1.1.1.1	Thu Nov 28 14:07:46 2002
+++ linuxx/include/asm-i386/processor.h	Thu Nov 28 20:22:54 2002
@@ -83,6 +83,7 @@
 
 #define cpu_has_pge	(test_bit(X86_FEATURE_PGE,  boot_cpu_data.x86_capability))
 #define cpu_has_pse	(test_bit(X86_FEATURE_PSE,  boot_cpu_data.x86_capability))
+#define cpu_has_pse36	(test_bit(X86_FEATURE_PSE36,boot_cpu_data.x86_capability))
 #define cpu_has_pae	(test_bit(X86_FEATURE_PAE,  boot_cpu_data.x86_capability))
 #define cpu_has_tsc	(test_bit(X86_FEATURE_TSC,  boot_cpu_data.x86_capability))
 #define cpu_has_de	(test_bit(X86_FEATURE_DE,   boot_cpu_data.x86_capability))
Index: linuxx/include/asm-i386/suspend.h
diff -u /dev/null linuxx/include/asm-i386/suspend.h:1.1.6.1.6.2
--- /dev/null	Fri Feb 28 23:56:02 2003
+++ linuxx/include/asm-i386/suspend.h	Wed Feb  5 22:53:50 2003
@@ -0,0 +1,278 @@
+ /*
+  * Copyright 2001-2002 Pavel Machek <pavel@suse.cz>
+  * Based on code
+  * Copyright 2001 Patrick Mochel <mochel@osdl.org>
+  */
+#include <asm/desc.h>
+#include <asm/i387.h>
+
+/* image of the saved processor state */
+struct 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));
+
+struct saved_context saved_context;
+
+#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" (saved_context.gdt_limit));
+	asm volatile ("sidt (%0)" : "=m" (saved_context.idt_limit));
+	asm volatile ("sldt (%0)" : "=m" (saved_context.ldt));
+	asm volatile ("str (%0)"  : "=m" (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" (saved_context.esp));
+	asm volatile ("movl %%eax, (%0)" : "=m" (saved_context.eax));
+	asm volatile ("movl %%ebx, (%0)" : "=m" (saved_context.ebx));
+	asm volatile ("movl %%ecx, (%0)" : "=m" (saved_context.ecx));
+	asm volatile ("movl %%edx, (%0)" : "=m" (saved_context.edx));
+	asm volatile ("movl %%ebp, (%0)" : "=m" (saved_context.ebp));
+	asm volatile ("movl %%esi, (%0)" : "=m" (saved_context.esi));
+	asm volatile ("movl %%edi, (%0)" : "=m" (saved_context.edi));
+
+	/*
+	 * segment registers
+	 */
+	asm volatile ("movw %%es, %0" : "=r" (saved_context.es));
+	asm volatile ("movw %%fs, %0" : "=r" (saved_context.fs));
+	asm volatile ("movw %%gs, %0" : "=r" (saved_context.gs));
+	asm volatile ("movw %%ss, %0" : "=r" (saved_context.ss));
+
+	/*
+	 * control registers 
+	 */
+	asm volatile ("movl %%cr0, %0" : "=r" (saved_context.cr0));
+	asm volatile ("movl %%cr2, %0" : "=r" (saved_context.cr2));
+	asm volatile ("movl %%cr3, %0" : "=r" (saved_context.cr3));
+	asm volatile ("movl %%cr4, %0" : "=r" (saved_context.cr4));
+
+	/*
+	 * eflags
+	 */
+	asm volatile ("pushfl ; popl (%0)" : "=m" (saved_context.eflags));
+}
+
+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);	/* 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);
+	}
+
+}
+
+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).
+ */
+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" (saved_context.cr4));
+	asm volatile ("movl %0, %%cr3" :: "r" (saved_context.cr3));
+	asm volatile ("movl %0, %%cr2" :: "r" (saved_context.cr2));
+	asm volatile ("movl %0, %%cr0" :: "r" (saved_context.cr0));
+	
+	/*
+	 * segment registers
+	 */
+	asm volatile ("movw %0, %%es" :: "r" (saved_context.es));
+	asm volatile ("movw %0, %%fs" :: "r" (saved_context.fs));
+	asm volatile ("movw %0, %%gs" :: "r" (saved_context.gs));
+	asm volatile ("movw %0, %%ss" :: "r" (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" (saved_context.esp));
+	asm volatile ("movl %0, %%ebp" :: "m" (saved_context.ebp));
+	asm volatile ("movl %0, %%eax" :: "m" (saved_context.eax));
+	asm volatile ("movl %0, %%ebx" :: "m" (saved_context.ebx));
+	asm volatile ("movl %0, %%ecx" :: "m" (saved_context.ecx));
+	asm volatile ("movl %0, %%edx" :: "m" (saved_context.edx));
+	asm volatile ("movl %0, %%esi" :: "m" (saved_context.esi));
+	asm volatile ("movl %0, %%edi" :: "m" (saved_context.edi));
+
+	/*
+	 * now restore the descriptor tables to their proper values
+	 * ltr is done i fix_processor_context().
+	 */
+	asm volatile ("lgdt (%0)" :: "m" (saved_context.gdt_limit));
+	asm volatile ("lidt (%0)" :: "m" (saved_context.idt_limit));
+	asm volatile ("lldt (%0)" :: "m" (saved_context.ldt));
+
+	fix_processor_context();
+
+	/*
+	 * the flags
+	 */
+	asm volatile ("pushl %0 ; popfl" :: "m" (saved_context.eflags));
+
+	do_fpu_end();
+}
+
+#ifdef SUSPEND_C
+/* Local variables for do_magic */
+static int loop __nosavedata = 0;
+static int loop2 __nosavedata = 0;
+
+/*
+ * 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.
+ */
+static void do_suspend_lowlevel(int resume)
+{
+	if (!resume) {
+		do_magic_suspend_1();
+		save_processor_context();	/* We need to capture registers and memory at "same time" */
+		do_magic_suspend_2();		/* If everything goes okay, this function does not return */
+		return;
+	}
+
+	/* We want to run from swapper_pg_dir, since swapper_pg_dir is stored in constant
+	 * place in memory 
+	 */
+
+        __asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_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_magic_resume_1; copying works, because nr_copy_pages,
+ * pagedir_nosave, loop and loop2 are nosavedata.
+ */
+	do_magic_resume_1();
+
+	for (loop=0; loop < pageset1_size; loop++) {
+		/* You may not call something (like copy_page) here: see above */
+		for (loop2=0; loop2 < PAGE_SIZE; loop2++) {
+			*(((char *)(PAGEDIR_ENTRY(pagedir_nosave,loop)->address1))+loop2) =
+				*(((char *)(PAGEDIR_ENTRY(pagedir_nosave,loop)->address3))+loop2);
+			__flush_tlb();
+		}
+	}
+
+	restore_processor_context();
+
+/* Ahah, we now run with our old stack, and with registers copied from
+   suspend time */
+
+	do_magic_resume_2();
+}
+#endif 
Index: linuxx/include/linux/bitops.h
diff -u linuxx/include/linux/bitops.h:1.1.1.1 linuxx/include/linux/bitops.h:1.1.1.1.12.1
--- linuxx/include/linux/bitops.h:1.1.1.1	Thu Nov 28 14:07:56 2002
+++ linuxx/include/linux/bitops.h	Thu Nov 28 20:22:58 2002
@@ -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(int 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
  */
@@ -65,8 +106,6 @@
         res = (res & 0x33) + ((res >> 2) & 0x33);
         return (res & 0x0F) + ((res >> 4) & 0x0F);
 }
-
-#include <asm/bitops.h>
 
 
 #endif
Index: linuxx/include/linux/init.h
diff -u linuxx/include/linux/init.h:1.1.1.1 linuxx/include/linux/init.h:1.1.1.1.12.1
--- linuxx/include/linux/init.h:1.1.1.1	Thu Nov 28 14:07:56 2002
+++ linuxx/include/linux/init.h	Thu Nov 28 20:22:58 2002
@@ -111,6 +111,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
Index: linuxx/include/linux/mm.h
diff -u linuxx/include/linux/mm.h:1.1.1.1.6.1 linuxx/include/linux/mm.h:1.1.1.1.12.1.6.3
--- linuxx/include/linux/mm.h:1.1.1.1.6.1	Thu Feb  6 07:42:30 2003
+++ linuxx/include/linux/mm.h	Thu Feb  6 13:20:26 2003
@@ -298,6 +298,11 @@
 #define PG_reserved		14
 #define PG_launder		15	/* written out by VM pressure.. */
 #define PG_fs_1			16	/* Filesystem specific */
+#define PG_nosave		17      /* swsusp - used to be 16!! */
+#define PG_free			18	/* swsusp - page is unused - dont rely on outside swsusp! */
+#define PG_knowshared		19
+#define PG_dontcopy		20
+#define PG_collides		18	/* Reuse during start of resume */
 
 /* Make it prettier to test the above... */
 #define UnlockPage(page)	unlock_page(page)
@@ -315,6 +320,21 @@
 #define PageLaunder(page)	test_bit(PG_launder, &(page)->flags)
 #define SetPageLaunder(page)	set_bit(PG_launder, &(page)->flags)
 #define ClearPageLaunder(page)	clear_bit(PG_launder, &(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)
+#define PageFree(page)		test_bit(PG_free, &(page)->flags)
+#define SetPageFree(page)	set_bit(PG_free, &(page)->flags)
+#define ClearPageFree(page)	clear_bit(PG_free, &(page)->flags)
+#define PageKnowshared(page)	test_bit(PG_knowshared, &(page)->flags)
+#define SetPageKnowshared(page)	set_bit(PG_knowshared, &(page)->flags)
+#define ClearPageKnowshared(page)	clear_bit(PG_knowshared, &(page)->flags)
+#define PageDontcopy(page)	test_bit(PG_dontcopy, &(page)->flags)
+#define SetPageDontcopy(page)	set_bit(PG_dontcopy, &(page)->flags)
+#define ClearPageDontcopy(page)	clear_bit(PG_dontcopy, &(page)->flags)
+#define PageCollides(page)		test_bit(PG_collides, &(page)->flags)
+#define SetPageCollides(page)	set_bit(PG_collides, &(page)->flags)
+#define ClearPageCollides(page)	clear_bit(PG_collides, &(page)->flags)
 
 /*
  * The zone field is never updated after free_area_init_core()
@@ -407,6 +427,12 @@
 #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 PageSetNosave(page)	set_bit(PG_nosave, &(page)->flags)
+#define PageTestandSetNosave(page)	test_and_set_bit(PG_nosave, &(page)->flags)
+#define PageClearNosave(page)		clear_bit(PG_nosave, &(page)->flags)
+#define PageTestandClearNosave(page)	test_and_clear_bit(PG_nosave, &(page)->flags)
+
 /*
  * Error return values for the *_nopage functions
  */
@@ -600,6 +626,8 @@
 #define __GFP_IO	0x40	/* Can start low memory physical IO? */
 #define __GFP_HIGHIO	0x80	/* Can start high mem physical IO? */
 #define __GFP_FS	0x100	/* Can call down to low-level FS? */
+#define __GFP_FAST	0x200	/* fast return in reschedule if out
+				   of page (used in swsusp) */
 
 #define GFP_NOHIGHIO	(__GFP_HIGH | __GFP_WAIT | __GFP_IO)
 #define GFP_NOIO	(__GFP_HIGH | __GFP_WAIT)
Index: linuxx/include/linux/reboot.h
diff -u linuxx/include/linux/reboot.h:1.1.1.1 linuxx/include/linux/reboot.h:1.1.1.1.12.1
--- linuxx/include/linux/reboot.h:1.1.1.1	Thu Nov 28 14:07:56 2002
+++ linuxx/include/linux/reboot.h	Thu Nov 28 20:22:58 2002
@@ -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__
@@ -45,6 +47,13 @@
 extern void machine_restart(char *cmd);
 extern void machine_halt(void);
 extern void machine_power_off(void);
+
+/*
+ * Architecture-independent suspend facility
+ */
+
+extern void software_suspend_pending(void);
+extern unsigned char software_suspend_enabled;
 
 #endif
 
Index: linuxx/include/linux/sched.h
diff -u linuxx/include/linux/sched.h:1.1.1.1.6.2 linuxx/include/linux/sched.h:1.1.1.1.10.1.6.3
--- linuxx/include/linux/sched.h:1.1.1.1.6.2	Thu Feb  6 07:42:31 2003
+++ linuxx/include/linux/sched.h	Thu Feb  6 13:20:26 2003
@@ -435,6 +435,10 @@
 #define PF_MEMDIE	0x00001000	/* Killed for out-of-memory */
 #define PF_FREE_PAGES	0x00002000	/* per process page freeing */
 #define PF_NOIO		0x00004000	/* avoid generating further I/O */
+#define PF_FROZEN	0x00008000	/* frozen for system suspend */
+#define PF_FREEZE	0x00010000	/* trying to freeze this task for suspend */
+#define PF_IOTHREAD	0x00020000	/* this thread is needed for doing I/O to swap */
+#define PF_REFRIGERATE  0x00040000	/* this task should be frozen when a suspend occurs */
 
 #define PF_USEDFPU	0x00100000	/* task used FPU this quantum (SMP) */
 
Index: linuxx/include/linux/suspend.h
diff -u /dev/null linuxx/include/linux/suspend.h:1.1.6.1.6.4
--- /dev/null	Fri Feb 28 23:56:53 2003
+++ linuxx/include/linux/suspend.h	Wed Feb  5 22:53:51 2003
@@ -0,0 +1,101 @@
+#ifndef _LINUX_SWSUSP_H
+#define _LINUX_SWSUSP_H
+
+#if defined(SUSPEND_C) || defined(ACPI_C)
+#include <asm/suspend.h>
+#endif
+#include <linux/swap.h>
+#include <linux/notifier.h>
+#include <linux/config.h>
+
+extern unsigned char software_suspend_enabled;
+
+#define NORESUME	 1
+#define RESUME_SPECIFIED 2
+
+#ifdef CONFIG_SOFTWARE_SUSPEND
+extern int swsusp_state[5];
+
+/* page backup entry */
+typedef struct pbe {
+	unsigned long address1;		/* original address of pageset1 entry */
+	unsigned long address2;		/* original address of pageset2 entry */
+	unsigned long address3;		/* copy back address */
+	swp_entry_t swap_address1;
+	swp_entry_t swap_address2;
+	swp_entry_t dummy[3];		/* we need at least one sizeof(long) scratch space at 
+					 * end of page (see link, diskpage) and
+					 * sizeof(struct pbe) must be multiple of 4*sizeof(long)
+					 */
+} suspend_pagedir_t;
+
+#define SWAP_FILENAME_MAXLENGTH	32
+
+struct suspend_header {
+	__u32 version_code;
+	unsigned long num_physpages;
+	char machine[65];
+	char version[65];
+	int num_cpus;
+	int page_size;
+	unsigned long base_mem_free;
+	unsigned long swapforimage;
+	unsigned int expected_size1;
+	unsigned int expected_size2;
+	suspend_pagedir_t **suspend_pagedir;
+	unsigned int pageset1_size;
+	unsigned int pageset2_size;
+	unsigned int pagedir_size;
+	int param0;
+	int param1;
+	int param2;
+	int param3;
+	int param4;
+	struct swap_location {
+		char filename[SWAP_FILENAME_MAXLENGTH];
+	} swap_location[MAX_SWAPFILES];
+};
+
+#define SUSPEND_PD_PAGES(x)     (((x)*sizeof(struct pbe))/PAGE_SIZE+1)
+#define PAGEDIR_CAPACITY(x)     (((x)*PAGE_SIZE/sizeof(struct pbe)))
+#define PAGEDIR_ENTRY(pagedir, i) (pagedir[i/PAGEDIR_CAPACITY(1)] + (i%PAGEDIR_CAPACITY(1)))
+   
+extern struct tq_struct suspend_tq;
+
+/* mm/vmscan.c */
+extern int shrink_mem(void);
+
+/* kernel/suspend.c */
+extern void software_suspend_pending(void);
+extern void software_resume(void);
+
+extern int register_suspend_notifier(struct notifier_block *);
+extern int unregister_suspend_notifier(struct notifier_block *);
+extern void refrigerator(unsigned long);
+extern struct buffer_head * suspend_swap_bh;
+
+extern struct page * last_suspend_cache_page;
+extern unsigned int suspend_task;
+
+#ifdef CONFIG_MTRR
+#define SOFTWARE_SUSPEND_MTRR 1
+#else
+#undef SOFTWARE_SUSPEND_MTRR
+#endif
+
+#define TASK_SUSPENDED(p) ((suspend_task > 0) && \
+		((p->flags & PF_IOTHREAD) == 0) && \
+		((!(p->flags & PF_REFRIGERATE)) || (p->flags & PF_FROZEN)) && \
+		(p->pid != suspend_task) && \
+		(p->state != TASK_ZOMBIE) && \
+		(strcmp(p->comm, "swapper") != 0))
+
+#else
+#define software_suspend_pending()	do { } while(0)
+#define software_resume()		do { } while(0)
+#define register_suspend_notifier(a)	do { } while(0)
+#define unregister_suspend_notifier(a)	do { } while(0)
+#define refrigerator(a)			do { BUG(); } while(0)
+#endif
+
+#endif /* _LINUX_SWSUSP_H */
Index: linuxx/include/linux/swap.h
diff -u linuxx/include/linux/swap.h:1.1.1.1.6.2 linuxx/include/linux/swap.h:1.1.1.1.10.1.6.2
--- linuxx/include/linux/swap.h:1.1.1.1.6.2	Thu Feb  6 07:42:31 2003
+++ linuxx/include/linux/swap.h	Thu Feb  6 13:20:26 2003
@@ -118,7 +118,9 @@
 
 /* linux/mm/page_io.c */
 extern void rw_swap_page(int, struct page *);
-extern void rw_swap_page_nolock(int, swp_entry_t, char *);
+extern void __rw_swap_page_nolock(int, swp_entry_t, char *, int);
+#define rw_swap_page_nolock(rw, entry, buf) __rw_swap_page_nolock(rw, entry, buf, 1)
+#define rw_swap_page_nolock_norfree(rw, entry, buf) __rw_swap_page_nolock(rw, entry, buf, 0)
 
 /* linux/mm/page_alloc.c */
 
Index: linuxx/include/linux/sysctl.h
diff -u linuxx/include/linux/sysctl.h:1.1.1.1.6.3 linuxx/include/linux/sysctl.h:1.1.1.1.10.1.6.3
--- linuxx/include/linux/sysctl.h:1.1.1.1.6.3	Fri Feb 28 22:09:27 2003
+++ linuxx/include/linux/sysctl.h	Fri Feb 28 22:46:57 2003
@@ -124,6 +124,7 @@
 	KERN_CORE_USES_PID=52,		/* int: use core or core.%pid */
 	KERN_TAINTED=53,	/* int: various kernel tainted flags */
 	KERN_CADPID=54,		/* int: PID of the process to notify on CAD */
+	KERN_SWSUSP=55,		/* struct: interface to activate software suspension */
  	KERN_CORE_PATTERN=56,	/* string: pattern for core-files */
 };
 
Index: linuxx/include/linux/raid/md.h
diff -u linuxx/include/linux/raid/md.h:1.1.1.1 linuxx/include/linux/raid/md.h:1.1.1.1.12.1
--- linuxx/include/linux/raid/md.h:1.1.1.1	Thu Nov 28 14:07:58 2002
+++ linuxx/include/linux/raid/md.h	Thu Nov 28 20:22:58 2002
@@ -85,6 +85,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 
Index: linuxx/init/main.c
diff -u linuxx/init/main.c:1.1.1.1.6.1 linuxx/init/main.c:1.1.1.1.6.1.2.1
--- linuxx/init/main.c:1.1.1.1.6.1	Thu Nov 28 18:51:50 2002
+++ linuxx/init/main.c	Thu Nov 28 20:22:58 2002
@@ -27,6 +27,7 @@
 #include <linux/iobuf.h>
 #include <linux/bootmem.h>
 #include <linux/tty.h>
+#include <linux/suspend.h>
 
 #include <asm/io.h>
 #include <asm/bugs.h>
@@ -534,6 +535,10 @@
 
 	start_context_thread();
 	do_initcalls();
+
+	/* This has to be before mounting root, because even readonly mount of reiserfs would replay
+	   log corrupting stuff */
+	software_resume();
 
 #ifdef CONFIG_IRDA
 	irda_proto_init();
Index: linuxx/kernel/Makefile
diff -u linuxx/kernel/Makefile:1.1.1.1 linuxx/kernel/Makefile:1.1.1.1.12.1
--- linuxx/kernel/Makefile:1.1.1.1	Thu Nov 28 14:07:58 2002
+++ linuxx/kernel/Makefile	Thu Nov 28 20:22:58 2002
@@ -9,7 +9,7 @@
 
 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 suspend.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 \
@@ -19,6 +19,7 @@
 obj-$(CONFIG_UID16) += uid16.o
 obj-$(CONFIG_MODULES) += ksyms.o
 obj-$(CONFIG_PM) += pm.o
+obj-$(CONFIG_SOFTWARE_SUSPEND) += suspend.o
 
 ifneq ($(CONFIG_IA64),y)
 # According to Alan Modra <alan@linuxcare.com.au>, the -fno-omit-frame-pointer is
Index: linuxx/kernel/context.c
diff -u linuxx/kernel/context.c:1.1.1.1 linuxx/kernel/context.c:1.1.1.1.12.1
--- linuxx/kernel/context.c:1.1.1.1	Thu Nov 28 14:07:59 2002
+++ linuxx/kernel/context.c	Thu Nov 28 20:22:58 2002
@@ -72,6 +72,7 @@
 
 	daemonize();
 	strcpy(curtask->comm, "keventd");
+	current->flags |= PF_IOTHREAD;
 	keventd_running = 1;
 	keventd_task = curtask;
 
Index: linuxx/kernel/sched.c
diff -u linuxx/kernel/sched.c:1.1.1.1.6.2 linuxx/kernel/sched.c:1.1.1.1.16.2
--- linuxx/kernel/sched.c:1.1.1.1.6.2	Thu Feb  6 07:42:33 2003
+++ linuxx/kernel/sched.c	Wed Feb  5 22:53:51 2003
@@ -29,6 +29,9 @@
 #include <linux/completion.h>
 #include <linux/prefetch.h>
 #include <linux/compiler.h>
+#ifdef CONFIG_SOFTWARE_SUSPEND
+#include <linux/suspend.h>
+#endif
 
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
@@ -116,12 +119,12 @@
 
 #define idle_task(cpu) (init_tasks[cpu_number_map(cpu)])
 #define can_schedule(p,cpu) \
-	((p)->cpus_runnable & (p)->cpus_allowed & (1UL << cpu))
+	((!TASK_SUSPENDED(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_SUSPENDED(p))
 
 #endif
 
@@ -633,6 +636,9 @@
 	sched_data->curr = next;
 	task_set_cpu(next, this_cpu);
 	spin_unlock_irq(&runqueue_lock);
+
+	if (unlikely(TASK_SUSPENDED(next)))
+		printk("Scheduling suspended task %s!\n", next->comm);
 
 	if (unlikely(prev == next)) {
 		/* We won't go through the normal tail, so do this by hand */
Index: linuxx/kernel/signal.c
diff -u linuxx/kernel/signal.c:1.1.1.1.6.2 linuxx/kernel/signal.c:1.1.1.1.12.1.6.2
--- linuxx/kernel/signal.c:1.1.1.1.6.2	Thu Feb  6 07:42:33 2003
+++ linuxx/kernel/signal.c	Thu Feb  6 13:20:28 2003
@@ -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;
 
Index: linuxx/kernel/softirq.c
diff -u linuxx/kernel/softirq.c:1.1.1.1.6.1 linuxx/kernel/softirq.c:1.1.1.1.10.1.6.1
--- linuxx/kernel/softirq.c:1.1.1.1.6.1	Fri Nov 29 13:55:10 2002
+++ linuxx/kernel/softirq.c	Tue Dec  3 18:37:11 2002
@@ -365,6 +365,7 @@
 
 	daemonize();
 	current->nice = 19;
+	current->flags |= PF_IOTHREAD;
 	sigfillset(&current->blocked);
 
 	/* Migrate to the right CPU */
Index: linuxx/kernel/suspend.c
diff -u /dev/null linuxx/kernel/suspend.c:1.1.6.1.6.9
--- /dev/null	Fri Feb 28 23:56:58 2003
+++ linuxx/kernel/suspend.c	Thu Feb 27 19:19:10 2003
@@ -0,0 +1,2668 @@
+/*
+ * linux/kernel/suspend.c
+ *
+ * This file is to realize architecture-independent
+ * machine suspend feature using pretty near only high-level routines
+ *
+ * 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>
+ *
+ * I'd like to thank the following people for their work:
+ * 
+ * Pavel Machek <pavel@ucw.cz>:
+ * Modifications, defectiveness pointing, being with me 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.
+ *
+ * Nigel Cunningham <ncunningham@clear.net.nz>
+ * Nice display and other patches.
+ *
+ * More state savers are welcome. Especially for the scsi layer...
+ *
+ * For TODOs,FIXMEs also look in Documentation/swsusp.txt
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/locks.h>
+#include <linux/swapctl.h>
+#include <linux/console_struct.h>
+#include <linux/suspend.h>
+#include <linux/proc_fs.h>
+#include <linux/smp_lock.h>
+#include <linux/file.h>
+#include <linux/utsname.h>
+#include <linux/init.h>
+#include <linux/compile.h>
+#include <linux/delay.h>
+#include <linux/reboot.h>
+#include <linux/vt_kern.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/kbd_kern.h>
+#include <linux/keyboard.h>
+#include <linux/spinlock.h>
+#include <linux/genhd.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/blk.h>
+#include <linux/swap.h>
+#include <linux/pm.h>
+#include <linux/raid/md.h>
+#include <linux/selection.h>
+#include <linux/console.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+
+#include <asm/uaccess.h>
+#include <asm/mmu_context.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/mtrr.h>
+
+unsigned char software_suspend_enabled = 0;
+unsigned int suspend_task = 0;
+/*
+ * Poll the swsusp state every second
+ */
+#define SWSUSP_CHECK_TIMEOUT	(HZ)
+
+#define SUSPEND_CONSOLE	(MAX_NR_CONSOLES-1)
+#if 0 || !defined(CONFIG_VT) || !defined(CONFIG_VT_CONSOLE)
+# undef SUSPEND_CONSOLE
+#endif
+
+#define TIMEOUT	(6 * HZ)			/* Timeout for stopping processes */
+#define ADDRESS(x) ((unsigned long) phys_to_virt(((x) << PAGE_SHIFT)))
+
+extern int C_A_D;
+
+/* References to section boundaries */
+extern char _text, _etext, _edata, __bss_start, _end;
+extern char __nosave_begin, __nosave_end;
+
+extern int console_loglevel;
+static int is_head_of_free_region(struct page * page);
+static void generate_free_page_map(void);
+extern inline void signal_wake_up(struct task_struct *t);
+asmlinkage long sys_sched_yield(void);
+asmlinkage void sys_sync(void);	/* it's really int */
+extern void ide_disk_unsuspend(int);
+extern void ide_disk_suspend(void);
+extern kdev_t name_to_kdev_t(char *line) __init;
+extern void vt_console_print(struct console *co, const char * b, unsigned count);
+extern void reset_terminal(int currcons, int do_clear);
+extern void hide_cursor(int currcons);
+extern void free_suspend_cache_page(void);
+extern inline pte_t *lookup_address(unsigned long address);
+
+/* Locks */
+spinlock_t suspend_pagedir_lock __nosavedata = SPIN_LOCK_UNLOCKED;
+
+/* Variables to be preserved over suspend */
+static int new_loglevel = 7;
+static int orig_loglevel = 0;
+static int orig_fgconsole;
+static int pageset1_size_check, pageset2_size_check;
+
+static int resume_status = 0;
+static char resume_file[256] = "";			/* For resume= kernel option */
+/* Local variables that should not be affected by save */
+static int pageset1_size, pageset2_size; // Can't make unsigned int because mucks up MAX function
+static int expected_size1, expected_size2; 
+
+static int pm_suspend_state = 0;
+
+/* Suspend pagedir is allocated before final copy, and saved with data
+ * as well as separately, so it can be used to load pageset2 at resume.
+ *
+ * At resume, the original pagedir is loaded as pagedir_nosave and then
+ * moved to a place where it doesn't collide with the data to be copied
+ * back. Then the data is read (again into spots that don't collide) and
+ * then copied back, giving us access to the saved pagedir again and
+ * forgetting the loaded pagedir at the same time. We then used the saved
+ * pagedir to load pageset2 (if necessary) before freeing that pagedir.
+ */
+static suspend_pagedir_t **pagedir_nosave __nosavedata = NULL;
+static suspend_pagedir_t **pagedir_save = NULL;
+
+#ifdef SUSPEND_CONSOLE
+static int orig_kmsg;
+static int barwidth = 0, barposn = -1, newbarposn = 0, lastpercentage = 0;
+#define suspend_console (struct console *) NULL	/* Used in some console calls - obsolete parameter */
+#endif
+
+struct link {
+	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 link link;
+	struct suspend_header sh;
+};
+
+union p_diskpage {
+	union diskpage *pointer;
+	char *ptr;
+        unsigned long address;
+};
+
+typedef struct pageset_sizes_result {
+	int size1;
+	int size2;
+} pageset_sizes_t;
+
+/*
+ * XXX: We try to keep some more pages free so that I/O operations succeed
+ * without paging. Might this be more?
+ */
+#define PAGES_FOR_IO 512
+
+#define name_suspend "Suspend Machine:  "
+#define name_resume  "Resume Machine:   "
+#define swsusp_version "beta 19"
+#define name_swsusp  "Swsusp " swsusp_version ":   "
+#define console_suspend "S U S P E N D   T O   D I S K"
+#define console_resume "R E S U M E   F R O M   D I S K"
+static int now_resuming = 0;
+/*
+ * Debug
+ */
+#define	DEBUG_DEFAULT	1	/* activate debugging setting in /proc/sys/kernel/swsusp
+				   Commenting out will save very few cycles and prevent from
+				   debugging new hardware. */
+/* first status register */
+#define SUSPEND_PENDING (swsusp_state[0] & 0x1)
+#define SUSPEND_ABORTING (swsusp_state[0] & 0x2)
+/* second status register */
+#define SUSPEND_REBOOT  (swsusp_state[1] & 0x1)
+#define SUSPEND_FREEMEM (swsusp_state[1] & 0x2)
+#define SUSPEND_PAUSE (swsusp_state[1] & 0x4)
+#define SUSPEND_NOPAGESET2 (swsusp_state[1] & 0x8)
+#define SUSPEND_NOPAGESET2INACTIVE (swsusp_state[1] & 0x10)
+#define SUSPEND_NOPAGESET2ACTIVE (swsusp_state[1] & 0x20)
+/* third status register */
+#define SUSPEND_DEBUG   	0x1
+#define SUSPEND_PROGRESS   	0x2
+#define SUSPEND_VERBOSE   	0x4
+#define SUSPEND_SLOW		0x8
+#define SUSPEND_BEEP		0x10
+/* fourth status register */
+#define STAGE_FREEZER 		0x1
+#define STAGE_EAT_MEMORY 	0x2
+#define STAGE_MARK_PAGESET2 	0x4
+#define STAGE_WRITE_PAGESETS 	0x8
+#define STAGE_FREE_PAGES 	0x10
+
+static void printnolog(const char *fmt, ...);
+
+#ifdef DEBUG_DEFAULT
+# define PRINTTHRESH 3		/* Number of lines at the start/end of pagesets to show */
+static int currentstage = 0;	/* Current stage (STAGEPRINT macro) */
+# define STAGEPRINT ((currentstage == 0) || (swsusp_state[3] & currentstage))
+# define PRINTK(mask, f, a...)	((((mask) & swsusp_state[2]) && STAGEPRINT) ? printk(f, ## a):0)
+# define PRINTNOLOG(mask, f, a...)	((((mask) & swsusp_state[2]) && STAGEPRINT) ? printnolog(f, ## a):0)
+# define MDELAY(a) 		if (swsusp_state[2] & SUSPEND_SLOW)  mdelay(a); else
+static int currentbeep = 260;
+# define beepOK if (swsusp_state[2] & SUSPEND_BEEP)  { currentbeep += 200;  kd_mksound(currentbeep,HZ/8); mdelay(150);} else
+# define beepERR if (swsusp_state[2] & SUSPEND_BEEP) { kd_mksound(300,HZ/4); mdelay(300); } else
+int swsusp_state[5] = {0,	/* when set to 1 swsusp_mainloop activates software_suspend
+				   bit 0: off = normal state, on = suspend required
+				   bit 1: aborting suspend
+				*/
+		       0,	/* action parameter:
+				   bit 0: off = halt, on = reboot 
+				   bit 1: off = eatmem, on = freemem
+				   bit 2: pause between steps
+				   bit 3: no pageset2
+				*/
+		       SUSPEND_PROGRESS | SUSPEND_BEEP,
+				/* debugging parameter is an OR of flags:
+				   bit 0: debugging messages
+				   bit 1: display progression
+				   bit 2: very verbose mode
+				   bit 3: slow down process
+				   bit 4: emit beeps
+				   This value can also be set by swsusp_dbg= kernel boot option.
+				*/
+		       0,	/* which section to debug */
+		       0};	/* limit on image size in MB (test assumes 4K pages) */
+#else
+# define PRINTK(f, a...)
+# define MDELAY(a)
+int swsusp_state[5] = {0,0,SUSPEND_PROGRESS, 0, 0}; /* see above for signification of values */
+# define beepOK
+# define beepERR
+#endif
+
+// |= 0x2 is the flag that we are aborting the process.
+#define abort_suspend(f, a...) do { \
+	if (!SUSPEND_ABORTING) { \
+		printk("\n"); \
+		printk(f, ## a); \
+		display_debug_info(); \
+		beepERR; \
+                printk("Waiting for you to press and release SHIFT before continuing.\n"); \
+		while (!(shift_state & (1 << KG_SHIFT))) \
+			schedule(); \
+		while ((shift_state & (1 << KG_SHIFT))) \
+			schedule(); \
+		swsusp_state[0] |= 0x2; \
+	} \
+} while(0)
+
+static void generate_free_page_map(void) 
+{
+	int i, loop;
+	struct page * page;
+	pg_data_t *pgdat = pgdat_list;
+	unsigned type;
+	unsigned long flags;
+
+	for(i=0; i < max_mapnr; i++)
+		ClearPageFree(mem_map+i);
+	
+	for (type=0;type < MAX_NR_ZONES; type++) {
+		zone_t *zone = pgdat->node_zones + type;
+		int order = MAX_ORDER - 1;
+		free_area_t *area;
+		struct list_head *head, *curr;
+		spin_lock_irqsave(&zone->lock, flags);
+		do {
+			int first_entry = 1;
+			area = zone->free_area + order;
+			head = &area->free_list;
+			curr = head;
+
+			for(;;) {
+				if(!curr) {
+//					printk(KERN_ERR name_suspend "FIXME: this should not happen but it does!!!");
+					break;
+				}
+				if (first_entry) 
+					first_entry--;
+				else {
+					page = list_entry(curr, struct page, list);
+					for(loop=0; loop < (1 << order); loop++)
+						SetPageFree(page+loop);
+				}
+
+				curr = curr->next;
+				if (curr == head)
+					break; 				
+			}
+		} while(order--);
+		spin_unlock_irqrestore(&zone->lock, flags);
+	}
+	PRINTK(SUSPEND_VERBOSE,"At end, free pages is %d\n", nr_free_pages());
+}
+
+static int is_head_of_free_region(struct page * page)
+{
+	struct page * posn = page;
+
+	while (((posn-mem_map) < max_mapnr) && (PageFree(posn))) 
+		posn++;
+	return (posn - page);
+}
+
+/* Exported variables */
+struct page * last_suspend_cache_page;		/* Pointer to the last cache page */
+struct buffer_head * suspend_swap_bh;
+static unsigned long orig_mem_free = 0, base_mem_free = 0, swapforimage = 0;
+
+#define MAX(a,b)	((a)>(b)?(a):(b))	/* Maximum of two numbers */
+
+#ifdef SUSPEND_CONSOLE 
+#define WAIT_FOR_SHIFT { \
+	if (SUSPEND_PAUSE) { \
+		int i; \
+		unsigned int currcons = fg_console; \
+		printk("\r"); \
+		for (i=0; i < (video_num_columns - 2); i++) \
+			vt_console_print(suspend_console, " ", 1); \
+		printk("\r"); \
+		for (i=0; i < ((video_num_columns - 49) / 2); i++) \
+			vt_console_print(suspend_console, " ", 1); \
+		printk("Waiting for you to press and release SHIFT before continuing.\n"); \
+		while (!(shift_state & (1 << KG_SHIFT))) \
+			schedule(); \
+		while ((shift_state & (1 << KG_SHIFT))) \
+			schedule(); \
+	} \
+}
+#else
+#define WAIT_FOR_SHIFT  do { } while (0)
+#endif
+
+static kdev_t resume_device;
+#define BUFFER_BUSY_BITS	((1<<BH_Dirty) | (1<<BH_Lock))
+#define buffer_busy(bh)		(atomic_read(&(bh)->b_count) | ((bh)->b_state & BUFFER_BUSY_BITS))
+
+#define BREAD_BUFFER_HEAD(pos) \
+	bh = bread(resume_device, pos/PAGE_SIZE, PAGE_SIZE); \
+	if (!bh || (!bh->b_data)) { \
+		return -EIO; \
+	}
+
+#define BFREE(bh) \
+	if (!buffer_busy(bh)) \
+		try_to_free_buffers(bh->b_page, __GFP_FAST); \
+	free_suspend_cache_page();
+
+#define PRINTFREEMEM printk("Free memory: %d.\n", nr_free_pages())
+#define PANIC(f, a...) 	panic(f, ## a) 
+
+static int pagedir_size_check;
+static int pagedir_size;
+static int pagedir_capacity;
+static int pagedir_alloc_from;
+static struct sysinfo swapinfo;
+static unsigned long copybuff = 0;
+
+static void drivers_unsuspend(void);
+static int drivers_suspend(void);
+static int read_secondary_pagedir(suspend_pagedir_t ** pagedir);
+static int read_primary_suspend_image(char * specialfile, int noresume);
+static void markpagesforpageset2(void);
+static void display_debug_info(void);
+
+/*
+ * This function is intended to do the same job as printk, but without actually
+ * 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"
+ */
+static void printnolog(const char *fmt, ...)
+{
+#if defined(SUSPEND_CONSOLE)
+	static char printk_buf[1024];	/* Same as printk - should be safe */
+	va_list args;
+	int printed_len;
+
+	/*
+	 * We only print these messages if SUSPEND_PROGRESS is enabled, but
+	 * not if SUSPEND_PROGRESS is the only bit enabled (when we do the
+	 * nice display).
+	 */
+
+	/* Emit the output into the temporary buffer */
+	va_start(args, fmt);
+	printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
+	va_end(args);
+
+	vt_console_print(suspend_console, printk_buf, printed_len);
+#endif
+}
+static void prepare_status(char * action)
+{
+#if defined(SUSPEND_CONSOLE)
+	char posn[2];
+	unsigned int currcons = fg_console;
+
+	if ((swsusp_state[2]& ~SUSPEND_BEEP) != SUSPEND_PROGRESS)
+		return;
+	
+	hide_cursor(fg_console);
+
+	barwidth = (video_num_columns - (video_num_columns / 2) - 1);
+	barposn = 0;
+
+	/* Print version */
+	posn[0] = (char) (0);
+	posn[1] = (char) (video_num_lines);
+	putconsxy(fg_console, posn);
+	vt_console_print(suspend_console, name_swsusp, strlen(name_swsusp));
+
+	/* Print header */
+	posn[0] = (char) ((video_num_columns - 29) / 2);
+	posn[1] = (char) ((video_num_lines / 3) -4);
+	putconsxy(fg_console, posn);
+	if (now_resuming)
+		vt_console_print(suspend_console, console_resume, strlen(console_resume));
+	else
+		vt_console_print(suspend_console, console_suspend, strlen(console_suspend));
+		
+	/* Print action */
+	posn[0] = (char) (video_num_columns / 4);
+	posn[1] = (char) (video_num_lines / 3);
+	putconsxy(fg_console, posn);
+	
+	/* Blank out previous contents of line - assumes text length <= bar width */
+	for (barposn = 0; barposn < barwidth; barposn++) 
+		vt_console_print(suspend_console, " ", 1);
+	
+	putconsxy(fg_console, posn);
+	vt_console_print(suspend_console, action, strlen(action)); 
+
+	/* Draw left bracket of progress bar. */
+	posn[1]++;
+	putconsxy(fg_console, posn);
+	vt_console_print(suspend_console, "[", 1);
+
+	/* Draw right bracket of progress bar. */
+	posn[0] = (char) (video_num_columns - (video_num_columns / 4));
+	putconsxy(fg_console, posn);
+	vt_console_print(suspend_console, "]", 1);
+
+	/* Position at start of progress */
+	posn[0] = (char) (video_num_columns / 4 + 1);
+	putconsxy(fg_console, posn);
+
+	/* Clear bar */
+	for (barposn = 0; barposn < barwidth; barposn++)
+		vt_console_print(suspend_console, " ", 1);
+	putconsxy(fg_console, posn);
+
+	barposn = 0;
+	lastpercentage = 0;
+	hide_cursor(fg_console);
+	MDELAY(1000);
+#endif
+}
+
+static void update_status(int percentage)
+{
+#if defined(SUSPEND_CONSOLE)
+	char posn[2];
+	unsigned int currcons = fg_console;
+
+	if ((swsusp_state[2] & ~SUSPEND_BEEP)!= SUSPEND_PROGRESS)
+		return;
+	
+	if (percentage < 0)
+		percentage = 0;
+	if (percentage > 100)
+		percentage = 100;
+
+	newbarposn = (int) (percentage * barwidth / 100);
+	
+	if (newbarposn == barposn)
+		return;       
+
+	posn[0] = (char) (video_num_columns / 4 + 1 + barposn);
+	posn[1] = (char) ((video_num_lines / 3) + 1);
+	putconsxy(fg_console, posn);
+
+	lastpercentage = percentage;
+	
+	for (; barposn < newbarposn; barposn++)
+		vt_console_print(suspend_console, "-", 1);
+	barposn = newbarposn;
+	hide_cursor(fg_console);
+#endif
+}
+/*
+ * Refrigerator and related stuff
+ */
+
+#define INTERESTING(p) \
+			/* We don't want to touch kernel_threads..*/ \
+			if (p->flags & PF_IOTHREAD) \
+				continue; \
+			if (p == current) \
+				continue; \
+			if (p->state == TASK_ZOMBIE) \
+				continue;
+
+static int processestofreeze = 0;
+static int processesfrozen = 0;
+
+/* Refrigerator is place where frozen processes are stored :-). */
+void refrigerator(unsigned long flag)
+{
+	/* 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. 
+	 */
+	processesfrozen++;
+	
+	if (flag)
+		flush_signals(current); /* We have signaled a kernel thread, which isn't normal behaviour
+					   and that may lead to 100%CPU sucking because those threads
+					   just don't manage signals. */
+	current->flags |= PF_FROZEN;
+	schedule();			/* Won't return until suspend_task is set to 0 again */
+	current->flags &= ~PF_FROZEN;
+	current->flags &= ~PF_FREEZE;
+}
+
+/* 0 = success, else # of processes that we failed to stop */
+static int freeze_processes(void)
+{
+	int todo, start_time;
+	struct task_struct *p;
+	
+	processestofreeze = 0;
+	processesfrozen = 0;
+
+	PRINTK(SUSPEND_VERBOSE,name_suspend "Stopping tasks\n" );
+	prepare_status("Freezing processes...");
+
+	suspend_task = current->pid;
+		
+	start_time = jiffies;
+	do {
+		todo = 0;
+		read_lock(&tasklist_lock);
+		for_each_task(p) {
+			unsigned long flags;
+			PRINTK(SUSPEND_VERBOSE, "%s: ", p->comm);
+			if (p->flags & PF_IOTHREAD) {
+				//PRINTK(SUSPEND_VERBOSE, "Suspend task.\n");
+				continue;
+			}
+			if (!(p->flags & PF_REFRIGERATE)) {
+				//PRINTK(SUSPEND_VERBOSE, "Refrigerate flag not set (%lx).\n", p->flags);
+				continue;
+			}
+			if (p->flags & PF_FROZEN) {
+				//PRINTK(SUSPEND_VERBOSE, "Already frozen.\n");
+				continue;
+			}
+			/* FIXME: smp problem here: we may not access other process' flags
+			   without locking */
+			p->flags |= PF_FREEZE;
+			PRINTK(SUSPEND_VERBOSE, "Signalling %s\n", p->comm);
+			spin_lock_irqsave(&p->sigmask_lock, flags);
+			signal_wake_up(p);
+			spin_unlock_irqrestore(&p->sigmask_lock, flags);
+			todo++;
+		}
+		read_unlock(&tasklist_lock);
+		if (processestofreeze == 0) {
+			PRINTK(SUSPEND_VERBOSE,"%d processes to be frozen.\n", todo);
+			processestofreeze = todo;
+		}
+		yield();
+		if (time_after(jiffies, start_time + TIMEOUT)) {
+			printk(KERN_ERR name_suspend " stopping tasks failed (%d tasks remaining)\n", todo );
+			return todo;
+		}
+		if (processestofreeze) // Don't update when enter fridge b/c requires process to acquire suspend console.
+			update_status((processestofreeze - processesfrozen) * 100 / processestofreeze);
+		PRINTK(SUSPEND_VERBOSE, "----\n");
+	} while(processestofreeze > processesfrozen);
+	
+	return 0;
+}
+
+static void thaw_processes(void)
+{
+	PRINTK(SUSPEND_VERBOSE, name_resume "Restarting tasks\n");
+	suspend_task = 0;
+}
+
+/*
+ * Saving part...
+ */
+
+static __inline__ int fill_suspend_header(struct suspend_header *sh)
+{
+	memset((char *)sh, 0, sizeof(*sh));
+
+	sh->version_code = LINUX_VERSION_CODE;
+	sh->num_physpages = num_physpages;
+	sh->base_mem_free = orig_mem_free;
+	sh->swapforimage = swapforimage;
+	sh->expected_size1 = expected_size1;
+	sh->expected_size2 = expected_size2;
+	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->suspend_pagedir = pagedir_save;
+	sh->pagedir_size = pagedir_size;
+	sh->pageset1_size = pageset1_size;
+	sh->pageset2_size = pageset2_size;
+	sh->param0 = swsusp_state[0];
+	sh->param1 = swsusp_state[1];
+	sh->param2 = swsusp_state[2];
+	sh->param3 = swsusp_state[3];
+	sh->param4 = swsusp_state[4];
+	/* TODO: needed? mounted fs' last mounted date comparison
+	 * [so they haven't been mounted since last suspend.
+	 * Maybe it isn't.] [we'd need to do this for _all_ fs-es]
+	 */
+	return 0;
+}
+
+/*
+ * This is our sync function. With this solution we probably won't sleep
+ * but that should not be a problem since tasks are stopped..
+ */
+
+static void do_suspend_sync(void)
+{
+	while (1) {
+		run_task_queue(&tq_disk);
+		if (!TQ_ACTIVE(tq_disk))
+			break;
+		printk(KERN_ERR "Hm, tq_disk is not empty after run_task_queue\n");
+	}
+}
+
+/* We memorize in swapfile_used what swap devices are used for suspension */
+#define SWAPFILE_UNUSED    0
+#define SWAPFILE_SUSPEND   1	/* This is the suspending device */
+#define SWAPFILE_IGNORED   2	/* Those are other swap devices ignored for suspension */
+
+static unsigned short swapfile_used[MAX_SWAPFILES];
+static unsigned short root_swap;
+#define MARK_SWAP_SUSPEND 0
+#define MARK_SWAP_RECOVER 1
+#define MARK_SWAP_RESUME 2
+
+static inline int sync_page(struct page *page)
+{
+	struct address_space *mapping = page->mapping;
+
+	if (mapping && mapping->a_ops && mapping->a_ops->sync_page)
+		return mapping->a_ops->sync_page(page);
+	return 0;
+}
+
+static int rw_swap_page_nofree(int rw, swp_entry_t entry, union p_diskpage cur)
+{
+	struct page *page;
+
+	page=virt_to_page(cur.address);
+	lock_page(page);
+	rw_swap_page_nolock_norfree(rw, entry, cur.ptr);
+	sync_page(page);
+	if (suspend_swap_bh) {
+		lock_page(page);
+		if (!buffer_busy(page->buffers))
+			try_to_free_buffers(page->buffers->b_page, __GFP_FAST);
+		free_suspend_cache_page();
+		unlock_page(page);
+	}
+	if(!Page_Uptodate(page)) {
+		abort_suspend("Page not up-to-date in rw_swap_page_nofree!\n");
+		return 1;
+	}
+	return 0;
+}
+
+#define RW_SWAP_PAGE_SYNC(a, b, c) rw_swap_page_sync(a, b, c)
+static int rw_swap_page_sync(int rw, swp_entry_t entry, union p_diskpage cur)
+{
+	struct page *page;
+	//unsigned long dummy1;
+	//struct inode *dummy2;
+
+	page=virt_to_page(cur.address);	
+	lock_page(page);
+	//get_swaphandle_info(entry, &dummy1, &suspend_device, &dummy2);
+	rw_swap_page_nolock(rw, entry, cur.ptr);
+	sync_page(page);
+	if (suspend_swap_bh) {
+		lock_page(page);
+		if (!buffer_busy(page->buffers))
+			try_to_free_buffers(page->buffers->b_page, __GFP_FAST);
+		free_suspend_cache_page();
+		unlock_page(page);
+	}
+	if(!Page_Uptodate(page)) {
+		abort_suspend("Page not up-to-date in rw_swap_page_nofree!\n");
+		return 1;
+	}
+	return 0;
+}
+
+static void mark_swapfiles(swp_entry_t prev, int mode)
+{
+	swp_entry_t entry;
+	union p_diskpage cur;
+
+	cur.address = copybuff;
+	/* XXX: this is dirty hack to get first page of swap file */
+	entry = SWP_ENTRY(root_swap, 0);
+	if (RW_SWAP_PAGE_SYNC(READ, entry, cur)) {
+		return;
+	}
+
+	switch(mode) {
+	case MARK_SWAP_RESUME:
+		if (!memcmp("SUSP1R",cur.pointer->swh.magic.magic,6))
+			memcpy(cur.pointer->swh.magic.magic,"SWAP-SPACE",10);
+		else if (!memcmp("SUSP2R",cur.pointer->swh.magic.magic,6))
+			memcpy(cur.pointer->swh.magic.magic,"SWAPSPACE2",10);
+		else printk(name_resume "Unable to find suspended-data signature (%.10s - misspelled?\n", 
+			    cur.pointer->swh.magic.magic);
+		break;
+	case MARK_SWAP_RECOVER:
+		if (!memcmp("SUSP1R",cur.pointer->swh.magic.magic,6))
+			memcpy(cur.pointer->swh.magic.magic,"SWAP-SPACE",10);
+		else if (!memcmp("SUSP2R",cur.pointer->swh.magic.magic,6))
+			memcpy(cur.pointer->swh.magic.magic,"SWAPSPACE2",10);
+	  	else if ((!memcmp("SWAP-SPACE",cur.pointer->swh.magic.magic,10))
+			 &&(!memcmp("SWAPSPACE2",cur.pointer->swh.magic.magic,10))) {
+			abort_suspend("\nSwapspace is altered (%.10s)\n", cur.pointer->swh.magic.magic);
+			return;
+		}
+		break;
+	case MARK_SWAP_SUSPEND:
+	  	if ((!memcmp("SWAP-SPACE",cur.pointer->swh.magic.magic,10)))
+		  	memcpy(cur.pointer->swh.magic.magic,"SUSP1R....",10);
+		else if ((!memcmp("SWAPSPACE2",cur.pointer->swh.magic.magic,10)))
+			memcpy(cur.pointer->swh.magic.magic,"SUSP2R....",10);
+		else {
+			abort_suspend("\nSwapspace is not swapspace (%.10s)\n", cur.pointer->swh.magic.magic);
+			return;
+		}
+		cur.pointer->link.next = prev; /* prev is the first/last swap page of the resume area */
+		/* link.next lies *no more* in last 4 bytes of magic */
+		break;
+	}
+	RW_SWAP_PAGE_SYNC(WRITE, entry, cur);
+}
+
+static void read_swapfiles(void) /* This is called before saving image */
+{
+	int i, len;
+	
+	len=strlen(resume_file);
+	root_swap = 0xFFFF;
+	
+	swap_list_lock();
+	for(i=0; i<MAX_SWAPFILES; i++) {
+		if (swap_info[i].flags == 0) {
+			swapfile_used[i]=SWAPFILE_UNUSED;
+		} else {
+			if (!len) {
+	    			printk("resume= option should be used to set suspend device" );
+				if (root_swap == 0xFFFF) {
+					swapfile_used[i] = SWAPFILE_SUSPEND;
+					root_swap = i;
+				} else
+					swapfile_used[i] = SWAPFILE_IGNORED;				  
+			} else {
+	  			/* we ignore all swap devices that are not the resume_file */
+				if (resume_device == swap_info[i].swap_device) {
+					swapfile_used[i] = SWAPFILE_SUSPEND;
+					root_swap = i;
+				} else {
+					PRINTK(SUSPEND_DEBUG, name_suspend "device %s (%x != %x) ignored\n", swap_info[i].swap_file->d_name.name, swap_info[i].swap_device, resume_device);
+				  	swapfile_used[i] = SWAPFILE_IGNORED;
+				}
+			}
+		}
+	}
+	swap_list_unlock();
+}
+
+static void lock_swapdevices(void) /* This is called after saving image so modification
+				      will be lost after resume... and that's what we want. */
+{
+	int i;
+
+	swap_list_lock();
+	for(i = 0; i< MAX_SWAPFILES; i++)
+		if (swapfile_used[i] == SWAPFILE_IGNORED) {
+			swap_info[i].flags ^= 0xFF; /* we make the device unusable. A new call to
+						       lock_swapdevices can unlock the devices. */
+		}
+	swap_list_unlock();
+	beepOK;			/* beep at start/end of writing */
+}
+
+static int write_pageset(struct pbe **pagedir, int whichtowrite)
+{
+	int i, size;
+	swp_entry_t entry;
+	union p_diskpage cur;
+
+#define abort_and_exit(f, a...) do { \
+			abort_suspend(f, ## a); \
+			drivers_suspend(); \
+			return -1; \
+	} while(0)
+
+	drivers_unsuspend();
+	if (whichtowrite == 1) {
+		size = pageset1_size;
+		prepare_status("Writing suspend image (1)...");
+	} else {
+		size = pageset2_size;
+		prepare_status("Writing suspend image (2)...");
+	}	
+	cur.address = copybuff;
+
+	for (i=0; i<size; i++) {
+		if (!(i%100))
+			PRINTK(SUSPEND_DEBUG, ".");
+		PRINTNOLOG(SUSPEND_VERBOSE,name_suspend "\rWriting data to swap (%d/%d pages).", i + 1, size);
+		update_status((int) (i * 100 / size));
+
+		entry = get_swap_page();
+		if (!entry.val)
+			abort_and_exit("Not enough swapspace when writing data.\n");
+		if (swapfile_used[SWP_TYPE(entry)] != SWAPFILE_SUSPEND)
+			abort_and_exit("Page %d: not enough swapspace on suspend device.\n", i);
+	    
+		if (whichtowrite == 1) {
+			PAGEDIR_ENTRY(pagedir,i)->swap_address1 = entry;
+#ifdef DEBUG_DEFAULT
+			if ((i < PRINTTHRESH) || ((i + PRINTTHRESH) > size))
+				PRINTNOLOG(SUSPEND_VERBOSE, "!%d! [1]%lx -> [2]%lx -> [sw]%lx.\n", i, PAGEDIR_ENTRY(pagedir, i)->address1, PAGEDIR_ENTRY(pagedir, i)->address2, entry.val);
+#endif
+		} else {
+			PAGEDIR_ENTRY(pagedir, i)->swap_address2 = entry;
+#ifdef DEBUG_DEFAULT
+			if ((i < PRINTTHRESH) || ((i + PRINTTHRESH) > size))
+				PRINTNOLOG(SUSPEND_VERBOSE, "!%d! [2]%lx -> [sw]%lx\n", i, PAGEDIR_ENTRY(pagedir, i)->address2, entry.val);
+#endif
+		}
+
+		copy_page((char *) copybuff, (char *) PAGEDIR_ENTRY(pagedir, i)->address2);
+		__flush_tlb();
+
+		if (RW_SWAP_PAGE_SYNC(WRITE, entry, cur))
+			abort_and_exit("Failed writing page to swap.\n");
+	}
+	PRINTK(SUSPEND_DEBUG, "|\n");
+	drivers_suspend();
+	return 0;
+}
+
+static int  write_pagedir_and_suspend_header(struct pbe **pagedir)
+{
+	int i, len, *ptr;
+	swp_entry_t entry, prev = { 0 };
+	union p_diskpage cur,  buffer;
+
+#define abort_and_exit(f, a...) do { \
+			abort_suspend(f, ## a); \
+			drivers_suspend(); \
+			return -1; \
+	} while(0)
+	
+	buffer.address = copybuff;
+
+	drivers_unsuspend();
+	PRINTK(SUSPEND_DEBUG, name_suspend "Writing pagedir (%d pages)\n", pagedir_size);
+	for (i=0; i<pagedir_size; i++) {
+		cur.pointer = (union diskpage *) pagedir[i];
+		PRINTK(SUSPEND_DEBUG, "."); 
+		update_status((int) ((i+pageset1_size+pageset2_size) * 100 / (pageset1_size+pageset2_size+pagedir_size+2)));
+		entry = get_swap_page();
+		if (!entry.val)
+			abort_and_exit("Not enough swapspace when writing pgdir\n");		
+		if (swapfile_used[SWP_TYPE(entry)] != SWAPFILE_SUSPEND)
+			abort_and_exit("Not enough swapspace for pagedir on suspend device\n");
+		if (sizeof(swp_entry_t) != sizeof(long))
+			abort_and_exit("Size of swp_entry_t != size of long in write_pagedir_and_suspend_header!\n");
+		if (PAGE_SIZE % sizeof(struct pbe))
+			abort_and_exit("Page size (%ld) mod size of struct pbe (%d) is not 0 (%ld) in write_pagedir_and_suspend_header!\n", PAGE_SIZE, sizeof(struct pbe), PAGE_SIZE % sizeof(struct pbe));
+		cur.pointer->link.next = prev;				
+		if (rw_swap_page_nofree(WRITE, entry, cur))
+			abort_and_exit("Failed to write pagedir page.\n");
+		prev = entry;
+	}
+	PRINTK(SUSPEND_DEBUG, "H");
+#ifdef SOFTWARE_SUSPEND_MTRR
+	ptr = mtrr_suspend();
+	if(ptr)
+		len = *ptr;
+	else
+#endif
+	{
+		len = 4;	/* we need to put a zero */
+		ptr = NULL;
+	}
+	if (len + sizeof(struct suspend_header) > PAGE_SIZE-sizeof(swp_entry_t))
+		abort_and_exit("Size of suspend header too big!\n");
+	if (sizeof(union diskpage) != PAGE_SIZE)
+		abort_and_exit("Size of a disk page is not PAGE_SIZE!\n");
+	entry = get_swap_page();
+	if (!entry.val)
+		abort_and_exit("Not enough swapspace when writing header!\n");
+	if (swapfile_used[SWP_TYPE(entry)] != SWAPFILE_SUSPEND)
+		abort_and_exit("Not enough swapspace for header on suspend device!\n" );
+
+	cur = buffer;
+	if (fill_suspend_header(&(cur.pointer->sh)))
+		abort_and_exit("Out of memory while writing header!\n");
+	if(ptr)
+		memcpy(cur.ptr+sizeof(struct suspend_header), ptr, len);
+	else {
+		len = 0;
+		ptr = &len;
+		memcpy(cur.ptr+sizeof(struct suspend_header), ptr, sizeof(int));
+	}	
+	cur.pointer->link.next = prev;
+
+	if (rw_swap_page_nofree(WRITE, entry, cur))
+		abort_and_exit("Failed to write pagedir header.\n");
+	prev = entry;
+
+	PRINTK(SUSPEND_DEBUG, "S");
+	update_status((int) ((pagedir_size+pageset1_size+1) * 100 / (pageset1_size+pagedir_size+2)));
+	mark_swapfiles(prev, MARK_SWAP_SUSPEND);
+	if (SUSPEND_ABORTING)
+		return -1;
+	PRINTK(SUSPEND_DEBUG, "|\n");
+	update_status(100);
+
+	MDELAY(1000);
+	drivers_suspend();
+	return 0;
+}
+
+static pageset_sizes_t count_data_pages(struct pbe **pagedir_p)
+{
+	int chunk_size, excess_pages = 0, loop;
+	int assignedstart1 = -1, assignedend1 = -1;
+	int assignedstart2 = -1, assignedend2 = -1;
+	pageset_sizes_t result;
+	result.size1 = 0;
+	result.size2 = 0;
+	
+	generate_free_page_map();
+	if (max_mapnr != num_physpages) {
+		abort_suspend("mapnr is not expected");
+		result.size1 = -1;
+		result.size2 = -1;
+		return result;
+	}
+	for (loop = 0; loop < max_mapnr; loop++) {
+		if (PageHighMem(mem_map+loop)) {
+			abort_suspend("Swsusp not supported on highmem boxes. Send 1GB of RAM to <pavel@ucw.cz> and try again ;-).");
+			result.size1 = -1;
+			result.size2 = -1;
+			return result;
+		}
+		if (!PageReserved(mem_map+loop)) {
+			if (PageNosave(mem_map+loop))
+				continue;
+
+			if ((chunk_size=is_head_of_free_region(mem_map+loop))!=0) {
+				loop += chunk_size - 1;
+				continue;
+			}
+		} else if (PageReserved(mem_map+loop)) {
+			if (PageNosave(mem_map+loop)) {
+				abort_suspend("Reserved page marked nosave!\n");
+				result.size1 = 0;
+				result.size2 = 0;
+				return result;
+			}
+			/*
+			 * Just copy whole code segment. Hopefully it is not that big.
+			 */
+			if (ADDRESS(loop) >= (unsigned long)
+				&__nosave_begin && ADDRESS(loop) < 
+				(unsigned long)&__nosave_end) {
+				continue;
+			}
+			/* Hmm, perhaps copying all reserved pages is not too healthy as they may contain 
+			   critical bios data? */
+
+		} else BUG();
+
+		if (pagedir_p) {
+			if (!PageDontcopy(mem_map+loop)) {
+				PAGEDIR_ENTRY(pagedir_p, result.size1)->address1 = ADDRESS(loop);
+				if (assignedstart1 == -1) {
+					assignedstart1 = result.size1;
+				}
+				assignedend1 = result.size1;
+				if (result.size1 > pagedir_capacity) {
+					PRINTK(SUSPEND_VERBOSE, "@%d@\n", result.size1);
+					excess_pages++;
+					continue;
+				}
+			} else {
+				PAGEDIR_ENTRY(pagedir_p,result.size2)->address2 = ADDRESS(loop);
+				if (assignedstart2 == -1) {
+					assignedstart2 = result.size2;
+				}
+				assignedend2 = result.size2;
+			}
+		}
+		
+		if (PageDontcopy(mem_map+loop))
+			result.size2++;
+		else
+			result.size1++;
+	}
+	if (assignedstart1 != -1) {
+		PRINTK(SUSPEND_VERBOSE,"Address1 set for range %d-%d.\n", assignedstart1, assignedend1);
+	}
+	if (assignedstart2 != -1) {
+		PRINTK(SUSPEND_VERBOSE,"Address2 set for range %d-%d.\n", assignedstart2, assignedend2);
+	}
+	if (excess_pages)
+		abort_suspend("%d more pages to be copied than were allowed for! Pageset1 size is %d\n", \
+				excess_pages, \
+				result.size1);
+	return result;
+}
+
+static void copy_pageset1(struct pbe **pagedir_p)
+{
+	int i;
+	int bugstart = 0, bugend = 0;
+	int progress_max = 0;
+
+	if (pagedir_capacity < pageset1_size) {
+		printk("Pagedir capacity (%d) is less than Pageset1 size! (%d).\n", pagedir_capacity, pageset1_size);
+		return;
+	}
+
+	if (pageset1_size > pageset2_size)
+		progress_max = pageset2_size + ((pageset1_size - pageset2_size) / 8);
+	else
+		progress_max = pageset2_size;
+	
+	prepare_status("Copying pages...");
+	for (i = 0; i < pageset1_size; i++) {
+		if (i < pageset2_size)
+			update_status((int) (i * 100 / progress_max));
+		else 
+			update_status((int) ((pageset2_size + (i - pageset2_size) / 8) * 100 / progress_max));
+		if (PAGEDIR_ENTRY(pagedir_p,i)->address2) {
+#ifdef DEBUG_DEFAULT
+			if ((i < PRINTTHRESH) || ((i + PRINTTHRESH) > pageset1_size))  {
+				PRINTNOLOG(SUSPEND_VERBOSE, "!%d! [1]%lx -> [2]%lx.\n", i, PAGEDIR_ENTRY(pagedir_p,i)->address1, PAGEDIR_ENTRY(pagedir_p,i)->address2);
+			}
+#endif
+			copy_page((char *) PAGEDIR_ENTRY(pagedir_p,i)->address2, (char *) PAGEDIR_ENTRY(pagedir_p,i)->address1);
+			__flush_tlb();
+		} else {
+			if (bugstart && !bugend)
+				bugend = i;
+			else if (!bugstart) {
+				bugstart = i;
+				abort_suspend("Address2 pointers NULL!\n");
+			}
+		}
+	}
+	if (bugstart && !bugend)
+		bugend = i;
+	if (bugstart)
+		printk("Address2 not set for range %d-%d!\n", bugstart, bugend);
+}
+
+static void free_suspend_pagedir(struct pbe **this_pagedir)
+{
+	int i;
+	int rangestart = -1, rangeend = -1;
+	struct pbe **p = this_pagedir;
+
+	if (pagedir_size == 0)
+		return;
+
+	for(i = 0; i < pageset1_size; i++) {
+		if (i >= pageset2_size) {
+			if (PAGEDIR_ENTRY(p,i)->address2) {
+				if (rangestart > -1) {
+					printk("Pagedir entry %d-%d address2 not set!\n", rangestart, rangeend);
+					rangestart = -1;
+				}
+				free_page(PAGEDIR_ENTRY(p,i)->address2);
+			} else {
+				if (rangestart == -1)
+					rangestart = i;
+				rangeend = i;
+			}
+		}
+	}
+		
+	if (rangestart > -1)
+		printk("Pagedir entry %d-%d address2 not set!\n", rangestart, pageset1_size - 1);
+
+	for(i = 0; i < pagedir_size; i++)
+		free_page((unsigned long) *(this_pagedir+i));
+
+	free_page((unsigned long) this_pagedir);
+	this_pagedir = NULL;
+}
+
+static int prepare_suspend_console(void)
+{
+	if ((!swsusp_state[2]) && (!swsusp_state[3]))  /* No output should be produced. 
+							  It would be good if we could tell X to 
+							  refresh the display at resume if it's 
+							  running on the  current VT. (In a script? */
+		return 0;
+
+	orig_loglevel = console_loglevel;
+	console_loglevel = new_loglevel;
+
+#ifdef CONFIG_VT
+	orig_fgconsole = fg_console;
+#ifdef SUSPEND_CONSOLE
+	if (vc_allocate(SUSPEND_CONSOLE))
+	  /* we can't have a free VC for now. Too bad,
+	   * we don't want to mess the screen for now. */
+		return 1;
+
+	set_console (SUSPEND_CONSOLE);
+	if (vt_waitactive(SUSPEND_CONSOLE)) {
+		PRINTK(SUSPEND_DEBUG, "Can't switch virtual consoles.");
+		return 1;
+	}
+	reset_terminal(SUSPEND_CONSOLE, 1);
+	orig_kmsg = kmsg_redirect;
+	kmsg_redirect = SUSPEND_CONSOLE;
+	prepare_status("");
+# endif
+#endif
+	return 0;
+}
+
+static void restore_console(void)
+{
+	if ((!swsusp_state[2]) && (!swsusp_state[3])) /* No output should have been produced */
+		return;
+
+	console_loglevel = orig_loglevel;
+#ifdef SUSPEND_CONSOLE
+	kmsg_redirect = orig_kmsg;
+	set_console (orig_fgconsole);
+#endif
+	return;
+}
+
+static int prepare_suspend_processes(void)
+{
+	if (freeze_processes()) {
+		PRINTK(SUSPEND_DEBUG, name_suspend "Not all processes stopped!\n");
+		thaw_processes();
+		return 1;
+	}
+	sys_sync();
+	return 0;
+}
+
+#define _calcpagedirsize(size) (SUSPEND_PD_PAGES(size))
+static int calcpagedirsize(int size1, int size2)
+{
+	/*
+	 * Size1 should not already include the pagedir!
+	 * 
+	 * If adding the pagedir will mean we need to store more pages that fit,
+	 * we need to expand the size until it does fit (the pagedir itself might
+	 * require more than one pagedir to store, hence the while).
+	 */
+	int result;
+	int max;
+	max = MAX(size1, size2);
+	result = _calcpagedirsize(max);
+	while (_calcpagedirsize(max + result) > result)
+		result++;
+	return result;
+}
+
+/*
+ * 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 swap (swapinfo.freeswap >= SWAP_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 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).
+ *
+ *    I have seen simply waiting for the user to press SHIFT increase the
+ *    number of pages to save by one (logs?), so tests are <, not <= to allow
+ *    one extra page. (Final test in save_suspend_image doesn't use EATEN_ENOUGH_MEMORY)
+ *
+ *    RAM_TO_RESUME includes +8 to allow for extra pages allocated during read_primary_suspend_image
+ */
+
+#define PAGEDIR_SIZE (calcpagedirsize(pageset1_size, pageset2_size))
+#define SWAP_NEEDED (pageset1_size + pageset2_size + 2 * (PAGEDIR_SIZE + 1))
+#define RAM_TO_SUSPEND (SWAP_NEEDED + - PAGEDIR_SIZE - 1 + MAX((pageset1_size - pageset2_size), 0) + PAGES_FOR_IO)
+#define RAM_TO_RESUME  ((pageset1_size + PAGEDIR_SIZE + 1) * 2 + PAGES_FOR_IO + max_mapnr - orig_mem_free + 8)
+#define EATEN_ENOUGH_MEMORY() ((RAM_TO_SUSPEND < max_mapnr) && \
+		(swapinfo.freeswap > SWAP_NEEDED) && \
+		(RAM_TO_RESUME < max_mapnr ) && \
+		((!swsusp_state[4]) || (SWAP_NEEDED < (swsusp_state[4] << 8))))
+
+static void display_debug_info(void)
+{
+	si_swapinfo(&swapinfo);
+	printk("\n\nPlease include the following information in any bug report:\n");
+	printk("- SWSUSP Version : %s\n", swsusp_version);
+	printk("- Swap available : %ld (amount unused when preparing image).\n", swapforimage);
+	printk("- Pageset sizes  : %d and %d. (Pagedir size: %d)\n", pageset1_size, pageset2_size, calcpagedirsize(pageset1_size, pageset2_size));
+	printk("- Expected sizes : %d and %d.\n", expected_size1, expected_size2);
+	printk("- Parameters     : %d %d %d %d %d\n", swsusp_state[0], swsusp_state[1], swsusp_state[2], swsusp_state[3], swsusp_state[4]);
+	printk("- Calculations   : Image size: %d. Ram to suspend: %d. To resume: %ld.\n", SWAP_NEEDED, RAM_TO_SUSPEND, RAM_TO_RESUME);
+	printk("- Limits         : %lu pages RAM. Initial boot: %lu. Current boot: %lu.\n", max_mapnr, orig_mem_free, base_mem_free);
+}
+	
+/* 
+ * We are assuming here that pageset1_size always excludes pagedir size.
+ */
+static void display_stats(unsigned int eaten)
+{ 
+	PRINTK(SUSPEND_VERBOSE, "Free:%d. Sets:%d(%d),%d. PD:%d. Swap:%d/%lu. RAM to suspend:%d; resume:%lu. Limits:%lu,%d\n", 
+			nr_free_pages() + eaten, 
+			pageset1_size, 
+			pageset1_size + 1 + PAGEDIR_SIZE,
+			pageset2_size,
+			PAGEDIR_SIZE,
+			SWAP_NEEDED,
+			swapinfo.freeswap,
+			RAM_TO_SUSPEND,
+			RAM_TO_RESUME,
+			max_mapnr,
+			swsusp_state[4] << 8); 
+}
+
+/*
+ * Eaten is the number of pages which have been eaten.
+ * Pagedirincluded is the number of pages which have been allocated for the pagedir.
+ */
+static void recalculate_stats(int pagedirincluded) 
+{ 
+	pageset_sizes_t result;
+	markpagesforpageset2();  /* Need to call this before getting pageset1_size! */
+	result = count_data_pages(NULL);
+	pageset1_size = result.size1 - pagedirincluded;
+	pageset2_size = result.size2;
+	si_swapinfo(&swapinfo);	/* FIXME: si_swapinfo(&i) returns all swap devices information.*/ 
+}
+
+static void eat_memory(void)
+{
+	int m = 0, iterations = 0, memory_still_to_eat = 0, orig_memory_still_to_eat = 0;
+
+	PRINTK(SUSPEND_DEBUG, name_suspend "Eating pages\n");
+
+	recalculate_stats(0);
+	if (SUSPEND_ABORTING) 
+		return;
+
+	display_stats(0);
+
+	orig_memory_still_to_eat = MAX(MAX(RAM_TO_SUSPEND, RAM_TO_RESUME) - max_mapnr, swapinfo.freeswap - SWAP_NEEDED);
+	orig_memory_still_to_eat = MAX(orig_memory_still_to_eat, 
+			(!swsusp_state[4]) ? (SWAP_NEEDED - (swsusp_state[4] << 8)) : 0);
+
+	prepare_status("Freeing memory (1)...");
+	// Note that if we have enough swap and enough free memory, we may exit without
+	// eating anything.
+	while ((!EATEN_ENOUGH_MEMORY()) &&
+		(m = try_to_free_pages_zone(&contig_page_data.node_zones[ZONE_HIGHMEM], GFP_KSWAPD))) {
+		recalculate_stats(0);
+		display_stats(0);
+		iterations++; 
+		
+		memory_still_to_eat = MAX(MAX(RAM_TO_SUSPEND, RAM_TO_RESUME) - max_mapnr, swapinfo.freeswap - SWAP_NEEDED);
+		memory_still_to_eat = MAX(memory_still_to_eat, 
+			(!swsusp_state[4]) ? (SWAP_NEEDED - (swsusp_state[4] << 8)) : 0);
+
+		if (orig_memory_still_to_eat)
+			update_status((orig_memory_still_to_eat - memory_still_to_eat) * 100 / orig_memory_still_to_eat);
+
+		if (EATEN_ENOUGH_MEMORY())  { /* Make sure we really have eaten enough before we leave the loop */
+			do_suspend_sync();
+			recalculate_stats(0);
+		}
+	}
+	update_status(100);
+	PRINTK(SUSPEND_VERBOSE,"\n");
+	if ((!m) && (iterations)) {
+		PRINTK(SUSPEND_DEBUG, "\n--- UNABLE TO GET ANY MORE PAGES ---\n");
+		recalculate_stats(0);
+	}
+	
+	expected_size1 = pageset1_size + 1 + PAGEDIR_SIZE;
+	expected_size2 = pageset2_size;
+
+	WAIT_FOR_SHIFT;
+
+}
+
+/*
+ * Mark unshared pages in processes not needed for suspend as being able to be written
+ * out in a separate pagedir. 
+ */
+
+static void markpagesforpageset2(void)
+{
+	int usedinsuspend, pass;
+	struct vm_area_struct * vma;
+	struct task_struct *p;
+	struct mm_struct *mm;
+	struct list_head * entry;
+	int i;
+#ifdef DEBUG_DEFAULT
+	int oldstage;
+#endif
+	const int METHOD = 2;
+
+#ifdef DEBUG_DEFAULT
+	oldstage = currentstage;
+	currentstage = STAGE_MARK_PAGESET2;
+#endif
+
+	if (max_mapnr != num_physpages) {
+		abort_suspend("mapnr is not expected");
+#ifdef DEBUG_DEFAULT
+		currentstage = oldstage;
+#endif
+		return;
+	}
+	
+	for (i = 0; i < max_mapnr; i++) {
+		ClearPageDontcopy(mem_map+i);
+		ClearPageKnowshared(mem_map+i);
+	}
+
+	if (SUSPEND_NOPAGESET2)
+		return;
+
+	read_lock(&tasklist_lock);
+	for (pass = 0; pass < 2; pass++) {
+		for_each_task(p) {
+			usedinsuspend = (!TASK_SUSPENDED(p));
+			if ((pass == 0 && usedinsuspend) || (pass == 1 && !usedinsuspend))
+				continue;
+
+			mm = p->active_mm;
+			if (!mm) continue;
+		
+			if (!mm->mmap) continue;
+		
+			down_read(&mm->mmap_sem);
+			
+			for (vma = mm->mmap; vma ; vma=vma->vm_next) {
+
+				pgd_t * pgd;
+				pmd_t * pmd;
+				pte_t * pte;
+				int mapnr;
+				unsigned long posn;
+				
+				if (!vma->vm_start) continue;
+			
+				for (posn = vma->vm_start; posn < vma->vm_end; posn+= PAGE_SIZE) {
+					pgd = pgd_offset(mm, posn);
+
+					if (!pgd) continue;
+					
+					pmd = pmd_offset(pgd, posn);
+
+					if (!pmd) continue;
+				
+					pte = pte_offset(pmd, posn);
+					mapnr = 0;
+					if (pte) 
+						mapnr = ((*pte).pte_low >> PAGE_SHIFT);
+
+					if ((mapnr) && (mapnr < max_mapnr)) {
+						// Set don't copy flag if all of the following criteria are met:
+						// 1. Not reserved and NoSave flag not set.
+						// 2. Not used in a process that runs during suspend (including swapper).
+						
+						if (PageKnowshared(mem_map+mapnr))
+							continue;
+					
+						if (PageDontcopy(mem_map+mapnr)) {
+							// If already marked, must be shared.
+							// method2: allow shared pages provided not usedinsuspend.
+							if ((METHOD == 1) || (usedinsuspend)) {
+								ClearPageDontcopy(mem_map+mapnr);
+								SetPageKnowshared(mem_map+mapnr);
+								continue;
+							}
+						} else if (!PageReserved(mem_map+mapnr) && \
+							!PageNosave(mem_map+mapnr) && \
+							!usedinsuspend)
+							
+							SetPageDontcopy(mem_map+mapnr);
+					}
+				}
+			}
+			up_read(&(mm->mmap_sem));
+		}
+	}
+
+	/*
+	 * Try marking page cache pages as pageset2 too.
+	 */
+	
+	/*
+	 * Inactive list
+	 */
+	if (!SUSPEND_NOPAGESET2INACTIVE) {
+		{
+			entry = inactive_list.prev;
+			while (entry != &inactive_list) {
+				struct page * page;
+
+				page = list_entry(entry, struct page, lru);
+				if (!PageDontcopy(page) && !PageKnowshared(page))
+					SetPageDontcopy(page);
+				entry = entry->prev;
+			}
+		}
+	}
+
+	/*
+	 * Active list
+	 */
+	if (!SUSPEND_NOPAGESET2ACTIVE) {
+		{
+			entry = active_list.prev;
+			while (entry != &active_list) {
+				struct page * page;
+
+				page = list_entry(entry, struct page, lru);
+				if (!PageDontcopy(page) && !PageKnowshared(page))
+					SetPageDontcopy(page);
+				entry = entry->prev;
+			}
+		}
+	}
+
+	/*
+	 * Finally, ensure that pagedir pages aren't marked as pageset 2
+	 */
+
+	if (pagedir_save) {
+		if (PageDontcopy(virt_to_page(pagedir_save))) {
+			PRINTK(SUSPEND_VERBOSE, "Pagedir was marked as pageset2 - unmarking.\n");
+			ClearPageDontcopy(virt_to_page(pagedir_save));
+		}
+		for (i = 0; i < pagedir_size; i++)
+			if (PageDontcopy(virt_to_page(pagedir_save[i]))) {	// Must be assigned by the time recalc stats is called
+				PRINTK(SUSPEND_VERBOSE, "Pagedir[%d] was marked as pageset2 - unmarking.\n", i);
+				ClearPageDontcopy(virt_to_page(pagedir_save[i]));
+				pageset2_size--;
+			}
+	}
+
+	read_unlock(&tasklist_lock);
+#ifdef DEBUG_DEFAULT
+	currentstage = oldstage;
+#endif
+	if (pageset2_size < 0)
+		pageset2_size = 0;
+}
+
+/* Make disk drivers accept operations, again */
+static void drivers_unsuspend(void)
+{
+#ifdef CONFIG_BLK_DEV_IDE
+	ide_disk_unsuspend(0);
+#endif
+}
+
+/* Called from process context */
+static int drivers_suspend(void)
+{
+#ifdef CONFIG_BLK_DEV_MD
+	md_notify_reboot(NULL, SYS_HALT, NULL);
+#endif
+#ifdef CONFIG_BLK_DEV_IDE
+	ide_disk_suspend();
+#endif
+	if (!pm_suspend_state) {
+		if (pm_send_all(PM_SUSPEND,(void *)3)) {
+			printk(name_suspend "Problem while sending suspend event\n");
+			return(1);
+		}
+		pm_suspend_state=1;
+	} else
+		PRINTK(SUSPEND_VERBOSE, name_suspend "PM suspend state already raised\n");
+	  
+	return(0);
+}
+
+#define RESUME_PHASE1 1 /* Called from interrupts disabled */
+#define RESUME_PHASE2 2 /* Called with interrupts enabled */
+#define RESUME_ALL_PHASES (RESUME_PHASE1 | RESUME_PHASE2)
+static void drivers_resume(int flags)
+{
+  	if(flags & RESUME_PHASE2) {
+#ifdef CONFIG_BLK_DEV_HD
+		do_reset_hd();			/* Kill all controller state */
+#endif
+	}
+  	if (flags & RESUME_PHASE1) {
+#ifdef CONFIG_BLK_DEV_IDE
+		ide_disk_unsuspend(1);		
+#endif
+#ifdef CONFIG_BLK_DEV_MD
+		md_autostart_arrays();
+#endif
+	}
+  	if (flags & RESUME_PHASE2) {
+		if (pm_suspend_state) {
+			if (pm_send_all(PM_RESUME,(void *)0))
+				printk(name_resume "Problem while sending resume event\n");
+			pm_suspend_state=0;
+		} else
+			PRINTK(SUSPEND_DEBUG, name_resume "PM suspend state wasn't raised\n");
+
+#ifdef SUSPEND_CONSOLE
+		update_screen(fg_console);	/* Hmm, is this the problem? */
+#endif
+	}
+}
+
+static int save_suspend_image(void)
+{
+	pageset_sizes_t result;
+	int temp_result;
+
+#ifdef SUSPEND_CONSOLE
+	//update_screen(fg_console);	/* Hmm, is this the problem? */
+#endif
+	pagedir_nosave = NULL;
+	PRINTK(SUSPEND_DEBUG, "/critical section: Counting pages to copy.\n");
+
+	recalculate_stats(0);
+	display_stats(0);
+	
+	if (SUSPEND_ABORTING) 
+		return 1; 
+
+	if ((RAM_TO_SUSPEND > max_mapnr) || (RAM_TO_RESUME > max_mapnr)) {
+		printk(KERN_CRIT name_suspend "Couldn't get enough free pages, on %ld pages short\n",
+			 MAX(RAM_TO_RESUME, RAM_TO_SUSPEND) - max_mapnr);
+		spin_unlock_irq(&suspend_pagedir_lock);
+		return 1;
+	}
+	if (swapinfo.freeswap < SWAP_NEEDED)  {
+		printk(KERN_CRIT name_suspend "There's not enough swap space available (%ld pages available, need %d)\n",
+			 swapinfo.freeswap, SWAP_NEEDED);
+		spin_unlock_irq(&suspend_pagedir_lock);
+		return 1;
+	}
+
+	swapforimage = swapinfo.freeswap;
+
+	pagedir_size = calcpagedirsize(pageset1_size, pageset2_size);
+	pagedir_capacity = PAGEDIR_CAPACITY(pagedir_size);
+	
+	pagedir_save = (suspend_pagedir_t **)__get_free_pages(GFP_ATOMIC, 0);
+	
+	if (!pagedir_save) {
+		abort_suspend("Failed to allocate a pagedir!\n");
+		goto abort_saving;
+	}
+	
+	{
+		int i;
+		for (i = 0; i < pagedir_size; i++) {
+			pagedir_save[i] = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC, 0);
+			if (!pagedir_save[i]) {
+				int j;
+				for (j = 0; j < i; j++)
+					free_page((unsigned long) pagedir_save[j]);
+				free_page((unsigned long) pagedir_save);
+				abort_suspend("Unable to allocate a pagedir.\n");
+				spin_unlock_irq(&suspend_pagedir_lock);
+				return 1;
+			}
+		}
+	}
+	
+	{
+		int i;
+		
+		for(i=0; i < pagedir_capacity; i++) {
+			PAGEDIR_ENTRY(pagedir_save,i)->address1 = 0;
+			PAGEDIR_ENTRY(pagedir_save,i)->address2 = 0;
+			PAGEDIR_ENTRY(pagedir_save,i)->address3 = 0;
+			PAGEDIR_ENTRY(pagedir_save,i)->swap_address1.val = 0;
+			PAGEDIR_ENTRY(pagedir_save,i)->swap_address2.val = 0;
+		}
+	}
+	
+	PRINTK(SUSPEND_VERBOSE,"Created pagedir of %d pages at %p to store a maximum of %d pages.\n",
+			pagedir_size,
+			pagedir_save,
+			pagedir_capacity);
+
+	recalculate_stats(pagedir_size);  // Get stats including pagedir (which we save to use in reloading secondary pagedir)
+
+	PRINTK(SUSPEND_VERBOSE,"-- Calling count_data_pages to set pageset addresses.\n");
+	
+	result = count_data_pages(pagedir_save);
+	pageset2_size = pageset2_size_check = result.size2;
+	pageset1_size = result.size1;
+	
+	PRINTK(SUSPEND_VERBOSE,"-- Actual values: %d and %d.", pageset1_size, pageset2_size);
+	
+	if (SUSPEND_ABORTING) 
+		goto abort_saving;
+
+	PRINTK(SUSPEND_VERBOSE, "Capacity:%d Size:%d\n", pagedir_capacity, pagedir_size);
+
+	pageset1_size_check = pageset1_size;
+	pageset2_size_check = pageset2_size;
+	pagedir_size_check = pagedir_size;
+
+	if ((pageset1_size > pagedir_capacity) || (pageset2_size > pagedir_capacity)) {
+		abort_suspend(KERN_CRIT name_suspend "Number of pages (%d & %d) grown too much! I only allowed for %d.", pageset1_size, pageset2_size, pagedir_capacity);
+		goto abort_saving;
+	}
+	
+	PRINTK(SUSPEND_VERBOSE,"-- Allocating pages for pagedir\n");
+	
+	pagedir_alloc_from = (pageset1_size > pageset2_size ? pageset2_size : pageset1_size);
+	
+	{
+		int i, numnosaveallocated=0;
+	
+		for(i=pagedir_alloc_from; i < pageset1_size; i++) {
+			PAGEDIR_ENTRY(pagedir_save,i)->address2 = get_zeroed_page(GFP_ATOMIC);
+			if (!PAGEDIR_ENTRY(pagedir_save,i)->address2) {
+				abort_suspend("Unable to allocate pages for pagedir!\n");
+				goto abort_saving;
+			}
+			SetPageNosave(virt_to_page(PAGEDIR_ENTRY(pagedir_save,i)->address2));
+			numnosaveallocated++;
+		}
+		if (pageset1_size > pagedir_alloc_from) {
+			PRINTK(SUSPEND_VERBOSE,"Allocated memory for pages from %d-%d.\n", pagedir_alloc_from, pageset1_size - 1);
+		}
+	}
+	PRINTK(SUSPEND_VERBOSE,"-- Allocating pages for pagedir done\n");
+
+	PRINTK(SUSPEND_VERBOSE,"-- Preparing to write pages\n");
+	
+	WAIT_FOR_SHIFT;
+
+	spin_unlock_irq(&suspend_pagedir_lock); 
+	
+	PRINTK(SUSPEND_VERBOSE,"-- Write pageset2\n");
+	lock_swapdevices(); /* this locks every swap device except the one set by resume= option */
+	temp_result = write_pageset(pagedir_save, 2);
+	lock_swapdevices();	/* This will unlock ignored swap devices since writing is finished */
+
+	PRINTK(SUSPEND_VERBOSE,"-- Written pageset2\n");
+	
+	WAIT_FOR_SHIFT;
+
+	spin_lock_irq(&suspend_pagedir_lock); 
+
+	if (temp_result || SUSPEND_ABORTING) {
+		goto abort_saving;
+	}
+	PRINTK(SUSPEND_VERBOSE,"-- Copying pageset1\n");
+
+	copy_pageset1(pagedir_save);
+
+	WAIT_FOR_SHIFT;
+	/*
+	 *  ---------------------   FROM HERE ON, NEED TO REREAD PAGESET2 IF ABORTING!!! -----------------
+	 */
+	
+	PRINTK(SUSPEND_VERBOSE,"-- Done\n");
+	
+	if (SUSPEND_ABORTING)
+		goto abort_reloading_pagedir_two;
+
+	/*
+	 * End of critical section. From now on, we can write to memory,
+	 * but we should not touch disk. This specially means we must _not_
+	 * touch swap space! Except we must write out our image of course.
+	 *
+	 */
+	
+	spin_unlock_irq(&suspend_pagedir_lock);
+	
+	PRINTK(SUSPEND_VERBOSE,"-- Writing pagset1\n");
+	lock_swapdevices(); /* this locks every swap device except the one set by resume= option */
+	temp_result = write_pageset(pagedir_save, 1);
+	lock_swapdevices();	/* This will unlock ignored swap devices since writing is finished */
+	PRINTK(SUSPEND_VERBOSE,"-- Done\n");
+
+	WAIT_FOR_SHIFT;
+	if (temp_result)
+		goto abort_reloading_pagedir_two;
+
+	lock_swapdevices(); /* this locks every swap device except the one set by resume= option */
+	temp_result = write_pagedir_and_suspend_header(pagedir_save);
+	lock_swapdevices();	/* This will unlock ignored swap devices since writing is finished */
+	if (temp_result)
+		goto abort_reloading_pagedir_two;
+
+	WAIT_FOR_SHIFT;
+
+	si_swapinfo(&swapinfo);
+	PRINTK(SUSPEND_VERBOSE, "Finished writing image. %ld pages of swap left.\n", swapinfo.freeswap);
+	return 0;
+
+abort_reloading_pagedir_two:
+	temp_result = read_secondary_pagedir(pagedir_save);
+	if (!temp_result)
+		panic("Attempt to reload pagedir 2 while aborting a suspend failed.");
+abort_saving:
+	free_suspend_pagedir(pagedir_save);
+	pagedir_save = NULL;
+	spin_unlock_irq(&suspend_pagedir_lock);
+
+	PRINTK(SUSPEND_VERBOSE, " ***** AT END ABORT ***** ");
+	WAIT_FOR_SHIFT;
+
+	return -1;		
+
+}
+
+extern void (*pm_power_off)(void);
+extern void swsusp_power_off(void);
+void suspend_power_down(void)
+{
+	C_A_D = 0;
+	PRINTK(SUSPEND_DEBUG, name_suspend "Trying to power down.\n");
+	beepOK;			/* last beep */
+	mdelay(1000);		/* FIXME: we need to flush hw disk cache
+				   in a more certain way */
+#ifdef CONFIG_VT
+	WAIT_FOR_SHIFT;
+	if (SUSPEND_REBOOT ^ (!!(shift_state & (1 << KG_CTRL))))
+		machine_restart(NULL);
+	else
+#endif
+	{
+		if(!pm_power_off) {
+			pm_power_off=swsusp_power_off;
+			PRINTK(SUSPEND_DEBUG, name_suspend "Trying to use an apm bios call (you should enable apm or acpi)\n"); 
+		}
+		machine_power_off();
+	}
+
+	printk(KERN_EMERG name_suspend "Probably not capable for powerdown.\n");
+	machine_halt();
+	printk(KERN_EMERG name_suspend "System is now halted.\n");
+	while (1)
+		cpu_relax();
+	/* NOTREACHED */
+}
+
+/*
+ * Magic happens here
+ */
+
+void do_magic_resume_1(void)
+{
+	barrier();
+	mb();
+	spin_lock_irq(&suspend_pagedir_lock);	/* Done to disable interrupts */ 
+
+	PRINTK(SUSPEND_DEBUG, name_resume "Waiting for DMAs to settle down...\n");
+	mdelay(1000);	/* We do not want some readahead with DMA to corrupt our memory, right?
+			   Do it with disabled interrupts for best effect. That way, if some
+			   driver scheduled DMA, we have good chance for DMA to finish ;-). */
+}
+
+inline void restore_processor_context (void);
+
+void do_magic_resume_2(void)
+{
+	__flush_tlb_global();
+	now_resuming = 1;
+	
+#ifdef SUSPEND_CONSOLE
+	update_screen(fg_console);	/* Hmm, is this the problem? */
+#endif
+
+	WAIT_FOR_SHIFT;
+
+	read_secondary_pagedir(pagedir_save);	
+	prepare_status("Cleaning up...");
+	update_status(100);
+	
+	PRINTK(SUSPEND_DEBUG, name_resume "Freeing prev allocated pagedir.\n");
+	free_suspend_pagedir(pagedir_save);
+	pagedir_save = NULL;
+	PRINTK(SUSPEND_DEBUG, name_resume "Resume drivers.\n");
+	drivers_resume(RESUME_ALL_PHASES);
+	PRINTK(SUSPEND_DEBUG, name_resume "Free pagedir lock.\n");
+	spin_unlock_irq(&suspend_pagedir_lock);
+	PRINTK(SUSPEND_DEBUG, name_resume "Fixing swap signatures... ");
+	mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RESUME);
+	PRINTK(SUSPEND_DEBUG, "ok\n");
+
+#ifdef SUSPEND_CONSOLE
+	update_screen(fg_console);	/* Hmm, is this the problem? */
+#endif
+}
+
+void do_magic_suspend_1(void)
+{
+	mb();
+	barrier();
+	spin_lock_irq(&suspend_pagedir_lock);
+}
+
+void do_magic_suspend_2(void)
+{
+#ifdef DEBUG_DEFAULT
+	currentstage = STAGE_WRITE_PAGESETS;
+#endif
+	PRINTK(SUSPEND_DEBUG, "-- AT START OF DO_MAGIC_SUSPEND_2 --\n");
+	PRINTK(SUSPEND_VERBOSE,"-- read_swapfiles()\n");
+	read_swapfiles();
+	if (!save_suspend_image())
+		suspend_power_down ();
+	
+#ifdef DEBUG_DEFAULT
+	currentstage = 0;
+#endif
+	printk(KERN_EMERG name_suspend "Suspend failed, trying to recover...\n");
+	MDELAY(1000); /* So user can wait and report us messages if armageddon comes :-) */
+
+	barrier();
+	mb();
+	drivers_resume(RESUME_ALL_PHASES);
+	mark_swapfiles(((swp_entry_t) {0}), MARK_SWAP_RECOVER);
+}
+
+#define SUSPEND_C
+#include <asm/suspend.h>
+
+static void do_software_suspend(void)
+{
+	unsigned long flags;
+	int i;
+
+	si_swapinfo(&swapinfo);	/* FIXME: si_swapinfo(&i) returns all swap devices information.*/ 
+	if (!swapinfo.freeswap) {
+		printk(KERN_CRIT "You need some swap space to be able to suspend to disk.\n");
+		return;
+	}
+	now_resuming = 0;
+	if (prepare_suspend_console())
+		printk(name_suspend "Can't allocate a console... proceeding\n");
+#ifdef DEBUG_DEFAULT
+	currentstage = STAGE_FREEZER;
+#endif
+	beepOK;			/* first beep */
+	/* Order is significant. 
+	 * We set up the nosavemap first so all maps are recorded as nosave.
+	 */
+	PRINTK(SUSPEND_DEBUG, "Allocating nosavemap\n");
+	for (i = 0; i < max_mapnr; i++) {
+		ClearPageNosave(mem_map+i);
+		ClearPageDontcopy(mem_map+i);
+		ClearPageKnowshared(mem_map+i);
+	}
+	SetPageNosave(virt_to_page(copybuff));
+	
+	// Tasks allowed to run while eating memory.
+	prepare_status("Getting tq_disk lock...");
+	spin_lock_irqsave(&tq_disk, flags);
+	prepare_status("Calling prepare_suspend_processes...");
+	if (prepare_suspend_processes()) {
+		spin_unlock_irqrestore(&tq_disk, flags);
+		prepare_status("Failed to freeze all processes!");
+	} else {
+		spin_unlock_irqrestore(&tq_disk, flags);
+		prepare_status("Processes frozen, lock released...");
+		/* Get the tq_disk lock before freezing processes */
+		prepare_status("Processes frozen...");
+#ifdef DEBUG_DEFAULT
+		currentstage = STAGE_EAT_MEMORY;
+#endif
+		beepOK;		/* second beep */
+		eat_memory();
+#ifdef DEBUG_DEFAULT
+		currentstage = 0;
+#endif
+#if 0
+		free_memory(nb);
+#endif
+		if (!SUSPEND_ABORTING) {
+			/* No need to invalidate any vfsmnt list -- they will be valid after resume, anyway.
+			 *
+			 * We sync here -- so you have consistent filesystem state when things go wrong.
+			 * -- so that noone writes to disk after we do atomic copy of data.
+			 */
+			PRINTK(SUSPEND_VERBOSE, name_suspend "Syncing disks before copy\n" );
+			prepare_status("Syncing disks...");
+			do_suspend_sync();
+			if (drivers_suspend()==0) { 
+				prepare_status("Starting low level suspend...");
+				do_suspend_lowlevel(0);			/* This function returns after machine woken up from resume */
+				beepOK;
+			}
+		}
+	}
+	if (SUSPEND_VERBOSE)
+		display_debug_info();
+
+	PRINTK(SUSPEND_VERBOSE, name_resume "Restarting processes with %d pages free...\n", nr_free_pages());
+	thaw_processes();
+	
+	MDELAY(1000);
+	restore_console ();
+}
+
+void software_suspend(void)
+{
+	PRINTK(SUSPEND_DEBUG, name_swsusp "Entering software_suspend\n");
+	if (!software_suspend_enabled) {
+		printk(name_swsusp "software suspend is disabled\n");
+		return;
+	}
+	if (!(cpu_has_pse||cpu_has_pse36)) {
+		printk(name_swsusp "pse or pse36 is required, disabling software suspend");
+		return;
+	}
+	if (in_interrupt())
+		BUG();
+	software_suspend_enabled = 0;
+	do_software_suspend();
+	software_suspend_enabled = 1;
+}
+
+/*
+ * This is main interface to the outside world. It needs to be
+ * called from process context. It notifies kswsuspd thread
+ * through SUSPEND_PENDING and wait for resume.
+ */
+void software_suspend_pending(void)
+{
+	swsusp_state[0] = 1;
+	while(SUSPEND_PENDING) {
+	  	schedule();
+		if (suspend_task)
+			refrigerator(PF_REFRIGERATE);
+		set_current_state(TASK_INTERRUPTIBLE); /* wait for resume */
+	}
+}
+
+static void warmup_collision_cache(suspend_pagedir_t **pagedir) {
+	int i;
+	
+	PRINTK(SUSPEND_DEBUG, "Setting up pagedir cache...");
+	for (i = 0; i < max_mapnr; i++)
+		ClearPageCollides(mem_map+i);
+
+	for(i=0; i < pageset1_size; i++) {
+		SetPageCollides(virt_to_page(PAGEDIR_ENTRY(pagedir, i)->address1));
+		if (!(i%800)) {
+			PRINTK(SUSPEND_DEBUG, ".");
+		}
+	}
+	PRINTK(SUSPEND_DEBUG, "%d", i);
+	PRINTK(SUSPEND_DEBUG, "|\n");
+}
+
+
+/*
+ * Returns true if given address collides with any orig_address 
+ */
+#define does_collide(pagedir, address) (PageCollides(virt_to_page(address)))
+
+/*
+ * 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
+ */
+
+static int check_pagedir(void)
+{
+	int i, nrdone = 0;
+	void **eaten_memory = NULL;
+	void **c = eaten_memory, *f, *addr;
+
+	// 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.
+	
+	PRINTK(SUSPEND_VERBOSE,"\nAllocating memory.\n");
+	for(i=0; i < MAX(pageset1_size, pageset2_size); i++) {
+
+		while ((addr = (void *) get_zeroed_page(GFP_ATOMIC))) {
+			memset(addr, 0, PAGE_SIZE);
+			if (!does_collide(pagedir_nosave, (unsigned long) addr)) {
+				break;
+			}
+			eaten_memory = addr;
+			*eaten_memory = c;
+			c = eaten_memory;
+		}
+		PAGEDIR_ENTRY(pagedir_nosave,i)->address3 = (unsigned long) addr;
+		nrdone++;
+	}
+
+	// Free unwanted memory
+	c = eaten_memory;
+	while(c) {
+		//PRINTK(SUSPEND_DEBUG, ":");
+		f = c;
+		c = *c;
+		if (f)
+			free_page((unsigned long) f);
+	}
+	eaten_memory = NULL;
+	
+	PRINTK(SUSPEND_VERBOSE,"Check_pagedir: Prepared %d of %d pages.\n", nrdone, MAX(pageset1_size, pageset2_size));
+	
+	WAIT_FOR_SHIFT;
+
+	return 0;
+}
+
+static int relocate_pagedir(void)
+{
+	void **eaten_memory = NULL;
+	void **c = eaten_memory, *m = NULL, *f;
+	int oom = 0, i, numeaten = 0;
+
+	PRINTK(SUSPEND_VERBOSE,"Relocating conflicting parts of pagedir.\n");
+	for (i = -1; i < pagedir_size; i++) {
+		int this_collides = 0;
+
+		if (i == -1)
+			this_collides = does_collide(pagedir_nosave, (unsigned long) pagedir_nosave);
+		else
+			this_collides = does_collide(pagedir_nosave, (unsigned long) pagedir_nosave[i]);
+
+		if (this_collides) {
+			while ((m = (void *) __get_free_pages(GFP_ATOMIC, 0))) {
+				memset(m, 0, PAGE_SIZE);
+				if (!does_collide(pagedir_nosave, (unsigned long)m)) {
+					if (i == -1) {
+						copy_page(m, pagedir_nosave);
+						free_page((unsigned long) pagedir_nosave);
+						pagedir_nosave = m;
+					}
+					else {
+						copy_page(m, (void *) pagedir_nosave[i]);
+						free_page((unsigned long) pagedir_nosave[i]);
+						pagedir_nosave[i] = m;
+					}
+					//__flush_tlb();
+					break;
+				}
+				numeaten ++;
+				eaten_memory = m;
+				PRINTNOLOG(SUSPEND_VERBOSE,"Eaten: %d. Still to try:%d\r", numeaten, nr_free_pages()); 
+				*eaten_memory = c;
+				c = eaten_memory;
+			}
+			if (!m) {
+				printk("\nRan out of memory trying to relocate pagedir (tried %d pages).\n", numeaten);
+				oom = 1;
+				break;
+			}
+		}
+
+	}
+		
+	PRINTK(SUSPEND_VERBOSE,"\nFreeing rejected memory locations...");
+	c = eaten_memory;
+	while(c) {
+		f = c;
+		c = *c;
+		if (f)
+			free_pages((unsigned long) f, 0);
+	}
+	eaten_memory = NULL;
+	
+	PRINTK(SUSPEND_VERBOSE,"\n");
+	
+	WAIT_FOR_SHIFT;
+
+	if (oom) 
+		return -ENOMEM;
+	else
+		return 0;
+}
+
+/*
+ * Sanity check if this image makes sense with this kernel/swap context
+ * I really don't think that it's foolproof but more than nothing..
+ */
+
+static int sanity_check_failed(char *reason)
+{
+	beepERR;
+	printk(KERN_ERR name_resume "%s\n",reason);
+	printk(KERN_ERR "BIG FAT WARNING: if you continue booting with this kernel, be sure to mkswap your swap partition. Otherwise, you may severely damage your filesystem on next reboot with the kernel that wrote the resume image.");
+	printk(KERN_ERR "Press SHIFT to reboot or CONTROL to continue booting with this kernel\n");
+	while (!(shift_state & ((1 << KG_SHIFT) | (1 << KG_CTRL)))) 
+		schedule(); 
+	if (shift_state & (1 << KG_SHIFT))
+		machine_restart(NULL);
+	printk(KERN_ERR "ARE YOU SURE: Unless you really know what you're doing just reboot on the correct kernel or use the noresume boot option.");
+	printk(KERN_ERR "Press SHIFT to reboot or CONTROL to continue booting with this kernel\n");
+	while (!(shift_state & ((1 << KG_SHIFT) | (1 << KG_CTRL)))) 
+		schedule(); 
+	if (shift_state & (1 << KG_SHIFT))
+		machine_restart(NULL);
+	return -EPERM;
+}
+
+static int sanity_check(struct suspend_header *sh)
+{
+	if (sh->version_code != LINUX_VERSION_CODE)
+		return sanity_check_failed("Incorrect kernel version");
+	if (sh->num_physpages != num_physpages)
+		return sanity_check_failed("Incorrect memory size");
+	if (strncmp(sh->machine, system_utsname.machine, 65))
+		return sanity_check_failed("Incorrect machine type");
+	if (strncmp(sh->version, system_utsname.version, 65))
+		return sanity_check_failed("Incorrect version");
+	if (sh->num_cpus != smp_num_cpus)
+		return sanity_check_failed("Incorrect number of cpus");
+	if (sh->page_size != PAGE_SIZE)
+		return sanity_check_failed("Incorrect PAGE_SIZE");
+	return 0;
+}
+
+static int bdev_read_page(long pos, void *buf)
+{
+	struct buffer_head *bh;
+	if (pos%PAGE_SIZE) {
+		abort_suspend("pos > pagesize!\n");
+		return -EIO;
+	}
+	BREAD_BUFFER_HEAD(pos);
+	memcpy(buf, bh->b_data, PAGE_SIZE);
+	if (!buffer_uptodate(bh)) {
+		abort_suspend("buffer_uptodate false in bdev_read_page!\n");
+		brelse(bh);
+		BFREE(bh);
+		return -1;
+	}
+	brelse(bh);
+	BFREE(bh);
+	return 0;
+} 
+
+static int swsusp_mainloop(void *unused)
+{
+  	printk(name_swsusp "kswsuspd starting\n"); /* This will print the current version on boot */
+	daemonize();
+
+	strcpy(current->comm, "kswsuspd");
+	sigfillset(&current->blocked);
+
+	for(;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		for(;!SUSPEND_PENDING;) {	  
+			schedule_timeout(SWSUSP_CHECK_TIMEOUT);
+			set_current_state(TASK_INTERRUPTIBLE);
+		}
+		software_suspend();
+		swsusp_state[0] = 0;
+#ifdef DEBUG_DEFAULT
+		currentbeep = 100; /* not the original one, so that we know if this is first or subsequent suspend */
+#endif
+	}
+	return(0);
+}
+
+static int noresume_fix_signature(union p_diskpage cur)
+{
+	struct buffer_head *bh;
+				/* We don't do a sanity check here: we want to restore the swap
+				   whatever version of kernel made the suspend image */
+	printk(name_resume "%s: Fixing swap signatures...\n", resume_file);
+				/* We need to write swap, but swap is *not* enabled so
+				   we must write the device directly */
+	PRINTK(SUSPEND_VERBOSE,"Swap signature will be set to %10s\n",cur.pointer->swh.magic.magic);
+	BREAD_BUFFER_HEAD(0);
+	if (is_read_only(bh->b_dev)) {
+		printk(KERN_ERR name_resume "Can't write to read-only device %s\n",
+		       kdevname(bh->b_dev));
+		BFREE(bh);
+		return -EIO;
+	}
+	
+	memcpy(bh->b_data, cur.ptr, PAGE_SIZE);
+	generic_make_request(WRITE, bh);
+	wait_on_buffer(bh);
+	if (buffer_uptodate(bh)) {
+		brelse(bh);
+		BFREE(bh);
+		free_suspend_cache_page();
+		/* As with fix_signature, try to read to ensure its written. */
+		BREAD_BUFFER_HEAD(0);
+		brelse(bh);
+		BFREE(bh);
+		return 0;
+	}
+	printk(name_resume "Warning %s: Fixing swap signatures unsuccessful...\n", resume_file);
+	bforget(bh);
+	BFREE(bh);
+	return -EINVAL;		/* non fatal error */
+}
+
+static int __read_primary_suspend_image(union p_diskpage cur, int noresume)
+{				/* returned values:
+				   -EINVAL in case of swapspace or if noresume is set and swap signature
+				   wasn't correctly fixed (non fatal error),
+				   0 if suspended image was correctly read or if noresume is set
+				   and swap signature was correctly fixed,
+				   other non zero values are errors. */
+	swp_entry_t next;
+	int i, tries = 0, len, *ptr;
+	unsigned long pagedirend = 0;
+	void **eaten_memory = NULL;
+	void **c = eaten_memory, *f;
+	//int orig_free = nr_free_pages();
+
+#define PREPARENEXT do { \
+		next = cur.pointer->link.next; \
+		next.val = SWP_OFFSET(next) * PAGE_SIZE; \
+        } while(0)
+
+	if (bdev_read_page(0, cur.ptr)) return -EIO;
+
+	if ((!memcmp("SWAP-SPACE",cur.pointer->swh.magic.magic,10)) ||
+	    (!memcmp("SWAPSPACE2",cur.pointer->swh.magic.magic,10))) {
+		printk(KERN_ERR name_resume "This is normal swap space\n" );
+		return -EINVAL;	/* non fatal error */
+	}
+	PREPARENEXT; /* We have to read next position before we overwrite it */
+
+	if (!memcmp("SUSP1R",cur.pointer->swh.magic.magic,6))
+		memcpy(cur.pointer->swh.magic.magic,"SWAP-SPACE",10);
+	else if (!memcmp("SUSP2R",cur.pointer->swh.magic.magic,6))
+		memcpy(cur.pointer->swh.magic.magic,"SWAPSPACE2",10);
+	else {
+		printk("%sUnable to find suspended-data signature (%.10s - misspelled?\n", 
+			name_resume, cur.pointer->swh.magic.magic);
+		return -EINVAL;
+		/* We want to abort_suspend even with noresume -- we certainly don't want to add
+		   our signature into your ext2 filesystem ;-) */
+	}
+	if (noresume)
+		return(noresume_fix_signature(cur));
+	
+	PRINTK(SUSPEND_VERBOSE, name_resume "Signature found, resuming\n");
+	MDELAY(1000);
+	
+	if (bdev_read_page(next.val, cur.ptr)) return -EIO;
+	if (sanity_check(&(cur.pointer->sh))) /* Is this the same machine? */
+		return -EPERM;
+#ifdef SOFTWARE_SUSPEND_MTRR
+	ptr = ((int *)(cur.ptr+sizeof(struct suspend_header)));
+	len = *ptr;
+	if(len)
+		mtrr_resume(ptr);
+#endif
+	PREPARENEXT;
+
+	base_mem_free = cur.pointer->sh.base_mem_free;
+	swapforimage = cur.pointer->sh.swapforimage;
+	pagedir_save = cur.pointer->sh.suspend_pagedir;
+	expected_size1 = cur.pointer->sh.expected_size1;
+	expected_size2 = cur.pointer->sh.expected_size2;
+	pageset1_size = cur.pointer->sh.pageset1_size;
+	pageset2_size = cur.pointer->sh.pageset2_size;
+	pagedir_capacity = MAX(pageset1_size, pageset2_size);
+	pagedir_size = cur.pointer->sh.pagedir_size;
+	swsusp_state[0] = cur.pointer->sh.param0;
+	swsusp_state[1] = cur.pointer->sh.param1;
+	swsusp_state[2] = cur.pointer->sh.param2;
+	swsusp_state[3] = cur.pointer->sh.param3;
+	/*
+	 * We don't need to save swsusp_state[4] because it's not used during resume
+	 * and will be restored with the image anyway
+	 */
+	
+	now_resuming = 1;
+	if (prepare_suspend_console())
+		printk(name_resume "Can't allocate a console... proceeding\n");
+
+	pagedir_nosave = (suspend_pagedir_t **)__get_free_pages(GFP_ATOMIC, 0);
+	
+	if (!pagedir_nosave)
+		return -ENOMEM;
+
+	{
+		int i;
+		for (i = 0; i < pagedir_size; i++) {
+			pagedir_nosave[i] = (suspend_pagedir_t *)__get_free_pages(GFP_ATOMIC, 0);
+			if (!pagedir_nosave[i]) {
+				int j;
+				for (j = 0; j < i; j++)
+					free_page((unsigned long) pagedir_nosave[j]);
+				free_page((unsigned long) pagedir_nosave);
+				abort_suspend("Unable to allocate a pagedir.\n");
+				spin_unlock_irq(&suspend_pagedir_lock);
+				return -ENOMEM;
+			}
+		}
+	}
+	
+	PRINTK(SUSPEND_VERBOSE,name_resume "Reading pagedir of %d pages, %lx", pagedir_size, next.val);
+
+	/* We get pages in reverse order of saving! */
+	for (i=pagedir_size-1; i>=0; i--) {
+		PRINTNOLOG(SUSPEND_VERBOSE,"\r%d to read, %d free. ", i+1, nr_free_pages());
+#ifdef DEBUG_DEFAULT
+		if ((i < PRINTTHRESH) || ((i+PRINTTHRESH) > pagedir_size))
+			PRINTNOLOG(SUSPEND_VERBOSE, "[sw]%lx -> [pd%d]%p.\n", next.val, i, pagedir_nosave[i]);
+#endif
+		if (!next.val) {
+			abort_suspend("next.val NULL in __read_primary_suspend_image!\n");
+			return -EPERM;
+		}
+		cur.pointer = (union diskpage *) pagedir_nosave[i];
+		if (bdev_read_page(next.val, cur.ptr)) return -EIO;
+		pagedirend = next.val;
+		PREPARENEXT;
+	}
+	PRINTK(SUSPEND_VERBOSE,"\r%d read, %d free.   ", pagedir_size, nr_free_pages());
+	if (next.val) {
+		abort_suspend("next.val not NULL in __read_primary_suspend_image!\n");
+		return -EPERM;
+	}
+
+	WAIT_FOR_SHIFT;
+
+ 	warmup_collision_cache(pagedir_nosave);
+ 
+	PRINTK(SUSPEND_VERBOSE, "Attempting to relocate pagedir with %d pages free.\n", nr_free_pages());
+	if (relocate_pagedir())
+		return -ENOMEM;
+	if (check_pagedir())
+		return -ENOMEM;
+
+	PRINTK(SUSPEND_VERBOSE,name_resume "Reading primary pageset data (%d pages). Pagedir at %lx\n", pageset1_size, (unsigned long) pagedir_nosave);
+	
+	PRINTK(SUSPEND_VERBOSE,"Seeking copy buffer location.\n");
+	while ((copybuff = (unsigned long) get_zeroed_page(GFP_ATOMIC))) {
+		memset((void *) copybuff, 0, PAGE_SIZE);
+		if (!does_collide(pagedir_nosave, copybuff)) {
+			break;
+		}
+		tries++;
+		PRINTK(SUSPEND_VERBOSE,"\r%d rejected copybuff locations.", tries);
+		eaten_memory = (void *) copybuff;
+		*eaten_memory = c;
+		c = eaten_memory;
+	}
+	
+	PRINTK(SUSPEND_VERBOSE,"\nFreeing rejected copy buffer locations:");
+	tries = 0;
+	c = eaten_memory;
+	while(c) {
+		tries++;
+		PRINTK(SUSPEND_VERBOSE,"\r%d rejected copybuff locations freed.", tries);
+		f = c;
+		c = *c;
+		if (f)
+			free_page((unsigned long) f);
+	}
+	PRINTK(SUSPEND_VERBOSE,"\n");
+	eaten_memory = NULL;
+
+	WAIT_FOR_SHIFT;
+
+	prepare_status("Reading suspend image (1)...");
+
+	for(i=0; i < pageset1_size; i++) {
+		swp_entry_t swap_address = PAGEDIR_ENTRY(pagedir_nosave,i)->swap_address1;
+		PRINTNOLOG(SUSPEND_VERBOSE,"\r%d/%d read. %d free.", i+1, pageset1_size, nr_free_pages());
+#ifdef DEBUG_DEFAULT
+		if ((i+PRINTTHRESH) > pageset1_size)
+			PRINTNOLOG(SUSPEND_VERBOSE, "!%d! [sw]%lx -> [3]%lx (->[1]%lx).\n", i, next.val, PAGEDIR_ENTRY(pagedir_nosave,i)->address3, PAGEDIR_ENTRY(pagedir_nosave,i)->address1);
+#endif
+		update_status((int) (i * 100 / pageset1_size));
+		next.val = SWP_OFFSET (swap_address) * PAGE_SIZE;
+		/* You do not need to check for overlaps...
+		   ... check_pagedir already did this work */
+		if (bdev_read_page(next.val, (char *)(PAGEDIR_ENTRY(pagedir_nosave,i)->address3))) return -EIO;
+		//__flush_tlb();
+	}
+	PRINTK(SUSPEND_VERBOSE,"\n|\n");
+	
+	update_status(100);
+	
+	WAIT_FOR_SHIFT;
+
+	return 0;
+}
+
+static int read_primary_suspend_image(char * specialfile, int noresume)
+{
+	union p_diskpage cur;
+	unsigned long scratch_page = 0;
+	int error, blksize = 0;
+
+	beepOK;
+	resume_device = name_to_kdev_t(specialfile);
+	scratch_page = get_zeroed_page(GFP_ATOMIC);
+	if (scratch_page) {
+		cur.address = scratch_page;
+		PRINTK(SUSPEND_DEBUG, name_resume "Resuming from device %x\n", resume_device);
+		
+		if (!blksize_size[MAJOR(resume_device)]) {
+			PRINTK(SUSPEND_DEBUG, name_resume "Blocksize not known?\n");
+		} else blksize = blksize_size[MAJOR(resume_device)][MINOR(resume_device)];
+		if (!blksize) {
+			PRINTK(SUSPEND_DEBUG, name_resume "Blocksize not set?\n");
+			blksize = PAGE_SIZE;
+		}
+		set_blocksize(resume_device, PAGE_SIZE);
+		error = __read_primary_suspend_image(cur, noresume);
+		free_page(scratch_page);
+	} else error = -ENOMEM;
+
+	switch (error) {
+		case 0:
+			beepOK;
+		case -EINVAL:	/* non fatal error */
+			set_blocksize(resume_device, blksize);
+			software_suspend_enabled = 1;
+
+			MDELAY(1000);
+			return(error);
+			break;
+		case -EIO:
+			printk(KERN_CRIT name_resume "I/O error\n");
+			beepERR;
+			break;
+		case -ENOENT:
+			printk(KERN_CRIT name_resume "%s: No such file or directory\n", specialfile);
+			break;
+		case -EPERM:
+			printk(KERN_CRIT name_resume "Sanity check error\n");
+			beepERR;
+			break;
+		default:
+			printk(KERN_CRIT name_resume "Error %d resuming\n", error);
+			beepERR;
+			break;
+	}
+	__read_primary_suspend_image(cur, 1); /* Try to fix the swap signature */
+	abort_suspend("Error %d in read_primary_suspend_image",error);
+	return(error);
+}
+
+static int read_secondary_pagedir(suspend_pagedir_t ** pagedir)
+{
+	swp_entry_t next;
+	int i, result = 0, loop;
+
+	if (!pageset2_size)
+		return 0;
+
+	PRINTK(SUSPEND_DEBUG, "Beginning of read_secondary_pagedir: "); 
+	drivers_unsuspend(); 
+
+	PRINTK(SUSPEND_VERBOSE,name_resume "Reading secondary pageset data (%d pages). Pagedir at %lx.\n", pageset2_size, (unsigned long) pagedir);
+	if (!copybuff)
+		abort_suspend("Copy buffer isn't allocated!\n");
+
+	prepare_status("Reading suspend image (2)...");
+	beepOK;
+	
+	for(i=0; i < pageset2_size; i++) {
+		pte_t * pte;
+		int pageprot = 0, restore = 0;
+		swp_entry_t swap_address = PAGEDIR_ENTRY(pagedir, i)->swap_address2;
+		next.val = SWP_OFFSET (swap_address) * PAGE_SIZE;
+		PRINTNOLOG(SUSPEND_VERBOSE,"\r%d/%d. ", i+1, pageset2_size);
+		update_status((int) (i * 100 / pageset2_size));
+		if (bdev_read_page(next.val, (char *)(copybuff))) {
+			result = -EIO;
+			break;
+		}
+		swap_free(swap_address);
+		
+		pte = lookup_address(PAGEDIR_ENTRY(pagedir, i)->address2);
+		if (pte) {
+			pageprot = pte->pte_low & ~_PAGE_CHG_MASK;
+			if (pageprot != pgprot_val(PAGE_KERNEL_NOCACHE)) {
+				set_pte(pte, pte_modify(*pte, PAGE_KERNEL_NOCACHE));	// Remove any page protection while we restore contents
+				restore = 1;
+			}
+		}
+
+		for (loop=0; loop < PAGE_SIZE; loop++) {
+			*(((char *)(PAGEDIR_ENTRY(pagedir, i)->address2))+loop) =
+				*(((char *)(copybuff))+loop);
+		}
+
+		__flush_tlb_one(PAGEDIR_ENTRY(pagedir, i)->address2);
+		__flush_tlb_one(copybuff);
+
+		if (restore) {
+			set_pte(pte, pte_modify(*pte, __pgprot(pageprot)));
+		}
+	}
+	
+	update_status(100);
+	WAIT_FOR_SHIFT;
+
+	PRINTK(SUSPEND_VERBOSE,"\n");
+	__flush_tlb_global();		/* Flush again after reading secondary pagedir */
+	drivers_suspend(); 
+	return result;
+}
+
+/*
+ * Called from init kernel_thread.
+ * We check if we have an image and if so we try to resume
+ */
+
+void software_resume(void)
+{
+#ifdef CONFIG_SMP
+	printk(name_swsusp "malfunctioning SMP support. Disabled :(\n");
+#else
+	/* We enable the possibility of machine suspend */
+	software_suspend_enabled = 1;
+#endif
+	copybuff = get_zeroed_page(GFP_ATOMIC);
+	orig_mem_free = nr_free_pages();
+	SetPageNosave(virt_to_page(copybuff));
+
+	if (!resume_status)
+		return;
+
+	if (resume_status == NORESUME) {
+		PRINTK(SUSPEND_DEBUG, name_swsusp "resuming disabled\n");
+		if (resume_file[0])
+			read_primary_suspend_image(resume_file,1); /* non fatal error ignored */
+		kernel_thread(swsusp_mainloop, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
+		return;
+	}
+	MDELAY(1000);
+
+	orig_loglevel = console_loglevel;
+	console_loglevel = new_loglevel;
+
+	if (!resume_file[0] && resume_status == RESUME_SPECIFIED) {
+		printk(name_swsusp "suspension device unspecified\n");
+		software_suspend_enabled = 0;
+		return;
+	}
+
+	PRINTK(SUSPEND_DEBUG, name_swsusp "resuming from %s\n", resume_file);
+	if (read_primary_suspend_image(resume_file, 0)) { /* non fatal error (normal swap space) */
+		kernel_thread(swsusp_mainloop, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
+		console_loglevel = orig_loglevel;
+		return;
+	}		
+
+	prepare_status("Copying pages back (no status - sensitive!)...");
+	
+	do_suspend_lowlevel(1);
+	BUG();
+}
+
+static int __init resume_setup(char *str)
+{
+	if (resume_status)
+		return 1;
+	if( (str == NULL) || (strncmp(str, "/dev/h", 6)) ) {
+		printk(name_swsusp "software suspend is currently only compatible with IDE /dev/h?? swap partitions: disabled\n");
+		resume_status = 0;
+		software_suspend_enabled = 0;
+		return 1;
+	}
+
+	strncpy( resume_file, str, 255 );
+	resume_status = RESUME_SPECIFIED;
+
+	return 1;
+}
+
+static int __init swsusp_dbg_setup(char *str)
+{
+	if(str)
+		swsusp_state[2]=simple_strtol(str,NULL,0);
+	printk(name_swsusp "debug parameter set : %x\n",swsusp_state[2]);
+	PRINTK(SUSPEND_DEBUG,"debugging messages enabled\n");
+	PRINTK(SUSPEND_PROGRESS,"displaying progress bar enabled\n");
+	PRINTK(SUSPEND_VERBOSE,"verbose messages enabled\n");
+	PRINTK(SUSPEND_SLOW,"slow down delays enabled\n");
+	PRINTK(SUSPEND_BEEP,"beeps enabled\n");
+	return 0;
+}
+
+static int __init software_noresume(char *str)
+{
+	resume_status = NORESUME;
+	if (!resume_status) {
+		printk(name_swsusp "noresume option lacks a resume= option\n");
+		return 1;
+	}
+	return 0;
+}
+
+__setup("resume=", resume_setup);
+__setup("swsusp_dbg=", swsusp_dbg_setup);
+__setup("noresume", software_noresume);
+
+EXPORT_SYMBOL(software_suspend_pending);
+EXPORT_SYMBOL(swsusp_state);
+EXPORT_SYMBOL(software_suspend_enabled);
+EXPORT_SYMBOL(refrigerator);
+EXPORT_SYMBOL(suspend_task);
Index: linuxx/kernel/sys.c
diff -u linuxx/kernel/sys.c:1.1.1.1.6.1 linuxx/kernel/sys.c:1.1.1.1.12.1.6.1
--- linuxx/kernel/sys.c:1.1.1.1.6.1	Fri Feb 28 22:09:30 2003
+++ linuxx/kernel/sys.c	Fri Feb 28 22:47:01 2003
@@ -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>
@@ -341,6 +342,16 @@
 		printk(KERN_EMERG "Restarting system with command '%s'.\n", buffer);
 		machine_restart(buffer);
 		break;
+
+#ifdef CONFIG_SOFTWARE_SUSPEND
+	case LINUX_REBOOT_CMD_SW_SUSPEND:
+		if(!software_suspend_enabled)
+			return -EAGAIN;
+		
+		software_suspend_pending();
+		do_exit(0);
+		break;
+#endif
 
 	default:
 		unlock_kernel();
Index: linuxx/kernel/sysctl.c
diff -u linuxx/kernel/sysctl.c:1.1.1.1.6.1 linuxx/kernel/sysctl.c:1.1.1.1.12.1.6.3
--- linuxx/kernel/sysctl.c:1.1.1.1.6.1	Thu Feb  6 07:42:33 2003
+++ linuxx/kernel/sysctl.c	Thu Feb  6 13:20:28 2003
@@ -42,6 +42,9 @@
 /* External variables not in a header file. */
 extern int panic_timeout;
 extern int C_A_D;
+#ifdef CONFIG_SOFTWARE_SUSPEND
+extern int swsusp_state[];
+#endif
 extern int bdf_prm[], bdflush_min[], bdflush_max[];
 extern int sysctl_overcommit_memory;
 extern int max_threads;
@@ -198,6 +201,10 @@
 #endif
 	{KERN_CTLALTDEL, "ctrl-alt-del", &C_A_D, sizeof(int),
 	 0644, NULL, &proc_dointvec},
+#ifdef CONFIG_SOFTWARE_SUSPEND
+	{KERN_SWSUSP, "swsusp", &swsusp_state, 5*sizeof(int),
+	 0644, NULL, &proc_dointvec},
+#endif
 	{KERN_PRINTK, "printk", &console_loglevel, 4*sizeof(int),
 	 0644, NULL, &proc_dointvec},
 #ifdef CONFIG_KMOD
Index: linuxx/mm/filemap.c
diff -u linuxx/mm/filemap.c:1.1.1.1.6.3 linuxx/mm/filemap.c:1.1.1.1.16.4
--- linuxx/mm/filemap.c:1.1.1.1.6.3	Fri Feb 28 22:09:30 2003
+++ linuxx/mm/filemap.c	Fri Feb 28 22:47:02 2003
@@ -23,6 +23,7 @@
 #include <linux/init.h>
 #include <linux/mm.h>
 #include <linux/iobuf.h>
+#include <linux/suspend.h>
 
 #include <asm/pgalloc.h>
 #include <asm/uaccess.h>
@@ -79,6 +80,9 @@
 		next->pprev_hash = &page->next_hash;
 	if (page->buffers)
 		PAGE_BUG(page);
+#ifdef CONFIG_SOFTWARE_SUSPEND
+	last_suspend_cache_page = page;
+#endif
 	atomic_inc(&page_cache_size);
 }
 
Index: linuxx/mm/page_alloc.c
diff -u linuxx/mm/page_alloc.c:1.1.1.1.6.1 linuxx/mm/page_alloc.c:1.1.1.1.10.1.6.2
--- linuxx/mm/page_alloc.c:1.1.1.1.6.1	Fri Nov 29 13:55:12 2002
+++ linuxx/mm/page_alloc.c	Sat Jan  4 23:58:19 2003
@@ -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;
@@ -98,8 +99,10 @@
 
 	if (page->buffers)
 		BUG();
-	if (page->mapping)
+	if (page->mapping) {
+	  	printk("pagebug: %lx\n",page);
 		BUG();
+	}
 	if (!VALID_PAGE(page))
 		BUG();
 	if (PageLocked(page))
@@ -330,7 +333,9 @@
 	zone_t **zone, * classzone;
 	struct page * page;
 	int freed;
-
+#if CONFIG_SOFTWARE_SUSPEND
+	static unsigned int loopcount;
+#endif	
 	zone = zonelist->zones;
 	classzone = *zone;
 	if (classzone == NULL)
@@ -374,8 +379,25 @@
 	}
 
 	/* here we're in the low on memory slow path */
-
+#if CONFIG_SOFTWARE_SUSPEND
+	loopcount=0;
+#endif
 rebalance:
+#ifdef CONFIG_SOFTWARE_SUSPEND
+	if(gfp_mask & __GFP_FAST) {
+/* when using memeat, we ask for all pages that are really free.
+   800 calls to reschedule should be sufficient to recall all of them since
+   when a page can be found, it is after only one reschedule.
+   Actually I consider this as a bug of alloc_pages, since allocating a
+   page should not hang in an endless loop when it is clear that no
+   memory is available (cbd) */	  
+		loopcount++;
+		//if(!(loopcount%10))
+		//	printk("_");
+		if(loopcount > 800)
+			return NULL;
+	}
+#endif
 	if (current->flags & (PF_MEMALLOC | PF_MEMDIE)) {
 		zone = zonelist->zones;
 		for (;;) {
Index: linuxx/mm/page_io.c
diff -u linuxx/mm/page_io.c:1.1.1.1.6.1 linuxx/mm/page_io.c:1.1.1.1.10.1.6.1
--- linuxx/mm/page_io.c:1.1.1.1.6.1	Fri Nov 29 13:55:13 2002
+++ linuxx/mm/page_io.c	Tue Dec  3 18:37:15 2002
@@ -82,12 +82,19 @@
  *  - it's marked as being swap-cache
  *  - it's associated with the swap inode
  */
+#if 0
+extern long suspend_device;
+#endif
 void rw_swap_page(int rw, struct page *page)
 {
 	swp_entry_t entry;
 
 	entry.val = page->index;
 
+#if 0
+	if (suspend_device)
+		panic("I refuse to corrupt memory/swap.");
+#endif
 	if (!PageLocked(page))
 		PAGE_BUG(page);
 	if (!PageSwapCache(page))
@@ -101,7 +108,7 @@
  * Therefore we can't use it.  Later when we can remove the need for the
  * lock map and we can reduce the number of functions exported.
  */
-void rw_swap_page_nolock(int rw, swp_entry_t entry, char *buf)
+void __rw_swap_page_nolock(int rw, swp_entry_t entry, char *buf, int free)
 {
 	struct page *page = virt_to_page(buf);
 	
@@ -113,8 +120,10 @@
 	page->mapping = &swapper_space;
 	if (rw_swap_page_base(rw, entry, page))
 		lock_page(page);
-	if (!block_flushpage(page, 0))
-		PAGE_BUG(page);
+	if(free) {
+		if (!block_flushpage(page, 0))
+			PAGE_BUG(page);
+	}
 	page->mapping = NULL;
 	UnlockPage(page);
 }
Index: linuxx/mm/vmscan.c
diff -u linuxx/mm/vmscan.c:1.1.1.1.6.1 linuxx/mm/vmscan.c:1.1.1.1.10.1.6.3
--- linuxx/mm/vmscan.c:1.1.1.1.6.1	Fri Nov 29 13:55:13 2002
+++ linuxx/mm/vmscan.c	Wed Feb  5 22:53:51 2003
@@ -21,6 +21,7 @@
 #include <linux/smp_lock.h>
 #include <linux/pagemap.h>
 #include <linux/init.h>
+#include <linux/suspend.h>
 #include <linux/highmem.h>
 #include <linux/file.h>
 
@@ -738,18 +739,22 @@
 	 * us from recursively trying to free more memory as we're
 	 * trying to free the first piece of memory in the first place).
 	 */
-	tsk->flags |= PF_MEMALLOC;
+	tsk->flags |= PF_MEMALLOC | PF_REFRIGERATE;
 
 	/*
 	 * Kswapd main loop.
 	 */
 	for (;;) {
+		if (current->flags & PF_FREEZE)
+			refrigerator(PF_IOTHREAD);
 		__set_current_state(TASK_INTERRUPTIBLE);
 		add_wait_queue(&kswapd_wait, &wait);
 
 		mb();
-		if (kswapd_can_sleep())
+		if (kswapd_can_sleep()) {
 			schedule();
+		}
+		
 
 		__set_current_state(TASK_RUNNING);
 		remove_wait_queue(&kswapd_wait, &wait);
@@ -772,4 +777,175 @@
 	return 0;
 }
 
+#ifdef CONFIG_SOFTWARE_SUSPEND
+void free_suspend_cache_page(void)
+{
+	struct page * page;
+
+	page = last_suspend_cache_page;
+	
+	if (!page)
+		return;
+
+	last_suspend_cache_page = NULL;
+
+	spin_lock(&pagemap_lru_lock);
+
+	if (unlikely(!PageLRU(page)))
+		return;
+	if (unlikely(PageActive(page))) 
+		return;
+
+	/*
+	 * Zero page counts can happen because we unlink the pages
+	 * _after_ decrementing the usage count..
+	 */
+	if (unlikely(!page_count(page)))
+		goto outofhere;
+
+	/* Racy check to avoid trylocking when not worthwhile */
+	if (!page->buffers && (page_count(page) != 1 || !page->mapping))
+		goto outofhere;
+
+	/*
+	 * The page is locked. IO in progress?
+	 * Move it to the back of the list.
+	 */
+	if (unlikely(TryLockPage(page))) {
+		if (PageLaunder(page)) {
+			page_cache_get(page);
+			spin_unlock(&pagemap_lru_lock);
+			wait_on_page(page);
+			//printk("\nfree_suspend_cache_page: Freeing a page (%p) that had PageLaunder set!", page);
+			page_cache_release(page);
+			spin_lock(&pagemap_lru_lock);
+		}
+		goto outofhere;
+	}
+
+	if (PageDirty(page) && is_page_cache_freeable(page) && page->mapping) {
+		/*
+		 * It is not critical here to write it only if
+		 * the page is unmapped beause any direct writer
+		 * like O_DIRECT would set the PG_dirty bitflag
+		 * on the phisical page after having successfully
+		 * pinned it and after the I/O to the page is finished,
+		 * so the direct writes to the page cannot get lost.
+		 */
+		int (*writepage)(struct page *);
+
+		writepage = page->mapping->a_ops->writepage;
+		if (writepage) {
+			ClearPageDirty(page);
+			SetPageLaunder(page);
+			page_cache_get(page);
+			spin_unlock(&pagemap_lru_lock);
+
+			writepage(page);
+			
+			//printk("\nfree_suspend_cache_page: Freeing a page (%p) that had PageDirty etc!", page);
+			page_cache_release(page);
+
+			spin_lock(&pagemap_lru_lock);
+			goto outofhere;
+		}
+	}
+
+	/*
+	 * If the page has buffers, try to free the buffer mappings
+	 * associated with this page. If we succeed, the page will
+	 * not be freed because suspend_task is set.
+	 */
+	if (page->buffers) {
+		spin_unlock(&pagemap_lru_lock);
+
+		/* avoid to free a locked page */
+		page_cache_get(page);
+
+		if (try_to_release_page(page, 0)) {
+			if (!page->mapping) {
+				/*
+				 * We must not allow an anon page
+				 * with no buffers to be visible on
+				 * the LRU, so we unlock the page after
+				 * taking the lru lock
+				 */
+				spin_lock(&pagemap_lru_lock);
+				UnlockPage(page);
+				__lru_cache_del(page);
+
+				/* effectively free the page here */
+				//printk("\nfree_suspend_cache_page: Freeing a page (%p) that had buffers (path a)!", page);
+				page_cache_release(page);
+
+				goto outofhere;
+			} else {
+				/*
+				 * The page is still in pagecache so undo the stuff
+				 * before the try_to_release_page since we've not
+				 * finished and we can now try the next step.
+				 */
+				page_cache_release(page);
+
+				spin_lock(&pagemap_lru_lock);
+			}
+		} else {
+			/* failed to drop the buffers so stop here */
+			UnlockPage(page);
+			//printk("\nfree_suspend_cache_page: Freeing a page (%p) that had buffers (path b)!", page);
+			page_cache_release(page);
+
+			spin_lock(&pagemap_lru_lock);
+			goto outofhere;
+		}
+	}
+
+	spin_lock(&pagecache_lock);
+
+	/*
+	 * this is the non-racy check for busy page.
+	 */
+	if (!page->mapping || !is_page_cache_freeable(page)) {
+		spin_unlock(&pagecache_lock);
+		UnlockPage(page);
+		goto outofhere;
+
+	}
+
+	/*
+	 * It is critical to check PageDirty _after_ we made sure
+	 * the page is freeable* so not in use by anybody.
+	 */
+	if (PageDirty(page)) {
+		spin_unlock(&pagecache_lock);
+		UnlockPage(page);
+		goto outofhere;
+	}
+
+	/* point of no return */
+	if (likely(!PageSwapCache(page))) {
+		__remove_inode_page(page);
+		spin_unlock(&pagecache_lock);
+	} else {
+		swp_entry_t swap;
+		swap.val = page->index;
+		__delete_from_swap_cache(page);
+		spin_unlock(&pagecache_lock);
+		swap_free(swap);
+	}
+
+	__lru_cache_del(page);
+	UnlockPage(page);
+
+	/* effectively free the page here */
+	page_cache_release(page);
+	//printk("free_suspend_cache_page: Freeing a page (%p) at the end of the procedure!", page);
+	//printk("^%p^", page);
+
+outofhere:
+	spin_unlock(&pagemap_lru_lock);
+	return;
+}
+
+#endif
 module_init(kswapd_init)
Index: linuxx/net/sunrpc/sched.c
diff -u linuxx/net/sunrpc/sched.c:1.1.1.1.6.1 linuxx/net/sunrpc/sched.c:1.1.1.1.10.1.6.2
--- linuxx/net/sunrpc/sched.c:1.1.1.1.6.1	Fri Nov 29 13:55:19 2002
+++ linuxx/net/sunrpc/sched.c	Wed Feb  5 22:53:51 2003
@@ -19,6 +19,7 @@
 #include <linux/smp.h>
 #include <linux/smp_lock.h>
 #include <linux/spinlock.h>
+#include <linux/suspend.h>
 
 #include <linux/sunrpc/clnt.h>
 #include <linux/sunrpc/xprt.h>
@@ -998,6 +999,7 @@
 	spin_unlock_irq(&current->sigmask_lock);
 
 	strcpy(current->comm, "rpciod");
+	current->flags |= PF_REFRIGERATE;
 
 	dprintk("RPC: rpciod starting (pid %d)\n", rpciod_pid);
 	while (rpciod_users) {
@@ -1013,6 +1015,11 @@
 		}
 
 		if (!rpciod_task_pending()) {
+			if (current->flags & PF_FREEZE) { 
+				dprintk("Now suspending rpciod\n");
+				refrigerator(PF_IOTHREAD);
+				dprintk("Now resuming rpciod\n");
+			}
 			dprintk("RPC: rpciod back to sleep\n");
 			wait_event_interruptible(rpciod_idle, rpciod_task_pending());
 			dprintk("RPC: switch to rpciod\n");