diff -ruNp 855-plugin-checksumming-old/kernel/power/suspend_checksums.c 855-plugin-checksumming-new/kernel/power/suspend_checksums.c
--- 855-plugin-checksumming-old/kernel/power/suspend_checksums.c	1970-01-01 10:00:00.000000000 +1000
+++ 855-plugin-checksumming-new/kernel/power/suspend_checksums.c	2004-12-24 12:00:53.000000000 +1100
@@ -0,0 +1,630 @@
+#include <linux/suspend.h>
+#include <linux/highmem.h>
+#ifdef CONFIG_KDB
+#include <linux/kdb.h>
+#include <linux/kdbprivate.h>
+#endif
+#include <linux/module.h>
+
+#include "suspend2_core/suspend.h"
+#include "suspend2_core/pageflags.h"
+#include "suspend2_core/debug.h"
+#include "plugins.h"
+#include "proc.h"
+
+#define CHECKSUMS_PER_PAGE ((PAGE_SIZE - sizeof(void *)) / sizeof(unsigned long))
+#define NEXT_CHECKSUM_PAGE(page) *((unsigned long *) (((char *) (page)) + PAGE_SIZE - sizeof(void *)))
+static int checksum_pages;
+static unsigned long * first_checksum_page, *last_checksum_page;
+static int num_reload_pages = 0;
+
+struct reload_data
+{
+	int pageset;
+	int pagenumber;
+	struct page * page_address;
+	char * base_version;
+	char * compared_version;
+	struct reload_data * next;
+};
+
+static struct reload_data * first_reload_data, * last_reload_data;
+
+static unsigned long suspend_page_checksum(struct page * page)
+{
+	unsigned long * virt;
+	int i;
+	unsigned long value = 0;
+
+	virt = (unsigned long *) kmap_atomic(page, KM_USER0);
+	for (i = 0; i < (PAGE_SIZE / sizeof(unsigned long)); i++)
+		value += *(virt + i);
+	kunmap_atomic(virt, KM_USER0);
+	return value;
+}
+
+static void suspend_calculate_checksums(void)
+{
+	int i = 0, page_index = 0, whichpagedir = 1, current_pfn;
+	unsigned long * current_checksum_page = first_checksum_page;
+	
+	if (!first_checksum_page) {
+		prepare_status(1, 0, "Unable to checksum at this point.");
+		return;
+	}
+
+	prepare_status(1, 0, "Calculating checksums... ");
+	//printk("First checksum page is %p.\n", current_checksum_page);
+	
+	current_pfn = __get_next_bit_on(pageset1_map, -1);
+	
+	do {
+		//printk("Page number %d... Orig address %p.", i, pbe.origaddress);
+		*(current_checksum_page + page_index) =
+			suspend_page_checksum(pfn_to_page(current_pfn));
+		//printk("Checksum calculated as %lx.\n", *(current_checksum_page + page_index));
+		i++;
+		page_index++;
+		if (page_index == CHECKSUMS_PER_PAGE) {
+			page_index = 0;
+			current_checksum_page = (unsigned long *)
+				NEXT_CHECKSUM_PAGE(current_checksum_page);
+			//printk("Moving to new checksum page %p.\n", current_checksum_page);
+		}
+		if (whichpagedir == 1) {
+			if (pagedir1.pageset_size == i) {
+				//if (test_suspend_state(SUSPEND_PAGESET2_NOT_LOADED))
+					goto out;
+				current_pfn = __get_next_bit_on(pageset2_map, -1);
+				whichpagedir = 2;
+				i = 0;
+			} else
+				current_pfn = __get_next_bit_on(pageset1_map, current_pfn);
+		} else {
+			if (pagedir2.pageset_size == i)
+				goto out;
+			current_pfn = __get_next_bit_on(pageset1_map, current_pfn);
+		}
+	} while(1);
+	
+out:
+	prepare_status(1, 0, "Checksums done.");
+}
+
+void suspend_check_checksums(void)
+{
+	int i = 0, page_index = 0, whichpagedir = 1, num_differences = 0, current_pfn;
+	unsigned long * current_checksum_page = first_checksum_page;
+	unsigned long sum_now;
+	struct reload_data * next_reload_data = first_reload_data;
+
+	if (!first_checksum_page) {
+		prepare_status(1, 0, "Unable to checksum at this point.");
+		return;
+	}
+
+	//prepare_status(1, 0, "Checking checksums... ");
+	
+	current_pfn = __get_next_bit_on(pageset1_map, -1);
+	
+	do {
+		/* Also ignore the page containing our variables */
+		if (PageChecksumIgnore(pfn_to_page(current_pfn)) || 
+				(pfn_to_page(current_pfn) == virt_to_page(&i)))
+			goto skip;
+				
+		sum_now = suspend_page_checksum(pfn_to_page(current_pfn));
+		if (sum_now != *(current_checksum_page + page_index)) {
+			num_differences++;
+			if (next_reload_data) {
+				char * virt;
+				next_reload_data->pageset = whichpagedir;
+				next_reload_data->pagenumber = i;
+				next_reload_data->page_address = pfn_to_page(current_pfn);
+				virt = kmap_atomic(pfn_to_page(current_pfn), KM_USER0);
+				memcpy(next_reload_data->compared_version, 
+						virt, PAGE_SIZE);
+				kunmap_atomic(virt, KM_USER0);
+				next_reload_data = next_reload_data->next;
+			}
+		}
+skip:
+		i++;
+		page_index++;
+		if (page_index == CHECKSUMS_PER_PAGE) {
+			page_index = 0;
+			current_checksum_page =	(unsigned long *) 
+				NEXT_CHECKSUM_PAGE(current_checksum_page);
+		}
+		if (whichpagedir == 1) {
+			if (pagedir1.pageset_size == i) {
+				//if (test_suspend_state(SUSPEND_PAGESET2_NOT_LOADED))
+					goto out;
+				current_pfn = __get_next_bit_on(pageset2_map, -1);
+				whichpagedir = 2;
+				i = 0;
+			} else
+				current_pfn = __get_next_bit_on(pageset1_map, current_pfn);
+		} else {
+			if (pagedir2.pageset_size == i)
+				goto out;
+			current_pfn = __get_next_bit_on(pageset1_map, current_pfn);
+		}
+	} while(1);
+	
+out:
+	//printk("%d/%d different.\n", num_differences, i);
+	//prepare_status(1, 0, "Differencing done.");
+	return;
+}
+
+/*
+ * free_reload_data.
+ *
+ * Reload data begins on a page boundary.
+ */
+static void suspend_free_reload_data(void)
+{
+	struct reload_data * this_data = first_reload_data;
+	struct reload_data *prev_reload_data = this_data;
+	
+	while (this_data) {
+		if (this_data->compared_version) {
+		  ClearPageNosave(virt_to_page(this_data->compared_version));
+		  free_pages((unsigned long) this_data->compared_version, 0);
+		}
+
+		if (this_data->base_version) {
+			ClearPageNosave(virt_to_page(this_data->base_version));
+			free_pages((unsigned long) this_data->base_version, 0);
+		}
+
+		this_data = this_data->next;
+
+		if (!(((unsigned long) this_data) & ~PAGE_MASK)) {
+			//printk("Linking %p to %p.\n", prev_reload_data, this_data);
+			prev_reload_data->next = this_data;
+			prev_reload_data = this_data;
+		}
+	}
+
+	this_data = first_reload_data;
+	while (this_data) {
+		prev_reload_data = this_data;
+		this_data = this_data->next;
+		//printk("Freeing reload page %p.\n", prev_reload_data);
+		free_pages((unsigned long) prev_reload_data, 0);
+		num_reload_pages--;
+	}
+
+	first_reload_data = last_reload_data = NULL;
+
+}
+
+/* suspend_reread_pages()
+ *
+ * Description:	Reread pages from an image for diagnosing differences.
+ * Arguments:	page_list:	A list containing information on pages
+ *                              to be reloaded, sorted by pageset and
+ *                              page index.
+ * Returns:	Zero on success or -1 on failure.
+ */
+
+static int suspend_reread_pages(struct reload_data * page_list)
+{
+	int result = 0, whichtoread;
+	long i, current_pfn;
+	struct list_head *filter;
+	struct suspend_plugin_ops * this_plugin, * first_filter = get_next_filter(NULL);
+	unsigned long *** bitmap;
+
+	if (!page_list)
+		return 0;
+
+	PRINTFREEMEM("at start of read pageset");
+
+	for (whichtoread = page_list->pageset; whichtoread <= 2; whichtoread++) {
+		struct pagedir * pagedir;
+
+		switch (whichtoread) {
+			case 1:
+				pagedir = &pagedir1;
+				bitmap = &pageset1_map;
+				break;
+			case 2:
+				pagedir = &pagedir2;
+				bitmap = &pageset2_map;
+				break;
+			default:
+				goto out;
+		}
+
+		suspend_message(SUSPEND_IO, SUSPEND_LOW, 0,
+			"Reread pages from pagedir %d.\n", whichtoread);
+
+		/* Initialise page transformers */
+		list_for_each(filter, &suspend_filters) {
+			this_plugin = list_entry(filter, struct suspend_plugin_ops,
+				ops.filter.filter_list);
+			if (this_plugin->disabled)
+				continue;
+			if (this_plugin->read_init && 
+				this_plugin->read_init(whichtoread)) {
+				abort_suspend("Failed to initialise a filter.");
+				return 1;
+			}
+		}
+
+		/* Initialise writer */
+		if (active_writer->read_init(whichtoread)) {
+			abort_suspend("Failed to initialise the writer."); 
+			result = 1;
+			goto reread_free_buffers;
+		}
+
+		/* Initialise other plugins */
+		list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+			if (this_plugin->disabled)
+				continue;
+			if ((this_plugin->type == FILTER_PLUGIN) ||
+			    (this_plugin->type == WRITER_PLUGIN))
+				continue;
+			if (this_plugin->read_init)
+				if (this_plugin->read_init(whichtoread)) {
+					SET_RESULT_STATE(SUSPEND_ABORTED);
+					goto reread_free_buffers;
+				}
+		}
+
+		current_pfn = __get_next_bit_on(*bitmap, -1);
+
+		/* Read the pages */
+		for (i=0; i< pagedir->pageset_size; i++) {
+			/* Read */
+			result = first_filter->ops.filter.read_chunk(
+					virt_to_page(page_list->base_version),
+					SUSPEND_SYNC);
+
+			if (result) {
+				abort_suspend("Failed to read a chunk of the image.");
+				goto reread_free_buffers;
+			}
+
+			/* Interactivity*/
+			check_shift_keys(0, NULL);
+
+			/* Prepare next */
+			current_pfn = __get_next_bit_on(*bitmap, current_pfn);
+
+			/* Got the one we're after? */
+			if (i == page_list->pagenumber)
+				page_list = page_list->next;
+
+			if (page_list->pageset != whichtoread)
+				break;
+		}
+		
+reread_free_buffers:
+
+		/* Cleanup other plugins */
+		list_for_each_entry(this_plugin, &suspend_plugins, plugin_list) {
+			if (this_plugin->disabled)
+				continue;
+			if ((this_plugin->type == FILTER_PLUGIN) ||
+			    (this_plugin->type == WRITER_PLUGIN))
+				continue;
+			if (this_plugin->read_cleanup)
+				this_plugin->read_cleanup();
+		}
+
+		/* Cleanup reads from this pageset. */
+		list_for_each_entry(this_plugin, &suspend_filters, ops.filter.filter_list) {
+			if (this_plugin->disabled)
+				continue;
+			if (this_plugin->read_cleanup &&
+				this_plugin->read_cleanup()) {
+				abort_suspend("Failed to cleanup a filter.");
+				result = 1;
+			}
+		}
+
+		if (active_writer->read_cleanup()) {
+			abort_suspend("Failed to cleanup the writer.");
+			result = 1;
+		}
+	}
+out:
+	printk("\n");
+
+	return result;
+}
+static void suspend_free_checksum_pages(void)
+{
+	unsigned long * next_checksum_page;
+
+	while(first_checksum_page) {
+		next_checksum_page =
+		  (unsigned long *) NEXT_CHECKSUM_PAGE(first_checksum_page);
+		free_pages((unsigned long) first_checksum_page, 0);
+		first_checksum_page = next_checksum_page;
+	}
+	last_checksum_page = NULL;
+	checksum_pages = 0;
+	suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_PAGES, 1);
+}
+
+#define PRINTABLE(a) (((a) < 32 || (a) > 122) ? '.' : (a))
+extern int PageExtentPage(char * seeking);
+
+static void local_print_location(
+		unsigned char * real,
+		unsigned char * original,
+		unsigned char * resumetime)
+{
+	int i;
+
+	for (i = 0; i < 8; i++)
+		if (*(original + i) != *(resumetime + i))
+			break;
+	if (i == 8)
+		return;
+	
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "%p", real);
+	if (PageNosave(virt_to_page(real)))
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			" [NoSave]");
+	if (PageExtentPage(real))
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			" [ExtentPage]");
+	if (PageSlab(virt_to_page(real)))
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			" [Slab]");
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "\n");
+
+#ifdef CONFIG_KDB
+	for (i = 0; i < 8; i++) {
+		static const char *last_sym = NULL;
+		if (*(original + i) != *(resumetime + i)) {
+			kdb_symtab_t symtab;
+
+			kdbnearsym((unsigned long) real + i,
+					&symtab);
+
+			if ((!symtab.sym_name) ||
+					(symtab.sym_name == last_sym))
+				continue;
+
+			last_sym = symtab.sym_name;
+
+			suspend_message(SUSPEND_INTEGRITY, SUSPEND_LOW, 1,
+				"%p = %s\n",
+				symtab.sym_start,
+				symtab.sym_name);
+		}
+	}
+#endif
+
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%2x ", *(original + i));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "    ");
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%c", PRINTABLE(*(original + i)));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "    ");
+	
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%2x ", *(resumetime + i));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "    ");
+	for (i = 0; i < 8; i++)
+		suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1,
+			"%c", PRINTABLE(*(resumetime + i)));
+	suspend_message(SUSPEND_INTEGRITY, SUSPEND_HIGH, 1, "\n\n");
+}
+
+static int suspend_allocate_reload_data(int pages)
+{
+	struct reload_data * this_data;
+	unsigned long data_start;
+	int i;
+
+	if (num_reload_pages >= pages)
+		return 0;
+
+	for (i = 1; i <= pages; i++) {
+		data_start = suspend2_get_grabbed_pages(0);
+
+		if (!data_start)
+			return -ENOMEM;
+
+		SetPageChecksumIgnore(virt_to_page(data_start));
+		this_data = (struct reload_data *) data_start; 
+		num_reload_pages++;
+
+		while (data_start == 
+		  ((((unsigned long) (this_data + 1)) - 1) & PAGE_MASK)) {
+			struct page * page;
+			unsigned long virt;
+
+			virt = suspend2_get_grabbed_pages(0);
+			if (!virt) {
+				printk("Couldn't get a page in which to store "
+					"a changed page.\n");
+				return -ENOMEM;
+			}
+			page = virt_to_page(virt);
+
+			this_data->compared_version = (char *) virt;
+			SetPageNosave(page);
+			SetPageChecksumIgnore(page);
+			
+			virt = suspend2_get_grabbed_pages(0);
+			if (!virt) {
+				printk("Couldn't get a page in which to store "
+					"a baseline page.\n");
+				return -ENOMEM;
+			}
+			page = virt_to_page(virt);
+
+			this_data->base_version = (char *) virt;
+			SetPageNosave(page);
+			SetPageChecksumIgnore(page);
+
+			if (last_reload_data)
+				last_reload_data->next = this_data;
+			else
+				first_reload_data = this_data;
+			
+			last_reload_data = this_data;
+
+			this_data++;
+		}
+
+		check_shift_keys(0, NULL);
+	}
+
+	return 0;
+}
+
+static void suspend_print_differences(void)
+{
+	struct reload_data * this_data = first_reload_data;
+	int i;
+
+	suspend_reread_pages(first_reload_data);
+	
+	while (this_data) {
+		if (this_data->pageset && 
+		    this_data->pagenumber) {
+			suspend_message(SUSPEND_INTEGRITY, SUSPEND_MEDIUM, 1,
+				"Pagedir %d. Page %d. Address %p."
+				" Base %p. Copy %p.\n",
+				this_data->pageset,
+				this_data->pagenumber,
+				page_address(this_data->page_address),
+				this_data->base_version,
+				this_data->compared_version);
+			for (i= 0; i < (PAGE_SIZE / 8); i++) {
+				local_print_location(
+					page_address(this_data->page_address) + i * 8,
+					this_data->base_version + i * 8,
+					this_data->compared_version + i * 8);
+				check_shift_keys(0, NULL);
+			}
+			check_shift_keys(1, NULL);
+		} else
+			return;
+		this_data = this_data->next;
+	}
+}
+
+int __suspend_allocate_checksum_pages(void)
+{
+	int pages_required =
+		(pageset1_size + pageset2_size) / CHECKSUMS_PER_PAGE;
+	unsigned long this_page;
+
+	while (checksum_pages <= pages_required) {
+		this_page = suspend2_get_grabbed_pages(0);
+		if (!this_page)
+			return -ENOMEM;
+
+		if (!first_checksum_page)
+			first_checksum_page = 
+				(unsigned long *) this_page;
+		else
+			NEXT_CHECKSUM_PAGE(last_checksum_page) = this_page;
+		
+		last_checksum_page = (unsigned long *) this_page;
+		SetPageChecksumIgnore(virt_to_page(this_page));
+		checksum_pages++;
+	}
+	suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_PAGES, 0);
+
+	return suspend_allocate_reload_data(2);
+}
+
+static int suspend_checksum_init(void)
+{
+	if (allocate_local_pageflags(&checksum_map))
+		return 1;
+	PRINTFREEMEM("after allocating checksum map");
+	suspend_store_free_mem(5, 0);
+	return 0;
+}
+
+
+static void suspend_checksum_cleanup(void)
+{
+	suspend_free_reload_data();
+	suspend_free_checksum_pages();
+	
+	free_local_pageflags(&checksum_map);
+	PRINTFREEMEM("after freeing checksum map");
+	suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_MAP, 1);
+}
+
+static struct suspend_plugin_ops checksum_ops = 
+{
+	.name					= "Checksum",
+	.type					= CHECKSUM_PLUGIN,
+	.initialise				= suspend_checksum_init,
+	.cleanup				= suspend_checksum_cleanup,
+	.ops = {
+		.checksum = {
+			.calculate_checksums	= suspend_calculate_checksums,
+			.check_checksums	= suspend_check_checksums,
+			.print_differences	= suspend_print_differences,
+			.allocate_pages		= __suspend_allocate_checksum_pages,
+		}
+	}
+};
+
+static struct suspend_proc_data proc_params[] = {
+	{ .filename			= "disable_checksumming",
+	.permissions			= PROC_RW,
+	.type				= SUSPEND_PROC_DATA_INTEGER,
+	.data = {
+		.integer = {
+			.variable	= &checksum_ops.disabled,
+			.minimum	= 0,
+			.maximum	= 1,
+		}
+	 }
+	},
+};
+
+static __init int checksum_load(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+	int result;
+
+	if (!(result = suspend_register_plugin(&checksum_ops))) {
+		printk("Software Suspend Checksum Module\n");
+		for (i=0; i< numfiles; i++)
+			suspend_register_procfile(&proc_params[i]);
+	}
+	return result;
+}
+
+#ifdef MODULE
+static __exit void checksum_unload(void)
+{
+	int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data);
+
+	printk("Software Suspend Checksum module unloading.\n");
+
+	for (i=0; i< numfiles; i++)
+		suspend_unregister_procfile(&proc_params[i]);
+	suspend_unregister_plugin(&checksum_ops);
+}
+
+module_init(checksum_load);
+module_exit(checksum_unload);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nigel Cunningham");
+MODULE_DESCRIPTION("Suspend2 checksum module");
+#else
+late_initcall(checksum_load);
+#endif
