diff -ruN post-version-specific/include/linux/suspend-common.h software-suspend-core-2.0/include/linux/suspend-common.h --- post-version-specific/include/linux/suspend-common.h 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/include/linux/suspend-common.h 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,566 @@ +/* + * Software Suspend. + * + * Module level definitions. + * + */ + +#ifndef SWSUSP_COMMON_H +#define SWSUSP_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct rangechain { + struct range * first; + struct range * last; + int size; /* size of the range ie sum (max-min+1) */ + int allocs; + int frees; + int debug; + int timesusedoptimisation; + char * name; + struct range * lastaccessed, *prevtolastaccessed, *prevtoprev; +}; + +/* + * We rely on ranges not fitting evenly into a page. + * The last four bytes are used to store the number + * of the page, to make saving & reloading pages simpler. + */ +struct range { + unsigned long minimum; + unsigned long maximum; + struct range * next; +}; + +/* page backup entry */ +struct pbe { + struct page * origaddress; /* Original address of page */ + struct page * address; /* Address of copy of page */ + struct range * currentorigrange; + struct range * currentdestrange; + + struct pagedir * pagedir; +}; + +struct pagedir { + int pagedir_num; + int pageset_size; + int lastpageset_size; + struct rangechain origranges; + struct rangechain destranges; + struct rangechain allocdranges; +}; + +struct pbelink { + char dummy[sizeof(struct pbe) - sizeof(struct pbe *)]; + struct pbe * next; +}; + +#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 orig_mem_free; + int num_range_pages; + struct pagedir pagedir; + struct range * unused_ranges; + int pageset_2_size; + int param0; + int param1; + int param2; + int param3; + int param4; + int progress0; + int progress1; + int progress2; + int progress3; +}; + +extern struct tq_struct suspend_tq; +extern int swsusp_default_console_level; +extern spinlock_t suspend_irq_lock __nosavedata; +extern unsigned long swsuspirqflags __nosavedata; +extern int max_async_ios; +extern int image_size_limit; +extern int now_resuming; +extern struct pagedir pagedir1, pagedir2; + +struct pageset_sizes_result { + int size1; /* Can't be unsigned - breaks MAX function */ + int size2; + int size2low; + int needmorespace; +}; + +/* */ +extern int C_A_D; + +#if defined(CONFIG_VT) && defined(CONFIG_VT_CONSOLE) +#define DEFAULT_SUSPEND_CONSOLE (MAX_NR_CONSOLES-1) +#endif + +#define TIMEOUT (6 * HZ) /* Timeout for stopping processes */ +#define __ADDRESS(x) ((unsigned long) phys_to_virt(x)) +#define ADDRESS(x) __ADDRESS((x) << PAGE_SHIFT) + +#define MB(x) ((x) >> (20 - PAGE_SHIFT)) + +/* References to section boundaries */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) +extern char _text[], _etext[], _edata[], __bss_start[], _end[]; +extern char __nosave_begin[], __nosave_end[]; + +extern inline void signal_wake_up(struct task_struct *t, int resume); + +#else /* 2.4 version */ + +extern char _text, _etext, _edata, __bss_start, _end; +extern char __nosave_begin, __nosave_end; + +extern inline void signal_wake_up(struct task_struct *t); + +#endif /* Version specific */ + +extern void ide_disk_unsuspend(int); +extern void ide_disk_suspend(void); +extern int try_to_free_pages_swsusp(int amount_needed); + +extern unsigned long swsusp_debug_state; + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +extern int swsusp_pool_level(void); +#define PRINTFREEMEM(desn) printlog(SUSPEND_MEMORY, SUSPEND_MEDIUM, \ + "Free memory %s: %d+%d.\n", desn, nr_free_pages(), swsusp_pool_level()) +#else /* CONFIG_SOFTWARE_SUSPEND_DEBUG */ +#define PRINTFREEMEM(desn) do { } while(0) +#endif /* CONFIG_SOFTWARE_SUSPEND_DEBUG */ + +#ifdef CONFIG_VT +/* All output is suppressed if the user switches consoles after + * suspend starts or if the NO_OUTPUT bit is on. (A vendor might + * want this for a static display?). */ +extern int suspend_console; +#define NO_OUTPUT_OR_PAUSING ((fg_console != suspend_console) || \ + (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT))) +#else +#define NO_OUTPUT_OR_PAUSING (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT)) +#endif + +/* Conditions which determine whether a message is displayed. */ +#define CHECKMASK(mask) (((!mask) || (TEST_DEBUG_STATE(mask))) && (!NO_OUTPUT_OR_PAUSING)) + +#ifdef CONFIG_PREEMPT +#define PRINTPREEMPTCOUNT(desn) printlog(SUSPEND_FREEZER, SUSPEND_MEDIUM, \ + "Preempt count (CPU %d): %s: %d.\n", \ + smp_processor_id(), desn, THREAD_PREEMPT_COUNT) +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +#define swsusp_spin_lock_irq(lock) { \ + int count = THREAD_PREEMPT_COUNT; \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinlock %p. %d->%d.\n", lock, count, count+1); \ + spin_lock_irq(lock); \ +} + +#define swsusp_spin_lock_irqsave(lock, flags) { \ + int count = THREAD_PREEMPT_COUNT; \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinlock IRQSave %p. %d->%d.\n", lock, count, count+1); \ + spin_lock_irqsave(lock, flags); \ +} + +#define swsusp_spin_unlock_irq(lock) { \ + int count = THREAD_PREEMPT_COUNT; \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinunlock %p. %d->%d.\n", lock, count, count-1); \ + spin_unlock_irq(lock); \ +} + +#define swsusp_spin_unlock_irqrestore(lock, flags) { \ + int count = THREAD_PREEMPT_COUNT; \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinunlock IRQRestore %p. %d->%d\n", lock, count, count-1); \ + spin_unlock_irqrestore(lock, flags); \ +} +#else /* No debug */ +#define swsusp_spin_lock_irq(lock) spin_lock_irq(lock) +#define swsusp_spin_lock_irqsave(lock, flags) spin_lock_irqsave(lock, flags) +#define swsusp_spin_unlock_irq(lock) spin_unlock_irq(lock) +#define swsusp_spin_unlock_irqrestore(lock, flags) spin_unlock_irqrestore(lock, flags) +#endif +#else /* No preempt */ +#define PRINTPREEMPTCOUNT(desn) do { } while(0) +#define swsusp_spin_lock_irq(lock) { \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinlock %p.\n", lock); \ + spin_lock_irq(lock); \ +} + +#define swsusp_spin_lock_irqsave(lock, flags) { \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinlock IRQSave %p.\n", lock); \ + spin_lock_irqsave(lock, flags); \ +} + +#define swsusp_spin_unlock_irq(lock) { \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinunlock %p.\n", lock); \ + spin_unlock_irq(lock); \ +} + +#define swsusp_spin_unlock_irqrestore(lock, flags) { \ + printlog(SUSPEND_SPINLOCKS, SUSPEND_VERBOSE, "Spinunlock IRQRestore %p.\n", lock); \ + spin_unlock_irqrestore(lock, flags); \ +} +#endif + +/* Local variables that should not be affected by save */ +#define pageset1_size (pagedir1.pageset_size) +#define pageset2_size (pagedir2.pageset_size) + +#define BITS_PER_PAGE (PAGE_SIZE * 8) +#define PAGES_PER_BITMAP ((max_mapnr + BITS_PER_PAGE - 1) / BITS_PER_PAGE) +#define BITMAP_ORDER (get_bitmask_order((PAGES_PER_BITMAP) - 1)) + +/* + * XXX: We try to keep some more pages free so that I/O operations succeed + * without paging. Might this be more? + */ +#define MIN_FREE_RAM (max_mapnr >> 7) + +void prepare_status(int printalways, int clearbar, const char *fmt, ...); +void abort_suspend(const char *fmt, ...); + +void thaw_processes(void); +extern int suspend_snprintf(char * buffer, int buffer_size, const char *fmt, ...); + +/* ------ prepare_image.c ------ */ +extern unsigned long get_grabbed_pages(int order); + +/* ------ io.c ------ */ +void do_suspend_sync(void); +int sanity_check_failed(char *reason); + +/* ------ console.c ------ */ +void check_shift_keys(int pause, char * message); +unsigned long update_status(unsigned long value, unsigned long maximum, const char *fmt, ...); + +#define PBEPAGE_START(pbe) (((unsigned long) (pbe)) & PAGE_MASK) +extern unsigned long memory_for_plugins(void); +extern int expected_compression_ratio(void); + +#define MAIN_STORAGE_NEEDED \ + ((pageset1_size + pageset2_size) * expected_compression_ratio() / 100) + +#define HEADER_STORAGE_NEEDED \ + (num_range_pages + 1 + \ + (int) header_storage_for_plugins()) + +#define STORAGE_NEEDED \ + (MAIN_STORAGE_NEEDED + HEADER_STORAGE_NEEDED) + +#define RAM_TO_SUSPEND (1 + MAX((pageset1_size - pageset2_sizelow), 0) + MIN_FREE_RAM + \ + memory_for_plugins()) + +/* --------- Range chains ----------- */ + +#define RANGES_PER_PAGE (PAGE_SIZE / (sizeof(struct range))) +#define RANGE_PAGES_NEEDED(x) (((x)+RANGES_PER_PAGE-1)/RANGES_PER_PAGE) +#define RANGEPAGELINK(x) ((unsigned long *) ((((unsigned long) x) & PAGE_MASK) + PAGE_SIZE - sizeof(unsigned long))) + +#define range_for_each(rangechain, rangepointer, value) \ +if ((rangechain)->first) \ + for ((rangepointer) = (rangechain)->first, (value) = (rangepointer)->minimum; \ + ((rangepointer) && ((rangepointer)->next || (value) <= (rangepointer)->maximum)); \ + (((value) == (rangepointer)->maximum) ? \ + ((rangepointer) = (rangepointer)->next, (value) = ((rangepointer) ? (rangepointer)->minimum : 0)) : \ + (value)++)) + +/* + * When using compression and expected_compression > 0, + * we allocate less swap entries, so GET_RANGE_NEXT can + * validly run out of data to return. + */ +#define GET_RANGE_NEXT(currentrange, currentval) \ +{ \ + if (currentrange) { \ + if ((currentval) == (currentrange)->maximum) { \ + if ((currentrange)->next) { \ + (currentrange) = (currentrange)->next; \ + (currentval) = (currentrange)->minimum; \ + } else { \ + (currentrange) = NULL; \ + (currentval) = 0; \ + } \ + } else \ + currentval++; \ + } \ +} + +struct range_entry { + unsigned long thisvalue; + struct range * rangeptr; +}; + +extern int max_ranges_used; +extern int num_range_pages; +int add_to_range_chain(struct rangechain * chain, unsigned long value); +void put_range_chain(struct rangechain * chain); +void print_chain(int debuglevel, struct rangechain * chain, int printasswap); +void set_chain_names(struct pagedir * p); +int free_ranges(void); +int append_to_range_chain(int chain, unsigned long min, unsigned long max); +void relativise_ranges(void); +void relativise_chain(struct rangechain * chain); +void absolutise_ranges(unsigned long * range_pages); +void absolutise_chain(struct rangechain * chain, unsigned long * range_pages); +unsigned long * get_rangepages_list(void); +void put_rangepages_list(unsigned long * range_pages); +int relocate_rangepages(unsigned long * rangepages); + +extern struct range * first_range_page, * last_range_page; + +#define RANGE_RELATIVE(x) (struct range *) ((((unsigned long) x) & (PAGE_SIZE - 1)) | \ + ((*RANGEPAGELINK(x) & (PAGE_SIZE - 1)) << PAGE_SHIFT)) +#define RANGE_ABSOLUTE(entry, list) (struct range *) ((((unsigned long) (entry)) & (PAGE_SIZE - 1)) | \ + list[((unsigned long) (entry)) >> PAGE_SHIFT]) + +extern unsigned long * inusemap; +extern unsigned long * pageset2map; + +#define PAGENUMBER(page) (page-mem_map) +#define PAGEINDEX(page) ((PAGENUMBER(page))/(8*sizeof(unsigned long))) +#define PAGEBIT(page) ((int) ((PAGENUMBER(page))%(8 * sizeof(unsigned long)))) + +/* + * freepagesmap is used in two ways: + * - During suspend, to tag pages which are not used (to speed up count_data_pages); + * - During resume, to tag pages which are is pagedir1. This does not tag pagedir2 + * pages, so !== first use. + */ +#define PageInUse(page) test_bit(PAGEBIT(page), &inusemap[PAGEINDEX(page)]) +#define SetPageInUse(page) set_bit(PAGEBIT(page), &inusemap[PAGEINDEX(page)]) +#define ClearPageInUse(page) clear_bit(PAGEBIT(page), &inusemap[PAGEINDEX(page)]) + +#define PagePageset2(page) test_bit(PAGEBIT(page), &pageset2map[PAGEINDEX(page)]) +#define SetPagePageset2(page) set_bit(PAGEBIT(page), &pageset2map[PAGEINDEX(page)]) +#define TestAndSetPagePageset2(page) test_and_set_bit(PAGEBIT(page), &pageset2map[PAGEINDEX(page)]) +#define TestAndClearPagePageset2(page) test_and_clear_bit(PAGEBIT(page), &pageset2map[PAGEINDEX(page)]) +#define ClearPagePageset2(page) clear_bit(PAGEBIT(page), &pageset2map[PAGEINDEX(page)]) + +/* + * #defs that are dependant on CONFIG_SOFTWARE_SUSPEND_DEBUG + * + */ +#if defined(CONFIG_SOFTWARE_SUSPEND_DEBUG) +#define MDELAY(a) if (TEST_ACTION_STATE(SUSPEND_SLOW)) { mdelay(a); } else { do { } while (0); } +#define beepOK \ + if (TEST_ACTION_STATE(SUSPEND_BEEP)) { \ + currentbeep += 200; \ + kd_mksound(currentbeep,HZ/8); \ + mdelay(150); \ + } else { \ + do { } while (0); \ + } +#define beepERR \ + if (TEST_ACTION_STATE(SUSPEND_BEEP)) { \ + kd_mksound(300,HZ/4); \ + mdelay(300); \ + } else { \ + do { } while (0); \ + } + +#else // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +#define MDELAY(a) do { } while (0) +#define beepOK do { } while (0) +#define beepERR do { } while (0) +#endif // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + +#define FILTER_PLUGIN 1 +#define WRITER_PLUGIN 2 + +struct swsusp_filter_ops { + int (*expected_compression) (void); + struct list_head filter_list; +}; + +struct swsusp_writer_ops { + + /* Calls for allocating storage */ + + long (*storage_available) (void); // Maximum size of image we can save + // (incl. space already allocated). + + unsigned long (*storage_allocated) (void); + // Amount of storage already allocated + int (*release_storage) (void); + + /* + * Header space is allocated separately. Note that allocation + * of space for the header might result in allocated space + * being stolen from the main pool if there is no unallocated + * space. We have to be able to allocate enough space for + * the header. We can eat memory to ensure there is enough + * for the main pool. + */ + long (*allocate_header_space) (unsigned long space_requested); + int (*allocate_storage) (unsigned long space_requested); + + /* Read and write the metadata */ + int (*write_header_init) (void); + int (*write_header_chunk) (char * buffer_start, int buffer_size); + int (*write_header_cleanup) (void); + + int (*read_header_init) (void); + int (*read_header_chunk) (char * buffer_start, int buffer_size); + int (*read_header_cleanup) (void); + + /* Prepare metadata to be saved (relativise/absolutise ranges) */ + int (*prepare_save_ranges) (void); + int (*post_load_ranges) (void); + + /* Attempt to parse an image location */ + int (*parse_image_location) (char * buffer, int boot_time); + + /* Determine whether image exists that we can restore */ + int (*image_exists) (void); + + /* Destroy image if one exists */ + int (*invalidate_image) (void); + + /* Wait on I/O */ + int (*wait_on_io) (int flush_all); + + struct list_head writer_list; +}; + +#define SWSUSP_ASYNC 0 +#define SWSUSP_SYNC 1 + +struct swsusp_plugin_ops { + /* Functions common to filters and writers */ + int type; + char * name; + struct list_head plugin_list; + unsigned long (*memory_needed) (void); + unsigned long (*storage_needed) (void); + int (*print_debug_info) (char * buffer, int size); + int (*save_config_info) (char * buffer); + void (*load_config_info) (char * buffer, int len); + + /* Writing the image proper */ + int (*write_init) (int stream_number); + int (*write_chunk) (char * buffer_start); + int (*write_cleanup) (void); + + /* Reading the image proper */ + int (*read_init) (int stream_number); + int (*read_chunk) (char * buffer_start, int sync); + int (*read_cleanup) (void); + + /* Reset plugin if image exists but reading aborted */ + void (*noresume_reset) (void); + + union { + struct swsusp_filter_ops filter; + struct swsusp_writer_ops writer; + } ops; +}; + +extern struct swsusp_plugin_ops * active_writer; +extern struct list_head swsusp_filters, swsusp_writers, swsusp_plugins; +extern struct swsusp_plugin_ops * first_filter; +extern struct swsusp_plugin_ops * get_next_filter(struct swsusp_plugin_ops *); +extern int swsusp_register_plugin(struct swsusp_plugin_ops * plugin); + +struct swsusp_proc_data { + char * filename; + int permissions; + int type; + union { + struct { + unsigned long * bit_vector; + int bit; + } bit; + struct { + int * variable; + int minimum; + int maximum; + } integer; + struct { + unsigned long * variable; + unsigned long minimum; + unsigned long maximum; + } ul; + struct { + char * variable; + int max_length; + int (* write_proc) (void); /* Routine triggered by write after new value stored */ + } string; + struct { + void * read_proc; + void * write_proc; + void * data; + } special; + } data; + struct list_head proc_data_list; +}; + +#define SWSUSP_PROC_DATA_CUSTOM 0 +#define SWSUSP_PROC_DATA_BIT 1 +#define SWSUSP_PROC_DATA_INTEGER 2 +#define SWSUSP_PROC_DATA_UL 3 +#define SWSUSP_PROC_DATA_STRING 4 + +#ifdef CONFIG_SOFTWARE_SUSPEND_RELAXED_PROC +#define PROC_WRITEONLY 0222 +#define PROC_READONLY 0444 +#define PROC_RW 0666 +#else +#define PROC_WRITEONLY 0200 +#define PROC_READONLY 0400 +#define PROC_RW 0600 +#endif + +struct proc_dir_entry * swsusp_register_procfile(struct swsusp_proc_data * swsusp_proc_data); +void swsusp_unregister_procfile(struct swsusp_proc_data * swsusp_proc_data); + +struct debug_info_data +{ + int action; + char * buffer; + int buffer_size; + int bytes_used; +}; + +#endif /* #ifndef SWSUSP_COMMON_H */ diff -ruN post-version-specific/include/linux/suspend-debug.h software-suspend-core-2.0/include/linux/suspend-debug.h --- post-version-specific/include/linux/suspend-debug.h 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/include/linux/suspend-debug.h 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,122 @@ + +#ifndef _LINUX_SWSUSP_DEBUG_H +#define _LINUX_SWSUSP_DEBUG_H + +#include +#include + +/* Solely for comparison with the version specific patch revision */ +#define SWSUSP_CORE_REVISION 0x201 + +#define swsusp_version "2.0" +#define name_suspend "Software Suspend " swsusp_version ": " +#define console_suspend " S U S P E N D T O D I S K " /* Same length to ensure one overwrites the other */ +#define console_resume "R E S U M E F R O M D I S K" + +#define TEST_RESULT_STATE(bit) (test_bit(bit, &swsusp_result)) +#define SET_RESULT_STATE(bit) (test_and_set_bit(bit, &swsusp_result)) +#define CLEAR_RESULT_STATE(bit) (test_and_clear_bit(bit, &swsusp_result)) + +#define TEST_ACTION_STATE(bit) (test_bit(bit, &swsusp_action)) +#define SET_ACTION_STATE(bit) (test_and_set_bit(bit, &swsusp_action)) +#define CLEAR_ACTION_STATE(bit) (test_and_clear_bit(bit, &swsusp_action)) + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +#define TEST_DEBUG_STATE(bit) (swsusp_debug_state) && (test_bit(bit, &swsusp_debug_state)) +#define SET_DEBUG_STATE(bit) (test_and_set_bit(bit, &swsusp_debug_state)) +#define CLEAR_DEBUG_STATE(bit) (test_and_clear_bit(bit, &swsusp_debug_state)) +#else +#define TEST_DEBUG_STATE(bit) (0) +#define SET_DEBUG_STATE(bit) (0) +#define CLEAR_DEBUG_STATE(bit) (0) +#endif + +extern int suspend_console; + +/* first status register - this is swsusp's return code. */ +#define SUSPEND_ABORTED 0 +#define SUSPEND_ABORT_REQUESTED 1 +#define SUSPEND_NOSTORAGE_AVAILABLE 2 +#define SUSPEND_INSUFFICIENT_STORAGE 3 +#define SUSPEND_FREEZING_FAILED 4 +#define SUSPEND_UNEXPECTED_ALLOC 5 +#define SUSPEND_KEPT_IMAGE 6 +#define SUSPEND_WOULD_EAT_MEMORY 7 +#define SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY 8 + +/* second status register */ +#define SUSPEND_REBOOT 0 +#define SUSPEND_NO_OUTPUT 1 +#define SUSPEND_PAUSE 2 +#define SUSPEND_SLOW 3 +#define SUSPEND_BEEP 4 +#define SUSPEND_NOPAGESET2 7 +#define SUSPEND_LOGALL 8 +/* Set to disable compression when compiled in */ +#define SUSPEND_NO_COMPRESSION 9 +//#define SUSPEND_ENABLE_KDB 10 +#define SUSPEND_CAN_CANCEL 11 +#define SUSPEND_KEEP_IMAGE 13 +#define SUSPEND_FREEZER_TEST 14 +#define SUSPEND_FREEZER_TEST_SHOWALL 15 +#define SUSPEND_SINGLESTEP 16 + +/* debug sections - if debugging compiled in */ +#define SUSPEND_ANY_SECTION 0 +#define SUSPEND_FREEZER 1 +#define SUSPEND_EAT_MEMORY 2 +#define SUSPEND_PAGESETS 3 +#define SUSPEND_IO 4 +#define SUSPEND_BMAP 5 +#define SUSPEND_SWAP 9 +#define SUSPEND_MEMORY 10 +#define SUSPEND_RANGES 11 +#define SUSPEND_SPINLOCKS 12 +#define SUSPEND_MEM_POOL 13 +#define SUSPEND_RANGE_PARANOIA 14 +/* debugging levels - if debugging compiled in. + * These are straight integer values, not bit flags. + * (Stored as LEVEL << 16 + section_flags). + */ +#define SUSPEND_ERROR 2 +#define SUSPEND_LOW 3 +#define SUSPEND_MEDIUM 4 +#define SUSPEND_HIGH 5 +#define SUSPEND_VERBOSE 6 + +/* + * #defs that are dependant on CONFIG_SOFTWARE_SUSPEND_DEBUG + * + */ +#if defined(CONFIG_SOFTWARE_SUSPEND_DEBUG) +void printnolog(int mask, int level, int restartline, const char *fmt, ...); +void printlog(int mask, int level, const char *fmt, ...); + +#define MDELAY(a) if (TEST_ACTION_STATE(SUSPEND_SLOW)) { mdelay(a); } else { do { } while (0); } + +extern int currentbeep; +#define beepOK \ + if (TEST_ACTION_STATE(SUSPEND_BEEP)) { \ + currentbeep += 200; \ + kd_mksound(currentbeep,HZ/8); \ + mdelay(150); \ + } else { \ + do { } while (0); \ + } +#define beepERR \ + if (TEST_ACTION_STATE(SUSPEND_BEEP)) { \ + kd_mksound(300,HZ/4); \ + mdelay(300); \ + } else { \ + do { } while (0); \ + } + +#else // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +#define MDELAY(a) do { } while (0) +#define beepOK do { } while (0) +#define beepERR do { } while (0) +#define printlog(...) do { } while(0) +#define printnolog(...) do { } while(0) +#endif // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + +#endif diff -ruN post-version-specific/include/linux/suspend.h software-suspend-core-2.0/include/linux/suspend.h --- post-version-specific/include/linux/suspend.h 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/include/linux/suspend.h 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,171 @@ +#ifndef _LINUX_SWSUSP_H +#define _LINUX_SWSUSP_H + +#ifdef CONFIG_PM + +#include +#include +#if defined(SUSPEND_C) || defined(ACPI_C) +#include +#endif +#include + +extern unsigned long swsusp_action; +extern unsigned long swsusp_result; +extern unsigned char software_suspend_state; +struct page * get_swsusp_pool_page(unsigned int gfp_mask, unsigned int order); +void free_swsusp_pool_pages(struct page *page, unsigned int order); +extern char idletimeout; +extern __nosavedata unsigned char swsusp_state; +#define FREEZE_NEW_ACTIVITY 1 +#define FREEZE_UNREFRIGERATED 2 +#define BLOCK_PAGE_ALLOCATIONS 4 +#define USE_MEMORY_POOL 8 +#define STAGE2_CONTINUE 16 +#define FREEZE_SMP 32 +extern atomic_t swsusp_num_active; +#define SOFTWARE_SUSPEND_DISABLED 1 +#define SOFTWARE_SUSPEND_RUNNING 2 +#define SOFTWARE_SUSPEND_RESUME_DEVICE_OK 4 +#define SOFTWARE_SUSPEND_NORESUME_SPECIFIED 8 +#define SOFTWARE_SUSPEND_COMMANDLINE_ERROR 16 +#define SOFTWARE_SUSPEND_IGNORE_IMAGE 32 +#define SOFTWARE_SUSPEND_SANITY_CHECK_PROMPT 64 +extern int swsusp_min_free; + +#ifdef CONFIG_MTRR +#define SOFTWARE_SUSPEND_MTRR 1 +#else +#undef SOFTWARE_SUSPEND_MTRR +#endif + +/* kernel/suspend.c */ +extern void software_suspend_pending(void); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +extern void software_resume2(void); +#endif + +extern int register_suspend_notifier(struct notifier_block *); +extern int unregister_suspend_notifier(struct notifier_block *); +extern int register_resume_notifier(struct notifier_block *); +extern int unregister_resume_notifier(struct notifier_block * nb); +extern void refrigerator(unsigned long); +extern struct buffer_head * suspend_swap_bh; +extern inline void cleanup_finished_swsusp_io(void); +extern void __swsusp_activity_start(int, const char *, const char *); +extern atomic_t swsusp_num_doing_io; +#define SWSUSP_CANHOLD 1 +#define SWSUSP_WAIT_ON_ME 2 + +#define DECLARE_SWSUSP_LOCAL_VAR \ + unsigned long swsusp_orig_value = (current->flags & PF_FRIDGE_WAIT); \ + int swsusp_local_paused = 0; + +#define SWSUSP_THREAD_FLAGS_RESET \ + current->flags &= ~PF_FRIDGE_WAIT; \ + swsusp_orig_value = 0; + +#define SWSUSP_ACTIVITY_START(flags) do { \ + (void)(swsusp_orig_value); \ + (void)(swsusp_local_paused); \ + if (!swsusp_orig_value) \ + __swsusp_activity_start(flags, __FUNCTION__, __FILE__); \ +} while(0) + +#define SWSUSP_ACTIVITY_RESTARTING(freezer_flags) do { \ + (void)(swsusp_orig_value); \ + (void)(swsusp_local_paused); \ + if (swsusp_local_paused) { \ + __swsusp_activity_start(freezer_flags, __FUNCTION__, __FILE__); \ + swsusp_local_paused = 0; \ + } \ + if (unlikely(current->flags & PF_FREEZE)) \ + refrigerator(freezer_flags); \ +} while(0) + +#define SWSUSP_ACTIVITY_END do { \ + if (!swsusp_orig_value) { \ + if (current->flags & PF_FRIDGE_WAIT) { \ + current->flags &= ~PF_FRIDGE_WAIT; \ + atomic_dec(&swsusp_num_active); \ + if ((u32)atomic_read(&swsusp_num_active) > 0xffff) { \ + printk("Error: decremented swsusp_num_active below 0 in %s (%s).\n", \ + __FUNCTION__, \ + __FILE__); \ + } \ + if (idletimeout && !atomic_read(&swsusp_num_active)) { \ + printk("swsusp_num_active finally zero after timeout in %s (%s).\n", \ + __FUNCTION__, \ + __FILE__); \ + } \ + } else \ + if (likely((!suspend_task) || (current->pid != suspend_task))) { \ + printk("Error Ending: %s (%d) set but doesn't have FRIDGE_WAIT now in %s (%s).\n", \ + current->comm, \ + current->pid, \ + __FUNCTION__, \ + __FILE__); \ + }; \ + }; \ +} while(0) + +#define SWSUSP_ACTIVITY_PAUSING do { \ + if ((current->flags & PF_FRIDGE_WAIT) && \ + ((swsusp_state & FREEZE_UNREFRIGERATED) || \ + (!(current->flags & PF_SYNCTHREAD)))) { \ + current->flags &= ~PF_FRIDGE_WAIT; \ + atomic_dec(&swsusp_num_active); \ + if ((u32)atomic_read(&swsusp_num_active) > 0xffff) { \ + printk("Error: decremented swsusp_num_active below 0 in %s (%s).\n", \ + __FUNCTION__, \ + __FILE__); \ + } \ + swsusp_local_paused = 1; \ + } \ +} while(0) + +/* Like the above, but we decrement active count for SYNCTHREADs too. + * Note that the thread can start new activity. Indeed, it might need + * to when we begin our sync. + */ +#define SWSUSP_ACTIVITY_SYNCTHREAD_PAUSING do { \ + if (current->flags & PF_FRIDGE_WAIT) { \ + current->flags &= ~PF_FRIDGE_WAIT; \ + atomic_dec(&swsusp_num_active); \ + if ((u32)atomic_read(&swsusp_num_active) > 0xffff) { \ + printk("Error: decremented swsusp_num_active below 0 in %s (%s).\n", \ + __FUNCTION__, \ + __FILE__); \ + } \ + swsusp_local_paused = 1; \ + } \ +} while(0) +#ifdef CONFIG_SOFTWARE_SUSPEND_SWAPWRITER +extern struct page * last_suspend_cache_page; +#endif +extern unsigned int suspend_task; + +#define TASK_FROZEN(p) ((p->flags & PF_FROZEN) || \ + ((swsusp_state & FREEZE_UNREFRIGERATED) && \ + (!(p->flags & PF_FREEZE)) && \ + (p->pid) && \ + (suspend_task != p->pid))) + +#else +#define software_suspend_pending() do { } while(0) +#define software_resume2() 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) +#define DECLARE_SWSUSP_LOCAL_VAR +#define SWSUSP_ACTIVITY_START(flush) do { } while(0) +#define SWSUSP_ACTIVITY_END do { } while(0) +#define SWSUSP_ACTIVITY_PAUSING do { } while(0) +#define SWSUSP_ACTIVITY_SYNCTHREAD_PAUSING do { } while(0) +#define SWSUSP_ACTIVITY_RESTARTING(flush) do { } while(0) +#define SWSUSP_THREAD_FLAGS_RESET do { } while(0) +#define TASK_FROZEN(p) (0) +#define suspend_task (0) +#endif + +#endif /* _LINUX_SWSUSP_H */ diff -ruN post-version-specific/kernel/power/Changelog software-suspend-core-2.0/kernel/power/Changelog --- post-version-specific/kernel/power/Changelog 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/Changelog 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,103 @@ +(This list is not complete!) + +20040115-28 +Further bug fixes, cleanups, testing etc. + +20040114 +CDrom driver fixes. + +20040113 +Fixed Michael's first-time oops. +Added support for setting resume2= parameter after booting. +Further cleanups and small bug fixes. +Updated and tested 2.6 port. +Made 2.4 MTRR support into a PM handler. + +20040107 +Renamed console.c to ui.c. +Further cleanup and commenting of files. +Add more helpful makefile error if core patch not applied. + +20040101-06 +Further SMP testing and tweaking. +Plenty of smaller cleanups and bug fixes. +Work on getting the lowlevel code for 2.6 SMP compatible. + +20031230-31 +Completed initial working SMP version. +Further building and testing. + +20031225-29 + +Further work on SMP. +Fixed compressors so they work with HighMem (vmalloc_32 instead of vmalloc). + +20031221-24 +Further work on SMP support. +Separated out apm-poweroff functions for use by Software Suspend. +Merged Marc's LZF support. +Implemented readahead. +Built and tested under 2.4.21, 2.4.22, 2.4.23 and 2.6.0. +Fixed storage of compression statistics. +Fixed oops when aborting while writing pageset 2. +Fixed expected_compression functionality. +Cleaned up compressor debugging info. +Fixed compilation error in mm/page_alloc when CONFIG_PM off. +Further work on SMP support. + +20031213-20 +Planning for merging. +Initial SMP support. +Investigation of AGP/ DRI-DRM issues & whether setting pages nosave would +help. + +20031212 +Created swsusp25.bkbits.net/merge for merging to Patrick. +Learnt and applied correct way of initialising union structs. + +20031211 +Miscellaneous bug fixes resulting from release. +Beginnings of kernel/power/Internals document. +Preparatory work for merging. + +20031208-10 +Preparation and testing of core and version specific patches for 2.4.21/22/23 +and 2.6.0-test11. +Found and fixed long standing (but previously unnoticed) mtrr resume bug. + +20031208 +Completed getting swsusp2 to play nicely with pmdisk and swsusp +implementations in the 2.6 kernel. + +20031204-6 +Moved union initialisers to avoid gcc 2.96 bug/missing feature. +Converted swap partition to ext3 and tested swapfile support. +Also tested and improved operation running with a swapfile on a normal +partition. +Released 2L for testing. + +20031203 +Finished getting compression working again after changes. +Tested working when no transformers compiled in. +Rewrote Makefile to enforce choosing a writer. +Made the system not oops even if you should get suspend to compile without a +writer. +Got compiling and running under 2.6. +Began work on compilation under gcc 2.96. + +20031202 +Tested suspend with simple config. ~111 cycles, no problems. +Fixed math error in clear_pagemap. +Fixed noresume2 (broken in new API changes) +Completed changes to proc.c. +Put console specific entries into console.c and plugin specific entries into +the relevant plugins. +Added /proc/swsusp/all_settings; which is equivalent to +/proc/sys/kernel/swsusp. The later will be removed soon. + +20031201 + +Started this file. +Changing proc.c to make adding a new entry simpler, and to enable registering +new entries from plugins. This will provide a basis for kobj functionality in +the 2.6 variation diff -ruN post-version-specific/kernel/power/gzipcompress.c software-suspend-core-2.0/kernel/power/gzipcompress.c --- post-version-specific/kernel/power/gzipcompress.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/gzipcompress.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,535 @@ +/* + * kernel/power/gzip_compression.c + * + * Copyright (C) 2003,2004 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * This file contains data compression routines for swsusp. + * Compression is implemented using the zlib library. + * + */ + +#include + +/* Forward declaration for the ops structure we export */ +struct swsusp_plugin_ops gzip_compression_ops; + +/* The next driver in the pipeline */ +static struct swsusp_plugin_ops * next_driver; + +/* Zlib routines we use to compress/decompress the data */ +extern int zlib_compress(unsigned char *data_in, unsigned char *cpage_out, __u32 *sourcelen, __u32 *dstlen); +extern void zlib_decompress(unsigned char *data_in, unsigned char *cpage_out, __u32 srclen, __u32 destlen); + +/* Buffers */ +static void *compression_workspace = NULL; +static char *local_buffer = NULL; + +/* Configuration data we pass to zlib */ +static z_stream strm; + +/* Stats we save */ +static __nosavedata unsigned long bytes_in = 0, bytes_out = 0; + +/* Expected compression is used to reduce the amount of storage allocated */ +static int expected_gzip_compression = 0; + +/* ---- Zlib memory management ---- */ + +/* allocate_zlib_compression_space + * + * Description: Allocate space for zlib to use in compressing our data. + * Each call must have a matching call to free_zlib_memory. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ +static inline int allocate_zlib_compression_space(void) +{ + BUG_ON(compression_workspace); + + compression_workspace = vmalloc_32(zlib_deflate_workspacesize()); + if (!compression_workspace) { + printk(KERN_WARNING "Failed to allocate %d bytes for deflate workspace\n", zlib_deflate_workspacesize()); + return -ENOMEM; + } + + return 0; +} + +/* allocate_zlib_decompression_space + * + * Description: Allocate space for zlib to use in decompressing our data. + * Each call must have a matching call to free_zlib_memory. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ +static inline int allocate_zlib_decompression_space(void) +{ + BUG_ON(compression_workspace); + + compression_workspace = vmalloc_32(zlib_inflate_workspacesize()); + if (!compression_workspace) { + printk(KERN_WARNING "Failed to allocate %d bytes for inflate workspace\n", zlib_inflate_workspacesize()); + return -ENOMEM; + } + + return 0; +} + +/* free_zlib_memory + * + * Description: Frees memory allocated by either allocation routine (above). + */ +static inline void free_zlib_memory(void) +{ + if (!compression_workspace) + return; + + vfree(compression_workspace); + compression_workspace = NULL; +} + +/* ---- Local buffer management ---- */ + +/* allocate_local_buffer + * + * Description: Allocates a page of memory for buffering output. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ +static int allocate_local_buffer(void) +{ + if (local_buffer) + return 0; + + local_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + + if (!local_buffer) { + printk(KERN_ERR "Failed to allocate a page for compression driver's buffer.\n"); + return -ENOMEM; + } + + return 0; +} + +/* free_local_buffer + * + * Description: Frees memory allocated for buffering output. + */ +static inline void free_local_buffer(void) +{ + if (local_buffer) + free_pages((unsigned long) local_buffer, 0); + local_buffer = NULL; +} + +/* ---- Functions exported via operations struct ---- */ + +/* gzip_write_init + * + * Description: Allocate buffers and prepares zlib for deflating a new stream + * of data. + * Arguments: Stream_number: Ignored. + * Returns: Int. Zero if successful, otherwise an appropriate error number. + */ + +static int gzip_write_init(int stream_number) +{ + int result; + + next_driver = get_next_filter(&gzip_compression_ops); + + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) + return 0; + + if ((result = allocate_zlib_compression_space())) + return result; + + if ((result = allocate_local_buffer())) + return result; + + strm.total_in = 0; + strm.total_out = 0; + strm.workspace = compression_workspace; + strm.next_out = (char *) local_buffer; + strm.avail_out = PAGE_SIZE; + result = zlib_deflateInit(&strm, Z_BEST_SPEED); + + if (Z_OK != result) { + printk(KERN_ERR name_suspend "Failed to initialise zlib.\n"); + return -EPERM; + } + + /* Reset the statistics iif we are about to write the first part of the image */ + if (stream_number == 2) + bytes_in = bytes_out = 0; + + return 0; +} + +/* gzip_write_chunk() + * + * Description: Compress a page of data, buffering output and passing on + * filled pages to the next plugin in the pipeline. + * Arguments: Buffer_start: Pointer to a buffer of size PAGE_SIZE, + * containing data to be compressed. + * Returns: 0 on success. Otherwise the error is that returned by later + * plugins, -ECHILD if we have a broken pipeline or -EPERM if + * zlib errs. + */ + +static int gzip_write_chunk(char * buffer_start) +{ + int ret; + + if (!next_driver) { + printk("Compression Driver: Argh! No one wants my output!"); + return -ECHILD; + } + + /* Pass data through if compression disabled */ + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) { + /* Push directly to next driver */ + return next_driver->write_chunk(buffer_start); + } + + /* Work to do */ + strm.next_in = buffer_start; + strm.avail_in = PAGE_SIZE; + while (strm.avail_in) { + ret = zlib_deflate(&strm, Z_PARTIAL_FLUSH); + if (ret != Z_OK) { + printk("Zlib failed to compress our data. Result code was %d.\n", ret); + return -EPERM; + } + + if (!strm.avail_out) { + + if ((ret = next_driver->write_chunk(local_buffer))) + return ret; + strm.next_out = local_buffer; + strm.avail_out = PAGE_SIZE; + } + } + + return 0; +} + +/* gzip_write_cleanup() + * + * Description: Flush remaining data, update statistics and free allocated + * space. + * Returns: Zero. Never fails. Okay. Zlib might fail... but it shouldn't. + */ + +static int gzip_write_cleanup(void) +{ + int ret = 0, finished = 0; + + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) + return 0; + + while (!finished) { + if (strm.avail_out) { + ret = zlib_deflate(&strm, Z_FINISH); + + if (ret == Z_STREAM_END) { + ret = zlib_deflateEnd(&strm); + finished = 1; + } + + if ((ret != Z_OK) && (ret != Z_STREAM_END)) { + zlib_deflateEnd(&strm); + printk("Failed to finish compressing data. Result %d received.\n", ret); + return -EPERM; + } + } + + if ((!strm.avail_out) || (finished)) { + if ((ret = next_driver->write_chunk(local_buffer))) + return ret; + strm.next_out = local_buffer; + strm.avail_out = PAGE_SIZE; + } + } + + bytes_in+= strm.total_in; + bytes_out+= strm.total_out; + + free_zlib_memory(); + free_local_buffer(); + + return 0; +} + +/* gzip_read_init + * + * Description: Prepare to read a new stream of data. + * Arguments: Stream_number: Not used. + * Returns: Int. Zero if successful, otherwise an appropriate error number. + */ + +static int gzip_read_init(int stream_number) +{ + int result; + + next_driver = get_next_filter(&gzip_compression_ops); + + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) + return 0; + + if ((result = allocate_zlib_decompression_space())) + return result; + + if ((result = allocate_local_buffer())) + return result; + + strm.total_in = 0; + strm.total_out = 0; + strm.workspace = compression_workspace; + strm.avail_in = 0; + if ((result = zlib_inflateInit(&strm)) != Z_OK) { + printk(KERN_ERR name_suspend "Failed to initialise zlib.\n"); + return -EPERM; + } + + return 0; +} + +/* gzip_read_chunk() + * + * Description: Retrieve data from later plugins and decompress it until the + * input buffer is filled. + * Arguments: Buffer_start: Pointer to a buffer of size PAGE_SIZE. + * Sync: Whether the previous plugin (or core) wants its + * data synchronously. + * Returns: Zero if successful. Error condition from me or from downstream + * on failure. + */ + +static int gzip_read_chunk(char * buffer_start, int sync) +{ + int ret; + struct swsusp_plugin_ops * next_driver = get_next_filter(&gzip_compression_ops); + + if (!next_driver) { + printk("Compression Driver: Argh! No one wants to feed me data!"); + return -ECHILD; + } + + /* Pass request through if compression disabled */ + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) { + /* Push request directly to next driver */ + return next_driver->read_chunk(buffer_start, sync); + } + + /* + * All our reads must be synchronous - we can't decompress + * data that hasn't been read yet. + */ + + /* Work to do */ + strm.next_out = buffer_start; + strm.avail_out = PAGE_SIZE; + while (strm.avail_out) { + if (!strm.avail_in) { + if ((ret = next_driver->read_chunk(local_buffer, SWSUSP_SYNC)) < 0) + return ret; + strm.next_in = local_buffer; + strm.avail_in = PAGE_SIZE; + } + + ret = zlib_inflate(&strm, Z_PARTIAL_FLUSH); + + if ((ret == Z_BUF_ERROR) && (!strm.avail_in)) { + continue; + } + + if ((ret != Z_OK) && (ret != Z_STREAM_END)) { + printk("Zlib failed to decompress our data. Result code was %d.\n", ret); + return -EPERM; + } + } + + return 0; +} + +/* read_cleanup() + * + * Description: Clean up after reading part or all of a stream of data. + * Returns: int: Always zero. Never fails. + */ + +static int gzip_read_cleanup(void) +{ + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) + return 0; + + zlib_inflateEnd(&strm); + + free_zlib_memory(); + free_local_buffer(); + return 0; +} + +/* gzip_print_debug_stats + * + * Description: Print information to be recorded for debugging purposes into a + * buffer. + * Arguments: buffer: Pointer to a buffer into which the debug info will be + * printed. + * size: Size of the buffer. + * Returns: Number of characters written to the buffer. + */ + +static int gzip_print_debug_stats(char * buffer, int size) +{ + int pages_in = bytes_in >> PAGE_SHIFT, pages_out = bytes_out >> PAGE_SHIFT; + int len; + + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) { + len = suspend_snprintf(buffer, size, "- GZIP compressor disabled.\n"); + return len; + } + + //Output the compression ratio achieved. + len = suspend_snprintf(buffer, size, "- GZIP compressor enabled.\n"); + if (pages_in) + len+= suspend_snprintf(buffer+len, size - len, + " Compressed %ld bytes into %ld.\n Image compressed by %d percent.\n", + bytes_in, bytes_out, (pages_in - pages_out) * 100 / pages_in); + return len; +} + +/* compression_memory_needed + * + * Description: Tell the caller how much memory we need to operate during + * suspend/resume. + * Returns: Unsigned long. Maximum number of bytes of memory required for + * operation. + */ + +static unsigned long gzip_memory_needed(void) +{ + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) + return 0; + + return PAGE_SIZE + MAX(zlib_deflate_workspacesize(), zlib_inflate_workspacesize()); +} + +/* gzip_save_config_info + * + * Description: Save informaton needed when reloading the image at resume time. + * Arguments: Buffer: Pointer to a buffer of size PAGE_SIZE. + * Returns: Number of bytes used for saving our data. + */ + +static int gzip_save_config_info(char * buffer) +{ + *((unsigned long *) buffer) = bytes_in; + *((unsigned long *) (buffer + sizeof(unsigned long))) = bytes_out; + return 2 * sizeof(unsigned long); +} + +/* gzip_load_config_info + * + * Description: Reload information needed for decompressing the image at + * resume time. + * Arguments: Buffer: Pointer to the start of the data. + * Size: Number of bytes that were saved. + */ + +static void gzip_load_config_info(char * buffer, int size) +{ + if (size != 2 * sizeof(unsigned long)) { + printk("Huh? Size of plugin data is not right. (%d instead of %d).\n", + size, 2 * sizeof(unsigned long)); + return; + } + + bytes_in = *((unsigned long *) buffer); + bytes_out = *((unsigned long *) (buffer + sizeof(unsigned long))); +} + +/* gzip_get_expected_compression + * + * Description: Returns the expected ratio between data passed into this plugin + * and the amount of data output when writing. + * Returns: 100 if the plugin is disabled. Otherwise the value set by the + * user via our proc entry. + */ + +static int gzip_get_expected_compression(void) +{ + if (TEST_ACTION_STATE(SUSPEND_NO_COMPRESSION)) + return 100; + return 100 - expected_gzip_compression; +} + +/* + * data for our proc entries. + */ + +struct swsusp_proc_data expected_compression_proc_data = { + .filename = "expected_gzip_compression", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &expected_gzip_compression, + .minimum = 0, + .maximum = 100, + } + } +}; + +struct swsusp_proc_data disable_compression_proc_data = { + .filename = "disable_gzip_compression", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_NO_COMPRESSION, + } + } +}; + +/* + * Ops structure. + */ + +struct swsusp_plugin_ops gzip_compression_ops = { + .type = FILTER_PLUGIN, + .name = "Zlib Page Compressor", + .memory_needed = gzip_memory_needed, + .print_debug_info = gzip_print_debug_stats, + .save_config_info = gzip_save_config_info, + .load_config_info = gzip_load_config_info, + .write_init = gzip_write_init, + .write_chunk = gzip_write_chunk, + .write_cleanup = gzip_write_cleanup, + .read_init = gzip_read_init, + .read_chunk = gzip_read_chunk, + .read_cleanup = gzip_read_cleanup, + .ops = { + .filter = { + .expected_compression = gzip_get_expected_compression, + } + } +}; + +/* ---- Registration ---- */ + +static __init int gzip_load(void) +{ + int result; + + printk("Software Suspend Gzip Compression Driver v1.0\n"); + if (!(result = swsusp_register_plugin(&gzip_compression_ops))) { + swsusp_register_procfile(&expected_compression_proc_data); + swsusp_register_procfile(&disable_compression_proc_data); + } + return result; +} + +__initcall(gzip_load); + diff -ruN post-version-specific/kernel/power/Internals software-suspend-core-2.0/kernel/power/Internals --- post-version-specific/kernel/power/Internals 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/Internals 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,364 @@ + Software Suspend 2.0 Internal Documentation. + Version 1 + +1. Introduction. + + Software Suspend 2.0 is an addition to the Linux Kernel, designed to + allow the user to quickly shutdown and quickly boot a computer, without + needing to close documents or programs. It is equivalent to the + hibernate facility in some laptops. This implementation, however, + requires no special BIOS or hardware support. + + The code in these files is based upon the original implementation + prepared by Gabor Kuti and additional work by Pavel Machek and a + host of others. This code has been substantially reworked by Nigel + Cunningham, again with the help and testing of many others, not the + least of whom is Michael Frank, At its heart, however, the operation is + essentially the same as Gabor's version. + +2. Overview of operation. + + The basic sequence of operations is as follows: + + a. Quiesce all other activity. + b. Ensure enough memory and storage space are available, and attempt + to free memory/storage if necessary. + c. Allocate the required memory and storage space. + d. Write the image. + e. Power down. + + There are a number of complicating factors which mean that things are + not as simple as the above would imply, however... + + o The activity of each process must be stopped at a point where it will + not be holding locks necessary for saving the image, or unexpectedly + restart operations due to something like a timeout and thereby make + our image inconsistent. + + o It is desirous that we sync outstanding I/O to disk before calculating + image statistics. This reduces corruption if one should suspend but + then not resume, and also makes later parts of the operation safer (see + below). + + o We need to get as close as we can to an atomic copy of the data. + Inconsistencies in the image will result inconsistent memory contents at + resume time, and thus in instability of the system and/or file system + corruption. This would appear to imply a maximum image size of one half of + the amount of RAM, but we have a solution... (again, below). + + o In 2.6, we must play nicely with the other suspend-to-disk + implementations. + +3. Detailed description of internals. + + a. Quiescing activity. + + Safely quiescing the system is achieved in a number of steps. First, we + wait for existing activity to complete, while holding new activity until + post-resume. Second, we sync unwritten buffers. Third, we send a + 'pseudo-signal' to all processes that have not yet entered the + 'refrigerator' but should be frozen, causing them to be refrigerated. + + Waiting for existing activity to complete is achieved by using hooks at + the beginning and end of critical paths in the kernel code. When a process + enters a section where it cannot be safely refrigerated, the process flag + PF_FRIDGE_WAIT is set from the SWSUSP_ACTIVITY_STARTING macro. In the same + routine, at completion of the critical region, a SWSUSP_ACTIVITY_END macro + resets the flag. The _STARTING and _ENDING macros also atomically adjust + the global counter swsusp_num_active. While the counter is non-zero, + Software Suspend's freezer will wait. + + These macros serve two other additional purposes. Local variables are used + to ensure that processes can safely pass through multiple _STARTING and + _ENDING macros, and checks are made to ensure that the freezer is not + waiting for activity to finish. If a process wants to start on a critical + path when Suspend is waiting for activity to finish, it will be held at the + start of the critical path and refrigerated earlier than would normally be + the case. It will be allowed to continue operation after the Suspend cycle + is finished or aborted. + + A process in a critical path may also have a section where it releases + locks and can be safely stopped until post-resume. For these cases, the + SWSUSP_ACTIVITY_PAUSING and _RESTARTING macros may be used. They function + in a similar manner to the _STARTING and _ENDING macros. + + Finally, we remember that some threads may be necessary for syncing data to + storage. These threads have PF_SYNCTHREAD set, and may use the special macro + SWSUSP_ACTIVITY_SYNCTHREAD_PAUSING to indicate that Suspend can safely + continue, while not themselves entering the refrigerator. + + Once activity is stopped, Suspend will initiate a fsync of all devices. + This aims to increase the integrity of the disk state, just in case + something should go wrong. + + During the initial stage, Suspend indicates its desire that processes be + stopped by setting the FREEZE_NEW_ACTIVITY bit of swsusp_state. Once the + sync is complete, SYNCTHREAD processes no longer need to run. The + FREEZE_UNREFRIGERATED bit is now set, causing them to be refrigerated as + well, should they attempt to start new activity. (There should be nothing + for them to do, but just-in-case). + + Suspend can now put remaining processes in the refrigerator without fear + of deadlocking or leaving dirty data unsynced. The refrigerator is a + procedure where processes wait until the cycle is complete. While in there, + we can be sure that they will not perform activity that will make our + image inconsistent. Processes enter the refrigerator either by being + caught at one of the previously mentioned hooks, or by receiving a 'pseudo- + signal' from Suspend at this stage. I call it a pseudo signal because + signal_wake_up is called for the process when it actually hasn't been + signalled. A special hook in the signal handler then calls the refrigerator. + The refrigerator, in turn, recalculates the signal pending status to + ensure no ill effects result. + + Not all processes are refrigerated. The Suspend thread itself, of course, + is one such thread. Others are flagged by setting PF_NOFREEZE, usually + because they are needed during suspend. + + In 2.4, the dosexec thread (Win4Lin) is treated specially. It does not + handle us even pretending to send it a signal. This is worked-around by + us adjusting the can_schedule() macro in schedule.c to stop the task from + being scheduled during suspend. Ugly, but it works. The 2.6 version of + Win4Lin has been made compatible. + + b. Ensure enough memory & storage are available. + c. Allocate the required memory and storage space. + + These steps are merged together in the prepare_image function, found in + prepare_image.c. The functions are merged because of the cyclical nature + of the problem of calculating how much memory and storage is needed. Since + the data structures containing the information about the image must + themselves take memory and use storage, the amount of memory and storage + required changes as we prepare the image. Since the changes are not large, + only one or two iterations will be required to achieve a solution. + + d. Write the image. + + We previously mentioned the need to create an atomic copy of the data, and + the half-of-memory limitation that is implied in this. This limitation is + circumvented by dividing the memory to be saved into two parts, called + pagesets. + + Pageset2 contains the page cache - the pages on the active and inactive + lists. These pages are saved first and reloaded last. While saving these + pages, the swapwriter plugin carefully ensures that the work of writing + the pages doesn't make the image inconsistent. Pages added to the LRU + lists are immediately shot down, and careful accounting for available + memory aids debugging. No atomic copy of these pages needs to be made. + + Writing the image requires memory, of course, and at this point we have + also not yet suspended the drivers. To avoid the possibility of remaining + activity corrupting the image, we allocate a special memory pool. Calls + to __alloc_pages and __free_pages_ok are then diverted to use our memory + pool. Pages in the memory pool are saved as part of pageset1 regardless of + whether or not they are used. + + Once pageset2 has been saved, we suspend the drivers and save the CPU + context before making an atomic copy of pageset1, resuming the drivers + and saving the atomic copy. After saving the two pagesets, we just need to + save our metadata before powering down. + + Having saved pageset2 pages, we can safely overwrite their contents with + the atomic copy of pageset1. This is how we manage to overcome the half of + memory limitation. Pageset2 is normally far larger than pageset1, and + pageset1 is normally much smaller than half of the memory, with the result + that pageset2 pages can be safely overwritten with the atomic copy of + pageset1. This is where we need to be careful about syncing, however. + Pageset2 will probably contain filesystem meta data. If this is overwritten + with pageset1 and then a sync occurs, the filesystem will be corrupted - + at least until resume time and another sync of the restored data. Since + there is a possibility that the user might not resume or (may it never be!) + that suspend might oops, we do our utmost to avoid syncing filesystems after + copying pageset1. + + e. Power down. + + Powering down uses standard kernel routines. Prior to this, however, we + suspend drivers again, ensuring that write caches are flushed. + +4. The method of writing the image. + + Software Suspend 2.0rc3 and later contain an internal API which is + designed to simplify the implementation of new methods of transforming + the image to be written and writing the image itself. Prior to rc3, + compression support was inlined in the image writing code, and the data + structures and code for managing swap were intertwined with the rest of + the code. A number of people had expressed interest in implementing + image encryption, and alternative methods of storing the image. This + internal API makes that possible by implementing 'plugins'. + + A plugin is a single file which encapsulates the functionality needed + to transform a pageset of data (encryption or compression, for example), + or to write the pageset to a device. The former type of plugin is called + a 'page-transformer', the later a 'writer'. + + Plugins are linked together in pipeline fashion. There may be zero or more + page transformers in a pipeline, and there is always exactly one writer. + The pipeline follows this pattern: + + --------------------------------- + | Software Suspend Core | + --------------------------------- + | + | + --------------------------------- + | Page transformer 1 | + --------------------------------- + | + | + --------------------------------- + | Page transformer 2 | + --------------------------------- + | + | + --------------------------------- + | Writer | + --------------------------------- + + During the writing of an image, the core code feeds pages one at a time + to the first plugin. This plugin performs whatever transformations it + implements on the incoming data, completely consuming the incoming data and + feeding output in a similar manner to the next plugin. A plugin may buffer + its output. + + During reading, the pipeline works in the reverse direction. The core code + calls the first plugin with the address of a buffer which should be filled. + (Note that the buffer size is always PAGE_SIZE at this time). This plugin + will in turn request data from the next plugin and so on down until the + writer is made to read from the stored image. + + Part of definition of the structure of a plugin thus looks like this: + + /* Writing the image proper */ + int (*write_init) (int stream_number); + int (*write_chunk) (char * buffer_start); + int (*write_cleanup) (void); + + /* Reading the image proper */ + int (*read_init) (int stream_number); + int (*read_chunk) (char * buffer_start, int sync); + int (*read_cleanup) (void); + + It should be noted that the _cleanup routines may be called before the + full stream of data has been read or written. While writing the image, + the user may (depending upon settings) choose to abort suspending, and + if we are in the midst of writing the last portion of the image, a portion + of the second pageset may be reread. + + In addition to the above routines for writing the data, all plugins have a + number of other routines: + + TYPE indicates whether the plugin is a page transformer or a writer. + #define TRANSFORMER_PLUGIN 1 + #define WRITER_PLUGIN 2 + + NAME is the name of the plugin, used in generic messages. + + PLUGIN_LIST is used to link the plugin into the list of all plugins. + + MEMORY_NEEDED returns the number of pages of memory required by the plugin + to do its work. + + STORAGE_NEEDED returns the number of pages in the suspend header required + to store the plugin's configuration data. + + PRINT_DEBUG_INFO fills a buffer with information to be displayed about the + operation or settings of the plugin. + + SAVE_CONFIG_INFO returns a buffer of PAGE_SIZE or smaller (the size is the + return code), containing the plugin's configuration info. This information + will be written in the image header and restored at resume time. Since this + buffer is allocated after the atomic copy of the kernel is made, you don't + need to worry about the buffer being freed. + + LOAD_CONFIG_INFO gives the plugin a pointer to the the configuration info + which was saved during suspending. Once again, the plugin doesn't need to + worry about freeing the buffer. The kernel will be overwritten with the + original kernel, so no memory leak will occur. + + OPS contains the operations specific to transformers and writers. These are + described below. + + The complete definition of struct swsusp_plugin_ops is: + + struct swsusp_plugin_ops { + /* Functions common to transformers and writers */ + int type; + char * name; + struct list_head plugin_list; + unsigned long (*memory_needed) (void); + unsigned long (*storage_needed) (void); + int (*print_debug_info) (char * buffer, int size); + int (*save_config_info) (char * buffer); + void (*load_config_info) (char * buffer, int len); + + /* Writing the image proper */ + int (*write_init) (int stream_number); + int (*write_chunk) (char * buffer_start); + int (*write_cleanup) (void); + + /* Reading the image proper */ + int (*read_init) (int stream_number); + int (*read_chunk) (char * buffer_start, int sync); + int (*read_cleanup) (void); + + union { + struct swsusp_transformer_ops transformer; + struct swsusp_writer_ops writer; + } ops; + }; + + + The operations specific to transformers are few in number: + + struct swsusp_transformer_ops { + int (*expected_compression) (void); + struct list_head transformer_list; + }; + + Expected compression returns the expected ratio between the amount of + data sent to this plugin and the amount of data it passes to the next + plugin. The value is used by the core code to calculate the amount of + space required to write the image. If the ratio is not achieved, the + writer will complain when it runs out of space with data still to + write, and the core code will abort the suspend. + + transformer_list links together page transformers, in the order in + which they register, which is in turn determined by order in the + Makefile. + + There are many more operations specific to a writer: + + struct swsusp_writer_ops { + + long (*storage_available) (void); + + unsigned long (*storage_allocated) (void); + + int (*release_storage) (void); + + long (*allocate_header_space) (unsigned long space_requested); + int (*allocate_storage) (unsigned long space_requested); + + int (*write_header_init) (void); + int (*write_header_chunk) (char * buffer_start, int buffer_size); + int (*write_header_cleanup) (void); + + int (*read_header_init) (void); + int (*read_header_chunk) (char * buffer_start, int buffer_size); + int (*read_header_cleanup) (void); + + int (*prepare_save) (void); + int (*post_load) (void); + + int (*parse_image_location) (char * buffer); + + int (*image_exists) (void); + + int (*invalidate_image) (void); + + int (*wait_on_io) (int flush_all); + + struct list_head writer_list; + }; + + STORAGE_AVAILABLE is diff -ruN post-version-specific/kernel/power/io.c software-suspend-core-2.0/kernel/power/io.c --- post-version-specific/kernel/power/io.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/io.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,973 @@ +/* + * kernel/power/io.c + * + * Copyright (C) 1998-2001 Gabor Kuti + * Copyright (C) 1998,2001,2002 Pavel Machek + * Copyright (C) 2002-2003 Florent Chabaud + * Copyright (C) 2002-2004 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * This file contains data IO routines for swsusp. + * + */ + +#define SWSUSP_IO_C + +#include + +extern unsigned long orig_mem_free; +extern int swsusp_act_used; +extern int swsusp_lvl_used; +extern int swsusp_dbg_used; +extern void warmup_collision_cache(void); +extern int get_pageset1_load_addresses(void); +extern struct pagedir __nosavedata pagedir_resume; +extern struct range * unused_ranges; +extern int pm_prepare_console(void); + +extern void get_next_pbe(struct pbe * pbe); +extern void get_first_pbe(struct pbe * pbe, struct pagedir * pagedir); + +/* cleanup_finished_swsusp_io + * + * Description: Very simple helper function to save #including all the + * suspend-common code in fs/buffer.c and anywhere else we might + * want to wait on suspend I/O in future. + */ + +inline void cleanup_finished_swsusp_io(void) +{ + active_writer->ops.writer.wait_on_io(0); +} + +/* fill_suspend_header() + * + * Description: Fill the suspend header structure. + * Arguments: struct suspend_header: Header data structure to be filled. + */ + +static __inline__ void fill_suspend_header(struct suspend_header *sh) +{ + memset((char *)sh, 0, sizeof(*sh)); + + sh->version_code = LINUX_VERSION_CODE; + sh->num_physpages = num_physpages; + sh->orig_mem_free = orig_mem_free; + strncpy(sh->machine, system_utsname.machine, 65); + strncpy(sh->version, system_utsname.version, 65); + sh->num_cpus = NUM_CPUS; + sh->page_size = PAGE_SIZE; + sh->pagedir = pagedir1; + sh->pagedir.origranges.first = pagedir1.origranges.first; + sh->pagedir.destranges.first = pagedir1.destranges.first; + sh->pagedir.allocdranges.first = pagedir1.allocdranges.first; + sh->unused_ranges = unused_ranges; + sh->num_range_pages = num_range_pages; + sh->pageset_2_size = pagedir2.pageset_size; + sh->param0 = swsusp_result; + sh->param1 = swsusp_action; +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + sh->param2 = swsusp_debug_state; +#endif + sh->param3 = console_loglevel; + +} + +/* TODO: Handle page protection when saving and loading pages. */ +#if 0 +static void store_page_protection(void) +{ + pte_t * pte = NULL; + int pageprot = 0; + + clear_bit(IO_RESTORE_PAGE_PROT, &io_info->flags); + + // Remove any page protection while we restore contents + if (test_bit(IO_HANDLE_PAGE_PROT, &io_info->flags)) { + pte = lookup_address((unsigned long) data_address); + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "pte %p", pte); + 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)); + set_bit(IO_RESTORE_PAGE_PROT, &io_info->flags); + } + } + } + io_info->pte = pte; + io_info->pageprot = pageprot; + +} + +static void restore_page_protection(void) +{ + if (test_bit(IO_RESTORE_PAGE_PROT, &io_info->flags)) + set_pte(io_info->pte, pte_modify(*(io_info->pte), __pgprot(io_info->pageprot))); +} +#endif + +/* write_pageset() + * + * Description: Write a pageset to disk. + * Arguments: pagedir: Pointer to the pagedir to be saved. + * whichtowrite: Controls what debugging output is printed. + * Returns: Zero on success or -1 on failure. + */ + +int write_pageset(struct pagedir * pagedir, int whichtowrite) +{ + int nextupdate = 0, size, ret = 0, i, base = 0; + int barmax = pagedir1.pageset_size + pagedir2.pageset_size; + int start_time, end_time; + long error = 0; + struct pbe pbe; + unsigned int origfree = nr_free_pages(); + struct list_head *filter; + struct swsusp_plugin_ops * this_filter; + + PRINTFREEMEM("at start of write pageset"); + + size = pagedir->pageset_size; + if (!size) + return 0; + + if (whichtowrite == 1) { + prepare_status(1, 0, "Writing kernel & process data..."); + base = pagedir2.pageset_size; + } else { + prepare_status(1, 1, "Writing caches..."); + } + + start_time = jiffies; + + print_chain(SUSPEND_HIGH, &pagedir->origranges, 0); + print_chain(SUSPEND_HIGH, &pagedir->destranges, 0); + + /* Initialise page transformers */ + list_for_each(filter, &swsusp_filters) { + this_filter = list_entry(filter, struct swsusp_plugin_ops, ops.filter.filter_list); + if (this_filter->write_init) + this_filter->write_init(whichtowrite); + } + + /* Initialise writer */ + active_writer->write_init(whichtowrite); + + get_first_pbe(&pbe, pagedir); + + /* Write the data */ + for (i=0; i= nextupdate) || (!(i%(1 << (20 - PAGE_SHIFT))))) + nextupdate = update_status(i + base, barmax, " %d/%d MB ", MB(base+i+1), MB(barmax)); + printnolog(SUSPEND_IO, SUSPEND_MEDIUM, 1, name_suspend "Writing pageset: %d/%d ", i + 1, size); + + /* Write */ + if ((ret = first_filter->write_chunk(virt))) { + printk("Write chunk returned %d.\n", ret); + abort_suspend("Failed to write a chunk of the image.\n"); + error = -1; + goto write_pageset_free_buffers; + } + + kunmap(pbe.address); + + /* Interactivity */ + check_shift_keys(0, NULL); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + abort_suspend("Aborting as requested.\n"); + error = -1; + goto write_pageset_free_buffers; + } + + /* Prepare next */ + get_next_pbe(&pbe); + } + + update_status(base+size, barmax, " %d/%d MB ", MB(base+size), MB(barmax)); + printlog(SUSPEND_IO, SUSPEND_LOW, "|\n"); + +write_pageset_free_buffers: + + /* Flush data and cleanup */ + list_for_each(filter, &swsusp_filters) { + this_filter = list_entry(filter, struct swsusp_plugin_ops, ops.filter.filter_list); + if (this_filter->write_cleanup) + this_filter->write_cleanup(); + } + active_writer->write_cleanup(); + + /* Statistics */ + end_time = jiffies; + + if ((end_time - start_time) && (!TEST_RESULT_STATE(SUSPEND_ABORTED))) + printlog(SUSPEND_IO, SUSPEND_LOW, + "Time to write data: %d pages in %d jiffies => MB written per second: %lu.\n", + size, + (end_time - start_time), + (MB((unsigned long) size) * HZ / (end_time - start_time))); + + PRINTFREEMEM("at end of write pageset"); + + /* Sanity checking */ + if (nr_free_pages() != origfree) { + abort_suspend("Number of free pages at start and end of write pageset don't match! (%d != %d)", origfree, nr_free_pages()); + } + + return error; +} + +/* read_pageset() + * + * Description: Read a pageset from disk. + * Arguments: pagedir: Pointer to the pagedir to be saved. + * whichtowrite: Controls what debugging output is printed. + * overwrittenpagesonly: Whether to read the whole pageset or only part. + * Returns: Zero on success or -1 on failure. + */ + +int read_pageset(struct pagedir * pagedir, int whichtoread, int overwrittenpagesonly) +{ + int nextupdate = 0, result = 0, base = 0; + int start_time, end_time, finish_at = pagedir->pageset_size; + int barmax = pagedir1.pageset_size + pagedir2.pageset_size; + long i; + struct pbe pbe; + struct list_head *filter; + struct swsusp_plugin_ops * this_filter; + + PRINTFREEMEM("at start of read pageset"); + + if (whichtoread == 1) { + prepare_status(1, 1, "Reading kernel & process data..."); + } else { + prepare_status(1, 0, "Reading caches..."); + if (overwrittenpagesonly) + barmax = finish_at = MIN(pageset1_size, pageset2_size); + else { + base = pagedir1.pageset_size; + } + } + beepOK; + + start_time=jiffies; + + /* Initialise page transformers */ + list_for_each(filter, &swsusp_filters) { + this_filter = list_entry(filter, struct swsusp_plugin_ops, ops.filter.filter_list); + if ((this_filter->read_init) && (result = this_filter->read_init(whichtoread))) { + abort_suspend("Failed to initialise a filter.\n"); + result = -1; + goto read_pageset_free_buffers; + } + } + + /* Initialise writer */ + if ((result = active_writer->read_init(whichtoread))) { + abort_suspend("Failed to initialise the writer.\n"); + result = -1; + goto read_pageset_free_buffers; + } + + get_first_pbe(&pbe, pagedir); + + /* Read the pages */ + for (i=0; i< finish_at; i++) { + char * virt = (char *) kmap(pbe.address); + + /* Status */ + if (!(i&0x1FF)) + printnolog(SUSPEND_IO, SUSPEND_LOW, 0, "."); + if (((i+base) >= nextupdate) || (!(i%(1 << (20 - PAGE_SHIFT))))) + nextupdate = update_status(i+base, barmax, " %d/%d MB ", MB(base+i+1), MB(barmax)); + printnolog(SUSPEND_IO, SUSPEND_MEDIUM, 1, name_suspend "Reading pageset: %d/%d ", i + 1, finish_at); + + /* Read */ + if ((result = first_filter->read_chunk(virt, SWSUSP_ASYNC))) { + abort_suspend("Failed to read a chunk of the image.\n"); + result = -1; + goto read_pageset_free_buffers; + } + + kunmap(pbe.address); + + /* Interactivity*/ + check_shift_keys(0, NULL); + + /* Prepare next */ + get_next_pbe(&pbe); + } + + update_status(base+finish_at, barmax, " %d/%d MB ", MB(base+finish_at), MB(barmax)); + printlog(SUSPEND_IO, SUSPEND_LOW, "|\n"); + +read_pageset_free_buffers: + + /* Finish I/O, flush data and cleanup reads. */ + list_for_each(filter, &swsusp_filters) { + this_filter = list_entry(filter, struct swsusp_plugin_ops, ops.filter.filter_list); + if ((this_filter->read_cleanup) && (result = this_filter->read_cleanup())) + abort_suspend("Failed to cleanup a filter.\n"); + } + + if ((result = active_writer->read_cleanup())) + abort_suspend("Failed to cleanup the writer.\n"); + + /* Statistics */ + end_time=jiffies; + if ((end_time - start_time) && (!TEST_RESULT_STATE(SUSPEND_ABORTED))) + printlog(SUSPEND_IO, SUSPEND_LOW, + "Time to read data: %d pages in %d jiffies => MB read per second: %lu.\n", + finish_at, + (end_time - start_time), + (MB((unsigned long) finish_at) * HZ / (end_time - start_time))); + + PRINTFREEMEM("at end of read pageset"); + + return result; +} + +/* write_plugin_configs() + * + * Description: Store the configuration for each plugin in the image header. + * Returns: Int: Zero on success, Error value otherwise. + */ +static int write_plugin_configs(void) +{ + struct list_head *plugin; + struct swsusp_plugin_ops * this_plugin; + char * buffer = (char *) get_zeroed_page(GFP_ATOMIC); + int len; + + if (!buffer) { + printk("Failed to allocate a buffer for saving plugin configuration info.\n"); + return -ENOMEM; + } + + /* + * We have to know which data goes with which plugin, so we at + * least write a length of zero for a plugin. Note that we are + * also assuming every plugin's config data takes <= PAGE_SIZE. + */ + + /* For each plugin (in registration order) */ + list_for_each(plugin, &swsusp_plugins) { + this_plugin = list_entry(plugin, struct swsusp_plugin_ops, plugin_list); + + /* Get the data from the plugin */ + len = 0; + if (this_plugin->save_config_info) + len = this_plugin->save_config_info(buffer); + + /* Save the size of the data and any data returned */ + active_writer->ops.writer.write_header_chunk((char *) &len, sizeof(int)); + if (len) + active_writer->ops.writer.write_header_chunk(buffer, len); + } + + free_pages((unsigned long) buffer, 0); + return 0; +} + +/* read_plugin_configs() + * + * Description: Reload plugin configurations from the image header. + * Returns: Int. Zero on success, error value otherwise. + */ + +static int read_plugin_configs(void) +{ + struct list_head *plugin; + struct swsusp_plugin_ops * this_plugin; + char * buffer = (char *) get_zeroed_page(GFP_ATOMIC); + int len, result = 0; + + if (!buffer) { + printk("Failed to allocate a buffer for reloading plugin configuration info.\n"); + return -ENOMEM; + } + + /* For each plugin (in registration order) */ + list_for_each(plugin, &swsusp_plugins) { + this_plugin = list_entry(plugin, struct swsusp_plugin_ops, plugin_list); + + /* Get the length of the data (if any) */ + result = active_writer->ops.writer.read_header_chunk((char *) &len, sizeof(int)); + if (!result) { + printk("Failed to read the length of the next plugin's configuration data.\n"); + free_pages((unsigned long) buffer, 0); + return -EINVAL; + } + + /* Read any data and pass to the plugin */ + if (len) { + active_writer->ops.writer.read_header_chunk(buffer, len); + if (!this_plugin->save_config_info) { + printk("Huh? Plugin %s appears to have a save_config_info, but not a load_config_info function!\n", + this_plugin->name); + } else + this_plugin->load_config_info(buffer, len); + } + } + + free_pages((unsigned long) buffer, 0); + return 0; +} + +/* write_image_header() + * + * Description: Write the image header after write the image proper. + * Returns: Int. Zero on success or -1 on failure. + */ + +int write_image_header(void) +{ + int i, nextupdate = 0, ret; + int total = pagedir1.pageset_size+pagedir2.pageset_size+2; + int progress = total-1; + unsigned long * rangepageslist = NULL; + char * header_buffer = NULL; + + /* First, relativise all range information */ + rangepageslist = get_rangepages_list(); + if (!rangepageslist) + return -1; + + print_chain(SUSPEND_HIGH, &pagedir1.origranges, 0); + print_chain(SUSPEND_HIGH, &pagedir1.destranges, 0); + print_chain(SUSPEND_HIGH, &pagedir1.allocdranges, 0); + + if (unused_ranges) + unused_ranges = RANGE_RELATIVE(unused_ranges); + + relativise_chain(&pagedir1.origranges); + relativise_chain(&pagedir1.destranges); + relativise_chain(&pagedir1.allocdranges); + + if ((ret = active_writer->ops.writer.prepare_save_ranges())) { + abort_suspend("Active writer's prepare_save_ranges function failed.\n"); + goto write_image_header_abort1; + } + + relativise_ranges(); + + /* Now prepare to write the header */ + if ((ret = active_writer->ops.writer.write_header_init())) { + abort_suspend("Active writer's write_header_init function failed.\n"); + goto write_image_header_abort2; + } + + /* Get a buffer */ + header_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + if (!header_buffer) { + abort_suspend("Out of memory when trying to get page for header!\n"); + goto write_image_header_abort3; + } + + /* Write the meta data */ + fill_suspend_header((struct suspend_header *) header_buffer); + active_writer->ops.writer.write_header_chunk(header_buffer, sizeof(struct suspend_header)); + + /* Write plugin configurations */ + if ((ret = write_plugin_configs())) { + abort_suspend("Failed to write plugin configs.\n"); + goto write_image_header_abort3; + } + + /* Write range pages */ + printlog(SUSPEND_IO, SUSPEND_LOW, name_suspend "Writing %d range pages.\n", num_range_pages); + + for (i=1; i<=num_range_pages; i++) { + /* Status update */ + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 1, "%d/%d: %p.\n", + i, num_range_pages, rangepageslist[i]); + printlog(SUSPEND_IO, SUSPEND_LOW, "."); + + if (i >= nextupdate) + nextupdate = update_status(progress + i, total, NULL); + + /* Check for aborting/pausing */ + check_shift_keys(0, NULL); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + abort_suspend("Aborting as requested.\n"); + goto write_image_header_abort3; + } + + /* Write one range page */ + active_writer->ops.writer.write_header_chunk((char *) rangepageslist[i], PAGE_SIZE); + + if (ret) { + abort_suspend("Failed writing a page. Error number was %d.\n", ret); + goto write_image_header_abort3; + } + } + + update_status(total - 1, total, NULL); + + /* Flush data and let writer cleanup */ + if (active_writer->ops.writer.write_header_cleanup()) { + abort_suspend("Failed to cleanup writing header.\n"); + goto write_image_header_abort2; + } + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + goto write_image_header_abort2; + + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "|\n"); + update_status(total, total, NULL); + + MDELAY(1000); + free_pages((unsigned long) header_buffer, 0); + + return 0; + + /* + * Aborting. We need to... + * - let the writer cleanup (if necessary) + * - revert ranges to absolute values + */ +write_image_header_abort3: + active_writer->ops.writer.write_header_cleanup(); + +write_image_header_abort2: + absolutise_ranges(rangepageslist); + + put_rangepages_list(rangepageslist); + rangepageslist = NULL; + + if (active_writer->ops.writer.post_load_ranges) + active_writer->ops.writer.post_load_ranges(); + +write_image_header_abort1: + if (!rangepageslist) { + rangepageslist = get_rangepages_list(); + if (!rangepageslist) + panic("Unable to allocate rangepageslist.\n"); + } + + absolutise_chain(&pagedir1.origranges, rangepageslist); + print_chain(SUSPEND_HIGH, &pagedir1.origranges, 0); + absolutise_chain(&pagedir1.destranges, rangepageslist); + print_chain(SUSPEND_HIGH, &pagedir1.destranges, 0); + absolutise_chain(&pagedir1.allocdranges, rangepageslist); + print_chain(SUSPEND_HIGH, &pagedir1.allocdranges, 0); + + put_rangepages_list(rangepageslist); + + free_pages((unsigned long) header_buffer, 0); + return -1; +} + +extern int sanity_check_failed(char *reason); + +/* sanity_check() + * + * Description: Perform a few checks, seeking to ensure that the kernel being + * booted matches the one suspended. They need to match so we can + * be _sure_ things will work. It is not absolutely impossible for + * resuming from a different kernel to work, just not assured. + * Arguments: Struct suspend_header. The header which was saved at suspend + * time. + */ +static int sanity_check(struct suspend_header *sh) +{ + if (sh->version_code != LINUX_VERSION_CODE) + return 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 != 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; +} + +/* noresume_reset_plugins + * + * Description: When we read the start of an image, plugins (and especially the + * active writer) might need to reset data structures if we decide + * to invalidate the image rather than resuming from it. + */ + +static void noresume_reset_plugins(void) +{ + struct swsusp_plugin_ops * this_plugin; + struct list_head *plugin; + + list_for_each(plugin, &swsusp_plugins) { + this_plugin = list_entry(plugin, struct swsusp_plugin_ops, plugin_list); + if (this_plugin->noresume_reset) + this_plugin->noresume_reset(); + } +} + +/* __read_primary_suspend_image + * + * Description: Test for the existence of an image and attempt to load it. + * Returns: Int. Zero if image found and pageset1 successfully loaded. + * Error if no image found or loaded. + */ +static int __read_primary_suspend_image(void) +{ + int i, result = 0; + unsigned long * range_pages; + char * header_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + struct suspend_header * suspend_header; + struct range * last_range_page = NULL; + + if (!header_buffer) + return -ENOMEM; + + software_suspend_state |= SOFTWARE_SUSPEND_RUNNING; + + PRINTPREEMPTCOUNT("at entrance to __read_primary_suspend_image"); + + /* Check for an image */ + if (!(result = active_writer->ops.writer.image_exists())) { + result = -ENODATA; + noresume_reset_plugins(); + goto out; + } + + PRINTPREEMPTCOUNT("after checking whether image exists"); + + /* Check for noresume command line option */ + if (software_suspend_state & SOFTWARE_SUSPEND_NORESUME_SPECIFIED) { + active_writer->ops.writer.invalidate_image(); + result = -EINVAL; + noresume_reset_plugins(); + goto out; + } + + /* + * Prepare the active writer for reading the image header. The + * activate writer might read its own configuration or set up + * a network connection here. + * + * NB: This call may never return because there might be a signature + * for a different image such that we warn the user and they choose + * to reboot. (If the device ids look erroneous (2.4 vs 2.6) or the + * location of the image might be unavailable if it was stored on a + * network connection. + */ + PRINTPREEMPTCOUNT("before preparing to read header"); + + if ((result = active_writer->ops.writer.read_header_init())) { + noresume_reset_plugins(); + goto out; + } + + PRINTPREEMPTCOUNT("before preparing to read suspend header"); + + /* Read suspend header */ + if ((result = active_writer->ops.writer.read_header_chunk(header_buffer, sizeof(struct suspend_header))) < 0) { + noresume_reset_plugins(); + goto out; + } + + suspend_header = (struct suspend_header *) header_buffer; + + /* + * NB: This call may also result in a reboot rather than returning. + */ + PRINTPREEMPTCOUNT("before doing sanity check"); + + if (sanity_check(suspend_header)) { /* Is this the same machine? */ + active_writer->ops.writer.invalidate_image(); + result = -EINVAL; + noresume_reset_plugins(); + goto out; + } + + /* + * ---------------------------------------------------- + * We have an image and it looks like it will load okay. + * ---------------------------------------------------- + */ + + /* Get metadata from header. Don't override commandline parameters. + * + * We don't need to save swsusp_state[4] because it's not used during resume + * and will be restored with the image anyway + */ + + orig_mem_free = suspend_header->orig_mem_free; + memcpy((char *) &pagedir_resume, (char *) &suspend_header->pagedir, sizeof(pagedir_resume)); + unused_ranges = suspend_header->unused_ranges; + num_range_pages = suspend_header->num_range_pages; + swsusp_result = suspend_header->param0; + if (!swsusp_act_used) + swsusp_action = suspend_header->param1; +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + if (!swsusp_dbg_used) + swsusp_debug_state = suspend_header->param2; +#endif + if (!swsusp_lvl_used) + console_loglevel = suspend_header->param3; + pagedir1.pageset_size = pagedir_resume.pageset_size; + pagedir2.pageset_size = suspend_header->pageset_2_size; + + /* Prepare the console */ + now_resuming = 1; + if (pm_prepare_console()) + printk(name_suspend "Can't allocate a console... proceeding\n"); + + /* Read plugin configurations */ + if ((result = read_plugin_configs())) { + noresume_reset_plugins(); + goto out; + } + + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 1, ""); + + /* Read range pages */ + check_shift_keys(1, "About to read pagedir. "); + + printlog(SUSPEND_IO, SUSPEND_VERBOSE,name_suspend "Reading %d range pages.\n", num_range_pages); + + for (i=0; i < num_range_pages; i++) { + /* Get a page into which we will load the data */ + struct range * this_range_page = (struct range *)get_zeroed_page(GFP_ATOMIC); + if (!this_range_page) { + abort_suspend("Unable to allocate a pagedir."); + result = -ENOMEM; + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + /* Link to previous page */ + if (i == 0) + first_range_page = this_range_page; + else + *RANGEPAGELINK(last_range_page) = (i | (unsigned long) this_range_page); + + /* Read this page */ + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 0, "Reading range page %d [%p].\n", i, this_range_page); + if ((result = active_writer->ops.writer.read_header_chunk((char *) this_range_page, PAGE_SIZE)) < 0) { + printk("Active writer's read_header_chunk routine returned %d.\n", result); + free_page((unsigned long) this_range_page); + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + last_range_page = this_range_page; + } + + /* Set the last page's link to its index */ + *RANGEPAGELINK(last_range_page) = i; + + /* Clean up after reading the header */ + if ((result = active_writer->ops.writer.read_header_cleanup())) { + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "Pagedir_resume.pageset_size is %d\n", pagedir_resume.pageset_size); + + check_shift_keys(1, NULL); + + /* Okay. + * + * Now we need to move the range pages to a place such that they won't + * get overwritten while being used when copying the original kernel + * back. To achieve this, we need to absolutise them where they are + * now, prepare a bitmap of pages that collide and then relativise the + * range pages again. Having done that, we can relocate the range + * pages so that they don't collide with the image being restored, + * and absolutise them in that location. + */ + + range_pages = get_rangepages_list(); + + if (!range_pages) { + printk("Unable to allocate rangepageslist.\n"); + result = -ENOMEM; + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + /* Absolutise ranges so they can be used for building the map of + * pages that will be overwritten. */ + absolutise_ranges(range_pages); + absolutise_chain(&pagedir_resume.origranges, range_pages); + + /* Mark the pages used by the current and original kernels */ + warmup_collision_cache(); + + /* Prepare to move the pages so they don't conflict */ + relativise_chain(&pagedir_resume.origranges); + relativise_ranges(); + + /* Relocate the pages */ + relocate_rangepages(range_pages); + + /* Absolutise in final place */ + absolutise_ranges(range_pages); + + /* Done. + * + * Now we can absolutise all the pointers to the range chains. + */ + + set_chain_names(&pagedir_resume); + + absolutise_chain(&pagedir_resume.origranges, range_pages); + print_chain(SUSPEND_HIGH, &pagedir_resume.origranges, 0); + + /* + * We don't want the original destination ranges (the locations where + * the atomic copy of pageset1 was stored at suspend time); we release + * the chain's elements before getting new ones. (The kernel running + * right now could be using pages that were free when we suspended). + */ + + absolutise_chain(&pagedir_resume.destranges, range_pages); + + absolutise_chain(&pagedir_resume.allocdranges, range_pages); + print_chain(SUSPEND_HIGH, &pagedir_resume.allocdranges, 0); + + if (unused_ranges) + unused_ranges = RANGE_ABSOLUTE(unused_ranges, range_pages); + + put_rangepages_list(range_pages); + + /* + * The active writer should be using chains to record where it stored + * the data. Give it a chance to absolutise them. + */ + if (active_writer->ops.writer.post_load_ranges) + active_writer->ops.writer.post_load_ranges(); + + /* + * Get the addresses of pages into which we will load the kernel to + * be copied back + */ + put_range_chain(&pagedir_resume.destranges); + + if (get_pageset1_load_addresses()) { + result = -ENOMEM; + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + print_chain(SUSPEND_HIGH, &pagedir_resume.destranges, 0); + + printlog(SUSPEND_IO, SUSPEND_VERBOSE,"\n"); + + /* Read the original kernel back */ + check_shift_keys(1, "About to read pageset 1. "); + + if (read_pageset(&pagedir_resume, 1, 0)) { + result = -EPERM; + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + printlog(SUSPEND_IO, SUSPEND_VERBOSE,"\n|\n"); + + PRINTFREEMEM("after loading image."); + check_shift_keys(1, "About to restore original kernel. "); + result = 0; +out: + free_pages((unsigned long) header_buffer, 0); + return result; +outfreeingrangepages: + //FIXME Test i post loop and reset memory structures. + { + int j; + struct range * this_range_page = first_range_page; + struct range * next_range_page; + for (j = 0; j < i; j++) { + next_range_page = (struct range *) (((unsigned long) *RANGEPAGELINK(this_range_page)) & PAGE_MASK); + free_page((unsigned long) this_range_page); + this_range_page = next_range_page; + } + } + goto out; +} + +/* read_primary_suspend_image() + * + * Description: Attempt to read the header and pageset1 of a suspend image. + * Handle the outcome, complaining where appropriate. + */ +int read_primary_suspend_image(void) +{ + int error; + + beepOK; + error = __read_primary_suspend_image(); + + switch (error) { + case 0: + case -ENODATA: + beepOK; + case -EINVAL: /* non fatal error */ + software_suspend_state &= ~3; + MDELAY(1000); + return error; + break; + case -EIO: + printk(KERN_CRIT name_suspend "I/O error\n"); + beepERR; + break; + case -ENOENT: + printk(KERN_CRIT name_suspend "No such file or directory\n"); + break; + case -EPERM: + printk(KERN_CRIT name_suspend "Sanity check error\n"); + beepERR; + break; + default: + printk(KERN_CRIT name_suspend "Error %d resuming\n", error); + beepERR; + break; + } + abort_suspend("Error %d in read_primary_suspend_image",error); + return error; +} + +/* read_secondary_pagedir() + * + * Description: Read in part or all of pageset2 of an image, depending upon + * whether we are suspending and have only overwritten a portion + * with pageset1 pages, or are resuming and need to read them + * all. + * Arguments: Int. Boolean. Read only pages which would have been + * overwritten by pageset1? + * Returns: Int. Zero if no error, otherwise the error value. + */ +int read_secondary_pagedir(int overwrittenpagesonly) +{ + int result = 0; + + if (!pageset2_size) + return 0; + + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "Beginning of read_secondary_pagedir: "); + + result = read_pageset(&pagedir2, 2, overwrittenpagesonly); + + update_status(100, 100, NULL); + check_shift_keys(1, "Pagedir 2 read. "); + + printlog(SUSPEND_IO, SUSPEND_VERBOSE,"\n"); + return result; +} diff -ruN post-version-specific/kernel/power/lzf/lzf_c.c software-suspend-core-2.0/kernel/power/lzf/lzf_c.c --- post-version-specific/kernel/power/lzf/lzf_c.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/lzf/lzf_c.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2000-2003 Marc Alexander Lehmann + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define HSIZE (1 << (HLOG)) + +/* + * don't play with this unless you benchmark! + * decompression is not dependent on the hash function + * the hashing function might seem strange, just believe me + * it works ;) + */ +#define FRST(p) (((p[0]) << 8) + p[1]) +#define NEXT(v,p) (((v) << 8) + p[2]) +#define IDX(h) ((((h ^ (h << 5)) >> (3*8 - HLOG)) + h*3) & (HSIZE - 1)) +/* + * IDX works because it is very similar to a multiplicative hash, e.g. + * (h * 57321 >> (3*8 - HLOG)) + * the next one is also quite good, albeit slow ;) + * (int)(cos(h & 0xffffff) * 1e6) + */ + +#if 0 +/* original lzv-like hash function */ +# define FRST(p) (p[0] << 5) ^ p[1] +# define NEXT(v,p) ((v) << 5) ^ p[2] +# define IDX(h) ((h) & (HSIZE - 1)) +#endif + +#define MAX_LIT (1 << 5) +#define MAX_OFF (1 << 13) +#define MAX_REF ((1 << 8) + (1 << 3)) + +/* + * compressed format + * + * 000LLLLL ; literal + * LLLOOOOO oooooooo ; backref L + * 111OOOOO LLLLLLLL oooooooo ; backref L+7 + * + */ + +unsigned int +lzf_compress (const void *const in_data, unsigned int in_len, + void *out_data, unsigned int out_len, void *hbuf) +{ + const u8 **htab = hbuf; + const u8 **hslot; + const u8 *ip = (const u8 *)in_data; + u8 *op = (u8 *)out_data; + const u8 *in_end = ip + in_len; + u8 *out_end = op + out_len; + const u8 *ref; + + unsigned int hval = FRST (ip); + unsigned long off; + int lit = 0; + +#if INIT_HTAB +# if USE_MEMCPY + memset (htab, 0, sizeof (htab)); +# else + for (hslot = htab; hslot < htab + HSIZE; hslot++) + *hslot++ = ip; +# endif +#endif + + for (;;) + { + if (ip < in_end - 2) + { + hval = NEXT (hval, ip); + hslot = htab + IDX (hval); + ref = *hslot; *hslot = ip; + + if (1 +#if INIT_HTAB && !USE_MEMCPY + && ref < ip /* the next test will actually take care of this, but this is faster */ +#endif + && (off = ip - ref - 1) < MAX_OFF + && ip + 4 < in_end + && ref > (u8 *)in_data +#if STRICT_ALIGN + && ref[0] == ip[0] + && ref[1] == ip[1] + && ref[2] == ip[2] +#else + && *(u16 *)ref == *(u16 *)ip + && ref[2] == ip[2] +#endif + ) + { + /* match found at *ref++ */ + unsigned int len = 2; + unsigned int maxlen = in_end - ip - len; + maxlen = maxlen > MAX_REF ? MAX_REF : maxlen; + + do + len++; + while (len < maxlen && ref[len] == ip[len]); + + if (op + lit + 1 + 3 >= out_end) + return 0; + + if (lit) + { + *op++ = lit - 1; + lit = -lit; + do + *op++ = ip[lit]; + while (++lit); + } + + len -= 2; + ip++; + + if (len < 7) + { + *op++ = (off >> 8) + (len << 5); + } + else + { + *op++ = (off >> 8) + ( 7 << 5); + *op++ = len - 7; + } + + *op++ = off; + +#if ULTRA_FAST + ip += len; + hval = FRST (ip); + hval = NEXT (hval, ip); + htab[IDX (hval)] = ip; + ip++; +#else + do + { + hval = NEXT (hval, ip); + htab[IDX (hval)] = ip; + ip++; + } + while (len--); +#endif + continue; + } + } + else if (ip == in_end) + break; + + /* one more literal byte we must copy */ + lit++; + ip++; + + if (lit == MAX_LIT) + { + if (op + 1 + MAX_LIT >= out_end) + return 0; + + *op++ = MAX_LIT - 1; +#if USE_MEMCPY + memcpy (op, ip - MAX_LIT, MAX_LIT); + op += MAX_LIT; + lit = 0; +#else + lit = -lit; + do + *op++ = ip[lit]; + while (++lit); +#endif + } + } + + if (lit) + { + if (op + lit + 1 >= out_end) + return 0; + + *op++ = lit - 1; + lit = -lit; + do + *op++ = ip[lit]; + while (++lit); + } + + return op - (u8 *) out_data; +} diff -ruN post-version-specific/kernel/power/lzf/lzf_d.c software-suspend-core-2.0/kernel/power/lzf/lzf_d.c --- post-version-specific/kernel/power/lzf/lzf_d.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/lzf/lzf_d.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2000-2002 Marc Alexander Lehmann + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER- + * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE- + * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH- + * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +unsigned int +lzf_decompress (const void *const in_data, unsigned int in_len, + void *out_data, unsigned int out_len) +{ + u8 const *ip = in_data; + u8 *op = out_data; + u8 const *const in_end = ip + in_len; + u8 *const out_end = op + out_len; + + do + { + unsigned int ctrl = *ip++; + + if (ctrl < (1 << 5)) /* literal run */ + { + ctrl++; + + if (op + ctrl > out_end) + return 0; + +#if USE_MEMCPY + memcpy (op, ip, ctrl); + op += ctrl; + ip += ctrl; +#else + do + *op++ = *ip++; + while (--ctrl); +#endif + } + else /* back reference */ + { + unsigned int len = ctrl >> 5; + + u8 *ref = op - ((ctrl & 0x1f) << 8) - 1; + + if (len == 7) + len += *ip++; + + ref -= *ip++; + + if (op + len + 2 > out_end) + return 0; + + if (ref < (u8 *)out_data) + return 0; + + *op++ = *ref++; + *op++ = *ref++; + + do + *op++ = *ref++; + while (--len); + } + } + while (op < out_end && ip < in_end); + + return op - (u8 *)out_data; +} + diff -ruN post-version-specific/kernel/power/lzfcompress.c software-suspend-core-2.0/kernel/power/lzfcompress.c --- post-version-specific/kernel/power/lzfcompress.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/lzfcompress.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,550 @@ +/* + * kernel/power/lzf_compress.c + * + * Copyright (C) 2003 Marc Lehmann + * Copyright (C) 2003,2004 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * This file contains data compression routines for swsusp, + * using LZH compression. + * + */ + +#include + +#define FLAG_DISABLE 1 +static unsigned long flags; +static int expected_lzf_compression = 0; + +#define DISABLED (flags & FLAG_DISABLE) + +/* + * size of hashtable is (1 << HLOG) * sizeof (char *) + * decompression is independent of the hash table size + * the difference between 15 and 14 is very small + * for small blocks (and 14 is also faster). + * For a low-memory configuration, use HLOG == 13; + * For best compression, use 15 or 16. + */ +#ifndef HLOG +# define HLOG 14 +#endif + +/* + * sacrifice some compression quality in favour of compression speed. + * (roughly 1-2% worse compression for large blocks and + * 9-10% for small, redundant, blocks and >>20% better speed in both cases) + * In short: enable this for binary data, disable this for text data. + */ +#ifndef ULTRA_FAST +# define ULTRA_FAST 1 +#endif + +#define STRICT_ALIGN 0 +#define USE_MEMCPY 0 +#define INIT_HTAB 0 + +#include "lzf/lzf_c.c" +#include "lzf/lzf_d.c" + +static struct swsusp_plugin_ops lzf_compression_ops; +static struct swsusp_plugin_ops * next_driver; + +static void *compression_workspace = NULL; +static u8 *local_buffer = NULL; +static u8 *page_buffer = NULL; +static unsigned int bufofs; + +static __nosavedata unsigned long bytes_in = 0, bytes_out = 0; + +/* allocate_compression_space + * + * Description: Allocate space for use in [de]compressing our data. + * Each call must have a matching call to free_memory. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ + +static inline int allocate_compression_space(void) +{ + BUG_ON(compression_workspace); + + compression_workspace = vmalloc_32((1< PAGE_SIZE) { + unsigned int chunk = PAGE_SIZE - bufofs; + memcpy (local_buffer + bufofs, buffer, chunk); + buffer += chunk; + len -= chunk; + bufofs = 0; + if ((ret = next_driver->write_chunk(local_buffer)) < 0) + return ret; + } + memcpy (local_buffer + bufofs, buffer, len); + bufofs += len; + return 0; +} + +/* lzf_write_chunk() + * + * Description: Compress a page of data, buffering output and passing on + * filled pages to the next plugin in the pipeline. + * Arguments: Buffer_start: Pointer to a buffer of size PAGE_SIZE, + * containing data to be compressed. + * Returns: 0 on success. Otherwise the error is that returned by later + * plugins, -ECHILD if we have a broken pipeline or -EPERM if + * zlib errs. + */ + +static int lzf_write_chunk(char * buffer_start) +{ + int ret; + u16 len; + + if (!next_driver) { + printk("Compression Driver: Argh! No one wants my output!"); + return -ECHILD; + } + + /* Pass data through if compression disabled */ + if (DISABLED) { + /* Push directly to next driver */ + return next_driver->write_chunk(buffer_start); + } + + bytes_in += PAGE_SIZE; + + len = lzf_compress (buffer_start, PAGE_SIZE, page_buffer, PAGE_SIZE - 3, compression_workspace); + if ((ret = lzf_write ((u8 *)&len, 2)) < 0) + return ret; + + if (len) // some compression + return lzf_write (page_buffer, len); + else + return lzf_write (buffer_start, PAGE_SIZE); +} + +/* write_cleanup() + * + * Description: Write unflushed data and free workspace. + * Returns: Result of writing last page. + */ + +static int lzf_write_cleanup(void) +{ + int ret; + + if (DISABLED) + return 0; + + ret = next_driver->write_chunk(local_buffer); + + free_memory(); + free_local_buffer(); + + return ret; +} + +/* read_init() + * + * Description: Prepare to read a new stream of data. + * Arguments: int: Section of image about to be read. + * Returns: int: Zero on success, error number otherwise. + */ + +static int lzf_read_init(int stream_number) +{ + int result; + + next_driver = get_next_filter(&lzf_compression_ops); + + if (DISABLED) + return 0; + + if ((result = allocate_local_buffer())) + return result; + + bufofs = PAGE_SIZE; + + return 0; +} + +/* lzf_read() + * + * Description: Read data into compression buffer. + * Arguments: u8 *: Address of the buffer. + * unsigned int: Length + * Returns: int: Result of reading the image chunk. + */ + +static int lzf_read (u8 *buffer, unsigned int len) +{ + int ret; + + if (DISABLED) + return next_driver->read_chunk(buffer, PAGE_SIZE); + + while (len + bufofs > PAGE_SIZE) { + unsigned int chunk = PAGE_SIZE - bufofs; + memcpy (buffer, local_buffer + bufofs, chunk); + buffer += chunk; + len -= chunk; + bufofs = 0; + if ((ret = next_driver->read_chunk(local_buffer, SWSUSP_SYNC)) < 0) + return ret; + } + memcpy (buffer, local_buffer + bufofs, len); + bufofs += len; + return 0; +} + +/* lzf_read_chunk() + * + * Description: Retrieve data from later plugins and decompress it until the + * input buffer is filled. + * Arguments: Buffer_start: Pointer to a buffer of size PAGE_SIZE. + * Sync: Whether the previous plugin (or core) wants its + * data synchronously. + * Returns: Zero if successful. Error condition from me or from downstream + * on failure. + */ + +static int lzf_read_chunk(char * buffer_start, int sync) +{ + int ret; + u16 len; + struct swsusp_plugin_ops * next_driver = get_next_filter(&lzf_compression_ops); + + if (!next_driver) { + printk("Compression Driver: Argh! No one wants to feed me data!"); + return -ECHILD; + } + + /* Pass request through if compression disabled */ + if (DISABLED) { + /* Push request directly to next driver */ + return next_driver->read_chunk(buffer_start, sync); + } + + /* + * All our reads must be synchronous - we can't decompress + * data that hasn't been read yet. + */ + + if ((ret = lzf_read ((u8 *)&len, 2)) < 0) + return ret; + + if (len == 0) { // uncompressed + return lzf_read (buffer_start, PAGE_SIZE); + } else { // compressed + if ((ret = lzf_read (page_buffer, len)) < 0) + return ret; + ret = lzf_decompress (page_buffer, len, buffer_start, PAGE_SIZE); + if (ret != PAGE_SIZE) + return -EPERM; // why EPERM?? + return 0; + } + + BUG_ON (1); +} + +/* read_cleanup() + * + * Description: Clean up after reading part or all of a stream of data. + * Returns: int: Always zero. Never fails. + */ + +static int lzf_read_cleanup(void) +{ + if (DISABLED) + return 0; + + free_local_buffer(); + return 0; +} + +/* lzf_print_debug_stats + * + * Description: Print information to be recorded for debugging purposes into a + * buffer. + * Arguments: buffer: Pointer to a buffer into which the debug info will be + * printed. + * size: Size of the buffer. + * Returns: Number of characters written to the buffer. + */ + +static int lzf_print_debug_stats(char * buffer, int size) +{ + int pages_in = bytes_in >> PAGE_SHIFT, pages_out = bytes_out >> PAGE_SHIFT; + int len; + + if (DISABLED) { + len = suspend_snprintf(buffer, size, "- LZF Compression disabled.\n"); + return len; + } + + //Output the compression ratio achieved. + len = suspend_snprintf(buffer, size, "- LZF Compressor enabled.\n"); + if (pages_in) + len+= suspend_snprintf(buffer+len, size - len, + " Compressed %ld bytes into %ld (%d percent compression).\n", + bytes_in, bytes_out, (pages_in - pages_out) * 100 / pages_in); + return len; +} + +/* compression_memory_needed + * + * Description: Tell the caller how much memory we need to operate during + * suspend/resume. + * Returns: Unsigned long. Maximum number of bytes of memory required for + * operation. + */ + +static unsigned long lzf_memory_needed(void) +{ + if (DISABLED) + return 0; + + return PAGE_SIZE * 2 + (1< + * + * This file is released under the GPLv2. + * + * It contains routines for managing the memory pool during software suspend + * operation. + * + * The memory pool is a pool of pages from which page allocations + * are satisfied while we are suspending, and into which freed pages are + * released. In this way, we can keep the image size static and consistent + * while still using normal I/O routines to save the image and while saving + * the image in two parts. + * + * During suspend, almost all of the page allocations are order zero. Provision + * is made for one order one and one order two allocation. This provision is + * utilised by the swapwriter for allocating memory which is used for structures + * containing header page. (It could be made to use order zero allocations; this + * just hasn't been done yet). + */ + +#define SWSUSP_MEMORY_POOL_C +#include + +/* The maximum order we handle */ +#define MAX_POOL_ORDER 2 + +static unsigned long * memory_pool[MAX_POOL_ORDER + 1] = { NULL, NULL, NULL }; +static int pool_level_limit[MAX_POOL_ORDER + 1] = { 0, 1, 1 }; +static int pool_level[MAX_POOL_ORDER + 1] = { 0, 0, 0 }; +static spinlock_t memory_pool_lock = SPIN_LOCK_UNLOCKED; + +/* Abbreviations for debugging */ +#define MEMPOOLVERBOSE SUSPEND_MEM_POOL, SUSPEND_VERBOSE +#define MEMPOOLMEDIUM SUSPEND_MEM_POOL, SUSPEND_MEDIUM +#define MEMPOOLHIGH SUSPEND_MEM_POOL, SUSPEND_HIGH + +/* swsusp_pool_level() + * + * Description: Returns the number of pages currently in the pool. + * Returns: Int. Number of pages in the pool. + */ +int swsusp_pool_level(void) +{ + int order, sum = 0; + + for (order = 0; order <= MAX_POOL_ORDER; order++) + sum+= pool_level[order] * (1 << order); + + return sum; +} + +/* display_memory_pool_pages() + * + * Description: Display the current contents of the memory pool. + */ +void display_memory_pool_pages(void) +{ + unsigned long * next, flags; + int order, count; + + swsusp_spin_lock_irqsave(&memory_pool_lock, flags); + printlog(MEMPOOLVERBOSE, "Memory pool:\n"); + for (order = 0; order <= MAX_POOL_ORDER; order++) { + printlog(MEMPOOLVERBOSE, "- Order %d:\n", order); + next = memory_pool[order]; + count = 0; + while (next) { + printlog(MEMPOOLVERBOSE, "[%p] ", next); + count++; + next = (unsigned long *) *next; + } + if (count) + printlog(MEMPOOLVERBOSE, "(%d entries)\n", count); + else + printlog(MEMPOOLVERBOSE, "(empty)\n"); + } + spin_unlock_irqrestore(&memory_pool_lock, flags); +} + +/* fill_swsusp_memory_pool() + * + * Description: Fill the memory pool from the main free memory pool in the + * first instance, or grabbed pages if that fails. + * We allocate @sizesought order 0 pages, plus 1 each + * of the higher order allocations. + * Arguments: int. Number of order zero pages requested. + * Returns: int. Number of order zero pages obtained. + */ +int fill_swsusp_memory_pool(int sizesought) +{ + int i = 0, order, orig_state = swsusp_state & USE_MEMORY_POOL; + unsigned long *this = NULL; + unsigned long flags; + + spin_lock_irqsave(&memory_pool_lock, flags); + + /* Pool must not be active for this to work */ + swsusp_state &= ~USE_MEMORY_POOL; + + pool_level_limit[0] = sizesought; + + for (order = MAX_POOL_ORDER; order >= 0; order--) { + for (i = pool_level[order]; i < pool_level_limit[order]; i++) { + this = (unsigned long *) __get_free_pages(GFP_ATOMIC, order); + if (!this) + this = (unsigned long *) get_grabbed_pages(order); + if (!this) { + printk(name_suspend "Error. Wanted %d pages wanted of order %d for swsusp memory pool, got %d.\n", + pool_level_limit[order], order, i - 1); + break; + } + printlog(MEMPOOLHIGH, "Adding page %p to memory pool.\n", this); + *this = (unsigned long) memory_pool[order]; + memory_pool[order] = this; + pool_level[order]++; + } + } + + swsusp_state |= orig_state; + + spin_unlock_irqrestore(&memory_pool_lock, flags); + + display_memory_pool_pages(); + + return 0; +} + +/* empty_swsusp_memory_pool() + * + * Description: Drain our memory pool. + */ +void empty_swsusp_memory_pool(void) +{ + unsigned long flags; + int order; + + check_shift_keys(1, "About to drain swsusp memory pool.\n"); + display_memory_pool_pages(); + swsusp_spin_lock_irqsave(&memory_pool_lock, flags); + + /* Pool must not be active for this to work */ + swsusp_state &= ~USE_MEMORY_POOL; + + for (order = 0; order <= MAX_POOL_ORDER; order++) { + while (memory_pool[order]) { + unsigned long next = *memory_pool[order]; + printlog(MEMPOOLHIGH, + "Removing page %d (%lx) from memory pool.\n", + pool_level[order], memory_pool[order]); + if (page_count(virt_to_page((unsigned long) memory_pool[order])) != 1) + printk("Error freeing page %p from memory pool. Page count is %d (should be 1).\n", + memory_pool[order], + page_count(virt_to_page((unsigned long) memory_pool[order]))); + *memory_pool[order] = 0; + free_pages((unsigned long) memory_pool[order], order); + memory_pool[order] = (unsigned long *) next; + pool_level[order]--; + } + } + + swsusp_spin_unlock_irqrestore(&memory_pool_lock, flags); +} + +/* get_swsusp_pool_page() + * + * Description: Our equivalent to __alloc_pages (minus zone mask). + * May be called from interrupt context. + * Arguments: unsigned int: Mask. We really only care about __GFP_WAIT. + * order: The number of pages (1 << order) wanted. + * Returns: struct page *: Pointer (possibly NULL) to pages allocated. + */ +struct page * get_swsusp_pool_page(unsigned int gfp_mask, unsigned int order) +{ + unsigned long * this = NULL, flags; + struct page * page; + static int printederror = 0; + + if (order > 0) { + if (order <= MAX_POOL_ORDER) { + swsusp_spin_lock_irqsave(&memory_pool_lock, flags); + if (!pool_level[order]) { + printk("No order %d allocation available.\n", order); + swsusp_spin_unlock_irqrestore(&memory_pool_lock, flags); + return NULL; + } + goto check_and_return; + } + + if (!printederror) { + printederror = 1; + printk("Swsusp memory pool: Rejecting allocation of order %d.\n", order); + } + return NULL; + } + +try_again: + if ((!memory_pool[0]) && (!(gfp_mask & __GFP_WAIT))) + return NULL; + + while(!memory_pool[0]) { + active_writer->ops.writer.wait_on_io(0); + schedule(); + } + + swsusp_spin_lock_irqsave(&memory_pool_lock, flags); + if (!memory_pool[order]) { + swsusp_spin_unlock_irqrestore(&memory_pool_lock, flags); + goto try_again; + } +check_and_return: + this = memory_pool[order]; + memory_pool[order] = (unsigned long *) *this; + *this = 0; + pool_level[order]--; + printlog(MEMPOOLMEDIUM, + "Allocating page %p from memory pool for %s (%d). %d remaining.\n", + this, current->comm, current->pid, pool_level[order]); + spin_unlock_irqrestore(&memory_pool_lock, flags); + + page = virt_to_page(this); + + if (page_count(page) != 1) + printk("Error getting page %p from memory pool. Page count is %d (should be 1).\n", + this, + page_count(page)); + if (PageLRU(page)) + BUG(); + if (PageActive(page)) + BUG(); + + return page; +} + +/* free_swsusp_pool_pages() + * + * Description: Our equivalent to __free_pages. Put freed pages into the pool. + * Arguments: Struct page *: First page to be freed. + * Unsigned int: Size of allocation being freed. + */ +void free_swsusp_pool_pages(struct page *page, unsigned int order) +{ + unsigned long *new_head, flags; + int i; + + swsusp_spin_lock_irqsave(&memory_pool_lock, flags); + + if (order && (order <= MAX_POOL_ORDER) && (pool_level[order] < pool_level_limit[order])) { + new_head = (unsigned long *) page_address(page); + set_page_count(page, 1); + *new_head = (unsigned long) memory_pool[order]; + memory_pool[order] = new_head; + pool_level[order]++; + swsusp_spin_unlock_irqrestore(&memory_pool_lock, flags); + return; + } + + for (i = 0; i < (1 << order); i++) { + new_head = (unsigned long *) page_address(page + i); + set_page_count(page + i, 1); + *new_head = (unsigned long) memory_pool[0]; + memory_pool[0] = new_head; + pool_level[0]++; + printlog(MEMPOOLMEDIUM, + "%s (%d) freeing page %p to memory pool. Now %d free.\n", + current->comm, current->pid, + new_head, pool_level[0]); + } + swsusp_spin_unlock_irqrestore(&memory_pool_lock, flags); +} diff -ruN post-version-specific/kernel/power/nulltransformer.c software-suspend-core-2.0/kernel/power/nulltransformer.c --- post-version-specific/kernel/power/nulltransformer.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/nulltransformer.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,180 @@ +/* + * kernel/power/nulltransformer.c + * + * Copyright (C) 2003,2004 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * This file contains a dummy page transformer for testing and use as + * a template for new page transformers. + */ + +#include + +struct swsusp_plugin_ops nulltransformer_ops; + +/* write_init() + * + * Description: Routine called when Suspend wants to start to write a new + * stream of data in the image. If this plugin supports being + * disabled (by registering a proc entry itself), it may do + * nothing here and in read init, and may simply pass data + * through to the next plugin in the read/write_chunk routines. + * Arguments: Int. Stream number. Currently 1 or 2 depending upon which + * pageset we are writing. + * Returns: Int. Zero on success. Non zero return values are interpreted + * as error conditions and will lead to aborting saving the image. + */ +static int nulltrans_write_init(int stream_number) +{ + return 0; +} + +/* write_chunk() + * + * Description: Routine called when we are writing a chunk of the image. + * The transformer should use the entire input buffer (size + * PAGE_SIZE), not assuming that the data pointed to will be + * available once we return. It should buffer output, before + * sending it to the next filter one page at a time. + * Arguments: Char *. Pointer to a buffer of size PAGE_SIZE, containing + * data to be transformed and/or passed on to the next plugin. + * Returns: Zero if no error condition exists. Error conditions from + * later plugins should be passed back verbatim. + */ + +static int nulltrans_write_chunk(char * buffer_start) +{ + struct swsusp_plugin_ops * next_driver = get_next_filter(&nulltransformer_ops); + + if (!next_driver) { + printk("Null Pagetransformer: Argh! No one wants my output!"); + return -ECHILD; + } + + return next_driver->write_chunk(buffer_start); +} + +/* write_cleanup() + * + * Description: Cleanup after writing. This routine should flush buffered + * output (calling write_chunk of the next writer as usual), + * and free any buffers allocated. + * Returns: Zero. Never fails. + */ + +static int nulltrans_write_cleanup(void) +{ + return 0; +} + +/* read_init + * + * Description: Prepare to read a new stream of data. Like write_init, this + * routine should allocate space needed for buffering and perform + * any other operations needed prior to reading the first chunk of + * data. + * Arguments: Int. Portion of the image to be reread. + * Returns: Zero if no error. Error value otherwise. + */ + +static int nulltrans_read_init(int stream_number) +{ + return 0; +} + +/* read_chunk() + * + * Description: Fill an input buffer with data, reversing any transformation + * made when writing the image. + * Arguments: Char *. Pointer to the buffer (size PAGE_SIZE) to be filled. + * Int Sync. Whether the reading of this chunk must be completed + * immediately (1) or can be completed asynchronously (0). Since + * we can't decompress/encrypt data that hasn't yet been read, + * we should call later plugins with 1 and use the argument we + * received if disabled. The advantage to using this argument is + * that if no compression/encryption is enabled, the core code + * will be able to request asynchronous reads from the writer, + * resulting in faster throughput. Note that the writer may + * also implement readahead, helping throughput when we are + * operating synchronously here. + * Returns: Int: Zero if no error, error value of this or a later plugin + * otherwise. (Errors from later plugins should be passed back + * verbatim). + */ + +static int nulltrans_read_chunk(char * buffer_start, int sync) +{ + struct swsusp_plugin_ops * next_driver = get_next_filter(&nulltransformer_ops); + + if (!next_driver) { + printk("Null Pagetransformer: Argh! No one wants to feed me data!"); + return -ECHILD; + } + + return next_driver->read_chunk(buffer_start, sync); +} + +/* read_cleanup() + * + * Description: Clean up after reading part or all of a stream of data. + * (If the user aborts while writing pageset1, we reread + * the part of pageset2 that was overwritten by the atomic + * copy of the kernel). This routine should also free any + * buffers allocated by the read_init routine. + * Returns: Zero on success. Should always succeed! + */ + +static int nulltrans_read_cleanup(void) +{ + return 0; +} + +/* print_debug_stats() + * + * Description: Write debugging information to a buffer, for display + * at the end of a cycle or when the user does cat + * /proc/swsusp/debug_info. To prevent buffer overruns, + * suspend_snprintf should be used. It is like vsn_printf, + * but returns the number of bytes actually written (not + * what would have been written if the buffer was big + * enough! - see vsnprint man page). + * Arguments: Char *. Pointer to the buffer in which to store info. + * Int. Size of the buffer. Probably less than PAGE_SIZE! + * Returns: Number of bytes written. + */ +extern int suspend_snprintf(char * buffer, int buffer_size, const char *fmt, ...); + +static int nulltrans_print_debug_stats(char * buffer, int size) +{ + return 0; +} + +static unsigned long nulltrans_memory_needed(void) +{ + return 0; +} + +struct swsusp_plugin_ops nulltransformer_ops = { + .type = FILTER_PLUGIN, + .name = "Null Page Transformer", + .memory_needed = nulltrans_memory_needed, + .print_debug_info = nulltrans_print_debug_stats, + .write_init = nulltrans_write_init, + .write_chunk = nulltrans_write_chunk, + .write_cleanup = nulltrans_write_cleanup, + .read_init = nulltrans_read_init, + .read_chunk = nulltrans_read_chunk, + .read_cleanup = nulltrans_read_cleanup, +}; + +/* ---- Registration ---- */ + +static __init int nulltrans_load(void) +{ + printk("Software Suspend Null Page Transformer v1.0\n"); + return swsusp_register_plugin(&nulltransformer_ops); +} + +__initcall(nulltrans_load); + diff -ruN post-version-specific/kernel/power/nullwriter.c software-suspend-core-2.0/kernel/power/nullwriter.c --- post-version-specific/kernel/power/nullwriter.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/nullwriter.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,257 @@ +/* + * Nullwriter.c + * + * Copyright 2003 Nigel Cunningham + * + * Distributed under GPLv2. + * + * This file encapsulates functions for testing the calls to + * read/write an image. + * + */ + +#include + +#define CAPACITY 200 +static unsigned long available = CAPACITY; +static unsigned long mainpool_allocated = 0; +static unsigned long header_allocated = 0; + +static unsigned long nullwriter_storage_allocated(void) +{ + return mainpool_allocated + header_allocated; +} + +static long nullwriter_storage_available(void) +{ + return (long) (mainpool_allocated + header_allocated + available); +} + +static int nullwriter_release_storage(void) +{ +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if ((TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) && (now_resuming)) + return 0; +#endif + + available += (mainpool_allocated + header_allocated); + header_allocated = mainpool_allocated = 0; + + return 0; +} + +static long nullwriter_allocate_header_space(unsigned long space_requested) +{ + long extra_wanted = (space_requested - header_allocated); + + if (extra_wanted <= 0) + return space_requested; + + if (extra_wanted < available) { + long stolen = MIN((long) mainpool_allocated, (extra_wanted - (long) available)); + header_allocated += stolen; + mainpool_allocated -= stolen; + } + + header_allocated += MIN(extra_wanted, (long) available); + + return header_allocated; +} + +static int nullwriter_allocate_storage(unsigned long space_requested) +{ + + long extra_wanted = (space_requested - mainpool_allocated); + long got = MIN(extra_wanted, (long) available); + + if (extra_wanted <= 0) + return space_requested; + + mainpool_allocated += got; + available -= got; + + return mainpool_allocated; +} + +static int nullwriter_write_header_init(void) +{ + return 0; +} + +static int nullwriter_write_header_chunk(char * buffer, int buffer_size) +{ + return 0; +} + +static int nullwriter_write_header_cleanup(void) +{ + return 0; +} + +/* ------------------------- HEADER READING ------------------------- */ + +static int nullwriter_read_header_init(void) +{ + return 0; +} + +static int nullwriter_read_header_chunk(char * buffer, int buffer_size) +{ + return buffer_size; +} + +static int nullwriter_read_header_cleanup(void) +{ + return 0; +} + +static int nullwriter_prepare_save_ranges(void) +{ + return 0; +} + +static int nullwriter_post_load_ranges(void) +{ + return 0; +} + +static int nullwriter_write_init(int stream_number) +{ + return 0; +} + +static int nullwriter_write_chunk(char * buffer) +{ + return 0; +} + +static int nullwriter_write_cleanup(void) +{ + return 0; +} + +static int nullwriter_read_init(int stream_number) +{ + return 0; +} + +static int nullwriter_read_chunk(char * buffer, int sync) +{ + return 0; +} + +static int nullwriter_read_cleanup(void) +{ + return 0; +} + +static int nullwriter_invalidate_image(void) +{ + return 0; +} + +/* + * workspace_size + * + * Description: + * Returns the number of bytes of RAM needed for this + * code to do its work. (Used when calculating whether + * we have enough memory to be able to suspend & resume). + * + */ +static unsigned long nullwriter_memory_needed(void) +{ + return 0; +} + +/* + * Storage needed + * + * Returns amount of space in the header required + * for the nullwriter's data. + * + */ +static unsigned long nullwriter_storage_needed(void) +{ + return 0; +} + +/* + * Wait on I/O + * + */ + +static int nullwriter_wait_on_io(int flush_all) +{ + return 0; +} + +/* + * Image_exists + * + */ + +static int nullwriter_image_exists(void) +{ + return 0; +} + +/* + * Parse Image Location + * + */ + +static int nullwriter_parse_image_location(char * commandline, int boot_time) +{ + if (strncmp(commandline, "null:", 5)) { + printk(name_suspend "Commandline doesn't begin with 'null:'... nullwriter ignoring.\n"); + return 0; + } + + return 1; +} + +struct swsusp_plugin_ops nullwriterops = { + .type = WRITER_PLUGIN, + .name = "Null Writer", + .memory_needed = nullwriter_memory_needed, + .storage_needed = nullwriter_storage_needed, + .write_init = nullwriter_write_init, + .write_chunk = nullwriter_write_chunk, + .write_cleanup = nullwriter_write_cleanup, + .read_init = nullwriter_read_init, + .read_chunk = nullwriter_read_chunk, + .read_cleanup = nullwriter_read_cleanup, + .ops = { + .writer = { + .storage_available = nullwriter_storage_available, + .storage_allocated = nullwriter_storage_allocated, + .release_storage = nullwriter_release_storage, + .allocate_header_space = nullwriter_allocate_header_space, + .allocate_storage = nullwriter_allocate_storage, + .image_exists = nullwriter_image_exists, + .write_header_init = nullwriter_write_header_init, + .write_header_chunk = nullwriter_write_header_chunk, + .write_header_cleanup = nullwriter_write_header_cleanup, + .read_header_init = nullwriter_read_header_init, + .read_header_chunk = nullwriter_read_header_chunk, + .read_header_cleanup = nullwriter_read_header_cleanup, + .prepare_save_ranges = nullwriter_prepare_save_ranges, + .post_load_ranges = nullwriter_post_load_ranges, + .invalidate_image = nullwriter_invalidate_image, + .wait_on_io = nullwriter_wait_on_io, + .parse_image_location = nullwriter_parse_image_location, + } + } +}; + +/* ---- Registration ---- */ + +static __init int nullwriter_load(void) +{ + printk("Software Suspend Null Writer v1.0\n"); + return swsusp_register_plugin(&nullwriterops); +} + +__initcall(nullwriter_load); + diff -ruN post-version-specific/kernel/power/pagedir.c software-suspend-core-2.0/kernel/power/pagedir.c --- post-version-specific/kernel/power/pagedir.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/pagedir.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,475 @@ +/* + * kernel/power/pagedir.c + * + * Copyright (C) 1998-2001 Gabor Kuti + * Copyright (C) 1998,2001,2002 Pavel Machek + * Copyright (C) 2002-2003 Florent Chabaud + * Copyright (C) 2002-2004 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * Routines for handling pagesets. + * Note that pbes aren't actually stored as such. They're stored as + * ranges (extents is the term, I'm told). + */ + +#define SWSUSP_PAGEDIR_C +#include +extern struct pagedir pagedir1, pagedir2, pagedir_resume; + +/* setup_pbe_variable + * + * Description: Set up one variable in a page backup entry from the range list. + * Arguments: unsigned long: The variable which will contain the value. + * struct range**: Address of the pointer to the current range. + * struct rangechain*: Address of the rangechain we are traversing. + */ +void setup_pbe_variable(unsigned long * variable, struct range ** currentrange, struct rangechain * chain) +{ + *currentrange = chain->first; + if (chain->first) + *variable = chain->first->minimum; + else { + *variable = 0; + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 1, "Initialising variable from empty chain (%s).\n", chain->name); + } +} + +/* get_first_pbe + * + * Description: Get the first page backup entry for a pagedir. + * Arguments: struct pbe *: Address of the page backup entry we're populating. + * struct pagedir: Pagedir providing the data. + */ +void get_first_pbe(struct pbe * pbe, struct pagedir * pagedir) +{ + unsigned long currentorig, currentaddress; + + pbe->pagedir = pagedir; + + /* Get raw initial values */ + setup_pbe_variable((unsigned long *) &pbe->origaddress, &pbe->currentorigrange, &pagedir->origranges); + setup_pbe_variable((unsigned long *) &pbe->address, &pbe->currentdestrange, &pagedir->destranges); + + /* Convert to range values */ + currentorig = (unsigned long) pbe->origaddress; + currentaddress = (unsigned long) pbe->address; + + pbe->origaddress = mem_map + currentorig; + pbe->address = mem_map + currentaddress; + + if ((currentaddress < 0) || (currentaddress > max_mapnr)) + panic("Argh! Destination range value %ld is invalid!", currentaddress); + + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "origaddress: %lx. address: %lx.", + pbe->origaddress, + pbe->address); +} + +/* get_next_pbe + * + * Description: Get the next page backup entry in a pagedir. + * Arguments: struct pbe *: Address of the pbe we're updating. + */ +void get_next_pbe(struct pbe * pbe) +{ + unsigned long currentorig, currentaddress; + + /* Convert to range values */ + currentorig = (pbe->origaddress - mem_map); + currentaddress = (pbe->address - mem_map); + + /* Update values */ + GET_RANGE_NEXT(pbe->currentorigrange, currentorig); + GET_RANGE_NEXT(pbe->currentdestrange, currentaddress); + + pbe->origaddress = mem_map + currentorig; + pbe->address = mem_map + currentaddress; + + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "origaddress: %lx. address: %lx.", + pbe->origaddress, + pbe->address); +} + +/* + * -------------------------------------------------------------------------------------- + * + * Local Page Flags routines. + * + * Rather than using the rare and precious flags in struct page, we allocate + * our own bitmaps dynamically. + * + */ + +/* clear_map + * + * Description: Clear an array used to store local page flags. + * Arguments: unsigned long *: The pagemap to be cleared. + */ + +static void clear_map(unsigned long * pagemap) +{ + int i; + for(i=0; i <= ((max_mapnr - 1) >> (generic_fls(sizeof(unsigned long) * 8 - 1))); i++) + pagemap[i]=0; +} + +/* allocatemap + * + * Description: Allocate a bitmap for local page flags. + * Arguments: unsigned long **: Pointer to the bitmap. + * int: Whether to set nosave flags for the + * newly allocated pages. + * Note: This looks suboptimal, but remember that we might be allocating + * the Nosave bitmap here. + */ +int allocatemap(unsigned long ** pagemap, int setnosave) +{ + unsigned long * check; + int i; + if (*pagemap) { + printk("Error. Pagemap already allocated.\n"); + clear_map(*pagemap); + } else { + check = (unsigned long *) __get_free_pages(GFP_ATOMIC, BITMAP_ORDER); + if (!check) { + abort_suspend("Error. Unable to allocate memory for pagemap."); + return 1; + } + clear_map(check); + *pagemap = check; + if (setnosave) { + struct page * firstpage = virt_to_page((unsigned long) check); + for (i = 0; i < (1 << BITMAP_ORDER); i++) + SetPageNosave(firstpage + i); + } + } + return 0; +} + +/* freemap + * + * Description: Free a local pageflags bitmap. + * Arguments: unsigned long **: Pointer to the bitmap being freed. + * Note: Map being freed might be Nosave. + */ +int freemap(unsigned long ** pagemap) +{ + int i; + if (!*pagemap) + return 1; + else { + struct page * firstpage = virt_to_page((unsigned long) *pagemap); + for (i = 0; i < (1 << BITMAP_ORDER); i++) + ClearPageNosave(firstpage + i); + free_pages((unsigned long) *pagemap, BITMAP_ORDER); + *pagemap = NULL; + return 0; + } +} + +//---------------------------------------------------------------------------- + +/* copy_pageset1 + * + * Description: Make the atomic copy of pageset1. We can't use copy_page (as we + * once did) because we can't be sure what side effects it has. On + * my Duron, with 3DNOW, kernel_fpu_begin increments preempt + * count, making our preempt count at resume time 4 instead of 3. + */ +void copy_pageset1(void) +{ + int i = 0; + struct pbe pbe; + + get_first_pbe(&pbe, &pagedir1); + + for (i = 0; i < pageset1_size; i++) { + int loop; + unsigned long * origpage = page_address(pbe.address); + unsigned long * copypage = page_address(pbe.origaddress); + + for (loop=0; loop < (PAGE_SIZE / sizeof(unsigned long)); loop++) + *(origpage + loop) = *(copypage + loop); + get_next_pbe(&pbe); + } +} + +/* free_pagedir + * + * Description: Free a previously allocated pagedir. + * Arguments: struct pagedir *: Pointer to the pagedir being freed. + */ +void free_pagedir(struct pagedir * p) +{ + PRINTFREEMEM("at start of free_pagedir"); + + if (p->allocdranges.first) { + /* Free allocated pages */ + struct range * rangepointer; + unsigned long pagenumber; + range_for_each(&p->allocdranges, rangepointer, pagenumber) { + ClearPageNosave(mem_map+pagenumber); + free_page((unsigned long) page_address(mem_map+pagenumber)); + check_shift_keys(0, NULL); + } + } + + /* For pagedir 2, destranges == origranges */ + if (p->pagedir_num == 2) + p->destranges.first = NULL; + + put_range_chain(&p->origranges); + put_range_chain(&p->destranges); + put_range_chain(&p->allocdranges); + + PRINTFREEMEM("at end of free_pagedir"); + printlog(SUSPEND_SWAP, SUSPEND_MEDIUM, "Pageset size was %d.\n", p->pageset_size); + p->pageset_size = 0; +} + +/* allocate_extra_pagedir_memory + * + * Description: Allocate memory for making the atomic copy of pagedir1 in the + * case where it is bigger than pagedir2. + * Arguments: struct pagedir *: The pagedir for which we should + * allocate memory. + * int: Size of pageset 1. + * int: Size of pageset 2. + * Result: int. Zero on success. One if unable to allocate enough memory. + */ +int allocate_extra_pagedir_memory(struct pagedir * p, int pageset_size, int alloc_from) +{ + int num_to_alloc = pageset_size - alloc_from - p->allocdranges.size; + + prepare_status(0, 0, "Preparing page directory."); + + PRINTFREEMEM("at start of allocate_extra_pagedir_memory"); + + if (num_to_alloc < 1) + num_to_alloc = 0; + + if (num_to_alloc) { + int i, numnosaveallocated=0; +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + int origallocd = p->allocdranges.size; +#endif + + PRINTFREEMEM("prior to attempt"); + for(i=1; i <= num_to_alloc; i++) { + struct page * newpage; + unsigned long virt; + virt = get_grabbed_pages(0); + if (!virt) { + p->pageset_size = i; + printlog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, + " Allocated (extra) memory for pages from %d-%d (%d pages).\n", + alloc_from, pageset_size - 1, p->allocdranges.size - origallocd); + printk("Couldn't get enough yet. %d pages short.\n", + num_to_alloc - i + 1); + PRINTFREEMEM("at abort of allocate_extra_pagedir_memory"); + return 1; + } + newpage = virt_to_page(virt); + SetPageNosave(newpage); + add_to_range_chain(&p->allocdranges, newpage - mem_map); + numnosaveallocated++; + check_shift_keys(0, NULL); + } + printlog(SUSPEND_PAGESETS, SUSPEND_VERBOSE," Allocated (extra) memory for pages from %d-%d (%d pages).\n", alloc_from, pageset_size - 1, p->allocdranges.size - origallocd); + } + + p->pageset_size = pageset_size; + + PRINTFREEMEM("at end of allocate_extra_pagedir_memory"); + return 0; +} + +/* mark_page_pageset2 + * + * Description: This looks a bit pointless, but it seems necessary for 2.6. + * I'll look at it some more. + * Arguments: struct page *: The page to be marked as Pageset2. + */ +static inline void mark_page_pageset2(struct page * page) +{ + SetPagePageset2(page); +} + +/* mark_pages_for_pageset2 + * + * Description: Mark unshared pages in processes not needed for suspend as + * being able to be written out in a separate pagedir. + * HighMem pages are simply marked as pageset2. They won't be + * needed during suspend. + * Since we don't know which highmem pages are free, we can't + * return a count any more. + */ + +void mark_pages_for_pageset2(void) +{ + int i, numpageset2 = 0; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + struct zone * zone; +#endif + + if (max_mapnr != num_physpages) { + abort_suspend("mapnr is not expected"); + return; + } + + /* Start by marking no pages as pageset2 */ + for (i = 0; i < max_mapnr; i++) + ClearPagePageset2(mem_map+i); + + /* Add LRU pages */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + for_each_zone(zone) { + spin_lock(&zone->lru_lock); + INACTIVE_LIST_FOR_EACH(SetPagePageset2); + ACTIVE_LIST_FOR_EACH(SetPagePageset2); + spin_unlock(&zone->lru_lock); + } +#else + spin_lock(&pagemap_lru_lock); + INACTIVE_LIST_FOR_EACH(SetPagePageset2); + ACTIVE_LIST_FOR_EACH(SetPagePageset2); + spin_unlock(&pagemap_lru_lock); +#endif + + /* Ensure range pages are not Pageset2 */ + if (num_range_pages) { + unsigned long * range_pages; + range_pages = get_rangepages_list(); + if (!range_pages) { + abort_suspend("Unable to get list of range pages. Number of range pages is %d.", num_range_pages); + return; + } + for (i = 1; i <= num_range_pages; i++) { + struct page * page; + page = virt_to_page(range_pages[i]); + if (PagePageset2(page)) { // Must be assigned by the time recalc stats is called + printlog(SUSPEND_PAGESETS, SUSPEND_ERROR, "Pagedir[%d] was marked as pageset2 - unmarking.\n", i); + ClearPagePageset2(page); + numpageset2--; + } + } + put_rangepages_list(range_pages); + } + + /* Finally, ensure HighMem pages are pageset 2 and that Slab pages are + * not. */ + + for (i = 0; i < max_mapnr; i++) { + if (PageSlab(mem_map+i)) { + if (TestAndClearPagePageset2(mem_map+i)) { + printk("Found page %d is slab page but marked pageset 2.\n", i); + numpageset2--; + } + } + if (PageHighMem(mem_map+i)) + if (!TestAndSetPagePageset2(mem_map+i)) + numpageset2++; + } +} + +/* warmup_collision_cache + * + * Description: Mark the pages which are used by the original kernel. + */ +void warmup_collision_cache(void) { + int i; + struct range * rangepointer = NULL; + unsigned long pagenumber; + + allocatemap(&inusemap, 0);/* Doesn't get deallocated because forgotten + when we copy PageDir1 back. Doesn't matter + if collides because not used during copy back. + */ + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "Setting up pagedir cache..."); + for (i = 0; i < max_mapnr; i++) + ClearPageInUse(mem_map+i); + + range_for_each(&pagedir_resume.origranges, rangepointer, pagenumber) + SetPageInUse(mem_map+pagenumber); + + print_chain(SUSPEND_MEDIUM, &pagedir_resume.origranges, 0); + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "%d", i); + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "|\n"); +} + +/* get_pageset1_load_addresses + * + * Description: We check here that pagedir & pages it points to won't collide + * with pages where we're going to restore from the loaded pages + * later. + * Returns: Zero on success, one if couldn't find enough pages (shouldn't + * happen). + */ + +int get_pageset1_load_addresses(void) +{ + int i, nrdone = 0, result = 0; + void **eaten_memory = NULL; + void **c = eaten_memory, *f, *addr; + struct page * pageaddr = NULL; + + // Because we're trying to make this work when we're saving as much memory as possible + // we need to remember the pages we reject here and then free them when we're done. + + printlog(SUSPEND_IO, SUSPEND_VERBOSE,"\nAllocating memory.\n"); + for(i=0; i < pagedir_resume.pageset_size; i++) { + while ((addr = (void *) get_zeroed_page(GFP_ATOMIC))) { + memset(addr, 0, PAGE_SIZE); + pageaddr = virt_to_page(addr); + if (!PageInUse(pageaddr)) { + break; + } + eaten_memory = addr; + *eaten_memory = c; + c = eaten_memory; + } + if (!addr) { + abort_suspend("Error: Ran out of memory seeking locations for reloading data.\n"); + result = 1; + break; + } + add_to_range_chain(&pagedir_resume.destranges, pageaddr - mem_map); + nrdone++; + } + + // Free unwanted memory + c = eaten_memory; + while(c) { + f = c; + c = *c; + if (f) + free_page((unsigned long) f); + } + eaten_memory = NULL; + + printlog(SUSPEND_IO, SUSPEND_VERBOSE,"Check_pagedir: Prepared %d of %d pages.\n", nrdone, pagedir_resume.pageset_size); + + check_shift_keys(1, "Pagedir prepared. "); + + return result; +} + +char* origrangesname = "original addresses"; +char* destrangesname = "destination addresses"; +char* allocdrangesname = "allocated addresses"; + +/* set_chain_names + * + * Description: Set the chain names for a pagedir. (For debugging). + * Arguments: struct pagedir: The pagedir on which we want to set the names. + */ + +void set_chain_names(struct pagedir * p) +{ + p->origranges.name = origrangesname; + p->destranges.name = destrangesname; + p->allocdranges.name = allocdrangesname; +} diff -ruN post-version-specific/kernel/power/prepare_image.c software-suspend-core-2.0/kernel/power/prepare_image.c --- post-version-specific/kernel/power/prepare_image.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/prepare_image.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,727 @@ +/* + * Prepare_image.c + * + * We need to eat memory until we can: + * 1. Perform the save without changing anything (RAM_NEEDED < max_mapnr) + * 2. Fit it all in available space (active_writer->(available_space() >= STORAGE_NEEDED) + * 3. Reload the pagedir and pageset1 to places that don't collide with their + * final destinations, not knowing to what extent the resumed kernel will + * overlap with the one loaded at boot time. I think the resumed kernel should overlap + * completely, but I don't want to rely on this as it is an unproven assumption. We + * therefore assume there will be no overlap at all (worse case). + * 4. Meet the user's requested limit (if any) on the size of the image. + * The limit is in MB, so pages/256 (assuming 4K pages). + * + * (Final test in save_image doesn't use EATEN_ENOUGH_MEMORY) + */ + +#define SWSUSP_PREPARE_IMAGE_C + +#include +#include +extern int pageset2_sizelow; +extern unsigned long orig_mem_free; +extern void mark_pages_for_pageset2(void); +extern int image_size_limit; +extern int fill_swsusp_memory_pool(int sizesought); + +static int amount_eaten = 0, arefrozen = 0, numnosave = 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++) + SetPageInUse(mem_map+i); + + for (type=0;type < MAX_NR_ZONES; type++) { + ZONE_TYPE *zone = pgdat->node_zones + type; + int order = MAX_ORDER - 1; + FREE_AREA_TYPE *area; + struct list_head *head, *curr; + swsusp_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) + break; + if (first_entry) + first_entry--; + else { + page = list_entry(curr, struct page, list); + for(loop=0; loop < (1 << order); loop++) + ClearPageInUse(page+loop); + } + + curr = curr->next; + if (curr == head) + break; + } + } while(order--); + swsusp_spin_unlock_irqrestore(&zone->lock, flags); + } +} + +static int is_head_of_free_region(struct page * page) +{ + struct page * posn = page; + + while (((posn-mem_map) < max_mapnr) && (!PageInUse(posn))) + posn++; + return (posn - page); +} + +/* + * header_storage_for_plugins + * + * Returns the amount of space needed to store configuration + * data needed by the plugins prior to copying back the original + * kernel. We can exclude data for pageset2 because it will be + * available anyway once the kernel is copied back. + */ +unsigned long header_storage_for_plugins(void) +{ + struct list_head *plugin; + struct swsusp_plugin_ops * this_plugin; + unsigned long bytes = 0; + + list_for_each(plugin, &swsusp_plugins) { + this_plugin = list_entry(plugin, struct swsusp_plugin_ops, plugin_list); + if (this_plugin->storage_needed) + bytes += this_plugin->storage_needed(); + } + + return ((bytes + PAGE_SIZE - 1) >> PAGE_SHIFT); +} + +/* + * expected_compression_ratio + * + * Returns the expected ratio between the amount of memory + * to be saved and the amount of space required on the + * storage device. + */ +int expected_compression_ratio(void) +{ + struct list_head *filter; + struct swsusp_plugin_ops * this_filter; + int ratio = 100; + + list_for_each(filter, &swsusp_filters) { + this_filter = list_entry(filter, struct swsusp_plugin_ops, ops.filter.filter_list); + if (this_filter->ops.filter.expected_compression) + ratio = ratio * this_filter->ops.filter.expected_compression() / 100; + } + + return ratio; +} + +/* + * memory_for_plugins + * + * Returns the amount of memory requested by plugins for + * doing their work during the cycle. + */ + +unsigned long memory_for_plugins(void) +{ + unsigned long bytes = 0; + struct list_head *plugin; + struct swsusp_plugin_ops * this_plugin; + + list_for_each(plugin, &swsusp_plugins) { + this_plugin = list_entry(plugin, struct swsusp_plugin_ops, plugin_list); + if (this_plugin->memory_needed) + bytes += this_plugin->memory_needed(); + } + + return ((bytes + PAGE_SIZE - 1) >> PAGE_SHIFT); +} + +static struct pageset_sizes_result count_data_pages(void) +{ + int chunk_size, loop, numfree = 0; + int ranges = 0, currentrange = 0; + int usepagedir2; + int rangemin = 0; + struct pageset_sizes_result result; + struct range * rangepointer; + unsigned long value; + + result.size1 = 0; + result.size2 = 0; + result.size2low = 0; + result.needmorespace = 0; + + numnosave = 0; + + put_range_chain(&pagedir1.origranges); + put_range_chain(&pagedir1.destranges); + put_range_chain(&pagedir2.origranges); + pagedir2.destranges.first = NULL; + pagedir2.destranges.size = 0; + + generate_free_page_map(); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + result.size1 = -1; + result.size2 = -1; + result.size2low = -1; + result.needmorespace = 0; + return result; + } + + if (max_mapnr != num_physpages) { + abort_suspend("Max_mapnr is not equal to num_physpages."); + result.size1 = -1; + result.size2 = -1; + result.size2low = -1; + result.needmorespace = 0; + return result; + } + for (loop = 0; loop < max_mapnr; loop++) { + if (!PageReserved(mem_map+loop)) { + if (PageNosave(mem_map+loop)) { + numnosave++; + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + continue; + } + + if ((chunk_size=is_head_of_free_region(mem_map+loop))!=0) { + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + numfree += chunk_size; + loop += chunk_size - 1; + continue; + } + } else { + if (PageNosave(mem_map+loop)) { + /* + abort_suspend("Reserved page marked nosave! (%lx)", ADDRESS(loop)); + result.size1 = -1; + result.size2 = -1; + result.size2low = -1; + result.needmorespace = 0; + return result; + */ + + /* AGP pages can be Reserved and Nosave */ + numnosave++; + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + continue; + } + +#ifdef CONFIG_HIGHMEM + if (loop >= highstart_pfn) { + /* HighMem pages may be marked Reserved. We ignore them. */ + numnosave++; + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + continue; + } +#endif + + /* + * Just copy whole code segment. Hopefully it is not that big. + */ + if ((ADDRESS(loop) >= (unsigned long) &__nosave_begin) && + (ADDRESS(loop) < (unsigned long)&__nosave_end)) { + numnosave++; + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + continue; + } + /* Hmm, perhaps copying all reserved pages is not too healthy as they may contain + critical bios data? */ + + }; + + usepagedir2 = (PageHighMem(mem_map+loop) || PagePageset2(mem_map+loop)); + + if (currentrange != (1 + usepagedir2)) { + if (currentrange) + append_to_range_chain(currentrange, rangemin, loop - 1); + currentrange = usepagedir2 + 1; + rangemin = loop; + ranges++; + } + + if (PagePageset2(mem_map+loop)) { + result.size2++; + if (!PageHighMem(mem_map+loop)) + result.size2low++; + } else + result.size1++; + } + + if (currentrange) + append_to_range_chain(currentrange, rangemin, loop - 1); + check_shift_keys(0, NULL); + + if ((pagedir1.pageset_size) && (result.size1 > pagedir1.pageset_size)) + result.needmorespace = 1; + if ((pagedir2.pageset_size) && (result.size2 > pagedir2.pageset_size)) + result.needmorespace = 1; + printnolog(SUSPEND_RANGES, SUSPEND_MEDIUM, 0, "Counted %d ranges.\n", ranges); + pagedir2.destranges.first = pagedir2.origranges.first; + pagedir2.destranges.size = pagedir2.origranges.size; + range_for_each(&pagedir1.allocdranges, rangepointer, value) { + add_to_range_chain(&pagedir1.destranges, value); + check_shift_keys(0, NULL); + } + + printnolog(SUSPEND_EAT_MEMORY, SUSPEND_MEDIUM, 0, + "Count data pages: Set1 (%d) + Set2 (%d) + Nosave (%d) + NumFree (%d) = %d.\n", + result.size1, result.size2, numnosave, numfree, + result.size1 + result.size2 + numnosave + numfree); + return result; +} + +static int amount_needed(void) +{ + + int max1 = MAX( (int) (RAM_TO_SUSPEND - nr_free_pages() - amount_eaten), + ((int) (STORAGE_NEEDED - active_writer->ops.writer.storage_available()))); + return MAX( max1, + (image_size_limit > 0) ? (STORAGE_NEEDED - (image_size_limit << 8)) : 0); +} + +#define EATEN_ENOUGH_MEMORY() (amount_needed() < 1) +unsigned long storage_available = 0; + +void display_stats(void) +{ +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + unsigned long storage_allocated = active_writer->ops.writer.storage_allocated(); + printlog(SUSPEND_EAT_MEMORY, SUSPEND_MEDIUM, + "Free:%d+%d+%d=%d. Sets:%d,%d(%d). Header:%d. Nosave:%d-%d-%d=%d. Storage:%d/%lu(%lu). Needed:%d|%d|%d\n", + + /* Free */ + nr_free_pages(), amount_eaten, swsusp_pool_level(), + nr_free_pages() + amount_eaten + swsusp_pool_level(), + + /* Sets */ + pageset1_size, + pageset2_size, pageset2_sizelow, + + /* Header */ + num_range_pages, + + /* Nosave */ + numnosave, pagedir1.allocdranges.size, amount_eaten, + numnosave - pagedir1.allocdranges.size - amount_eaten, + + /* Storage - converted to pages for comparison */ + storage_allocated, + STORAGE_NEEDED, + storage_available, + + /* Needed */ + RAM_TO_SUSPEND - nr_free_pages() - amount_eaten, + STORAGE_NEEDED - storage_available, + (image_size_limit > 0) ? (STORAGE_NEEDED - (image_size_limit << 8)) : 0); +#endif +} + +/* + * Eaten is the number of pages which have been eaten. + * Pagedirincluded is the number of pages which have been allocated for the pagedir. + */ +extern int allocate_extra_pagedir_memory(struct pagedir * p, int pageset_size, int alloc_from); +extern unsigned long space_for_image; +extern unsigned char swsusp_memory_pool_active; + +struct pageset_sizes_result recalculate_stats(void) +{ + struct pageset_sizes_result result; + + mark_pages_for_pageset2(); /* Need to call this before getting pageset1_size! */ + result = count_data_pages(); + pageset2_sizelow = result.size2low; + pageset2_size = result.size2; + pageset1_size = result.size1; + storage_available = active_writer->ops.writer.storage_available(); + return result; +} + +static int update_image(void) +{ + struct pageset_sizes_result result; + int iteration = 0, orig_num_range_pages; + int alloc_from; + + do { + iteration++; + + orig_num_range_pages = num_range_pages; + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW,"-- Iteration %d.\n", iteration); + + if (allocate_extra_pagedir_memory(&pagedir2, pageset2_size, pageset2_size)) { + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, + "Still need to get more pages for pagedir 2.\n"); + return 1; + } + + alloc_from = (pageset1_size > pageset2_sizelow ? pageset2_sizelow : pageset1_size); + + if (allocate_extra_pagedir_memory(&pagedir1, pageset1_size, alloc_from)) { + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, + "Still need to get more pages for pagedir 1.\n"); + return 1; + } + + if (active_writer->ops.writer.allocate_storage(MAIN_STORAGE_NEEDED)) { + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, + "Still need to get more storage space for the image proper.\n"); + return 1; + } + + if (active_writer->ops.writer.allocate_header_space(HEADER_STORAGE_NEEDED)) { + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, + "Still need to get more storage space for header.\n"); + return 1; + } + + result = recalculate_stats(); + display_stats(); + + } while (((orig_num_range_pages < num_range_pages) || + result.needmorespace || + active_writer->ops.writer.storage_allocated() < (HEADER_STORAGE_NEEDED + MAIN_STORAGE_NEEDED)) + && (!TEST_RESULT_STATE(SUSPEND_ABORTED))); + + return (amount_needed() > 0); +} + +struct eaten_memory_t +{ + void * next; + int order; /* Order of _this_ allocation */ +}; + +struct eaten_memory_t *eaten_memory = NULL; + +static void grab_free_memory(void) +{ + int order, k; + struct eaten_memory_t *prev = eaten_memory; + + /* + * First, quickly eat all memory that's already free. + */ + + for (order = MAX_ORDER - 1; order > -1; order--) { + eaten_memory = (struct eaten_memory_t *) __get_free_pages(EAT_MEMORY_FLAGS, order); + while (eaten_memory) { + struct page * page = virt_to_page(eaten_memory); + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Grab free memory got %x, order %d.\n", + eaten_memory, order); + check_shift_keys(0, NULL); + eaten_memory->next = prev; + eaten_memory->order = order; + prev = eaten_memory; + amount_eaten += (1 << order); + for (k=0; k < (1 << order); k++) + SetPageNosave(page + k); + eaten_memory = (struct eaten_memory_t *) __get_free_pages(EAT_MEMORY_FLAGS, order); + } + } + + eaten_memory = prev; +} + +static void free_grabbed_memory(void) +{ + struct eaten_memory_t *next = NULL; + int j, num_freed = 0; + + /* Free all eaten pages immediately */ + while(eaten_memory) { + struct page * page = virt_to_page(eaten_memory); + next = eaten_memory->next; + for (j=0; j < (1 << (eaten_memory->order)); j++) { + ClearPageNosave(page + j); + num_freed++; + } + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Free grabbed pages returning %x, order %d. (Fast path).\n", + eaten_memory, eaten_memory->order); + check_shift_keys(0, NULL); + free_pages((unsigned long) eaten_memory, eaten_memory->order); + eaten_memory = next; + } + amount_eaten -= num_freed; +} + +unsigned long get_grabbed_pages(int order) +{ + struct eaten_memory_t *prev = NULL, *next=NULL, *this=eaten_memory; + struct eaten_memory_t *alternative = NULL, *alternative_prev = NULL; + int alternative_order = 999; + unsigned long result; + + /* Get a single grabbed page for swsusp's use */ + /* We only look at pages of the desired order */ + /* Code could be added to split a higher order) */ + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "\nNew call to get_grabbed_pages. Seeking order %d.\n", + order); + + while(this) { + next = this->next; + if (this->order == order) { + struct page * page = virt_to_page(this); + int j; + if (prev) + prev->next = next; + else + eaten_memory = next; + for (j=0; j < (1 << order); j++) { + ClearPageNosave(page + j); + clear_page(page_address(page + j)); + } + amount_eaten -= (1 << order); + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Get grabbed pages returning %x, order %d. (Fast path).\n", + this, order); + check_shift_keys(0, NULL); + return (unsigned long) this; + } + if ((this->order < alternative_order) && (this->order > order)) { + alternative_prev = prev; + alternative = this; + alternative_order = this->order; + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "New alternative: %p, order %d.\n", alternative, alternative->order); + } + prev = this; + this = next; + } + + /* Maybe we didn't eat any memory - try normal get */ + if (!alternative) { + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "No alternative. Trying __get_free_pages..."); + this = (struct eaten_memory_t *) __get_free_pages(GFP_ATOMIC, order); + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Result is %p, order %d.\n", + this, order); + check_shift_keys(0, NULL); + return (unsigned long) this; + } + + if (alternative_prev) + alternative_prev->next = alternative->next; + else + eaten_memory = alternative->next; + + + { + struct page * page = virt_to_page(alternative); + int j; + for (j=0; j < (1 << (alternative_order)); j++) { + ClearPageNosave(page + j); + clear_page(page_address(page + j)); + } + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Freeing %p, Order %d.\n", + alternative, alternative_order); + free_pages((unsigned long) alternative, alternative_order); + amount_eaten -= (1 << alternative_order); + } + + /* Get the chunk we want to return */ + result = __get_free_pages(EAT_MEMORY_FLAGS, order); + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Got %p, Order %d back.\n", + result, order); + clear_page(result); + + /* Grab the rest */ + grab_free_memory(); + + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Get grabbed pages returning %x, order %d. (Slow path).\n", + result, order); + check_shift_keys(0, NULL); + return result; +} + +#define MAX_ATTEMPTS 3 +extern int freeze_processes(int no_progress); + +static int attempt_to_freeze(void) +{ + int lastfreezeresult, freezeattemptsleft = MAX_ATTEMPTS; + + /* Stop processes before checking again */ + do { + thaw_processes(); + prepare_status(1, 1, "Freezing processes: Attempt %d", MAX_ATTEMPTS + 1 - freezeattemptsleft); + lastfreezeresult = freeze_processes(0); + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "- Freeze_processes returned %d.\n", + lastfreezeresult); + freezeattemptsleft--; + //printk(KERN_ERR "Last freeze result was %d.\n", lastfreezeresult); + } while ((freezeattemptsleft && lastfreezeresult) && + (!TEST_RESULT_STATE(SUSPEND_ABORTED))); + + + //printk("Exiting attempt to freeze with result %d.\n", lastfreezeresult); + if (lastfreezeresult) { + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_FREEZING_FAILED); + } else + arefrozen = 1; + + return lastfreezeresult; +} + +static int eat_memory(void) +{ + int orig_memory_still_to_eat, last_amount_needed = 0, times_criteria_met = 0; + + /* + * Note that if we have enough storage space and enough free memory, we may + * exit without eating anything. We give up when the last 10 iterations ate + * no extra pages because we're not going to get much more anyway, but + * the few pages we get will take a lot of time. + * + * We freeze processes before beginning, and then unfreeze them if we + * need to eat memory until we think we have enough. If our attempts + * to freeze fail, we give up and abort. + */ + + /* ----------- Stage 1: Freeze Processes ------------- */ + + + prepare_status(0, 1, "Eating memory."); + + recalculate_stats(); + display_stats(); + + orig_memory_still_to_eat = amount_needed(); + last_amount_needed = orig_memory_still_to_eat; + + if ((image_size_limit == -1) && (orig_memory_still_to_eat)) { + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_WOULD_EAT_MEMORY); + } + + /* ----------- Stage 2: Eat memory ------------- */ + + while ((!EATEN_ENOUGH_MEMORY()) && (!TEST_RESULT_STATE(SUSPEND_ABORTED)) && (times_criteria_met < 10)) { + if (orig_memory_still_to_eat) + update_status(orig_memory_still_to_eat - amount_needed(), orig_memory_still_to_eat, " Image size %d ", MB(STORAGE_NEEDED)); + + if ((last_amount_needed - amount_needed()) < 10) + times_criteria_met++; + else + times_criteria_met = 0; + last_amount_needed = amount_needed(); + try_to_free_pages_swsusp(amount_needed()); + grab_free_memory(); + recalculate_stats(); + display_stats(); + + check_shift_keys(0, NULL); + } + + grab_free_memory(); + + printlog(SUSPEND_EAT_MEMORY, SUSPEND_VERBOSE, "\n"); + + printlog(SUSPEND_EAT_MEMORY, SUSPEND_VERBOSE, + "(Freezer exit:) Swap needed calculated as (%d+%d)*%d/100+%d+1+%d=%d.\n", + pageset1_size, + pageset2_size, + expected_compression_ratio, + num_range_pages, + header_storage_for_plugins(), + STORAGE_NEEDED); + + /* Blank out image size display */ + update_status(100, 100, " "); + if ((amount_needed() > 0)) { + printk("Unable to free sufficient memory to suspend. Still need %d pages.\n", + amount_needed()); + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY); + } + + check_shift_keys(1, "Memory eating completed. "); + return 0; +} + +int prepare_image(void) +{ + int result = 1, sizesought; + + arefrozen = 0; + + sizesought = 100 + memory_for_plugins(); + + if (fill_swsusp_memory_pool(sizesought)) + return 1; + + if (attempt_to_freeze()) + return 1; + + if (!active_writer->ops.writer.storage_available()) { + printk(KERN_ERR "You need some storage available to be able to suspend.\n"); + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_NOSTORAGE_AVAILABLE); + return 1; + } + + do { + if (eat_memory() || TEST_RESULT_STATE(SUSPEND_ABORTED)) + break; + + /* Top up */ + if (fill_swsusp_memory_pool(sizesought)) + continue; + + do_suspend_sync(); + + result = update_image(); + + } while ((result) && (!TEST_RESULT_STATE(SUSPEND_ABORTED))); + + PRINTFREEMEM("after preparing image"); + + /* Release memory that has been eaten */ + free_grabbed_memory(); + + if (!TEST_RESULT_STATE(SUSPEND_ABORTED)) + swsusp_state |= USE_MEMORY_POOL; + + return result; +} diff -ruN post-version-specific/kernel/power/proc.c software-suspend-core-2.0/kernel/power/proc.c --- post-version-specific/kernel/power/proc.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/proc.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,615 @@ +/* + * /kernel/power/proc.c + * + * Copyright (C) 2002-2003 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * This file is to realize proc entries for tuning Software Suspend. + * + * Versions: + * 1: /proc/sys/kernel/swsusp the only tuning interface + * 2: Initial version of this file + * 3: Removed KDB parameter. + * Added checkpage parameter (for checking checksum of a page over time). + * 4: Added entry for maximum granularity in splash screen progress bar. + * (Progress bar is slow, but the right setting will vary with disk & + * processor speed and the user's tastes). + * 5: Added enable_escape to control ability to cancel aborting by pressing + * ESC key. + * 6: Removed checksumming and checkpage parameter. Made all debugging proc + * entries dependant upon debugging being compiled in. + * Meaning of some flags also changed in this version. + * 7: Added header_locations entry to simplify getting the resume= parameter for + * swapfiles easy and swapfile entry for automatically doing swapon/off from + * swapfiles as well as partitions. + * 8: Added option for marking process pages as pageset 2 (processes_pageset2). + * 9: Added option for keep image mode. + * Enumeration patch from Michael Frank applied. + * 10: Various corrections to when options are disabled/enabled; + * Added option for specifying expected compression. + * 11: Added option for freezer testing. Debug only. + * 12: Removed test entries no_async_[read|write], processes_pageset2 and + * NoPageset2. + * 13: Make default_console_level available when debugging disabled, but limited + * to 0 or 1. + * 14: Rewrite to allow for dynamic registration of proc entries and smooth the + * transition to kobjects in 2.6. + * 15: Add setting resume2 parameter without rebooting (still need to run lilo + * though!). + * 16: Add support for generic string handling and switch resume2 to use it. Add + * pre_suspend_script and post_suspend_script parameters and async activate + * flag. + */ + +#define SWSUSP_PROC_C + +static int swsusp_proc_version = 15; +static int proc_initialised = 0; + +#include +#include +#include + +static struct list_head swsusp_proc_entries; +static struct proc_dir_entry *swsusp_dir, *compat_entry, *compat_parent; + +extern void swsusp_console_proc_init(void); +extern char resume_file[256]; /* For resume= kernel option */ + +/* + * swsusp_write_compat_proc. + * + * This entry allows all of the settings to be set at once. + * It was originally for compatibility with pre- /proc/swsusp + * versions, but has been retained because it makes saving and + * restoring the configuration simpler. + */ +static int swsusp_write_compat_proc(struct file *file, const char * buffer, + unsigned long count, void * data) +{ + char * buf = (char *) get_zeroed_page(GFP_ATOMIC), *lastbuf; + int i; + unsigned long nextval; + + if (!buf) + return -ENOMEM; + + if (count > PAGE_SIZE) + count = PAGE_SIZE; + + if (copy_from_user(buf, buffer, count)) + return -EFAULT; + for (i = 0; i < 6; i++) { + if (!buf) + break; + lastbuf = buf; + nextval = simple_strtoul(buf, &buf, 0); + if (buf == lastbuf) + break; + switch (i) { + case 0: + swsusp_result = nextval; + break; + case 1: + swsusp_action = nextval; + break; + case 2: +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + swsusp_debug_state = nextval; +#endif + break; + case 3: + swsusp_default_console_level = nextval; +#ifndef CONFIG_SOFTWARE_SUSPEND_DEBUG + if (swsusp_default_console_level > 1) + swsusp_default_console_level = 1; +#endif + break; + case 4: + image_size_limit = nextval; + break; + case 5: + max_async_ios = nextval; + break; + + } + buf++; + while (*buf == ' ') + buf++; + } + buf[count] = 0; + free_pages((unsigned long) buf, 0); + return count; +} + +/* + * swsusp_read_compat_proc. + * + * Like it's _write_ sibling, this entry allows all of the settings + * to be read at once. + * It too was originally for compatibility with pre- /proc/swsusp + * versions, but has been retained because it makes saving and + * restoring the configuration simpler. + */ +static int swsusp_read_compat_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int len = 0; + + len = sprintf(page, "%ld %ld %ld %d %d %d\n", + swsusp_result, + swsusp_action, + swsusp_debug_state, + swsusp_default_console_level, + image_size_limit, + max_async_ios); + *eof = 1; + return len; +} + +/* + * proc_software_suspend_pending + * + * This routine initiates a suspend cycle when /proc/swsusp/activity is + * written to. The value written is ignored. + */ + +static int proc_software_suspend_pending(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + software_suspend_pending(); + return count; +} + +extern int attempt_to_parse_resume_device(int boot_time); + +static int resume2_write_proc(void) +{ + return attempt_to_parse_resume_device(0); +} + +/* + * debuginfo_read_proc + * Functionality : Displays information that may be helpful in debugging + * software suspend. + */ +extern void start_kswsuspd(void * data); + +static int debuginfo_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + static struct debug_info_data kswsuspd_data; + + kswsuspd_data.action = 2; + kswsuspd_data.buffer = page; + kswsuspd_data.buffer_size = count; + start_kswsuspd(&kswsuspd_data); + *eof = 1; + return kswsuspd_data.bytes_used; +} + +/* + * version_read_proc + * + * Displays the version of software suspend installed in the + * currently running kernel. + */ +static int version_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + *eof = 1; + return sprintf(page, "%s\n", swsusp_version); +} + +/* + * generic_read_proc + * + * Generic handling for reading the contents of bits, integers, + * unsigned longs and strings. + */ +static int generic_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int len = 0; + struct swsusp_proc_data * proc_data = (struct swsusp_proc_data *) data; + + switch (proc_data->type) { + case SWSUSP_PROC_DATA_CUSTOM: + printk("Error! /proc/swsusp/%s marked as having custom routines, but the generic read routine has been invoked.\n", + proc_data->filename); + break; + case SWSUSP_PROC_DATA_BIT: + len = sprintf(page, "%d\n", -test_bit(proc_data->data.bit.bit, proc_data->data.bit.bit_vector)); + break; + case SWSUSP_PROC_DATA_INTEGER: + { + int * variable = proc_data->data.integer.variable; + len = sprintf(page, "%d\n", *variable); + break; + } + case SWSUSP_PROC_DATA_UL: + { + long * variable = proc_data->data.ul.variable; + len = sprintf(page, "%lu\n", *variable); + break; + } + case SWSUSP_PROC_DATA_STRING: + { + char * variable = proc_data->data.string.variable; + len = sprintf(page, "%s\n", variable); + break; + } + } + *eof = 1; + return len; +} + +/* + * generic_write_proc + * + * Generic routine for handling writing to files representing + * bits, integers and unsigned longs. + */ + +static int generic_write_proc(struct file *file, const char * buffer, + unsigned long count, void * data) +{ + struct swsusp_proc_data * proc_data = (struct swsusp_proc_data *) data; + char * my_buf = (char *) get_zeroed_page(GFP_ATOMIC); + int result = count; + + if (!my_buf) + return -ENOMEM; + + if (count > PAGE_SIZE) + count = PAGE_SIZE; + + if (copy_from_user(my_buf, buffer, count)) + return -EFAULT; + my_buf[count] = 0; + + switch (proc_data->type) { + case SWSUSP_PROC_DATA_CUSTOM: + printk("Error! /proc/swsusp/%s marked as having custom routines, but the generic write routine has been invoked.\n", + proc_data->filename); + break; + case SWSUSP_PROC_DATA_BIT: + { + int value = simple_strtoul(my_buf, NULL, 0); + if (value) + set_bit(proc_data->data.bit.bit, (proc_data->data.bit.bit_vector)); + else + clear_bit(proc_data->data.bit.bit, (proc_data->data.bit.bit_vector)); + } + break; + case SWSUSP_PROC_DATA_INTEGER: + { + int * variable = proc_data->data.integer.variable; + int minimum = proc_data->data.integer.minimum; + int maximum = proc_data->data.integer.maximum; + *variable = simple_strtoul(my_buf, NULL, 0); + /* Set minimum == -1 for no minimum */ + if (((*variable) < minimum) && (minimum != -1)) + *variable = minimum; + + /* Likewise for no maximum */ + if (((*variable) > maximum) && (maximum != -1)) + *variable = maximum; + break; + } + case SWSUSP_PROC_DATA_UL: + { + unsigned long * variable = proc_data->data.ul.variable; + unsigned long minimum = proc_data->data.ul.minimum; + unsigned long maximum = proc_data->data.ul.maximum; + *variable = simple_strtoul(my_buf, NULL, 0); + /* Set minimum == -1 (well yes, 0xFFFFFFFF) for no minimum */ + if ((minimum != (unsigned long) -1) && ((*variable) < minimum)) + *variable = minimum; + + /* Likewise for no maximum */ + if ((minimum != (unsigned long) -1) && ((*variable) > maximum)) + *variable = maximum; + break; + } + break; + case SWSUSP_PROC_DATA_STRING: + { + int copy_len = (count > proc_data->data.string.max_length) ? + proc_data->data.string.max_length : count; + char * variable = proc_data->data.string.variable; + strncpy(variable, my_buf, copy_len); + if ((copy_len) && (my_buf[copy_len - 1] == '\n')) + variable[count - 1] = 0; + variable[count] = 0; + if (proc_data->data.string.write_proc) { + int routine_result = proc_data->data.string.write_proc(); + if (routine_result < 0) + result = routine_result; + } + } + break; + } + free_pages((unsigned long) my_buf, 0); + return result; +} + +/* + * Non-plugin proc entries. + * + * This array contains entries that are automatically registered at + * boot. Plugins and the console code register their own entries separately. + */ + +static struct swsusp_proc_data proc_params[] = { + { .filename = "activate", + .permissions = PROC_WRITEONLY, + .type = SWSUSP_PROC_DATA_CUSTOM, + .data = { + .special = { + .write_proc = proc_software_suspend_pending + } + } + }, + + { .filename = "all_settings", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_CUSTOM, + .data = { + .special = { + .read_proc = swsusp_read_compat_proc, + .write_proc = swsusp_write_compat_proc, + } + } + }, + + { .filename = "async_io_limit", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &max_async_ios, + .minimum = 1, + .maximum = 5000, + } + } + }, + + { .filename = "debug_info", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_CUSTOM, + .data = { + .special = { + .read_proc = debuginfo_read_proc, + } + } + }, + + { .filename = "image_size_limit", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &image_size_limit, + .minimum = 0, + .maximum = -1, + } + } + }, + + { .filename = "interface_version", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &swsusp_proc_version, + } + } + }, + + { .filename = "last_result", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_UL, + .data = { + .ul = { + .variable = &swsusp_result, + } + } + }, + + { .filename = "resume2", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_STRING, + .data = { + .string = { + .variable = resume_file, + .max_length = 255, + .write_proc = resume2_write_proc, + } + } + }, + + + { .filename = "version", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_CUSTOM, + .data = { + .special = { + .read_proc = version_read_proc, + } + } + }, + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + { .filename = "freezer_test", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_FREEZER_TEST, + } + } + }, + + { .filename = "reboot", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_REBOOT, + } + } + }, + + { .filename = "slow", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_SLOW, + } + } + }, +#endif + +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + { .filename = "keep_image", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_KEEP_IMAGE, + } + } + }, +#endif +}; + +/* + * swsusp_initialise_proc + * + * Initialise the /proc/swsusp tree. + * + */ + +int swsusp_initialise_proc(void) +{ + swsusp_dir = proc_mkdir("swsusp", NULL); + + if (swsusp_dir == NULL) { + printk(KERN_ERR "Failed to create /proc/swsusp.\n"); + return 1; + } + + INIT_LIST_HEAD(&swsusp_proc_entries); + + proc_initialised = 1; + + return 0; +} + +/* + * swsusp_register_procfile + * + * Helper for registering a new /proc/swsusp entry. + */ + +struct proc_dir_entry * swsusp_register_procfile(struct swsusp_proc_data * swsusp_proc_data) +{ + struct proc_dir_entry * new_entry; + + if ((!proc_initialised) && (swsusp_initialise_proc())) + return NULL; + + new_entry = create_proc_entry( + swsusp_proc_data->filename, + swsusp_proc_data->permissions, + swsusp_dir); + if (new_entry) { + list_add_tail(&swsusp_proc_data->proc_data_list, &swsusp_proc_entries); + if (swsusp_proc_data->type) { + new_entry->read_proc = generic_read_proc; + new_entry->write_proc = generic_write_proc; + } else { + new_entry->read_proc = swsusp_proc_data->data.special.read_proc; + new_entry->write_proc = swsusp_proc_data->data.special.write_proc; + } + new_entry->data = swsusp_proc_data; + } + return new_entry; +} + +/* + * swsusp_unregister_procfile + * + * Helper for removing unwanted /proc/swsusp entries. + * + */ +void swsusp_unregister_procfile(struct swsusp_proc_data * swsusp_proc_data) +{ + remove_proc_entry( + swsusp_proc_data->filename, + swsusp_dir); + list_del(&swsusp_proc_data->proc_data_list); +} + +/* + * find_proc_dir_entry. + * + * Based on remove_proc_entry. + * This will go shortly, once user space utilities + * are updated to look at /proc/swsusp/all_settings. + */ + +struct proc_dir_entry * find_proc_dir_entry(const char *name, struct proc_dir_entry *parent) +{ + struct proc_dir_entry **p; + int len; + + len = strlen(name); + for (p = &parent->subdir; *p; p=&(*p)->next ) { + if (proc_match(len, name, *p)) { + return *p; + } + } + return NULL; +} + +/* + * swsusp_init_proc + * + * Initialise proc file support at boot time. This is called + * by swsusp2.c::software_resume2. + */ +int swsusp_init_proc(void) +{ + int i; + int numfiles = sizeof(proc_params) / sizeof(struct swsusp_proc_data); + + if ((!proc_initialised) && swsusp_initialise_proc()) + return 1; + + for (i=0; i< numfiles; i++) + swsusp_register_procfile(&proc_params[i]); + + swsusp_console_proc_init(); + + compat_parent = find_proc_dir_entry("sys", &proc_root); + if (compat_parent) { + compat_parent = find_proc_dir_entry("kernel", compat_parent); + if (compat_parent) { + compat_entry = create_proc_entry("swsusp", + 0600, + compat_parent); + compat_entry->read_proc = swsusp_read_compat_proc; + compat_entry->write_proc = swsusp_write_compat_proc; + } + } + + return 0; +} diff -ruN post-version-specific/kernel/power/process.c software-suspend-core-2.0/kernel/power/process.c --- post-version-specific/kernel/power/process.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/process.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,682 @@ +/* + * kernel/power/freeze_and_free.c + * + * Copyright (C) 1998-2001 Gabor Kuti + * Copyright (C) 1998,2001,2002 Pavel Machek + * Copyright (C) 2002-2003 Florent Chabaud + * Copyright (C) 2002-2003 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * Freeze_and_free contains the routines software suspend uses to freeze other + * processes during the suspend cycle and to (if necessary) free up memory in + * accordance with limitations on the image size. + * + * Ideally, the image saved to disk would be an atomic copy of the entire + * contents of all RAM and related hardware state. One of the first + * prerequisites for getting our approximation of this is stopping the activity + * of other processes. We can't stop all other processes, however, since some + * are needed in doing the I/O to save the image. Freeze_and_free.c contains + * the routines that control suspension and resuming of these processes. + * + * Under high I/O load, we need to be careful about the order in which we + * freeze processes. If we freeze processes in the wrong order, we could + * deadlock others. The freeze_order array this specifies the order in which + * critical processes are frozen. All others are suspended after these have + * entered the refrigerator. + * + * Another complicating factor is that freeing memory requires the processes + * to not be frozen, but at the end of freeing memory, they need to be frozen + * so that we can be sure we actually have eaten enough memory. This is why + * freezing and freeing are in the one file. The freezer is not called from + * the main logic, but indirectly, via the code for eating memory. The eat + * memory logic is iterative, first freezing processes and checking the stats, + * then (if necessary) unfreezing them and eating more memory until it looks + * like the criteria are met (at which point processes are frozen & stats + * checked again). + */ + +#define SWSUSP_FREEZER_C + +#include +#include + +char idletimeout; +atomic_t swsusp_num_active = { 0 }; +atomic_t __nosavedata swsusp_cpu_counter = { 0 }; + +#if 0 +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +extern void ide_suspend(void); +extern void ide_unsuspend(void); +#endif +#endif + +unsigned char software_suspend_state = SOFTWARE_SUSPEND_DISABLED; +unsigned int suspend_task = 0; +unsigned char __nosavedata swsusp_state = 0; + +unsigned long swsusp_action = 0; +unsigned long swsusp_result = 0; + +/* Locks */ +spinlock_t suspend_irq_lock = SPIN_LOCK_UNLOCKED; +unsigned long swsuspirqflags; + +int now_resuming = 0; + +int swsusp_default_console_level = 0; +extern void suspend_relinquish_console(void); + +/* ------------------------------------------------------------------------ */ + + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) +extern int wakeup_bdflush(long nr_pages); +void do_suspend_sync(void) +{ + wakeup_bdflush(0); + blk_run_queues(); + io_schedule(); + yield(); +} + +#else +extern int nr_buffers_type[NR_LIST]; +void do_suspend_sync(void) +{ + int i; + int orig[NR_LIST]; + + for (i = 0; i < NR_LIST; i++) + orig[i] = nr_buffers_type[i]; + + do { } while (sync_buffers(NODEV, 1)); + do { } while (fsync_dev(NODEV)); + + 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"); + } + + if (nr_buffers_type[1] || nr_buffers_type[2]) { + printk("Entry to do_suspend_sync:\n"); + for (i = 0; i < NR_LIST; i++) + printk(" nr_buffers_type[i] = %d.\n", orig[i]); + + printk("Exit from do_suspend_sync:\n"); + for (i = 0; i < NR_LIST; i++) + printk(" nr_buffers_type[i] = %d.\n", nr_buffers_type[i]); + check_shift_keys(1, "Not all buffers clean when exiting do_suspend_sync."); + } +} +#endif + +#ifdef CONFIG_SMP +static void smp_pause(void * data) +{ + atomic_inc(&swsusp_cpu_counter); + while(swsusp_state & FREEZE_SMP) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + atomic_dec(&swsusp_cpu_counter); +} + +#endif + + /* Processes which need to be frozen after others in this particular order */ +static char * freeze_order[] = {"[OTHERS]" }; +static const int freeze_order_num = 0; +static const int freeze_rest_index = 0; +static int to_be_frozen(struct task_struct * p) { + + if ((p == current) || +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + (!(strcmp(p->comm, "dosexec"))) || +#endif + (p->flags & PF_NOFREEZE) || + (p->state == TASK_ZOMBIE) || + (p->state == TASK_STOPPED)) + return 0; + return 1; +} + +static int processesfrozen = 0; +static int freezing = 0; + +/** + * refrigerator - idle routine for frozen processes + * @flag: unsigned long, non zero if signals to be flushed. + * + * A routine for kernel threads which should not do work during suspend + * to enter and spin in until the process is finished. + * + */ + +void refrigerator(unsigned long flag) +{ + unsigned long flags; + long save; + + if (unlikely(current->flags & PF_NOFREEZE)) { + swsusp_spin_lock_irqsave(PROCESS_SIG_MASK(current), flags); + RECALC_SIGPENDING; + swsusp_spin_unlock_irqrestore(PROCESS_SIG_MASK(current), flags); + return; + } + + /* You need correct to work with real-time processes. + OTOH, this way one process may see (via /proc/) some other + process in stopped state (and thereby discovered we were + suspended. We probably do not care). + */ + processesfrozen++; + if ((flag) && (current->flags & PF_FREEZE)) { + + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "%s (%d) refrigerated and sigpending recalculated.\n", + current->comm, current->pid); + swsusp_spin_lock_irqsave(PROCESS_SIG_MASK(current), flags); + RECALC_SIGPENDING; + swsusp_spin_unlock_irqrestore(PROCESS_SIG_MASK(current), flags); + } else + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "%s (%d) refrigerated.\n", + current->comm, current->pid); + + if (swsusp_state) { + save = current->state; + current->flags |= PF_FROZEN; + while (current->flags & PF_FROZEN) { + current->state = TASK_STOPPED; + schedule(); + if (flag) { + swsusp_spin_lock_irqsave(PROCESS_SIG_MASK(current), flags); + RECALC_SIGPENDING; + swsusp_spin_unlock_irqrestore(PROCESS_SIG_MASK(current), flags); + } + } + current->state = save; + } + current->flags &= ~PF_FREEZE; + processesfrozen--; +} + +/* + * swsusp_activity_start + */ +void __swsusp_activity_start(int flags, const char * function, const char * file) +{ + if (unlikely((suspend_task) && (current->pid == suspend_task))) + return; + + if (unlikely(current->flags & PF_FREEZE)) + refrigerator(flags); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + if (unlikely((swsusp_state & FREEZE_NEW_ACTIVITY) && + strcmp(current->comm, "dosexec") && + ((!(current->flags & PF_SYNCTHREAD)) || + (swsusp_state & FREEZE_UNREFRIGERATED)))) +#else + if (unlikely((swsusp_state & FREEZE_NEW_ACTIVITY) && + ((!(current->flags & PF_SYNCTHREAD)) || + (swsusp_state & FREEZE_UNREFRIGERATED)))) +#endif + refrigerator(flags); + + if (unlikely(current->flags & PF_FRIDGE_WAIT)) { + printk("__swsusp_activity_start: Task %s already has FRIDGE_WAIT set.\n", + current->comm); + return; + } + + current->flags |= PF_FRIDGE_WAIT; + atomic_inc(&swsusp_num_active); +} + +/* + * freeze_processes - Freeze processes prior to saving an image of memory to disk + * + * Return value: 0 = success, else # of processes that we failed to stop + */ +extern int sync_old_buffers(void); +extern void show_task(struct task_struct * p); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +extern spinlock_t io_request_lock; +#endif + +/* Freeze_processes. + * If the flag no_progress is non-zero, progress bars not be updated. + * Debugging output is still printed. + */ +int freeze_processes(int no_progress) +{ + int todo = 0, threadcount, newtodo; + int lastprocessesfrozen = processesfrozen; + struct task_struct FOR_EACH_THREAD_TASK_STRUCTS; + int index = 0, origfrozen = processesfrozen; + int orignum_active, lastnum_active = 0; + unsigned long start_time, last_time; + int showidlelist; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + unsigned long iorequestflags = 0; +#endif + + freezing = 1; + suspend_task = current->pid; + + showidlelist = 1; + + swsusp_state = FREEZE_NEW_ACTIVITY; + swsusp_result = 0; /* Might be called from pm_disk or swsusp - ensure reset */ + + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (p->flags & PF_SYNCTHREAD) + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "%s (%d) is a syncthread at entrance to fridge\n", + p->comm, p->pid); + if (!(p->flags & PF_FRIDGE_WAIT)) + continue; + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "%s (%d) busy at entrance to fridge.\n", + p->comm, p->pid); + if ((console_loglevel >= SUSPEND_MEDIUM) && (CHECKMASK(SUSPEND_FREEZER))) + show_task(p); + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, "\n"); + + /* First, wait for syncing to finish */ + if (!no_progress) + prepare_status(1, 1, "Freezing processes: Waiting for activity to finish."); + lastnum_active = orignum_active = atomic_read(&swsusp_num_active); + if (!no_progress) + update_status(0, lastnum_active, " %d remaining.", lastnum_active); + + last_time = start_time = jiffies; + while (atomic_read(&swsusp_num_active)) { + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (p->flags & PF_SYNCTHREAD) + printk("%s (%d) is still a syncthread when aborting.\n", + p->comm, p->pid); + if (!(p->flags & PF_FRIDGE_WAIT)) + continue; + printk("%s (%d) still busy when aborting.\n", + p->comm, + p->pid); + + if (!TEST_ACTION_STATE(SUSPEND_FREEZER_TEST_SHOWALL)) + show_task(p); + todo++; + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + if (TEST_ACTION_STATE(SUSPEND_FREEZER_TEST_SHOWALL)) + show_state(); + freezing = 0; + idletimeout=0; + return todo; + } + set_task_state(current, TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/5); + if (atomic_read(&swsusp_num_active) < lastnum_active) { + lastnum_active = atomic_read(&swsusp_num_active); + if (!no_progress) + update_status(orignum_active - lastnum_active, + orignum_active, " %d remaining.", + lastnum_active); + last_time = jiffies; + showidlelist=1; + } else if (jiffies > last_time + 250) { + idletimeout = 1; + printk("swsusp_num_active is %d, Idle for 250 jiffies\n", + atomic_read(&swsusp_num_active)); + if (showidlelist) { + showidlelist = 0; + show_state(); + } + last_time = jiffies; + } + } + + idletimeout=0; + if (!no_progress) + check_shift_keys(1, "I/O activity stopped."); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + return 1; + } + + /* Now do our own sync, just in case one wasn't running already */ + if (!no_progress) + prepare_status(1, 1, "Freezing processes: Syncing remaining I/O."); + current->flags |= PF_SYNCTHREAD; + do_suspend_sync(); + current->flags &= ~PF_SYNCTHREAD; + + if (!no_progress) + check_shift_keys(1, "Syncing finished."); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + return 1; + } + + swsusp_state |= FREEZE_UNREFRIGERATED; + + /* Now freeze remaining processes */ + if (!no_progress) + prepare_status(1, 1, "Freezing processes: Freezing remaining tasks."); + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (!to_be_frozen(p)) + continue; + todo++; + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + + start_time = jiffies; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + swsusp_spin_lock_irqsave(&io_request_lock, iorequestflags); +#endif + + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, " ** %s **\n", + freeze_order[index]); + + do { + int numsignalled = 0; + newtodo = 0; + threadcount = 0; + + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "SMP Freeze. "); + + /* + * Pause the other processors so we can safely + * change threads' flags + */ + swsusp_state |= FREEZE_SMP; + smp_call_function(smp_pause, NULL, 0, 0); + + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Wait on SMP Freeze."); + while ((atomic_read(&swsusp_cpu_counter) < (NUM_CPUS - 1)) && + (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + goto aborting; + + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Signalling. "); + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + unsigned long flags; + int i; + int thread_index = -1; + if (!to_be_frozen(p)) + continue; + for (i = 0; i < freeze_order_num; i++) + if (!strcmp(p->comm, freeze_order[i])) { + thread_index = i; + break; + } + if (thread_index > -1) { + if (thread_index != index) + continue; + } else + if (freeze_rest_index != index) + continue; + threadcount++; + /* + * We don't signal sync; new instances automatically + * enter freezer (on swsusp_state flag) and we wait + * for old ones to finish and exit. + */ + if (!(p->flags & PF_FREEZE)) { + numsignalled++; + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "%s%s thread %d", + numsignalled > 1 ? "," : "", + index == freeze_order_num ? + p->comm : "", + p->pid); + p->flags |= PF_FREEZE; + /* FIXME: smp problem here: we may not access other process' flags + without locking */ + swsusp_spin_lock_irqsave(PROCESS_SIG_MASK(p), flags); + WAKE_UP(p); + swsusp_spin_unlock_irqrestore(PROCESS_SIG_MASK(p), flags); + } + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Unfreeze SMP. "); + swsusp_state &= ~FREEZE_SMP; + while ((atomic_read(&swsusp_cpu_counter)) && + (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + if (numsignalled) + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, ".\n"); + if (!threadcount && index < freeze_order_num) { + index++; + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, "\n ** %s **\n", + freeze_order[index]); + } + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Schedule. "); + set_task_state(current, TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Examine effect. "); + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (!to_be_frozen(p)) + continue; + /* + * We signal uninterruptible processes, but don't have a hernia + * if they don't enter the refrigerator. The signal ensures they + * do refrigerate if they wake up. If they don't, they will + * harmlessly run through the freezer after suspend. + * + * Note that these uninterruptible processes are counted in the + * number to be frozen (for the sake of the progress bar), but + * won't be shown as processes that refused to be frozen if + * freezing fails. + */ + if (p->state == TASK_UNINTERRUPTIBLE) + continue; + /* Count all processes yet to be frozen */ + newtodo++; + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Show results. "); + if (lastprocessesfrozen < processesfrozen) { + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "Waiting on:"); + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (!to_be_frozen(p)) + continue; + if (p->state == TASK_UNINTERRUPTIBLE) + continue; + /* Only print a process if we're waiting for it now */ + if (p->flags & PF_FREEZE) + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "%s (%d); ", + p->comm, p->pid); + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "\n"); + } + lastprocessesfrozen = processesfrozen; + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Check time. "); + if ((time_after(jiffies, start_time + TIMEOUT)) && (newtodo)) { + printk(KERN_ERR name_suspend "Stopping tasks failed.\n"); + printk(KERN_ERR "Tasks that refused to be refrigerated and haven't since exited:\n"); + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (!to_be_frozen(p)) + continue; + if (p->state == TASK_UNINTERRUPTIBLE) + continue; + /* + * We still let the process enter the refrigerator when it's + * ready. It will just flush signals and exit immediately + * because suspend_task isn't set. + */ + if (p->flags & PF_FREEZE) { + printk(" - %s (#%d) signalled but didn't enter refrigerator.\n", + p->comm, p->pid); + show_task(p); + } else + printk(" - %s (#%d) wasn't signalled.\n", + p->comm, p->pid); + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + goto aborting; + } + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Update status. "); + if ((todo > newtodo) && (!no_progress)) + update_status(todo - newtodo, todo, + "%d/%d", todo - newtodo, todo); + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Post update status "); + } while(newtodo && (!TEST_RESULT_STATE(SUSPEND_ABORTED))); + +out: + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, "Left freezer loop.\n"); + freezing = 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + swsusp_spin_unlock_irqrestore(&io_request_lock, iorequestflags); +#endif + + if (!no_progress) + check_shift_keys(1, "Freezing processes completed. "); + + swsusp_state &= ~FREEZE_SMP; + + while (atomic_read(&swsusp_cpu_counter)) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + + return newtodo; +aborting: + newtodo = todo - (processesfrozen - origfrozen); + goto out; +} + +void thaw_processes(void) +{ + struct task_struct FOR_EACH_THREAD_TASK_STRUCTS; + printnolog(SUSPEND_FREEZER, SUSPEND_LOW, 1, "Thawing tasks\n"); + + /* Must be done before we thaw processes, to avoid problems + * with them trying to grab /dev/console when we still have + * it. + */ + suspend_relinquish_console(); + + suspend_task = 0; + swsusp_state = 0; + STORAGE_UNSUSPEND + + /* + * Pause the other processors so we can safely + * change threads' flags + */ + + swsusp_state |= FREEZE_SMP; + smp_call_function(smp_pause, NULL, 0, 0); + + while (atomic_read(&swsusp_cpu_counter) < (NUM_CPUS - 1)) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + + read_lock(&tasklist_lock); + + FOR_EACH_THREAD_START { + if (p->flags & PF_FROZEN) { + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "Waking %5d: %s.\n", p->pid, p->comm); + p->flags &= ~PF_FROZEN; + wake_up_process(p); + } + } FOR_EACH_THREAD_END + + read_unlock(&tasklist_lock); + + swsusp_state = 0; + + while (atomic_read(&swsusp_cpu_counter)) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } +} + +/* + * The following code is based on the reboot notifier code in kernel/sys.c. + * It is used to notify drivers when a suspend cycle finishes, so that + * timers which have been stopped can be restarted. + */ + + +/* + * Notifier list for kernel code which wants to be called + * at resume. This is used to restart timers which were + * stopped during suspend. + */ + +static struct notifier_block *resume_notifier_list; +rwlock_t swsusp_notifier_lock = RW_LOCK_UNLOCKED; + +/** + * register_resume_notifier - Register function to be called at resume time + * @nb: Info about notifier function to be called + * + * Registers a function with the list of functions + * to be called at resume time. + * + * Currently always returns zero, as notifier_chain_register + * always returns zero. + */ + +int register_resume_notifier(struct notifier_block * nb) +{ + return notifier_chain_register(&resume_notifier_list, nb); +} + +/** + * unregister_resume_notifier - Unregister previously registered resume notifier + * @nb: Hook to be unregistered + * + * Unregisters a previously registered resume + * notifier function. + * + * Returns zero on success, or %-ENOENT on failure. + */ + +int unregister_resume_notifier(struct notifier_block * nb) +{ + return notifier_chain_unregister(&resume_notifier_list, nb); +} + +inline int notify_resume(void) +{ + return notifier_call_chain(&resume_notifier_list, 0, NULL); +} + +EXPORT_SYMBOL(__swsusp_activity_start); +EXPORT_SYMBOL(swsusp_num_active); +EXPORT_SYMBOL(swsusp_state); +EXPORT_SYMBOL(idletimeout); +EXPORT_SYMBOL(refrigerator); +EXPORT_SYMBOL(suspend_task); +EXPORT_SYMBOL(swsusp_action); +EXPORT_SYMBOL(software_suspend_state); diff -ruN post-version-specific/kernel/power/range.c software-suspend-core-2.0/kernel/power/range.c --- post-version-specific/kernel/power/range.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/range.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,845 @@ +/* pm_disk routines for manipulating ranges. + * + * (C) 2003, Nigel Cunningham. + * + * These encapsulate the manipulation of ranges. I learnt after writing this + * code that ranges are more commonly called extents. They work like this: + * + * A lot of the data that pm_disk saves involves continguous ranges of memory + * or storage. Let's say that we're storing data on disk in blocks 1-32768 and + * 49152-49848 of a swap partition. Rather than recording 1, 2, 3... in arrays + * pointing to the locations, we simply use: + * + * struct range { + * unsigned long min; + * unsigned long max; + * struct range * next; + * } + * + * We can then store 1-32768 and 49152-49848 in 2 struct ranges, using 24 bytes + * instead of something like 133,860. This is of course inefficient where a range + * covers only one or two values, but the benefits gained by the much larger + * ranges more than outweight these instances. + * + * Whole pages are allocated to store ranges, with unused structs being chained + * together and linked into an unused_ranges list: + * + * struct range * unused_ranges; (just below). + * + * We can fit 341 ranges in a 4096 byte page (rangepage), with 4 bytes left over. + * These four bytes, referred to as the RangePageLink, are used to link the pages + * together. The RangePageLink is a pointer to the next page, or'd with the index + * number of the page. + * + * (The following will apply once the range page code is merged and the data is + * switched over to being stored in ranges)... + * + * RangePages are stored in the header of the suspend image. For portability + * between suspend time and resume time, we 'relativise' the contents of each page + * before writing them to disk. That is, each .next and each RangePageLink is + * changed to point not to an absolute location, but to the relative location in + * the list of pages. This makes all the information valid and usable (after it + * has been absolutised again, of course) regardless of where it is reloaded to + * at resume time. + */ + +#include + +/* Abbreviations for debugging */ +#define RANGE_VERBOSE SUSPEND_RANGES, SUSPEND_VERBOSE +#define RANGE_HIGH SUSPEND_RANGES, SUSPEND_HIGH + +struct range * unused_ranges = NULL; +int nr_unused_ranges = 0; +int max_ranges_used = 0; +int num_range_pages = 0; +static unsigned long ranges_allocated = 0; +struct range * first_range_page = NULL, * last_range_page = NULL; + +/* Add_range_page + * Allocates and initialises new pages for storing ranges. + * Returns 1 on failure to get a page. + * Otherwise adds the new pages to the unused_ranges pool + * and returns 0. + * During resuming, it ensures the page added doesn't + * collide with memory that will be overwritten when + * copying the original kernel back. + */ + +static int add_range_pages(int number_requested) +{ + int i, j; + struct range * ranges; + unsigned long next; + void **eaten_memory = NULL, **c = NULL, *f; + + for (j = 0; j < number_requested; j++) { + if (now_resuming) { + struct page * pageaddr; + /* Make sure page doesn't collide when we're resuming */ + while ((next = get_zeroed_page(GFP_ATOMIC))) { + pageaddr = virt_to_page(next); + if (!PageInUse(pageaddr)) + break; + eaten_memory = (void *) next; + *eaten_memory = c; + c = eaten_memory; + } + // Free unwanted memory + c = eaten_memory; + while(c) { + f = c; + c = *c; + if (f) + free_page((unsigned long) f); + } + eaten_memory = NULL; + } else { + next = get_zeroed_page(GFP_ATOMIC); + if (!next) + next = get_grabbed_pages(0); + } + + + if (!next) { + printk("Failed to allocate a new range page.\n"); + return 1; + } + + num_range_pages++; + printnolog(SUSPEND_RANGES, SUSPEND_LOW, 0, "Got a new range page (#%d) at %lx\n", + num_range_pages, next); + if (!first_range_page) + first_range_page = (struct range *) next; + if (last_range_page) + *RANGEPAGELINK(last_range_page) |= next; + *RANGEPAGELINK(next) = num_range_pages; + last_range_page = (struct range *) next; + ranges = (struct range *) next; + for (i = 0; i < RANGES_PER_PAGE; i++) + (ranges+i)->next = (ranges+i+1); + (ranges + i - 1)->next = unused_ranges; + unused_ranges = ranges; + nr_unused_ranges += i; + if (console_loglevel > 5) { + for (i = 0; i < RANGES_PER_PAGE; i++) + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Ranges %d = %p. Next = %p\n", + i, + (ranges + i), + (ranges + i)->next); + } + } + return 0; +} + + +/* + * Free ranges. + * + * Frees pages allocated by add_range_pages() + * + * Checks that all ranges allocated have been freed and aborts + * if this is not true. + * + * Ranges may not be in memory order but we don't + * mind. We just look for a range that is on a + * page boundary. That gives us the pages to be + * freed. As we find them, we link them together + * into a new chain (we're not going to use the + * other ranges anyway) and then free the chain. + */ + +int free_ranges(void) +{ + int i; + struct range * this_range_page = first_range_page, * next_range_page = NULL; + + if (ranges_allocated) + printk(" *** Warning: %ld ranges still allocated when free_ranges() called.\n", ranges_allocated); + + for (i = 0; i < num_range_pages; i++) { + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Freeing range page %p.\n", this_range_page); + next_range_page = (struct range *) (((unsigned long) (*RANGEPAGELINK(this_range_page))) & PAGE_MASK); + free_pages((unsigned long) this_range_page, 0); + this_range_page = next_range_page; + } + + nr_unused_ranges = num_range_pages = ranges_allocated = 0; + unused_ranges = last_range_page = first_range_page = NULL; + + return 0; +} + +/* get_range + * + * Returns a free range, having removed it from the + * unused list and having incremented the usage counter. + * May imply allocating a new page and may therefore + * fail, returning NULL instead. + * + * No locking. This is because we are only called + * from swsusp, which is single threaded + */ + +static struct range * get_range(void) +{ + struct range * result; + + if ((!unused_ranges) && (add_range_pages(1))) + return NULL; + + result = unused_ranges; + unused_ranges = unused_ranges->next; + nr_unused_ranges--; + ranges_allocated++; + if (ranges_allocated > max_ranges_used) + max_ranges_used++; + result->minimum = result->maximum = 0; + result->next = NULL; + return result; +} + +/* + * put_range. + * + * Returns a range to the pool of unused pages and + * decrements the usage counter. + * + * Assumes unlinking is done by the caller. + */ +void put_range(struct range * range) +{ + if (!range) { + printk("Error! put_range called with NULL range.\n"); + return; + } + range->minimum = range->maximum = 0; + range->next = unused_ranges; + unused_ranges = range; + ranges_allocated--; + nr_unused_ranges++; +} + +/* + * put_range_chain. + * + * Returns a whole chain of ranges to the + * unused pool. + */ +void put_range_chain(struct rangechain * chain) +{ + int count = 0; + struct range * this; + + printnolog(RANGE_HIGH, 0, "\n"); + + if (chain->first) { + this = chain->first; + while (this) { + this->minimum = this->maximum = 0; + this=this->next; + } + chain->last->next = unused_ranges; + unused_ranges = chain->first; + chain->first = NULL; + count = chain->allocs - chain->frees; + ranges_allocated -= count; + nr_unused_ranges += count; + + printnolog(SUSPEND_RANGES, SUSPEND_MEDIUM, 0, "Emptied %s. For this chain, allocated %d and freed %d. Used optimisation %d times.\n", + chain->name, + chain->allocs, + chain->frees, + chain->timesusedoptimisation); + chain->allocs = 0; + chain->frees = 0; + chain->size = 0; + chain->timesusedoptimisation = 0; + chain->lastaccessed = NULL; /* Invalidate optimisation info */ + chain->last = NULL; + } +} + +/* printmethod: + * 0: integer + * 1: hex + * 2: page number + */ +void print_chain(int debuglevel, struct rangechain * chain, int printmethod) +{ + struct range * this = chain->first; + int count = 0, size = 0; + + if ((console_loglevel < debuglevel) || (!this) || (!TEST_DEBUG_STATE(SUSPEND_RANGES))) + return; + + if (!chain->name) + printnolog(RANGE_VERBOSE, 0,"Chain %p\n", chain); + else + printnolog(RANGE_VERBOSE, 0,"%s\n", chain->name); + + while (this) { + /* 'This' is printed separately so it is displayed if an oops results */ + switch (printmethod) { + case 0: + printnolog(RANGE_VERBOSE, 0,"(%p) ", + this); + printnolog(RANGE_VERBOSE, 0,"%lx-%lx; ", + this->minimum, this->maximum); + break; + case 1: + printnolog(RANGE_VERBOSE, 0,"(%p)", + this); + printnolog(RANGE_VERBOSE, 0,"%lu-%lu; ", + this->minimum, this->maximum); + break; + case 2: + printnolog(RANGE_VERBOSE, 0,"(%p)", + this); + printnolog(RANGE_VERBOSE, 0,"%p-%p; ", + page_address(mem_map+this->minimum), + page_address(mem_map+this->maximum) + PAGE_SIZE - 1); + break; + } + size+= this->maximum - this->minimum + 1; + this = this->next; + count++; + if (!(count%4)) + printnolog(RANGE_VERBOSE, 0,"\n"); + } + + if ((count%4)) + printnolog(RANGE_VERBOSE, 0,"\n"); + + printnolog(RANGE_VERBOSE, 0,"%d entries/%ld allocated. Allocated %d and freed %d. Size %d.", + count, + ranges_allocated, + chain->allocs, + chain->frees, + size); + if (count != (chain->allocs - chain->frees)) { + chain->debug = 1; + check_shift_keys(1, "Discrepancy in chain."); + } + printnolog(RANGE_VERBOSE, 0,"\n"); +} + +static void check_chain(struct rangechain * chain, char * failuremessage) +{ + struct range * this = chain->first; + int count = 0; + + while (this) { + count++; + this = this->next; + } + + if (count != (chain->allocs - chain->frees)) { + printk("Descrepancy in chain found %s.\n", failuremessage); + print_chain(SUSPEND_ERROR, chain, 2); + } +} + +/* + * add_to_range_chain. + * + * Takes a value to be stored and a pointer + * to a chain and adds the value to the range + * chain, merging with an existing range or + * adding a new entry as necessary. Ranges + * are stored in increasing order. + * + * Values should be consecutive, and so may + * need to be transformed first. (eg for + * pages, would want to call with page-mem_map). + * + * Important optimisation: + * We store in the chain info the location of + * the last range accessed or added (and its + * previous). If the next value is outside this + * range by one, we start from the previous + * entry instead of the start of the chain. + * In cases of heavy fragmentation, this saves + * a lot of time searching. + * + * Returns: + * 0 if successful + * 1 if the value is already included. + * 2 if unable to allocate memory. + * 3 if fall out bottom (shouldn't happen). + */ + +int add_to_range_chain(struct rangechain * chain, unsigned long value) +{ + struct range * this, * prev = NULL, * prevtoprev = NULL; + int usedoptimisation = 0; + + printnolog(RANGE_HIGH, 0, "\n"); + + if (!chain->first) { /* Empty */ + chain->last = chain->first = get_range(); + if (!chain->first) { + printk("Error unable to allocate the first range for the chain.\n"); + return 2; + } + chain->allocs++; + chain->first->maximum = value; + chain->first->minimum = value; + printnolog(RANGE_HIGH, 0, "%s: Added initial entry %p to chain.\n", + chain->name, + chain->first); + print_chain(SUSPEND_VERBOSE, chain, 0); + chain->size++; + return 0; + } + + this = chain->first; + + if (chain->lastaccessed && chain->prevtolastaccessed && chain->prevtoprev) { + if ((value + 1) == chain->lastaccessed->minimum) { + prev = chain->prevtoprev; + this = chain->prevtolastaccessed; + usedoptimisation = 1; + } else if (((value - 1) == chain->lastaccessed->maximum)) { + prev = chain->prevtolastaccessed; + this = chain->lastaccessed; + usedoptimisation = 1; + } + + /* prev wrong but doesn't matter */ + if (usedoptimisation) { + printnolog(RANGE_HIGH, 0, + "Adding %ld, using last accessed: (%ld-%ld)(%ld-%ld)\n", + value, + prev->minimum, + prev->maximum, + this->minimum, + this->maximum); + chain->timesusedoptimisation++; + } + } + + while (this) { + /* Need new entry prior to this? */ + if ((value + 1) < this->minimum) { + struct range * new = get_range(); + if (!new) { + printk("Error unable to insert a new range for the chain.\n"); + return 2; + } + chain->allocs++; + new->minimum = value; + new->maximum = value; + new->next = this; + /* Prior to start of chain? */ + if (!prev) + chain->first = new; + else + prev->next = new; + printnolog(RANGE_HIGH, 0, + "%s: Adding new range %p prior to existing range.\n", + chain->name, + new); + print_chain(SUSPEND_VERBOSE, chain, 0); + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = new; + } + if (TEST_ACTION_STATE(SUSPEND_RANGE_PARANOIA)) { + if (usedoptimisation) + check_chain(chain, "after adding new range prior to existing range with optimisation.\n"); + else + check_chain(chain, "after adding new range prior to existing range.\n"); + } + chain->size++; + return 0; + } + + if ((this->minimum <= value) && (this->maximum >= value)) { + if (chain->name) + printk("%s:", chain->name); + else + printk("%p:", chain); + printk("Trying to add a value (%ld/0x%lx) already included in chain.\n", + value, value); + print_chain(SUSPEND_ERROR, chain, 0); + check_shift_keys(1, NULL); + return 1; + } + if ((value + 1) == this->minimum) { + this->minimum = value; + print_chain(SUSPEND_VERBOSE, chain, 0); + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = this; + } + if (TEST_ACTION_STATE(SUSPEND_RANGE_PARANOIA)) { + if (usedoptimisation) + check_chain(chain, "after expanding existing range downwards with optimisation.\n"); + else + check_chain(chain, "after expanding existing range downwards..\n"); + } + chain->size++; + return 0; + } + if ((value - 1) == this->maximum) { + if ((this->next) && (this->next->minimum == value + 1)) { + struct range * oldnext = this->next; + this->maximum = this->next->maximum; + this->next = this->next->next; + if ((chain->last) == oldnext) + chain->last = this; + put_range(oldnext); + chain->lastaccessed = NULL; /* Invalidate optimisation info */ + chain->frees++; + printnolog(SUSPEND_RANGES, + SUSPEND_HIGH, + 0, + "%s: Putting merged range %p.\n", + chain->name, + oldnext); + print_chain(SUSPEND_VERBOSE, chain, 0); + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = this; + } + if (TEST_ACTION_STATE(SUSPEND_RANGE_PARANOIA)) { + if (usedoptimisation) + check_chain(chain, "after merging this and next ranges with optimisation.\n"); + else + check_chain(chain, "after merging this and next ranges.\n"); + } + chain->size++; + return 0; + } + this->maximum = value; + print_chain(SUSPEND_VERBOSE, chain, 0); + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = this; + } + if (TEST_ACTION_STATE(SUSPEND_RANGE_PARANOIA)) { + if (usedoptimisation) + check_chain(chain, "after expanding a range upwards with optimisation.\n"); + else + check_chain(chain, "after expanding a range upwards.\n"); + } + chain->size++; + return 0; + } + if (!this->next) { + struct range * new = get_range(); + if (!new) { + printk("Error unable to append a new range to the chain.\n"); + return 2; + } + chain->allocs++; + new->minimum = value; + new->maximum = value; + new->next = NULL; + this->next = new; + chain->last = new; + printnolog(SUSPEND_RANGES, + SUSPEND_HIGH, + 0, + "%s: Got new range %p for end of chain.\n", + chain->name, + new); + print_chain(SUSPEND_VERBOSE, chain, 0); + if (!usedoptimisation) { + chain->prevtoprev = prev; + chain->prevtolastaccessed = this; + chain->lastaccessed = new; + } + if (TEST_ACTION_STATE(SUSPEND_RANGE_PARANOIA)) { + if (usedoptimisation) + check_chain(chain, "after appending a new range with optimisation.\n"); + else + check_chain(chain, "after appending a new range.\n"); + } + chain->size++; + return 0; + } + prevtoprev = prev; + prev = this; + this = this->next; + } + printk("\nFell out the bottom of add_to_range_chain. This shouldn't happen!\n"); + SET_RESULT_STATE(SUSPEND_ABORTED); + return 3; +} + +/* append_range + * Used where we know a range is to be added to the end of the list + * and does not need merging with the current last range. + * (count_data_pages only at the moment) + */ + +int append_range_to_range_chain(struct rangechain * chain, unsigned long minimum, unsigned long maximum) +{ + struct range * newrange = NULL; + + newrange = get_range(); + if (!newrange) { + printk("Error unable to append a new range to the chain.\n"); + return 2; + } + + chain->allocs++; + chain->size+= (maximum - minimum + 1); + newrange->minimum = minimum; + newrange->maximum = maximum; + newrange->next = NULL; + + if (chain->last) { + chain->last->next = newrange; + chain->last = newrange; + } else + chain->last = chain->first = newrange; + + printnolog(SUSPEND_RANGES, + SUSPEND_HIGH, + 0, + "%s: Got new range %p for end of chain.\n", + chain->name, + newrange); + print_chain(SUSPEND_VERBOSE, chain, 0); + /* No need to reset optimisation info since added to end */ + return 0; +} + +int append_to_range_chain(int chain, unsigned long min, unsigned long max) +{ + int result = 0; + + switch (chain) { + case 0: + return 0; + case 1: + result = append_range_to_range_chain(&pagedir1.origranges, min, max); + break; + case 2: + result = append_range_to_range_chain(&pagedir2.origranges, min, max); + if (!result) + result = append_range_to_range_chain(&pagedir1.destranges, min, max); + } + check_shift_keys(0, NULL); + return result; +} + +/* + * Prepare rangesets for save by translating addresses to relative indices. + */ +void relativise_ranges(void) +{ + struct range * this_range_page = first_range_page; + int i; + + while (this_range_page) { + struct range * this_range = this_range_page; + for (i = 0; i < RANGES_PER_PAGE; i++) { + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Setting pointer for range %p ", + this_range); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Orig: %p ", + this_range->next); + if (this_range->next) { + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "New: %lx. ", + (((unsigned long) this_range->next) & (PAGE_SIZE - 1)) | + ((*RANGEPAGELINK(this_range->next) & (PAGE_SIZE - 1)) << PAGE_SHIFT)); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "[%lx] %lx | %lx. ", + *RANGEPAGELINK(this_range->next), + (((unsigned long) this_range->next) & (PAGE_SIZE - 1)), + (((*RANGEPAGELINK(this_range->next)) & (PAGE_SIZE - 1)) << PAGE_SHIFT)); + } + + if (this_range->next) + this_range->next = RANGE_RELATIVE(this_range->next); + + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, " Value now %p.\n", this_range->next); + this_range++; + } + this_range_page = (struct range *) ((*RANGEPAGELINK(this_range_page)) & PAGE_MASK); + } + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "\n"); +} + +/* Convert ->next pointers for ranges back to absolute values. + * The issue is finding out what page the absolute value is now at. + * If we use an array of values, we gain speed, but then we need to + * be able to allocate contiguous pages. Fortunately, this is done + * prior to loading pagesets, so we can just allocate the pages + * needed, set up our array and use it and then discard the data + * before we exit. + */ + +void absolutise_ranges(unsigned long * range_pages) +{ + struct range * this_range_page = first_range_page; + int i; + + this_range_page = first_range_page; + while (this_range_page) { + struct range * this_range = this_range_page; + for (i = 0; i < RANGES_PER_PAGE; i++) { + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Setting range %p ", + this_range); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Orig: %p ", + this_range->next); + if (this_range->next) { + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "New: %p. ", + RANGE_ABSOLUTE(this_range->next, range_pages)); + } + + if (this_range->next) + this_range->next = RANGE_ABSOLUTE(this_range->next, range_pages); + + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, " Value is now %p.\n", this_range->next); + this_range++; + } + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "RangePagelink:"); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "%lx\n", *RANGEPAGELINK(this_range_page)); + this_range_page = (struct range *) ((*RANGEPAGELINK(this_range_page)) & PAGE_MASK); + } +} + +void absolutise_chain(struct rangechain * chain, unsigned long * range_pages) +{ + if (chain->first) + chain->first = RANGE_ABSOLUTE(chain->first, range_pages); + if (chain->last) + chain->last = RANGE_ABSOLUTE(chain->last, range_pages); + if (chain->lastaccessed) + chain->lastaccessed = RANGE_ABSOLUTE(chain->lastaccessed, range_pages); + if (chain->prevtolastaccessed) + chain->prevtolastaccessed = RANGE_ABSOLUTE(chain->prevtolastaccessed, range_pages); + if (chain->prevtoprev) + chain->prevtoprev = RANGE_ABSOLUTE(chain->prevtoprev, range_pages); +} + +void relativise_chain(struct rangechain * chain) +{ + if (chain->first) + chain->first = RANGE_RELATIVE(chain->first); + if (chain->last) + chain->last = RANGE_RELATIVE(chain->last); + if (chain->lastaccessed) + chain->lastaccessed = RANGE_RELATIVE(chain->lastaccessed); + if (chain->prevtolastaccessed) + chain->prevtolastaccessed = RANGE_RELATIVE(chain->prevtolastaccessed); + if (chain->prevtoprev) + chain->prevtoprev = RANGE_RELATIVE(chain->prevtoprev); +} + +#define POINTERS_PER_PAGE (PAGE_SIZE / sizeof(void *)) +static int rangepages_list_order = -1; +unsigned long * get_rangepages_list(void) +{ + struct range * this_range_page = first_range_page; + int i, pages_needed, pool_on = (swsusp_state & USE_MEMORY_POOL); + unsigned long * range_pages; + + pages_needed = ((num_range_pages + POINTERS_PER_PAGE - 1) / POINTERS_PER_PAGE); + printnolog(SUSPEND_RANGES, SUSPEND_LOW, 0, "Need %d pages for %d range pages.\n", pages_needed, num_range_pages); + swsusp_state &= ~USE_MEMORY_POOL; + rangepages_list_order = get_bitmask_order(pages_needed - 1); + range_pages = (unsigned long *) __get_free_pages(GFP_ATOMIC, rangepages_list_order); + swsusp_state |= pool_on; + if (!range_pages) + range_pages = (unsigned long *) get_grabbed_pages(get_bitmask_order(pages_needed - 1)); + if (!range_pages) + return NULL; + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Range pages is %p.\n", range_pages); + + for (i = 1; i <= num_range_pages; i++) { + range_pages[i] = (unsigned long) this_range_page; + + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "This range page is %p.", this_range_page); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "Links to %lx.", *(RANGEPAGELINK(this_range_page))); + this_range_page = (struct range *) (((unsigned long) (*RANGEPAGELINK(this_range_page))) & PAGE_MASK); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, "This range page is now %p.\n", this_range_page); + } + + return range_pages; +} + +void put_rangepages_list(unsigned long * range_pages) +{ + int pool_on = (swsusp_state & USE_MEMORY_POOL); + + swsusp_state &= ~USE_MEMORY_POOL; + free_pages((unsigned long) range_pages, rangepages_list_order); + swsusp_state |= pool_on; + rangepages_list_order = -1; +} + +int relocate_rangepages(unsigned long * range_pages) +{ + void **eaten_memory = NULL; + void **c = eaten_memory, *m = NULL, *f; + int oom = 0, i, numeaten = 0; + + printlog(SUSPEND_PAGESETS, SUSPEND_VERBOSE,"Relocating conflicting parts of range pages.\n"); + + for (i = 1; i <= num_range_pages; i++) { + int this_collides = 0; + + this_collides = PageInUse(virt_to_page(range_pages[i])); + + if (this_collides) { + printlog(SUSPEND_PAGESETS, SUSPEND_VERBOSE,"\nRelocating range page %d.\n", i); + while ((m = (void *) get_zeroed_page(GFP_ATOMIC))) { + memset(m, 0, PAGE_SIZE); + if (!PageInUse(virt_to_page(m))) { + copy_page(m, (void *) range_pages[i]); + free_page(range_pages[i]); + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, "\nRelocated page %d from %lx to %p.\n", + i, + range_pages[i], + m); + if (i == 1) + first_range_page = m; + else + *RANGEPAGELINK(range_pages[i-1]) = (i | (unsigned long) m); + range_pages[i] = (unsigned long) m; + break; + } + numeaten++; + eaten_memory = m; + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, "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 rangepages (tried %d pages).\n", numeaten); + oom = 1; + break; + } + } + } + + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, "\nFreeing rejected memory locations...\n"); + + c = eaten_memory; + while(c) { + f = c; + c = *c; + if (f) + free_pages((unsigned long) f, 0); + } + eaten_memory = NULL; + + printlog(SUSPEND_PAGESETS, SUSPEND_VERBOSE,"\n"); + + check_shift_keys(1, "Pagedir relocated. "); + + if (oom) + return -ENOMEM; + else + return 0; +} + diff -ruN post-version-specific/kernel/power/swapwriter.c software-suspend-core-2.0/kernel/power/swapwriter.c --- post-version-specific/kernel/power/swapwriter.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/swapwriter.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,2548 @@ +/* + * Swapwriter.c + * + * Copyright 2003 Nigel Cunningham + * + * Distributed under GPLv2. + * + * This file encapsulates functions for usage of swap space as a + * backing store. + * + * It currently contains the 2.4 and 2.5 versions in one file. This is + * only TEMPORARY, as a means of not duplicating work while the big + * cleanup is being done. + * + * Table of contents: + * - Forward declarations of functions and structures + * - Internal data structure declarations with helpers + * - Internal variable declarations + * - Higher level functions + * a) Lowlevel I/O + * b) Swap allocation + * - Exported functions + * - Ops declaration + */ + +#include +struct swsusp_plugin_ops swapwriterops; + +#define SIGNATURE_VER 6 + +/* ---------------------- 2.6 SPECIFIC --------------- */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +#include +#include +#include + +#define DEVICE_ID_TYPE dev_t +#define DEVICE_BLOCK_TYPE struct block_device * +#define DEVICE_BLOCK_NONE NULL +#define SYS_IO_STRUCT struct bio +#define SYS_ZONE_TYPE long +#define SIG_BDEV_START 2 +#define SWP_OFFSET swp_offset +#define SWP_TYPE swp_type +#define SWP_ENTRY swp_entry +#define UNUSED_SWAP_ENTRY(i) (!swap_info[i].swap_file) +#define SWAP_FILE_INODE(i) swap_info[i].swap_file->f_dentry->d_inode +#define SWAP_IS_PARTITION(i) (S_ISBLK(SWAP_FILE_INODE(i)->i_mode)) +#define SWAP_DEVICE_ID(i) (SWAP_FILE_INODE(i)->i_rdev) +#define SWAP_DEVICE_BDEV(i) (swap_info[i].bdev) +#define BDEV_TO_DEVICE_ID(bdev) ((bdev)->bd_dev) +#define SWAP_BLOCKSIZE(bdev) (block_size(bdev)) +#define RESUME_BDEV(i) swap_info[i].bdev +#define RUN_IO do { blk_run_queues(); io_schedule(); } while(0) +extern asmlinkage long sys_sync(void); +#define SYNC_IO do { RUN_IO; sys_sync(); schedule(); check_shift_keys(0, NULL); } while(0) + +/* ---------------------- 2.4 SPECIFIC --------------- */ +#else +#include +#include +#include + +#define DEVICE_ID_TYPE kdev_t +#define DEVICE_BLOCK_TYPE kdev_t +#define DEVICE_BLOCK_NONE 0 +#define SYS_IO_STRUCT struct buffer_head +#define SYS_ZONE_TYPE int +#define SIG_BDEV_START 3 +#define UNUSED_SWAP_ENTRY(i) ((!swap_info[i].swap_device) && (!swap_info[i].swap_file)) +#define SWAP_IS_PARTITION(i) (swap_info[i].swap_device) +#define SWAP_PARTITION_BDEV(i) swap_info[i].swap_device +#define SWAP_FILE_INODE(i) swap_info[i].swap_file->d_inode +#define SWAP_FILE_BDEV(i) SWAP_FILE_INODE(i)->i_dev +#define SWAP_DEVICE_ID(i) (SWAP_IS_PARTITION(i) ? SWAP_PARTITION_BDEV(i) : SWAP_FILE_BDEV(i)) +#define SWAP_DEVICE_BDEV(i) SWAP_DEVICE_ID(i) +#define BDEV_TO_DEVICE_ID(bdev) (bdev) +#define SWAP_BLOCKSIZE(bdev) (blksize_size[MAJOR(bdev)][MINOR(bdev)]) +#define SWAP_FILE_BLOCKSIZE(i) SWAP_FILE_INODE(i)->i_sb->s_blocksize +#define SWAP_DESCRIPTION(i) (SWAP_IS_PARTITION(i) ? SWAP_PARTITION_BDEV(i) : SWAP_FILE_BDEV(i)) +#define RESUME_BDEV(i) swap_info[i].swap_device +#define RUN_IO do { run_task_queue(&tq_disk); } while(0) +static void sync_swap_partitions(void); +#define SYNC_IO do { sync_swap_partitions(); do_suspend_sync(); } while(0) + +#endif + +/* --------- END OF VERSION SPECIFIC DECLARES --------- */ + +// Forward Declarations + +struct io_info; +struct submit_params; + +/* --- Struct of pages stored on disk */ + +struct swaplink { + char dummy[PAGE_SIZE - sizeof(swp_entry_t)]; + swp_entry_t next; +}; + +union diskpage { + union swap_header swh; /* swh.magic is the only member used */ + struct swaplink link; + struct suspend_header sh; +}; + +union p_diskpage { + union diskpage *pointer; + char *ptr; + unsigned long address; +}; + +#define SIGNATURE_LENGTH 10 + +// Lowlevel I/O functions +// - Get/set block size. +static inline int swsusp_get_block_size(DEVICE_BLOCK_TYPE bdev); +static inline int swsusp_set_block_size(DEVICE_BLOCK_TYPE bdev, int size); + +// - Get blocks for a swap entry. +static int get_phys_params(swp_entry_t entry); + +// - Get blocks for a header page (can't use chains). +static int get_header_params(struct submit_params * headerpage); + +// - Manage structures that store info on in-progress I/O. +static struct io_info * get_io_info_struct(void); + +// - Manage statistics for I/O structures. +void __inline reset_io_stats(void); +void check_io_stats(void); + +// - Submit & cleanup I/O. +static struct io_info * start_one(int rw, struct submit_params * submit_info); +static void cleanup_one(struct io_info * io_info); +static inline void cleanup_completed_io(void); +static void finish_all_io(void); + +// - Manage swap signature. +static int prepare_signature(struct submit_params * first_header_page, char * current_header); +static int parse_signature(char * signature, int restore); + +// Higher Level + +// - Synchronous (async + wait on completion) +static struct io_info * do_swsusp_io(int rw, struct submit_params * submit_info, int syncio); +static void bdev_page_io(int rw, DEVICE_BLOCK_TYPE bdev, long pos, struct page * page); + +/* Don't set MAX_READAHEAD > 31. We're only using an unsigned long */ +#define MAX_READAHEAD 31 +static struct page * readahead_page[MAX_READAHEAD]; +static int readahead_index = -1; +static unsigned long readahead_flags = 0; + +/* + * --------------------------------------------------------------- + * + * Internal Data Structures + * + * --------------------------------------------------------------- + */ + +/* header_data contains data that is needed to reload pagedir1, and + * is therefore saved in the suspend header. + * + * Pagedir2 swap comes before pagedir1 swap (save order), and the first swap + * entry for pagedir1 to use is set when pagedir2 is written (when we know how + * much swap it used). Since this first entry is almost certainly not at the + * start of a range, the firstoffset variable below tells us where to start in + * the range. All of this means we don't have to worry about getting different + * compression ratios for the kernel and cache (when compressing the image). + * We can simply allocate one pool of swap (size determined using expected + * compression ratio) and use it without worrying whether one pageset + * compresses better and the other worse (this is what happens). As long as the + * user gets the expected compression right, it will work. + */ + +struct { + /* Range chains for swap & blocks */ + struct rangechain swapranges; + struct rangechain block_chain[MAX_SWAPFILES]; + + /* Location of start of pagedir 1 */ + struct range * pd1start_block_range; + unsigned long pd1start_block_offset; + int pd1start_chain; + + /* Devices used for swap */ + DEVICE_ID_TYPE swapdevs[MAX_SWAPFILES]; + char blocksizes[MAX_SWAPFILES]; + + /* Asynchronous I/O limit */ + int max_async_ios; +} header_data; + + +/* + * --------------------------------------------------------------- + * + * IO in progress information storage and helpers + * + * --------------------------------------------------------------- + */ + +struct io_info { + SYS_IO_STRUCT * sys_struct; + SYS_ZONE_TYPE blocks[PAGE_SIZE/512]; + struct page * cache_page; + struct page * buffer_page; + struct page * data_page; + unsigned long flags; + DEVICE_BLOCK_TYPE dev; + int blocks_used; + int block_size; + struct list_head list; + int readahead_index; +}; + +/* [Max] number of pages used for above struct */ +static int infopages = 0; +static int maxinfopages = 0; + +/* [Max] number of I/O operations pending */ +static int outstanding_io = 0; +static int max_outstanding_io = 0; + +DEVICE_ID_TYPE header_device = 0; +DEVICE_BLOCK_TYPE header_block_device = DEVICE_BLOCK_NONE; +struct range * this_range_page = NULL, * next_range_page = NULL; +int headerblocksize = PAGE_SIZE; +int headerblock; + +/* Bits in struct io_info->flags */ +#define IO_WRITING 1 +#define IO_RESTORE_PAGE_PROT 2 +#define IO_AWAITING_READ 3 +#define IO_AWAITING_WRITE 4 +#define IO_CLEANUP_IN_PROGRESS 5 +#define IO_HANDLE_PAGE_PROT 6 + +static LIST_HEAD(ioinfo_free); +static LIST_HEAD(ioinfo_ready_for_cleanup); +static LIST_HEAD(ioinfo_busy); +static spinlock_t ioinfo_lists_lock = SPIN_LOCK_UNLOCKED; +static spinlock_t readahead_flags_lock = SPIN_LOCK_UNLOCKED; + +static char swapfilename[256] = ""; /* For swapfile automatically swapon/off'd. */ +extern asmlinkage long sys_swapon(const char * specialfile, int swap_flags); + +/* Must be silent - might be called from cat /proc/swsusp/debug_info + * Returns 0 if was off, -EBUSY if was on, error value otherwise. + */ +static int enable_swapfile(void) +{ + int activateswapresult = -EINVAL; + + if (swapfilename[0]) { + /* Attempt to swap on with maximum priority */ + activateswapresult = sys_swapon(swapfilename, 0xFFFF); + if (activateswapresult && (activateswapresult != -EBUSY)) + printk(name_suspend "The swapfile/partition specified by /proc/swsusp/swapfile (%s) could not be turned on (error %d). Attempting to continue.\n", + swapfilename, activateswapresult); + } + return activateswapresult; +} + +extern asmlinkage long sys_swapoff(const char * specialfile); +/* Returns 0 if was on, -EINVAL if was off, error value otherwise */ +static int disable_swapfile(void) +{ + int result = -EINVAL; + + if (swapfilename[0]) { + result = sys_swapoff(swapfilename); + if (result == -EINVAL) + return 0; /* Wasn't on */ + } + + return result; +} + +static int manage_swapfile(int enable) +{ + static int result; + + if (enable) + result = enable_swapfile(); + else + result = disable_swapfile(); + + return result; +} +/* Returns pointer to page which was cleaned up on success, -ENODATA if none ready for cleanup */ +static struct io_info * ioinfo_cleanup_one(void) +{ + struct io_info * this; + int readahead_index; + unsigned long flags; + + if (list_empty(&ioinfo_ready_for_cleanup)) + return ERR_PTR(-ENODATA); + + spin_lock_irqsave(&ioinfo_lists_lock, flags); + this = list_entry(ioinfo_ready_for_cleanup.next, struct io_info, list); + list_del(&this->list); + spin_unlock_irqrestore(&ioinfo_lists_lock, flags); + + readahead_index = this->readahead_index; + this->readahead_index = 0; + + cleanup_one(this); + + if (readahead_index) { + spin_lock_irqsave(&readahead_flags_lock, flags); + set_bit(readahead_index - 1, &readahead_flags); + spin_unlock_irqrestore(&readahead_flags_lock, flags); + } + + spin_lock_irqsave(&ioinfo_lists_lock, flags); + list_add_tail(&this->list, &ioinfo_free); + spin_unlock_irqrestore(&ioinfo_lists_lock, flags); + return this; +} + +static struct io_info * get_io_info_struct(void) +{ + unsigned long newpage = 0, flags; + struct io_info * this = NULL; + int iteration = 0, remaining = 0; + + do { + if (!(iteration%8)) + RUN_IO; + iteration++; + + /* We clean up an IO info struct if possible */ + ioinfo_cleanup_one(); + + if ((max_async_ios) && (outstanding_io >= max_async_ios)) + continue; + + if (!list_empty(&ioinfo_free)) { + spin_lock_irqsave(&ioinfo_lists_lock, flags); + break; + } + + /* Need to allocate a new page */ + newpage = get_zeroed_page(GFP_ATOMIC); + if (!newpage) + continue; + + printlog(SUSPEND_MEMORY, SUSPEND_VERBOSE, "[NewIOPage %lx]", newpage); + infopages++; + if (infopages > maxinfopages) + maxinfopages++; + this = (struct io_info *) newpage; + remaining = PAGE_SIZE; + spin_lock_irqsave(&ioinfo_lists_lock, flags); + while (remaining >= (sizeof(struct io_info))) { + list_add_tail(&this->list, &ioinfo_free); + this = (struct io_info *) (((char *) this) + sizeof(struct io_info)); + remaining -= sizeof(struct io_info); + } + break; + } while (1); + + this = list_entry(ioinfo_free.next, struct io_info, list); + list_move_tail(&this->list, &ioinfo_busy); + spin_unlock_irqrestore(&ioinfo_lists_lock, flags); + return this; +} + +/* + * Finishes all IO and frees all IO info struct pages + */ +static void finish_all_io(void) +{ + struct io_info * this, * next = NULL; + unsigned long flags; + + while (!list_empty(&ioinfo_busy)) + SYNC_IO; + + do { } while (!IS_ERR(ioinfo_cleanup_one())); + + /* Two stages, to avoid races. First free all io_info structs on + * a page except the first */ + spin_lock_irqsave(&ioinfo_lists_lock, flags); + list_for_each_entry_safe(this, next, &ioinfo_free, list) { + if (((unsigned long) this) & ~PAGE_MASK) + list_del(&this->list); + } + + /* Now we have only one reference to each page, and can safely + * free pages, knowing we're not going to be trying to access the + * same page after freeing it. */ + list_for_each_entry_safe(this, next, &ioinfo_free, list) { + list_del(&this->list); + free_pages((unsigned long) this, 0); + } + spin_unlock_irqrestore(&ioinfo_lists_lock, flags); +} + +static void wait_on_one_page(struct io_info * io_info) +{ + do { + SYNC_IO; + } while (ioinfo_cleanup_one() != io_info); +} + +void __inline reset_io_stats(void) +{ + max_outstanding_io = outstanding_io = 0; + maxinfopages = infopages = 0; +} + +void check_io_stats(void) +{ + if (outstanding_io) + printnolog(SUSPEND_IO, SUSPEND_MEDIUM, 0, "Outstanding_io after writing is %d.\n", outstanding_io); + printnolog(SUSPEND_IO, SUSPEND_LOW, 0, "Maximum outstanding_io was %d.\n", max_outstanding_io); + if (infopages) + printnolog(SUSPEND_IO, SUSPEND_MEDIUM, 0, "Info pages is %d.\n", infopages); + printnolog(SUSPEND_IO, SUSPEND_LOW, 0, "Max info pages was %d.\n", maxinfopages); +} + +struct submit_params { + swp_entry_t swap_address; + struct page * page; + DEVICE_BLOCK_TYPE dev; + long blocks[PAGE_SIZE/512]; + int blocks_used; + int readahead_index; +}; + +/* + * --------------------------------------------------------------- + * + * Current state. + * + * --------------------------------------------------------------- + */ + +/* Which pagedir are we saving/reloading? Needed so we can know whether to + * remember the last swap entry used at the end of writing pageset2, and + * get that location when saving or reloading pageset1.*/ +static int current_stream = 0; + +/* Pointer to current swap entry being loaded/saved. */ +static struct range * currentblockrange = NULL; +static unsigned long currentblockoffset = 0; +static int currentblockchain = 0; + +/* Last page added to cache (shot down so image kept consistent */ +struct page * last_suspend_cache_page; + +/* Header Page Information */ +static int header_pageio_order = -1; +static int header_pages_allocated = 0; +static struct submit_params * header_page_info = NULL; + +/* + * --------------------------------------------------------------- + * + * User Specified Parameters + * + * --------------------------------------------------------------- + */ + +static int resume_firstblock = 0; +static int resume_firstblocksize = PAGE_SIZE; +static DEVICE_ID_TYPE resume_device = 0; +static DEVICE_BLOCK_TYPE resume_block_device = DEVICE_BLOCK_NONE; + +/* + * --------------------------------------------------------------- + * + * Disk I/O routines + * + * --------------------------------------------------------------- + */ +extern char swapfilename[]; +struct page * last_suspend_cache_page = NULL; + +extern int max_async_ios; +extern int __nosavedata pages_written; +extern int expected_compression; + +struct sysinfo swapinfo; + +#define REAL_MAX_ASYNC ((max_async_ios ? max_async_ios : 32)) + +#define MARK_SWAP_SUSPEND 0 +#define MARK_SWAP_RESUME 1 + +/* swap_entry_to_range_val & range_val_to_swap_entry: + * We are putting offset in the low bits so consecutive swap entries + * make consecutive range values */ + +// --------------------------------------------------------------------------- +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) +// --------------------------------------------------------------------------- + +extern dev_t name_to_dev_t(char *line) __init; +#define swap_entry_to_range_val(swp_entry) (swp_entry.val) +#define range_val_to_swap_entry(val) (swp_entry_t) { (val) } + +// --------------------------------------------------------------------------- + +/** + * Using bio to read from swap. + * This code requires a bit more work than just using buffer heads + * but, it is the recommended way for 2.5/2.6. + */ + +static int swsusp_end_bio(struct bio * bio, unsigned int num, int err) +{ + struct io_info *io_info = (struct io_info *) bio->bi_private; + unsigned long flags; + + spin_lock_irqsave(&ioinfo_lists_lock, flags); + list_move_tail(&io_info->list, &ioinfo_ready_for_cleanup); + spin_unlock_irqrestore(&ioinfo_lists_lock, flags); + return 0; +} + +/** + * submit - submit BIO request. + * @rw: READ or WRITE. + * @io_info: IO info structure. + * + * Straight from the textbook - allocate and initialize the bio. + * If we're writing, make sure the page is marked as dirty. + * Then submit it and carry on. + */ + +static int submit(int rw, struct io_info * io_info) +{ + int error = 0; + struct bio * bio = NULL; + + while (!bio) { + bio = bio_alloc(GFP_ATOMIC,1); + if (!bio) { + blk_run_queues(); + io_schedule(); + ioinfo_cleanup_one(); + } + } + + bio->bi_sector = io_info->blocks[0] << (PAGE_SHIFT - 9); + bio->bi_bdev = io_info->dev; + bio->bi_private = io_info; + bio->bi_end_io = swsusp_end_bio; + io_info->sys_struct = bio; + + if (bio_add_page(bio, io_info->buffer_page, PAGE_SIZE, 0) < PAGE_SIZE) { + printk("ERROR: adding page to bio at %ld\n", io_info->blocks[0]); + bio_put(bio); + return -EFAULT; + } + + if (rw == WRITE) + bio_set_pages_dirty(bio); + submit_bio(rw,bio); + return error; +} + +static inline int swsusp_set_block_size(struct block_device * bdev, int size) +{ + return set_blocksize(bdev, size); +} + +static inline int swsusp_get_block_size(struct block_device * bdev) +{ + return SWAP_BLOCKSIZE(bdev); +} + +// --------------------------------------------------------------------------- +#else /* 2.4 version */ +// --------------------------------------------------------------------------- + +extern kdev_t name_to_kdev_t(char *line) __init; +#define swap_entry_to_range_val(swp_entry) ((swp_entry.val >> 8) | ((swp_entry.val & 0x3f) << 24)) +#define range_val_to_swap_entry(val) (swp_entry_t) { ((val >> 24) | ((val & 0xffffff) << 8)) } + +static int swsusp_writepage(struct page *page); + +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 struct address_space_operations swsusp_aops = { + writepage: swsusp_writepage, + sync_page: block_sync_page, +}; + +struct address_space swsusp_space = { + LIST_HEAD_INIT(swsusp_space.clean_pages), + LIST_HEAD_INIT(swsusp_space.dirty_pages), + LIST_HEAD_INIT(swsusp_space.locked_pages), + 0, /* nrpages */ + &swsusp_aops, +}; + +/* + * Nearly the fs/buffer.c version, but we want to mark the page as done in + * our own structures too. + */ +extern void end_buffer_io_async(struct buffer_head * bh, int uptodate); + +static void swsusp_end_buffer_io_async(struct buffer_head * bh, int uptodate) +{ + struct page *page = bh->b_page; + struct io_info * io_info = (struct io_info *) page->index; + unsigned long flags; + + end_buffer_io_async(bh, uptodate); + + if (Page_Uptodate(page)) { + spin_lock_irqsave(&ioinfo_lists_lock, flags); + list_move_tail(&io_info->list, &ioinfo_ready_for_cleanup); + spin_unlock_irqrestore(&ioinfo_lists_lock, flags); + } + + return; +} + +int swsusp_brw_page(int rw, struct io_info * io_info) +{ + struct buffer_head *head, *bh; + struct page * page = io_info->buffer_page; + DEVICE_BLOCK_TYPE dev = io_info->dev; + int *b = io_info->blocks; + int size = io_info->block_size; + + if (!PageLocked(page)) + panic("swsusp_brw_page: page not locked for I/O"); + + if (!page->buffers) + create_empty_buffers(page, dev, size); + + io_info->sys_struct = head = bh = page->buffers; + + do { + lock_buffer(bh); + bh->b_blocknr = *(b++); + set_bit(BH_Mapped, &bh->b_state); + if (rw == WRITE) { + set_bit(BH_Uptodate, &bh->b_state); + __mark_buffer_dirty(bh); + } else { + clear_bit(BH_Uptodate, &bh->b_state); + atomic_set_buffer_clean(bh); + __mark_buffer_clean(bh); + } + bh->b_flushtime = jiffies + 1; + bh->b_end_io = swsusp_end_buffer_io_async; + mark_buffer_async(bh, 1); + bh = bh->b_this_page; + } while (bh != head); + + do { + struct buffer_head *next = bh->b_this_page; + submit_bh(rw, bh); + bh = next; + } while (bh != head); + + return 0; +} + +static inline int swsusp_get_block_size(dev_t bdev) +{ + int blksize; + + if (!blksize_size[MAJOR(bdev)]) + blksize = 1024; + else + blksize = blksize_size[MAJOR(bdev)][MINOR(bdev)]; + + if (!blksize) { + printk(name_suspend "%x: Blocksize not set?", bdev); + blksize = PAGE_SIZE; + } + + return blksize; +} + +static inline int swsusp_set_block_size(dev_t bdev, int size) +{ + printk(name_suspend "Attempting to set blocksize for %x to %d.\n", bdev, size); + return set_blocksize(bdev, size); +} + +static int swsusp_writepage(struct page *page) +{ + struct io_info * io_info = (struct io_info *) page->index; + + printlog(SUSPEND_IO, SUSPEND_HIGH, "Writepage %p.", page); + if (remove_exclusive_swap_page(page)) { + printlog(SUSPEND_IO, SUSPEND_VERBOSE, "Unlocking page %p. (1)", page); + UnlockPage(page); + return 0; + } + swsusp_brw_page(WRITE, io_info); + return 0; +} + +static void sync_swap_partitions(void) +{ + int i; + + swap_list_lock(); + for(i=0; i KERNEL_VERSION(2,4,99) + unsigned long offset = swp_offset(entry); + struct swap_info_struct * sis = get_swap_info_struct(swapfilenum); + sector_t sector = map_swap_page(sis, offset); + + printnolog(SUSPEND_BMAP, + SUSPEND_VERBOSE, + 1, + "Add swap partition entry to chain: Swap address %lx, chain %d, bdev %x, block %d (sector %x).", + entry.val, + swapfilenum, + header_data.swapdevs[swapfilenum], + offset, + sector); + add_to_range_chain(&header_data.block_chain[swapfilenum], sector); +#else + unsigned long offset; + int blocks[PAGE_SIZE/512]; + DEVICE_BLOCK_TYPE dev = 0; + int block_size; + struct inode *swapf = 0; + get_swaphandle_info(entry, &offset, &dev, &swapf); + if (dev) { + /* We are assuming this. + blocks_used = 1; + block_size = PAGE_SIZE; + */ + printnolog(SUSPEND_BMAP, + SUSPEND_VERBOSE, + 0, + "Add swap partition entry to chain: Swap address %lx, chain %d, bdev %x, block %d.\n", + entry.val, + swapfilenum, + header_data.swapdevs[swapfilenum], + offset); + add_to_range_chain(&header_data.block_chain[swapfilenum], offset); + } else if (swapf) { + int i, j; + unsigned int block_num = offset + << (PAGE_SHIFT - swapf->i_sb->s_blocksize_bits); + block_size = swapf->i_sb->s_blocksize; + for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size) + if (!(blocks[i] = bmap(swapf,block_num++))) { + abort_suspend("get_phys_params: bad swap file"); + return 0; + } else { + printnolog(SUSPEND_BMAP, + SUSPEND_VERBOSE, + 0, + "Add swapfile to chain: Swap address %lx, chain %d, bdev %x, block %d/%ld-> block %d.\n", + entry.val, + swapfilenum, + header_data.swapdevs[swapfilenum], + i+1, + PAGE_SIZE / block_size, + blocks[i]); + add_to_range_chain(&header_data.block_chain[swapfilenum], blocks[i]); + } + } else { + printk("Warning! Trying to get invalid physical parameters! (Entry %lx).\n", entry.val); + return 0; + } +#endif + return 1; +} + +static int get_header_params(struct submit_params * headerpage) +{ + swp_entry_t entry = headerpage->swap_address; + int swapfilenum = SWP_TYPE(entry); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + unsigned long offset = SWP_OFFSET(entry); + struct swap_info_struct * sis = get_swap_info_struct(swapfilenum); + sector_t sector = map_swap_page(sis, offset); + + headerpage->dev = sis->bdev, + headerpage->blocks[0] = sector; + headerpage->blocks_used = 1; + headerpage->readahead_index = 0; + printnolog(SUSPEND_BMAP, SUSPEND_VERBOSE, 1, + "Header entry: %lx -> %x:%x.", + headerpage->swap_address.val, + headerpage->dev->bd_dev, + headerpage->blocks[0]); + /* We are assuming this. + blocks_used = 1; + block_size = PAGE_SIZE; + */ +#else + unsigned long offset; + int blocks[PAGE_SIZE/512]; + DEVICE_BLOCK_TYPE dev = 0; + int block_size; + struct inode *swapf = 0; + int i, j; + get_swaphandle_info(headerpage->swap_address, &offset, &dev, &swapf); + if (dev) { + headerpage->dev = dev; + headerpage->blocks[0] = offset; + headerpage->blocks_used = 1; + printnolog(SUSPEND_BMAP, SUSPEND_VERBOSE, 1, + "Header entry: %lx -> %x:%x.", + headerpage->swap_address.val, + headerpage->dev, + headerpage->blocks[0]); + /* We are assuming this. + blocks_used = 1; + block_size = PAGE_SIZE; + */ + } else if (swapf) { + unsigned int block_num = offset + << (PAGE_SHIFT - swapf->i_sb->s_blocksize_bits); + block_size = swapf->i_sb->s_blocksize; + headerpage->dev = SWAP_DEVICE_BDEV(swapfilenum); + //printk("Swapfilenum is %d -> %d.\n", swapfilenum, headerpage->dev); + headerpage->blocks_used = PAGE_SIZE / block_size; + for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size) + if (!(blocks[i] = bmap(swapf,block_num++))) { + abort_suspend("get_header_params: bad swap file"); + return -EFAULT; + } else { + headerpage->blocks[i] = blocks[i]; + printnolog(SUSPEND_BMAP, SUSPEND_VERBOSE, 1, + "Header entry: %lx -> %x:%x (%d/%d).", + headerpage->swap_address.val, + headerpage->dev, + blocks[i], + i+1, + headerpage->blocks_used); + } + } else { + printk("Warning! Trying to get invalid header params! (Entry %lx).\n", headerpage->swap_address.val); + return -EFAULT; + } +#endif + return 0; +} + +static struct io_info * start_one(int rw, struct submit_params * submit_info) +{ + struct io_info * io_info = get_io_info_struct(); + unsigned long buffer_virt; + char * to, * from; + struct page * buffer_page; + int i; + + printlog(SUSPEND_IO, SUSPEND_HIGH, "Start_IO: [%p]", io_info); + while (!(buffer_virt = get_zeroed_page(GFP_ATOMIC))) { + RUN_IO; + ioinfo_cleanup_one(); + } + printlog(SUSPEND_IO, SUSPEND_HIGH, "[ALLOC BUFFER]->%d", nr_free_pages()); + buffer_page = virt_to_page(buffer_virt); + + io_info->data_page = submit_info->page; + io_info->buffer_page = buffer_page; + io_info->readahead_index = submit_info->readahead_index; + + if (rw == WRITE) { + set_bit(IO_WRITING, &io_info->flags); + + to = (char *) buffer_virt; + from = kmap(io_info->data_page); + memcpy(to, from, PAGE_SIZE); + flush_dcache_page(io_info->data_page); + flush_dcache_page(buffer_page); + kunmap(io_info->data_page); + } + + atomic_inc(&buffer_page->count); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + lock_page(buffer_page); + + BUG_ON(buffer_page->mapping); + buffer_page->mapping = &swsusp_space; + buffer_page->index = (unsigned long) io_info; +#endif + + io_info->dev = submit_info->dev; + for (i = 0; i < submit_info->blocks_used; i++) + io_info->blocks[i] = submit_info->blocks[i]; + + io_info->blocks_used = submit_info->blocks_used; + io_info->block_size = PAGE_SIZE / submit_info->blocks_used; + + if (rw == READ) + set_bit(IO_AWAITING_READ, &io_info->flags); + else + set_bit(IO_AWAITING_WRITE, &io_info->flags); + + printlog(SUSPEND_IO, SUSPEND_HIGH, "-> (PRE BRW) %d\n", nr_free_pages()); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + swsusp_brw_page(rw, io_info); + if (PageError(buffer_page)) { + TryLockPage(buffer_page); /* Ensure it's locked */ + + if (!block_flushpage(buffer_page, 0)) + PAGE_BUG(buffer_page); + return NULL; + } +#else + submit(rw, io_info); +#endif + + io_info->cache_page = last_suspend_cache_page; + last_suspend_cache_page = NULL; + + outstanding_io++; + if (outstanding_io > max_outstanding_io) + max_outstanding_io++; + return io_info; +} + +static void cleanup_one(struct io_info * io_info) +{ + struct page * buffer_page; + struct page * data_page; + char *buffer_address, *data_address; + int reading; + + buffer_page = io_info->buffer_page; + data_page = io_info->data_page; + + if (test_and_set_bit(IO_CLEANUP_IN_PROGRESS, &io_info->flags)) + return; + + reading = test_bit(IO_AWAITING_READ, &io_info->flags); + printlog(SUSPEND_IO, SUSPEND_HIGH, "Cleanup IO: [%p]\n", + io_info); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + TryLockPage(buffer_page); /* Ensure it's locked */ + + if (!block_flushpage(buffer_page, 0)) + PAGE_BUG(buffer_page); + + sync_page(buffer_page); + + buffer_page->mapping = NULL; + buffer_page->index = 0; + UnlockPage(buffer_page); +#endif + + if (reading) { + data_address = (char *) kmap(data_page); + buffer_address = (char *) kmap(buffer_page); + memcpy(data_address, buffer_address, PAGE_SIZE); + flush_dcache_page(data_page); + kunmap(data_page); + kunmap(buffer_page); + + } + + if (atomic_read(&buffer_page->count) != 2) + printk(KERN_EMERG "Cleanup IO: Page count is %d. Not good!\n", atomic_read(&buffer_page->count)); + atomic_dec(&buffer_page->count); + __free_pages(buffer_page, 0); + + if (reading) { + free_single_cache_page(io_info->cache_page, GFP_ATOMIC); + io_info->cache_page = NULL; + } + + outstanding_io--; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + bio_put(io_info->sys_struct); +#endif + io_info->sys_struct = NULL; + io_info->flags = 0; +} + +static inline void cleanup_completed_io(void) +{ + /* We clean up all we can at once so we don't only batch the first lot */ + do { } while (!ioinfo_cleanup_one()); +} + +static struct io_info * do_swsusp_io(int rw, struct submit_params * submit_info, int syncio) +{ + struct io_info * io_info = start_one(rw, submit_info); + if (!io_info) { + /* Wait on pages until no more */ + wait_on_one_page(ERR_PTR(-ENODATA)); + } else if (syncio) { + //finish_all_io(); + wait_on_one_page(io_info); + } + return io_info; +} + +/* + * + */ + +int parse_signature(char * header, int restore) +{ + int type = -1; + + if (!memcmp("SWAP-SPACE",header,10)) + return 0; + else if (!memcmp("SWAPSPACE2",header,10)) + return 1; + + else if (!memcmp("pmdisk", header,6)) + type = 2; + + else if (!memcmp("S1SUSP",header,6)) + type = 4; + else if (!memcmp("S2SUSP",header,6)) + type = 5; + + else if (!memcmp("1R",header,2)) + type = 6; + else if (!memcmp("2R",header,2)) + type = 7; + + else if (!memcmp("std",header,3)) + type = 8; + else if (!memcmp("STD",header,3)) + type = 9; + + else if (!memcmp("sd",header,2)) + type = 10; + else if (!memcmp("SD",header,2)) + type = 11; + + else if (!memcmp("z",header,1)) + type = 12; + else if (!memcmp("Z",header,1)) + type = 13; + + /* Put bdev of suspend header in last byte of swap header (unsigned short) */ + if (type > 11) { + DEVICE_ID_TYPE * header_ptr = (DEVICE_ID_TYPE *) &header[1]; + unsigned char * headerblocksize_ptr = (unsigned char *) &header[5]; + unsigned long * headerblock_ptr = (unsigned long *) &header[6]; + header_device = *header_ptr; + headerblocksize = 512 * ((int) *headerblocksize_ptr); + headerblock = *headerblock_ptr; + } + + if ((restore) && (type > 5)) { + /* We only reset our own signatures */ + if (type & 1) + memcpy(header,"SWAPSPACE2",10); + else + memcpy(header,"SWAP-SPACE",10); + } + + return type; +} + +/* + * prepare_signature + */ + +static int prepare_signature(struct submit_params * header_page_info, char * current_header) +{ + int current_type = parse_signature(current_header, 0); + DEVICE_ID_TYPE * header_ptr = (DEVICE_ID_TYPE *) (¤t_header[1]); + unsigned char * headerblocksize_ptr = (unsigned char *) (¤t_header[5]); + unsigned long * headerblock_ptr = (unsigned long *) (¤t_header[6]); + + if ((current_type > 1) && (current_type < 6)) + return 1; + + if (current_type & 1) + current_header[0] = 'Z'; + else + current_header[0] = 'z'; + *header_ptr = BDEV_TO_DEVICE_ID(header_page_info->dev); + *headerblocksize_ptr = (unsigned char) (PAGE_SIZE / 512 / header_page_info->blocks_used); + *headerblock_ptr = (unsigned long) header_page_info->blocks[0]; /* prev is the first/last swap page of the resume area */ + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, "Saving header block size of %ld (%ld 512 byte blocks per page).\n", + PAGE_SIZE / header_page_info->blocks_used, + PAGE_SIZE / 512 / header_page_info->blocks_used); + return 0; +} + +/* We used to use bread here, but it doesn't correctly handle + * blocksize != PAGE_SIZE. Now we create a submit_info to get the data we + * want and use our normal routines (synchronously). + */ + +static void bdev_page_io(int rw, DEVICE_BLOCK_TYPE bdev, long pos, struct page * page) +{ + struct submit_params submit_info; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + submit_info.page = page; + submit_info.dev = bdev; + + submit_info.blocks[0] = pos; + submit_info.blocks_used = 1; +#else + int block_size; + int i, j; + submit_info.page = page; + submit_info.dev = bdev; + + block_size = blksize_size[MAJOR(bdev)][MINOR(bdev)]; + for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size) + submit_info.blocks[i] = pos+i; + submit_info.blocks_used = i; +#endif + submit_info.readahead_index = 0; + do_swsusp_io(rw, &submit_info, 1); +} + +extern int signature_check(char * header, int fix); + +static int free_swap_pages_for_header(void) +{ + int i; + + if (!header_page_info) + return 1; + + PRINTFREEMEM("at start of free_swap_pages_for_header"); + + for (i=1; i<=header_pages_allocated; i++) + swap_free(header_page_info[i-1].swap_address); + free_pages((unsigned long) header_page_info, header_pageio_order); + printnolog(SUSPEND_SWAP, SUSPEND_LOW, 1, " Freed %d swap pages in free_swap_pages_for_header.\n", + header_pages_allocated); + header_page_info = NULL; + header_pages_allocated = 0; + header_pageio_order = -1; + PRINTFREEMEM("at end of free_swap_pages_for_header"); + return 0; +} + +static void get_main_pool_phys_params(void) +{ + struct range * rangepointer = NULL; + unsigned long address; + int i; + + for (i = 0; i < MAX_SWAPFILES; i++) + if (header_data.block_chain[i].first) + put_range_chain(&header_data.block_chain[i]); + + range_for_each(&header_data.swapranges, rangepointer, address) + get_phys_params(range_val_to_swap_entry(address)); +} + +extern void put_range(struct range * range); + +static unsigned long swapwriter_storage_allocated(void) +{ + return (header_data.swapranges.size + header_pages_allocated); +} + +static long swapwriter_storage_available(void) +{ + manage_swapfile(1); + si_swapinfo(&swapinfo); + return (swapinfo.freeswap + (long) swapwriter_storage_allocated()); +} + +static int swapwriter_release_storage(void) +{ + int i = 0, swapcount = 0; + +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if ((TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) && (now_resuming)) + return 0; +#endif + + free_swap_pages_for_header(); + + if (header_data.swapranges.first) { + /* Free swap entries */ + struct range * rangepointer; + unsigned long rangevalue; + swp_entry_t entry; + //printk("Freeing swap ranges chain.\n"); + range_for_each(&header_data.swapranges, rangepointer, rangevalue) { + entry = range_val_to_swap_entry(rangevalue); + printnolog(SUSPEND_SWAP, SUSPEND_VERBOSE, 1, "(Free %d %lx)", i, + entry.val); + swap_free(entry); + swapcount++; + check_shift_keys(0, NULL); + } + put_range_chain(&header_data.swapranges); + for (i = 0; i < MAX_SWAPFILES; i++) + if (header_data.block_chain[i].first) + put_range_chain(&header_data.block_chain[i]); + } + + printlog(SUSPEND_SWAP, SUSPEND_MEDIUM, "Freed %d swap pages in free_swap.\n", swapcount); + + manage_swapfile(0); + + return 0; +} + +static long swapwriter_allocate_header_space(unsigned long space_requested) +{ + int numpages = space_requested; /* This was going to be in bytes... not yet */ + int i, new_order; + + PRINTFREEMEM("at start of allocate_header_space"); + + /* num_range_pages + 1 because we also need a submit_info for saving the header */ + new_order = get_bitmask_order((((sizeof(struct submit_params) * numpages) + PAGE_SIZE - 1) / PAGE_SIZE) - 1); + if (new_order != header_pageio_order) { + if (header_pageio_order > -1) + free_swap_pages_for_header(); + + printnolog(SUSPEND_SWAP, SUSPEND_MEDIUM, 0, + " Seeking order %d for %d pages.\n", + new_order, numpages); + header_page_info = (struct submit_params *) get_grabbed_pages(new_order); + + if (!header_page_info) { + printk("Unable to allocate page(s) for swap header info structures.\n"); + return -ENOSPC; + } + + header_pageio_order = new_order; + } + + for (i=(header_pages_allocated+1); i<=numpages; i++) { + header_page_info[i-1].swap_address = get_swap_page(); + if ((!header_page_info[i-1].swap_address.val) && (header_data.swapranges.first)) { + /* + * Steal one from pageset swap chain. If, as a result, + * it is too small, more swap will be allocated or + * memory eaten. + */ + + printk("Stealing a page from main swap pool. Pool size going from %d ", + header_data.swapranges.size); + + header_page_info[i-1].swap_address = range_val_to_swap_entry(header_data.swapranges.first->minimum); + if (header_data.swapranges.first->minimum < header_data.swapranges.first->maximum) + header_data.swapranges.first->minimum++; + else { + struct range * oldfirst = header_data.swapranges.first; + header_data.swapranges.first = oldfirst->next; + header_data.swapranges.frees++; + header_data.swapranges.lastaccessed = NULL; + if (header_data.swapranges.last == oldfirst) + header_data.swapranges.last = NULL; + put_range(oldfirst); + } + + header_data.swapranges.size--; + printk("to %d.\n", header_data.swapranges.size); + + //Recalculate block chains for main pool. + //We don't assume blocks are at start of a chain and + //don't know how many blocks per swap entry. + get_main_pool_phys_params(); + } + if (!header_page_info[i-1].swap_address.val) { + free_swap_pages_for_header(); + printk("Unable to allocate swap page for header.\n"); + return -ENOMEM; + } + if (get_header_params(&header_page_info[i-1])) + return -EFAULT; + printnolog(SUSPEND_SWAP, SUSPEND_MEDIUM, 0, + " Got header page %d/%d. Dev is %x. Block is %lu. Blocksperpage is %d.\n", + i+1, numpages+1, + header_page_info[i-1].dev, + header_page_info[i-1].blocks[0], + header_page_info[i-1].blocks_used); + } + header_pages_allocated = numpages; + printnolog(SUSPEND_SWAP, SUSPEND_LOW, 1, " Have %d swap pages in swapwriter::allocate_header_space.\n", + header_pages_allocated); + PRINTFREEMEM("at end of swapwriter::allocate_header_space"); + return 0; +} + +static int swapwriter_allocate_storage(unsigned long space_requested) +{ + int i, swapcount = 0, result = 0; + int lastsize = header_data.swapranges.size; + int numwanted = (int) (space_requested); + int pages_to_get = numwanted - header_data.swapranges.size; + + if (numwanted < 1) + return 0; + + printnolog(SUSPEND_SWAP, SUSPEND_MEDIUM, 0, + "Started with swapranges.size == %d. Seeking to allocate %d more.\n", + header_data.swapranges.size, + pages_to_get); + + for(i=0; i < pages_to_get; i++) { + swp_entry_t entry; + printnolog(SUSPEND_SWAP, SUSPEND_VERBOSE, 1, ""); + entry = get_swap_page(); + if (!entry.val) { + printlog(SUSPEND_SWAP, SUSPEND_MEDIUM, + "Allocated %d/%d swap pages for main pool in allocate_swap.\n", + swapcount, numwanted); + printk("Unable to allocate enough swap. Got %d pages of %d wanted.\n", + i, pages_to_get); + result = -ENOSPC; + goto out; + } + swapcount++; + { + int result = add_to_range_chain(&header_data.swapranges, swap_entry_to_range_val(entry)); + if (result) + printk("add_to_range_chain returned %d.\n", result); + } + if (header_data.swapranges.size != (lastsize + 1)) + printk("swapranges.size == %d.\n", header_data.swapranges.size); + lastsize = header_data.swapranges.size; + check_shift_keys(0, NULL); + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + break; + } + printlog(SUSPEND_SWAP, SUSPEND_MEDIUM, + " Allocated %d/%d swap pages in allocate_swap.\n", + swapcount, numwanted); + + printnolog(SUSPEND_SWAP, SUSPEND_MEDIUM, 0, + "Finished with swapranges.size == %d.\n", header_data.swapranges.size); + +out: + get_main_pool_phys_params(); + + print_chain(SUSPEND_HIGH, &header_data.swapranges, 1); + for (i = 0; i < MAX_SWAPFILES; i++) + if (header_data.block_chain[i].first) { + printnolog(SUSPEND_RANGES, SUSPEND_HIGH, 0, "\nBlock chain %d:", i); + print_chain(SUSPEND_HIGH, &header_data.block_chain[i], 1); + } + + return result; +} + +static char * swapwriter_buffer = NULL; +static int swapwriter_buffer_posn = 0; +static int current_page_number = 0; +static unsigned long * header_link = NULL; +#define BYTES_PER_HEADER_PAGE (PAGE_SIZE - sizeof(swp_entry_t)) + +static int swapwriter_write_header_chunk(char * buffer, int buffer_size); + +static int swapwriter_write_header_init(void) +{ + int i; + + for (i = 0; i < MAX_SWAPFILES; i++) + if (!UNUSED_SWAP_ENTRY(i)) { + header_data.swapdevs[i] = SWAP_DEVICE_ID(i); + header_data.blocksizes[i] = SWAP_BLOCKSIZE(SWAP_DEVICE_BDEV(i)); + } + + header_data.max_async_ios = max_async_ios; + + last_suspend_cache_page = NULL; + swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + header_link = (unsigned long *) (swapwriter_buffer + BYTES_PER_HEADER_PAGE); + current_page_number = 1; + + /* Info needed to bootstrap goes at the start of the header. + * First we save the 'header_data' struct, including the number + * of header pages. Then we save the structs containing data needed + * for reading the header pages back. + * Note that even if header pages take more than one page, when we + * read back the info, we will have restored the location of the + * next header page by the time we go to use it. + */ + swapwriter_write_header_chunk((char *) &header_data, sizeof(header_data)); + + return 0; +} + +static int swapwriter_write_header_chunk(char * buffer, int buffer_size) +{ + int bytes_left = buffer_size; + + /* + * We buffer the writes until a page is full and to use the last + * sizeof(swp_entry_t) bytes for links between pages. This is + * totally transparent to the caller. + * + * Note also that buffer_size can be > PAGE_SIZE. + */ + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "\nStart of write_header_chunk loop with %d bytes to store.\n", + buffer_size); + + while (bytes_left) { + char * source_start = buffer + buffer_size - bytes_left; + char * dest_start = swapwriter_buffer + swapwriter_buffer_posn; + int dest_capacity = BYTES_PER_HEADER_PAGE - swapwriter_buffer_posn; + swp_entry_t next_header_page; + if (bytes_left <= dest_capacity) { + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Storing %d bytes from %p-%p in page %d, %p-%p.\n", + bytes_left, + source_start, source_start + bytes_left - 1, + current_page_number, + dest_start, dest_start + bytes_left - 1); + memcpy(dest_start, source_start, bytes_left); + swapwriter_buffer_posn += bytes_left; + return 0; + } + + /* A page is full */ + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Storing %d bytes from %p-%p in page %d, %p-%p.\n", + dest_capacity, + source_start, source_start + dest_capacity - 1, + current_page_number, + dest_start, dest_start + dest_capacity - 1); + memcpy(dest_start, source_start, dest_capacity); + bytes_left -= dest_capacity; + + next_header_page = SWP_ENTRY(SWP_TYPE(header_page_info[current_page_number].swap_address), + header_page_info[current_page_number].blocks[0]); + + *header_link = next_header_page.val; + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Header link is at %p. Contents set to swap device #%ld, block %ld.\n", + header_link, + (long) SWP_TYPE(next_header_page), SWP_OFFSET(next_header_page)); + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Writing header page %d/%d. Dev is %x. Block is %lu. Blocksperpage is %d.\n", + current_page_number, header_pages_allocated, + BDEV_TO_DEVICE_ID(header_page_info[current_page_number-1].dev), + header_page_info[current_page_number-1].blocks[0], + header_page_info[current_page_number-1].blocks_used); + + header_page_info[current_page_number-1].page = virt_to_page(swapwriter_buffer); + do_swsusp_io(WRITE, &header_page_info[current_page_number-1], 0); + + swapwriter_buffer_posn = 0; + current_page_number++; + } + + return 0; +} + +static int swapwriter_write_header_cleanup(void) +{ + /* Write any unsaved data */ + if (swapwriter_buffer_posn) { + swp_entry_t next_header_page; + + next_header_page = SWP_ENTRY(SWP_TYPE(header_page_info[current_page_number].swap_address), + header_page_info[current_page_number].blocks[0]); + + *header_link = next_header_page.val; + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Header link is at %p. Contents set to swap device #%ld, block %ld.\n", + header_link, + (long) SWP_TYPE(next_header_page), SWP_OFFSET(next_header_page)); + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Writing header page %d/%d. Dev is %x. Block is %lu. Blocksperpage is %d.\n", + current_page_number, header_pages_allocated, + BDEV_TO_DEVICE_ID(header_page_info[current_page_number-1].dev), + header_page_info[current_page_number-1].blocks[0], + header_page_info[current_page_number-1].blocks_used); + + header_page_info[current_page_number-1].page = virt_to_page(swapwriter_buffer); + do_swsusp_io(WRITE, &header_page_info[current_page_number-1], 0); + + swapwriter_buffer_posn = 0; + current_page_number++; + } + + /* Adjust swap header */ + bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(swapwriter_buffer)); + + prepare_signature(&header_page_info[0], + ((union swap_header *) swapwriter_buffer)->magic.magic); + + bdev_page_io(WRITE, resume_block_device, resume_firstblock, virt_to_page(swapwriter_buffer)); + + free_pages((unsigned long) swapwriter_buffer, 0); + swapwriter_buffer = NULL; + header_link = NULL; + + finish_all_io(); + + return 0; +} + +/* ------------------------- HEADER READING ------------------------- */ + +/* + * read_header_init() + * + * Description: + * 1. Attempt to read the device specified with resume2=. + * 2. Check the contents of the swap header for our signature. + * 3. Warn, ignore, reset and/or continue as appropriate. + * 4. If continuing, read the swapwriter configuration section + * of the header and set up block device info so we can read + * the rest of the header & image. + * + * Returns: + * May not return if user choose to reboot at a warning. + * -EINVAL if cannot resume at this time. Booting should continue + * normally. + */ +static int swapwriter_invalidate_image(void); + +static int swapwriter_read_header_init(void) +{ + int i; + + current_page_number = 1; + + swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + + if (!header_device) { + printk("read_header_init called when we haven't verified there is an image!\n"); + return -EINVAL; + } + + /* + * If the header is not on the resume_device, get the resume device first. + */ + if (header_device != resume_device) { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + header_block_device = open_by_devnum(header_device, FMODE_READ, BDEV_RAW); + + if (IS_ERR(header_block_device)) { + if (sanity_check_failed("Failed to get access to the resume header device.\nYou could be booting with a 2.4 kernel when you suspended a 2.6 kernel or vice versa.")) + swapwriter_invalidate_image(); + + return -EINVAL; + } +#else + int blksize = 0; + header_block_device = header_device; + + if (!blksize_size[MAJOR(header_block_device)]) { + printk(name_suspend "%x: Blocksize not known?\n", header_block_device); + } else blksize = blksize_size[MAJOR(header_block_device)][MINOR(header_block_device)]; + if (!blksize) { + printk(name_suspend "%x: Blocksize not set?\n", header_block_device); + blksize = PAGE_SIZE; + } + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Header blocksize was %d.\n", blksize); + if (set_blocksize(header_block_device, headerblocksize)) { + if (sanity_check_failed("Failed to get access to the resume header device.\nYou could be booting with a 2.4 kernel when you suspended a 2.6 kernel or vice versa.")) + swapwriter_invalidate_image(); + + return -EINVAL; + } +#endif + } else + header_block_device = resume_block_device; + + /* Read swapwriter configuration */ + bdev_page_io(READ, header_block_device, headerblock, virt_to_page((unsigned long) swapwriter_buffer)); + //FIXME Remember location of next page to be read. + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Retrieving %d bytes from %x:%x to page %d, %p-%p.\n", + BDEV_TO_DEVICE_ID(header_block_device), headerblock, + sizeof(header_data), + current_page_number, + swapwriter_buffer, swapwriter_buffer + sizeof(header_data) - 1); + memcpy(&header_data, swapwriter_buffer, sizeof(header_data)); + + /* Restore device info */ + for (i = 0; i < MAX_SWAPFILES; i++) { + DEVICE_ID_TYPE thisdevice = header_data.swapdevs[i]; + + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 1, "Swap device %d is %x.", i, thisdevice); + + if (!thisdevice) + continue; + + if (thisdevice == resume_device) { + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 0, "Resume root device %x", thisdevice); + RESUME_BDEV(i) = resume_block_device; + continue; + } + + if (thisdevice == header_device) { + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 0, "Resume header device %x", thisdevice); + RESUME_BDEV(i) = header_block_device; + continue; + } + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + RESUME_BDEV(i) = open_by_devnum(thisdevice, FMODE_READ, BDEV_RAW); +#else + { + int blksize = 512 * (int) header_data.blocksizes[i]; + swap_info[i].swap_device = thisdevice; + + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 0, "Resume secondary device %x", thisdevice); + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, "Blocksize was %d.\n", blksize); + set_blocksize(thisdevice, blksize); + } +#endif + } + + max_async_ios = header_data.max_async_ios; + + swapwriter_buffer_posn = sizeof(header_data); + + return 0; +} + +static int swapwriter_read_header_chunk(char * buffer, int buffer_size) +{ + int bytes_left = buffer_size, ret = 0; + + /* Read a chunk of the header */ + while ((bytes_left) && (!ret)) { + swp_entry_t next = ((union p_diskpage) swapwriter_buffer).pointer->link.next; + DEVICE_BLOCK_TYPE dev = RESUME_BDEV(SWP_TYPE(next)); + int pos = SWP_OFFSET(next); + char * dest_start = buffer + buffer_size - bytes_left; + char * source_start = swapwriter_buffer + swapwriter_buffer_posn; + int source_capacity = BYTES_PER_HEADER_PAGE - swapwriter_buffer_posn; + + if (bytes_left <= source_capacity) { + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Retrieving %d bytes from page %d, %p-%p into %p-%p.\n", + bytes_left, + current_page_number, + source_start, source_start + bytes_left - 1, + dest_start, dest_start + bytes_left - 1); + memcpy(dest_start, source_start, bytes_left); + swapwriter_buffer_posn += bytes_left; + return buffer_size; + } + + /* Next to read the next page */ + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Retrieving %d bytes from page %d, %p-%p to %p-%p.\n", + source_capacity, + current_page_number, + source_start, source_start + source_capacity - 1, + dest_start, dest_start + source_capacity - 1); + memcpy(dest_start, source_start, source_capacity); + bytes_left -= source_capacity; + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Header link is at %p. Contents set to %lx = swap device #%x, block %d.\n", + &((union p_diskpage) swapwriter_buffer).pointer->link.next, + ((union p_diskpage) swapwriter_buffer).pointer->link.next.val, + BDEV_TO_DEVICE_ID(dev), pos); + + current_page_number++; + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Reading header page %d. Dev is %x. Block is %lu.\n", + current_page_number, dev, pos); + + bdev_page_io(READ, dev, pos, virt_to_page(swapwriter_buffer)); + + swapwriter_buffer_posn = 0; + } + + return buffer_size - bytes_left; +} + +static int swapwriter_read_header_cleanup(void) +{ + free_pages((unsigned long) swapwriter_buffer, 0); + return 0; +} + +static int swapwriter_prepare_save_ranges(void) +{ + int i; + + print_chain(SUSPEND_HIGH, &header_data.swapranges, 0); + for (i = 0; i < MAX_SWAPFILES; i++) + print_chain(SUSPEND_HIGH, &header_data.block_chain[i], 0); + + relativise_chain(&header_data.swapranges); + + for (i = 0; i < MAX_SWAPFILES; i++) + relativise_chain(&header_data.block_chain[i]); + + header_data.pd1start_block_range = RANGE_RELATIVE(header_data.pd1start_block_range); + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Pagedir1 firstblockrange is %p.\n", header_data.pd1start_block_range); + + return 0; +} + +static int swapwriter_post_load_ranges(void) +{ + int i; + unsigned long * range_pages = get_rangepages_list(); + + if (!range_pages) { + printk("Unable to allocate rangepageslist.\n"); + return -ENOMEM; + } + + absolutise_chain(&header_data.swapranges, range_pages); + print_chain(SUSPEND_HIGH, &header_data.swapranges, 0); + + for (i = 0; i < MAX_SWAPFILES; i++) { + absolutise_chain(&header_data.block_chain[i], range_pages); + print_chain(SUSPEND_HIGH, &header_data.block_chain[i], 0); + } + + header_data.pd1start_block_range = RANGE_ABSOLUTE(header_data.pd1start_block_range, range_pages); + + put_rangepages_list(range_pages); + + return 0; +} + +static int swapwriter_write_init(int stream_number) +{ + last_suspend_cache_page = NULL; + + if (stream_number == 1) { + currentblockrange = header_data.pd1start_block_range; + currentblockoffset = header_data.pd1start_block_offset; + currentblockchain = header_data.pd1start_chain; + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Beginning from position: range %p, block %ld.\n", + currentblockrange, currentblockoffset); + } else { + for (currentblockchain = 0; currentblockchain < MAX_SWAPFILES; currentblockchain++) + if (header_data.block_chain[currentblockchain].first) { + currentblockrange = header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + break; + } + } + + swapwriter_buffer_posn = 0; + + current_page_number = 1; + current_stream = stream_number; + + reset_io_stats(); + + return 0; +} + +static int swapwriter_write_chunk(char * buffer) +{ + int i; + struct submit_params submit_params; + int blksize = 0, num_blocks = 1; + + if (currentblockchain == MAX_SWAPFILES) { + int current_log_all; + printk("Error! We have run out of blocks for writing data.\n"); + for (i = 0; i < MAX_SWAPFILES; i++) { + if (UNUSED_SWAP_ENTRY(i)) + printk("Swap slot %d is unused.\n", i); + else + printk("Swap slot %d is device %x.\n", i, SWAP_DEVICE_ID(i)); + if (header_data.block_chain[i].size) + printk("Chain size for device %d is %d.\n", i, + header_data.block_chain[i].size); + } + printk("Swap range chain looks like this:\n"); + current_log_all = TEST_ACTION_STATE(SUSPEND_LOGALL); + SET_ACTION_STATE(SUSPEND_LOGALL); + print_chain(0, &header_data.swapranges, 0); + if (!current_log_all) + CLEAR_ACTION_STATE(SUSPEND_LOGALL); + return -ENOSPC; + } + blksize = swsusp_get_block_size(SWAP_DEVICE_BDEV(currentblockchain)); + num_blocks = PAGE_SIZE / blksize; + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Writing page %d. Dev is %x. Block is %lu. Blocksperpage is %d.\n", + current_page_number, + SWAP_DEVICE_BDEV(currentblockchain), + currentblockoffset, + num_blocks); + + submit_params.page = virt_to_page(buffer); + submit_params.dev = SWAP_DEVICE_BDEV(currentblockchain); + + /* Get the blocks */ + for (i = 0; i < num_blocks; i++) { + if (!currentblockrange) { + do { + currentblockchain++; + } while ((currentblockchain < MAX_SWAPFILES) && + (!header_data.block_chain[currentblockchain].first)); + + /* We can validly not have a new blockrange. We + * might be compressing data and the user was + * too optimistic in setting the compression + * ratio or we're just copying the pageset. */ + + if (currentblockchain == MAX_SWAPFILES) + return -ENOSPC; + + currentblockrange = header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + } + submit_params.blocks[i] = currentblockoffset; + GET_RANGE_NEXT(currentblockrange, currentblockoffset); + } + + submit_params.blocks_used = num_blocks; + submit_params.readahead_index = 0; + + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "page:%d. bdev:%x. blocks (%d):", + current_page_number, + submit_params.dev, + submit_params.blocks_used); + + for (i = 0; i < submit_params.blocks_used; i++) + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, + "0x%lx%s", + submit_params.blocks[i], + ((i+1) < submit_params.blocks_used) ? "," : "\n"); + + check_shift_keys(0, NULL); + + do_swsusp_io(WRITE, &submit_params, 0); + + swapwriter_buffer_posn = 0; + current_page_number++; + + return 0; +} + +static int swapwriter_write_cleanup(void) +{ + //printk("Cleanup: Stream number is %d.\n", current_stream); + if (current_stream == 2) { + header_data.pd1start_block_range = currentblockrange; + header_data.pd1start_block_offset = currentblockoffset; + header_data.pd1start_chain = currentblockchain; + //printk("Saving final position: range %p, block %ld.\n", + // currentblockrange, currentblockoffset); + } + + finish_all_io(); + + check_io_stats(); + + return 0; +} + +static int swapwriter_read_init(int stream_number) +{ + int i; + unsigned long flags; + + if (stream_number == 1) { + currentblockrange = header_data.pd1start_block_range; + currentblockoffset = header_data.pd1start_block_offset; + currentblockchain = header_data.pd1start_chain; + //printk("Starting from final range %p, block %ld.\n", + // currentblockrange, currentblockoffset); + } else { + for (i = 0; i < MAX_SWAPFILES; i++) + if (header_data.block_chain[i].first) { + currentblockrange = header_data.block_chain[i].first; + currentblockoffset = currentblockrange->minimum; + currentblockchain = i; + break; + } + } + + last_suspend_cache_page = NULL; + + current_page_number = 1; + + reset_io_stats(); + + readahead_index = 0; + spin_lock_irqsave(&readahead_flags_lock, flags); + readahead_flags = 0; + spin_unlock_irqrestore(&readahead_flags_lock, flags); + for (i = 1; i <= MAX_READAHEAD; i++) + readahead_page[i-1] = virt_to_page(get_zeroed_page(GFP_ATOMIC)); + + return 0; +} + +static int swapwriter_begin_read_chunk(struct page * page, int readahead_index, int sync) +{ + int i; + struct submit_params submit_params; + int blksize = 0, num_blocks = 1; + struct io_info * io_info; + + blksize = swsusp_get_block_size(SWAP_DEVICE_BDEV(currentblockchain)); + num_blocks = PAGE_SIZE / blksize; + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Reading page %d. Dev is %x. Block is %lu. Blocksperpage is %d. Page is %p. Readahead index is %d.", + current_page_number, + SWAP_DEVICE_BDEV(currentblockchain), + currentblockoffset, + num_blocks, page, readahead_index); + + submit_params.page = page; + submit_params.dev = SWAP_DEVICE_BDEV(currentblockchain); + submit_params.readahead_index = readahead_index; + + /* Get the blocks */ + for (i = 0; i < num_blocks; i++) { + submit_params.blocks[i] = currentblockoffset; + GET_RANGE_NEXT(currentblockrange, currentblockoffset); + if (!currentblockrange) { + do { + currentblockchain++; + } while ((!header_data.block_chain[currentblockchain].first) && + (i < MAX_SWAPFILES)); + + /* We can validly not have a new blockrange. We + * might be compressing data and the user was + * too optimistic in setting the compression + * ratio or we're just copying the pageset. */ + + if (i == MAX_SWAPFILES) + return -ENOSPC; + + currentblockrange = header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + } + } + + submit_params.blocks_used = num_blocks; + + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "page:%d. bdev:%x. blocks (%d):", + current_page_number, + submit_params.dev, + submit_params.blocks_used); + + for (i = 0; i < submit_params.blocks_used; i++) + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, + "0x%lx%s", + submit_params.blocks[i], + ((i+1) < submit_params.blocks_used) ? "," : "\n"); + + check_shift_keys(0, NULL); + + io_info = do_swsusp_io(READ, &submit_params, sync); + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "IO_info struct is at %p.\n", io_info); + + swapwriter_buffer_posn = 0; + current_page_number++; + + return 0; +} + +static int swapwriter_read_chunk(char * buffer, int sync) +{ + static int last_result; + int i; + unsigned long flags; + + if ((!sync) || (MAX_READAHEAD == 0)) + return swapwriter_begin_read_chunk(virt_to_page(buffer), 0, sync); + + /* Start the first one, plus read-ahead */ + if (readahead_index == 0) { + for (i = 1; i <= MAX_READAHEAD; i++) { + if ((last_result = swapwriter_begin_read_chunk(readahead_page[i-1], i, 0))) { + printk("Begin read chunk for page %d returned %d.\n", + i, last_result); + return last_result; + } + } + readahead_index++; + } + + /* read_ahead_index is the one we want to return */ + while (!test_bit(readahead_index - 1, &readahead_flags)) { + RUN_IO; + ioinfo_cleanup_one(); + } + + memcpy(buffer, page_address(readahead_page[readahead_index - 1]), PAGE_SIZE); + + /* Start a new readahead? */ + if (!last_result) { + spin_lock_irqsave(&readahead_flags_lock, flags); + clear_bit(readahead_index - 1, &readahead_flags); + spin_unlock_irqrestore(&readahead_flags_lock, flags); + last_result = swapwriter_begin_read_chunk(readahead_page[readahead_index - 1], readahead_index, 0); + if (last_result) + printk("Begin read chunk for page %d returned %d.\n", + readahead_index, last_result); + } + + readahead_index++; + if (readahead_index > MAX_READAHEAD) + readahead_index = 1; + + return 0; +} + +static int swapwriter_read_cleanup(void) +{ + int i; + + finish_all_io(); + check_io_stats(); + for (i = 0; i < MAX_READAHEAD; i++) + __free_pages(readahead_page[i], 0); + return 0; +} + +extern int nr_suspends; + +/* swapwriter_invalidate_image + * + */ +static int swapwriter_invalidate_image(void) +{ + union p_diskpage cur; + int result = 0; + char newsig[11]; + + cur.address = get_zeroed_page(GFP_ATOMIC); + if (!cur.address) { + printk("Unable to allocate a page for restoring the swap signature.\n"); + return -ENOMEM; + } + + /* + * If nr_suspends == 0, we must be booting, so no swap pages + * will be recorded as used yet. + */ + + if (nr_suspends > 0) + swapwriter_release_storage(); + + /* + * We don't do a sanity check here: we want to restore the swap + * whatever version of kernel made the suspend image. + * + * We need to write swap, but swap may not be enabled so + * we write the device directly + */ + + bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(cur.pointer)); + + result = parse_signature(cur.pointer->swh.magic.magic, 1); + + if (result < 4) + goto out; + + strncpy(newsig, cur.pointer->swh.magic.magic, 10); + newsig[10] = 0; + printlog(SUSPEND_ANY_SECTION, SUSPEND_VERBOSE,"Swap signature will be set to %s.\n", newsig); + + bdev_page_io(WRITE, resume_block_device, resume_firstblock, virt_to_page(cur.pointer)); + + if (!nr_suspends) + printk(KERN_WARNING name_suspend "Image invalidated.\n"); +out: + free_pages(cur.address, 0); + return 0; +} + +/* + * workspace_size + * + * Description: + * Returns the number of bytes of RAM needed for this + * code to do its work. (Used when calculating whether + * we have enough memory to be able to suspend & resume). + * + */ +static unsigned long swapwriter_memory_needed(void) +{ + return (MAX(REAL_MAX_ASYNC, MAX_READAHEAD) * (PAGE_SIZE + sizeof(struct io_info))); +} + +/* Print debug info + * + * Description: + */ + +static int swapwriter_print_debug_stats(char * buffer, int size) +{ + int len = 0, already_on = 0; + struct sysinfo sysinfo; + + if (active_writer != &swapwriterops) { + len = suspend_snprintf(buffer, size, "- Swapwriter inactive.\n"); + return len; + } + + len = suspend_snprintf(buffer, size, "- Swapwriter active.\n"); + if (swapfilename[0]) + len+= suspend_snprintf(buffer+len, size-len, + " Attempting to automatically swapon: %s.\n", swapfilename); + + already_on = manage_swapfile(1); + + si_swapinfo(&sysinfo); + + len+= suspend_snprintf(buffer+len, size-len, " Swap available for image: %ld.\n", + sysinfo.freeswap); + + if (!already_on) + manage_swapfile(0); + + return len; +} + +/* + * Storage needed + * + * Returns amount of space in the swap header required + * for the swapwriter's data. + * + */ +static unsigned long swapwriter_storage_needed(void) +{ + /* FIXME We need to add up space for dev info etc... */ + return sizeof(header_data); +} + +/* + * Wait on I/O + * + */ + +static int swapwriter_wait_on_io(int flush_all) +{ + if (flush_all) + finish_all_io(); + else + cleanup_completed_io(); + + return 0; +} + +/* + * Image_exists + * + */ + +static int swapwriter_image_exists(void) +{ + int signature_found; + union p_diskpage diskpage; + + if (!resume_device) { + printk("Not even trying to read header because resume_device is not set.\n"); + return 0; + } + + //PRINTFREEMEM("at start of swapwriter_image_exists."); + + last_suspend_cache_page = NULL; + + diskpage.address = get_zeroed_page(GFP_ATOMIC); + + /* FIXME: Make sure bdev_page_io handles wrong parameters */ + bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr)); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + + if (signature_found < 2) { + printk(KERN_ERR name_suspend "This is normal swap space.\n" ); + return 0; /* non fatal error */ + } else if (signature_found == -1) { + printk(KERN_ERR name_suspend "Unable to find a signature. Could you have moved a swap file?\n"); + return 0; + } else if (signature_found < 6) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) + if ((!(software_suspend_state & SOFTWARE_SUSPEND_NORESUME_SPECIFIED)) + && sanity_check_failed("Detected the signature of an alternate implementation.\n")) + software_suspend_state |= SOFTWARE_SUSPEND_NORESUME_SPECIFIED; +#else + printk(KERN_ERR name_suspend "Detected the signature of an alternate implementation.\n"); +#endif + return 0; + } else if ((signature_found >> 1) != SIGNATURE_VER) { + if ((!(software_suspend_state & SOFTWARE_SUSPEND_NORESUME_SPECIFIED)) && + sanity_check_failed("Found a different style suspend image signature.")) + software_suspend_state |= SOFTWARE_SUSPEND_NORESUME_SPECIFIED; + } + + return 1; +} + +/* + * Parse Image Location + * + * Attempt to parse a resume2= parameter. + * Swap Writer accepts: + * resume2=swap:DEVNAME[:FIRSTBLOCK][@BLOCKSIZE] + * + * Where: + * DEVNAME is convertable to a dev_t by name_to_dev_t + * FIRSTBLOCK is the location of the first block in the swap file + * (specifying for a swap partition is nonsensical but not prohibited). + * BLOCKSIZE is the logical blocksize >= 512 & <= PAGE_SIZE, + * mod 512 == 0 of the device. + * Data is validated by attempting to read a swap header from the + * location given. Failure will result in swapwriter refusing to + * save an image, and a reboot with correct parameters will be + * necessary. + */ + +static int swapwriter_parse_image_location(char * commandline, int boot_time) +{ + char *thischar, *colon = NULL, *at_symbol = NULL; + union p_diskpage diskpage; + int signature_found; + + if (strncmp(commandline, "swap:", 5)) { + printk(name_suspend "Swapwriter: Image location doesn't begin with 'swap:'\n"); + return 0; + } + + commandline += 5; + + thischar = commandline; + while ((*thischar != ':') && ((thischar - commandline) < 250) && (*thischar)) + thischar++; + + if (*thischar == ':') { + colon = thischar; + *colon = 0; + thischar++; + } + + while ((*thischar != '@') && ((thischar - commandline) < 250) && (*thischar)) + thischar++; + + if (*thischar == '@') { + at_symbol = thischar; + *at_symbol = 0; + } + + if (colon) + resume_firstblock = (int) simple_strtoul(colon + 1, NULL, 0); + else + resume_firstblock = 0; + printk("Looking for first block of swap header at block %x.\n", resume_firstblock); + + if (at_symbol) { + resume_firstblocksize = (int) simple_strtoul(at_symbol + 1, NULL, 0); + if (resume_firstblocksize & 0x1FF) + printk("Blocksizes are usually a multiple of 512. Don't expect this to work!\n"); + } else + resume_firstblocksize = 4096; + printk("Setting logical block size of resume device to %d.\n", resume_firstblocksize); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + resume_device = name_to_dev_t(commandline); + if (!resume_device) { + if (boot_time) + sanity_check_failed("Failed to get the location of the device on which Software Suspend's header should be found."); + else + printk(name_suspend "Failed to get the location of the device on which Software Suspend's header should be found."); + goto invalid; + } + + resume_block_device = open_by_devnum(resume_device, FMODE_READ, BDEV_RAW); + + if (IS_ERR(resume_block_device)) { + if (boot_time) + sanity_check_failed("Failed to get access to the device on which Software Suspend's header should be found."); + else + printk("Failed to get access to the device on which Software Suspend's header should be found."); + goto invalid; + } +#else + resume_block_device = resume_device = name_to_kdev_t(commandline); + + if (!resume_block_device) { + if (boot_time) + sanity_check_failed("Failed to get the location of the device on which Software Suspend's header should be found."); + else + printk("Failed to get the location of the device on which Software Suspend's header should be found."); + goto invalid; + } + +#endif + + if (colon) + *colon = ':'; + if (at_symbol) + *at_symbol = '@'; + + if ((swsusp_get_block_size(resume_block_device) != resume_firstblocksize) && + (swsusp_set_block_size(resume_block_device, resume_firstblocksize) == -EINVAL)) + goto invalid; + + diskpage.address = get_zeroed_page(GFP_ATOMIC); + bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr)); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + free_page((unsigned long) diskpage.address); + + if (signature_found != -1) { + printk(KERN_ERR name_suspend "Swap space signature found.\n" ); + return 1; + } + + printk(KERN_ERR name_suspend "Sorry. No swap signature found at specified location.\n"); + return -EINVAL; + +invalid: + if (colon) + *colon = ':'; + if (at_symbol) + *at_symbol = '@'; + printk(KERN_ERR name_suspend "Sorry. Location looks invalid.\n"); + return -EINVAL; +} + +static int swapfilename_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int len = 0; + + if (swapfilename) + len = sprintf(page, "%s\n", swapfilename); + *eof = 1; + return len; +} + +static int swapfilename_write_proc(struct file *file, const char * buffer, + unsigned long count, void * data) +{ + if (count > 255) + count = 255; + + if (copy_from_user(swapfilename, buffer, count)) + return -EFAULT; + + if ((count) && (swapfilename[count - 1] == '\n')) + swapfilename[count - 1] = 0; + + swapfilename[count] = 0; + + return count; +} + +int header_locations_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int i, printedpartitionsmessage = 0, len = 0, haveswap = 0, device_block_size; + struct inode *swapf = 0; + int zone; + char * path_page = (char *) __get_free_page(GFP_KERNEL); + char * path; + int path_len; + + *eof = 1; + if (!page) + return 0; + + for (i = 0; i < MAX_SWAPFILES; i++) { + if (UNUSED_SWAP_ENTRY(i)) + continue; + + if (SWAP_IS_PARTITION(i)) { + haveswap = 1; + if (!printedpartitionsmessage) { + len += sprintf(page + len, + "For swap partitions, simply use the format: resume2=swap:/dev/hda1.\n"); + printedpartitionsmessage = 1; + } + } else { + path_len = 0; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + path = d_path( swap_info[i].swap_file->f_dentry, + swap_info[i].swap_file->f_vfsmnt, + path_page, + PAGE_SIZE); +#else + path = d_path( swap_info[i].swap_file, + swap_info[i].swap_vfsmnt, + path_page, + PAGE_SIZE); +#endif + path_len = sprintf(path_page, "%-31s ", path); + + haveswap = 1; + swapf = SWAP_FILE_INODE(i); + device_block_size = SWAP_BLOCKSIZE(SWAP_DEVICE_BDEV(i)); + if (!(zone = bmap(swapf,0))) { + len+= sprintf(page + len, + "Swapfile %-31s has been corrupted. Reuse mkswap on it and try again.\n", + path_page); + } else { + len+= sprintf(page + len, "For swapfile `%s`, use resume2=swap:/dev/:0x%x@%d.\n", + path_page, + zone, device_block_size); + } + + } + } + + if (!haveswap) + len = sprintf(page, "You need to turn on swap partitions before examining this file.\n"); + + free_pages((unsigned long) path_page, 0); + return len; +} + +static void swapwriter_noresume_reset(void) +{ + int i; + + /* + * If we have read part of the image, we might have filled header_data with + * data that should be zeroed out. + */ + + memset((char *) &header_data, 0, sizeof(header_data)); + for (i = 0; i < MAX_SWAPFILES; i++) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + swap_info[i].swap_device = 0; +#endif + RESUME_BDEV(i) = DEVICE_BLOCK_NONE; + } + +} +struct swsusp_proc_data swapfilename_proc_data = { + .filename = "swapfilename", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_CUSTOM, + .data = { + .special = { + .read_proc = swapfilename_read_proc, + .write_proc = swapfilename_write_proc, + } + } +}; + +struct swsusp_proc_data headerlocations_proc_data = { + .filename = "headerlocations", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_CUSTOM, + .data = { + .special = { + .read_proc = header_locations_read_proc, + } + } +}; + +struct swsusp_plugin_ops swapwriterops = { + .type = WRITER_PLUGIN, + .name = "Swap Writer", + .memory_needed = swapwriter_memory_needed, + .print_debug_info = swapwriter_print_debug_stats, + .storage_needed = swapwriter_storage_needed, + .write_init = swapwriter_write_init, + .write_chunk = swapwriter_write_chunk, + .write_cleanup = swapwriter_write_cleanup, + .read_init = swapwriter_read_init, + .read_chunk = swapwriter_read_chunk, + .read_cleanup = swapwriter_read_cleanup, + .noresume_reset = swapwriter_noresume_reset, + .ops = { + .writer = { + .storage_available = swapwriter_storage_available, + .storage_allocated = swapwriter_storage_allocated, + .release_storage = swapwriter_release_storage, + .allocate_header_space = swapwriter_allocate_header_space, + .allocate_storage = swapwriter_allocate_storage, + .image_exists = swapwriter_image_exists, + .write_header_init = swapwriter_write_header_init, + .write_header_chunk = swapwriter_write_header_chunk, + .write_header_cleanup = swapwriter_write_header_cleanup, + .read_header_init = swapwriter_read_header_init, + .read_header_chunk = swapwriter_read_header_chunk, + .read_header_cleanup = swapwriter_read_header_cleanup, + .prepare_save_ranges = swapwriter_prepare_save_ranges, + .post_load_ranges = swapwriter_post_load_ranges, + .invalidate_image = swapwriter_invalidate_image, + .wait_on_io = swapwriter_wait_on_io, + .parse_image_location = swapwriter_parse_image_location, + } + } +}; + +/* ---- Registration ---- */ +static __init int swapwriter_load(void) +{ + int result; + printk("Software Suspend Swap Writer v1.0\n"); + + if (!(result = swsusp_register_plugin(&swapwriterops))) { + swsusp_register_procfile(&swapfilename_proc_data); + swsusp_register_procfile(&headerlocations_proc_data); + } + return result; +} + +__initcall(swapwriter_load); + diff -ruN post-version-specific/kernel/power/swsusp2.c software-suspend-core-2.0/kernel/power/swsusp2.c --- post-version-specific/kernel/power/swsusp2.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/swsusp2.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,1345 @@ +/* + * kernel/power/swsusp2.c + * + * Copyright (C) 1998-2001 Gabor Kuti + * Copyright (C) 1998,2001,2002 Pavel Machek + * Copyright (C) 2002-2003 Florent Chabaud + * Copyright (C) 2002-2003 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * This file is to realize architecture-independent + * machine suspend feature using pretty near only high-level routines + * + * We'd like to thank the following people for their work: + * + * Pavel Machek : + * 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 : + * Support the possibility of hardware state restoring. + * + * Raph : + * Support for preserving states of network devices and virtual console + * (including X and svgatextmode) + * + * Kurt Garloff : + * Straightened the critical function in order to prevent compilers from + * playing tricks with local variables. + * + * Andreas Mohr + * + * Alex Badea : + * Fixed runaway init + * + * Jeff Snyder + * ACPI patch + * + * Nathan Friess + * Some patches. + * + * Michael Frank + * Extensive testing and help with improving stability. + * + * More state savers are welcome. Especially for the scsi layer... + * + * For TODOs,FIXMEs also look in Documentation/swsusp.txt. + * + * Variable definitions which are needed if PM is enabled but + * SOFTWARE_SUSPEND is disabled are found near the top of process.c. + */ + +#define SWSUSP_MAIN_C + +#include +#include +#ifdef CONFIG_X86 +#include /* for kernel_fpu_end */ +#endif +#ifdef CONFIG_KDB +#include +#endif /* CONFIG_KDB */ + +#if (SWSUSP_VERSION_SPECIFIC_REVISION != SWSUSP_CORE_REVISION) +#error The core and version specific patch revisions of Software Suspend are incompatible. +#error You can find your core version from include/linux/suspend-debug.h +#error and you version specific revision from include/linux/suspend-version-specific.h. +#endif + +unsigned int nr_suspends = 0; + +/* Variables to be preserved over suspend */ +int pageset2_sizelow = 0; +struct pagedir __nosavedata pagedir_resume; +int __nosavedata pages_written = 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +static int pm_suspend_state = 0; +#endif + +struct list_head swsusp_filters, swsusp_writers, swsusp_plugins; +int num_filters = 0, num_writers = 0; +struct swsusp_plugin_ops * first_filter = NULL; +struct swsusp_plugin_ops * active_writer = NULL; + +int swsusp_act_used = 0; +int swsusp_lvl_used = 0; +int swsusp_dbg_used = 0; + +char resume_file[256] = CONFIG_SOFTWARE_SUSPEND_DEFAULT_RESUME2; /* For resume= kernel option */ + +unsigned long orig_mem_free = 0; + +static void drivers_resume(int); +extern void do_swsusp_lowlevel(int resume); +extern unsigned long header_storage_for_plugins(void); +extern int pm_prepare_console(void); +extern void pm_restore_console(void); +void empty_swsusp_memory_pool(void); +int read_primary_suspend_image(void); + +unsigned long * inusemap = NULL; +unsigned long * pageset2map = NULL; +unsigned long * nosavemap = NULL; + +/* 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_resume 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. + */ + +struct pagedir pagedir1 = { 1, 0, 0}, pagedir2 = {2, 0, 0}; + +unsigned long swsusp_debug_state = 0; + +int image_size_limit = 0; +int max_async_ios = 32; +int currentbeep = 260; + +/* Pagedir.c */ +extern void copy_pageset1(void); +extern int allocatemap(unsigned long ** pagemap, int setnosave); +extern void free_pagedir(struct pagedir * p); +extern int freemap(unsigned long ** pagemap); + +/* Prepare_image.c */ + +extern int prepare_image(void); + +/* proc.c */ + +extern int swsusp_init_proc(void); +extern int swsusp_cleanup_proc(void); + +/* process.c */ +extern atomic_t swsusp_cpu_counter; + +/* include/asm-i386/suspend.h */ +extern void smp_swsusp_lowlevel(void * info); + +static void ensure_on_processor_zero(void) +{ +#ifdef CONFIG_SMP + set_cpus_allowed(current, CPU0_MASK); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + if (unlikely(cpu_number_map(smp_processor_id()) != 0)) + BUG(); +#else + BUG_ON(smp_processor_id() != 0); +#endif +#endif +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +/* Make disk drivers accept operations, again */ +void storage_unsuspend(void) +{ +#ifdef CONFIG_SCSI + struct pm_dev *dev = NULL; + + while ((dev = pm_find(PM_SCSI_DEV, dev))) + pm_send(dev, PM_RESUME, (void *) 0); + +#endif +#ifdef CONFIG_BLK_DEV_IDE + ide_disk_unsuspend(0); +#endif +} + +void storage_suspend(void) +{ +#ifdef CONFIG_SCSI + struct pm_dev *dev = NULL; + + while ((dev = pm_find(PM_SCSI_DEV, dev))) + pm_send(dev, PM_SUSPEND, (void *) 3); + +#endif +#ifdef CONFIG_BLK_DEV_IDE + do_suspend_sync(); + + ide_disk_suspend(); +#endif +} +#endif + +#define RESUME_PHASE1 1 /* Called from interrupts disabled */ +#define RESUME_PHASE2 2 /* Called with interrupts enabled */ +#define RESUME_ALL_PHASES (RESUME_PHASE1 | RESUME_PHASE2) + +void drivers_resume(int flags) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + 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_SCSI + { + struct pm_dev *dev = NULL; + + while ((dev = pm_find(PM_SCSI_DEV, dev))) + pm_send(dev, PM_RESUME, (void *) 0); + } +#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_suspend "Problem while sending resume event\n"); + pm_suspend_state=0; + } else + printk(name_suspend "PM suspend state wasn't raised\n"); + +#ifdef DEFAULT_SUSPEND_CONSOLE + update_screen(fg_console); +#endif + } +#else + if (flags & RESUME_PHASE1) + device_resume(); +#endif +} + +/* Called from process context */ +static int drivers_suspend(void) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#ifdef CONFIG_BLK_DEV_MD + md_notify_reboot(NULL, SYS_HALT, NULL); +#endif + STORAGE_SUSPEND + if (!pm_suspend_state) { + if (pm_send_all(PM_SUSPEND,(void *)3)) { + printk(name_suspend "Problem while sending suspend event\n"); + drivers_resume(RESUME_ALL_PHASES); + return(1); + } + pm_suspend_state=1; + } + + return(0); +#else + return device_suspend(4); +#endif +} + +/* + * save_image + * Result code (int): Zero on success, non zero on failure. + * Functionality : High level routine which performs the steps necessary + * to prepare and save the image after preparatory steps + * have been taken. + * Key Assumptions : Processes frozen, sufficient memory available, drivers + * suspended. + * Called from : do_swsusp2_suspend_2 + */ +extern struct pageset_sizes_result recalculate_stats(void); +extern void display_stats(void); +extern int write_pageset(struct pagedir * pagedir, int whichtowrite); +extern int write_image_header(void); +extern int read_secondary_pagedir(int overwrittenpagesonly); + +static int save_image(void) +{ + int temp_result; + + if (RAM_TO_SUSPEND > max_mapnr) { + printk(KERN_CRIT name_suspend "Couldn't get enough free pages, on %ld pages short\n", + RAM_TO_SUSPEND - max_mapnr); + prepare_status(1, 1, "Couldn't get enough free pages, on %ld pages short\n", + RAM_TO_SUSPEND - max_mapnr); + goto abort_saving; + } + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW," - Final values: %d and %d.\n", + pageset1_size, + pageset2_size); + + pagedir1.lastpageset_size = pageset1_size; + pagedir2.lastpageset_size = pageset2_size; + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW,"-- Preparing to write pages\n"); + + check_shift_keys(1, "About to write pagedir2. "); + + temp_result = write_pageset(&pagedir2, 2); + + if (temp_result == -1 || TEST_RESULT_STATE(SUSPEND_ABORTED)) { + goto abort_saving; + } + + pages_written = temp_result; + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW,"-- Written pageset2\n"); + + check_shift_keys(1, "About to copy pageset 1."); + + drivers_suspend(); + + PRINTPREEMPTCOUNT("Prior to lowlevel suspend."); + + do_swsusp_lowlevel(0); + + PRINTPREEMPTCOUNT("At exit from save_image."); + return 0; +abort_saving: + PRINTPREEMPTCOUNT("At exit from save_image."); + return -1; +} + +int save_image_part1(void) +{ + int temp_result; + + prepare_status(1, 0, "Copying pageset1."); + + PRINTPREEMPTCOUNT("Prior to copying pageset 1."); + + copy_pageset1(); + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW,"-- Done\n"); + + /* + * --------------------- FROM HERE ON, NEED TO REREAD PAGESET2 IF ABORTING!!! ----------------- + * (We need to ensure saved pages are freed even if memory is still correct). + */ + + swsusp_spin_unlock_irqrestore(&suspend_irq_lock, swsuspirqflags); + +#ifdef CONFIG_PREEMPT + preempt_enable(); +#endif + +#ifdef CONFIG_X86 + kernel_fpu_end(); +#endif + + PRINTPREEMPTCOUNT("Prior to resuming drivers."); + + drivers_resume(RESUME_PHASE1); + + STORAGE_UNSUSPEND + + /* Other processors have waited for me to make the atomic copy of the kernel */ + swsusp_state &= ~FREEZE_SMP; + + while (atomic_read(&swsusp_cpu_counter) && + (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + goto abort_reloading_pagedir_two; + + check_shift_keys(1, "About to write pageset1. "); + + /* + * End of critical section. + */ + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW,"-- Writing pageset1\n"); + + temp_result = write_pageset(&pagedir1, 1); + + if (temp_result == -1 || TEST_RESULT_STATE(SUSPEND_ABORTED)) + goto abort_reloading_pagedir_two; + + pages_written += temp_result; + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW,"-- Done\n"); + + check_shift_keys(1, "About to write header. "); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + goto abort_reloading_pagedir_two; + + temp_result = write_image_header(); + + if (temp_result || (TEST_RESULT_STATE(SUSPEND_ABORTED))) + goto abort_reloading_pagedir_two; + + check_shift_keys(1, "About to power down or reboot."); + + PRINTPREEMPTCOUNT("At exit from save_image."); + return 0; + +abort_reloading_pagedir_two: + temp_result = read_secondary_pagedir(1); + if (temp_result) + panic("Attempt to reload pagedir 2 while aborting a suspend failed."); + + PRINTPREEMPTCOUNT("At exit from save_image."); + return -1; + +} + +/* + * suspend_power_down + * Functionality : Powers down or reboots the computer once the image + * has been written to disk. + * Key Assumptions : Able to reboot/power down via code called or that + * the warning emitted if the calls fail will be visible + * to the user (ie printk resumes devices). + * Called From : do_swsusp2_suspend_2 + */ + +extern asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd, void * arg); +extern void apm_power_off(void); + +void suspend_power_down(void) +{ + C_A_D = 0; + /* No delay should be needed - ide_disk_suspend purges cache now */ + beepOK; /* last beep */ + prepare_status(1, 0, "Ready to power down."); +#if defined(CONFIG_VT) && defined(CONFIG_SOFTWARE_SUSPEND_DEBUG) + if (TEST_ACTION_STATE(SUSPEND_REBOOT)) + sys_reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART, NULL); +#endif +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + drivers_suspend(); +#endif + sys_reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL); + + 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 */ +} + +/* + * do_swsusp2_resume_1 + * Functionality : Preparatory steps for copying the original kernel back. + * Called From : include/asm/suspend.h:do_swsusp_lowlevel + */ + +void do_swsusp2_resume_1(void) +{ + /* Get other cpus ready to restore their original contexts */ + + swsusp_state |= FREEZE_SMP; + + printk("Putting other CPUs in swsusp_lowevel in preparation for restoring contexts:\n"); + + smp_call_function(smp_swsusp_lowlevel, NULL, 0, 0); + + while ((atomic_read(&swsusp_cpu_counter) < (NUM_CPUS - 1)) && + (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + + printk("\nDone.\n"); + + drivers_suspend(); + barrier(); + mb(); + swsusp_spin_lock_irqsave(&suspend_irq_lock, swsuspirqflags); /* Done to disable interrupts */ + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, name_suspend "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 ;-). */ + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, name_suspend "About to copy pageset1 back...\n"); +} + +/* + * do_swsusp2_resume_2 + * Functionality : Steps taken after copying back the original kernel at + * resume. + * Key Assumptions : Will be able to read back secondary pagedir (if + * applicable). + * Called From : include/asm/suspend.h:do_swsusp_lowlevel + */ + +extern void post_resume_console_redraw(void); + +void do_swsusp2_resume_2(void) +{ + + flush_tlb_all(); + + tainted |= 4; /* Taint the kernel because we have suspended */ + now_resuming = 1; + +#ifdef DEFAULT_SUSPEND_CONSOLE + post_resume_console_redraw(); +#endif + + PRINTPREEMPTCOUNT("In resume_2 after copy back."); + + drivers_resume(RESUME_PHASE1); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + STORAGE_UNSUSPEND +#endif + + check_shift_keys(1, "About to reload secondary pagedir.\n"); + + read_secondary_pagedir(0); + + prepare_status(0, 0, "Cleaning up..."); +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if (TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) { + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, name_suspend "Not invalidating the image due to Keep Image being enabled.\n"); + SET_RESULT_STATE(SUSPEND_KEPT_IMAGE); + } else +#endif + { + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, name_suspend "Removing image... "); + active_writer->ops.writer.invalidate_image(); + } + swsusp_state &= ~USE_MEMORY_POOL; + + PRINTPREEMPTCOUNT("In resume_2 after fpu end."); + +#ifdef CONFIG_PREEMPT + preempt_enable(); +#endif + + PRINTPREEMPTCOUNT("After preempt enable."); + + swsusp_spin_unlock_irqrestore(&suspend_irq_lock, swsuspirqflags); + PRINTPREEMPTCOUNT("After unlocking irq_lock."); + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, name_suspend "Resume drivers.\n"); + drivers_resume(RESUME_ALL_PHASES); + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, "ok\n"); +} + +/* + * do_swsusp2_suspend_1 + * Functionality : Steps taken prior to saving CPU state and the image + * itself. + * Called From : include/asm/suspend.h:do_swsusp_lowlevel + */ + +void do_swsusp2_suspend_1(void) +{ + /* Save other cpu contexts */ + + swsusp_state |= FREEZE_SMP; + + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "Saving contexts:\n"); + + smp_call_function(smp_swsusp_lowlevel, NULL, 0, 0); + + while ((atomic_read(&swsusp_cpu_counter) < (NUM_CPUS - 1)) && + (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "All other processors have saved their contexts. CPU counter ->%d\n", + atomic_read(&swsusp_cpu_counter)); + + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "\nDone.\n"); + + mb(); + barrier(); + + PRINTPREEMPTCOUNT("Before preempt disable."); +#ifdef CONFIG_PREEMPT + preempt_disable(); +#endif + PRINTPREEMPTCOUNT("Before irq lock."); + swsusp_spin_lock_irqsave(&suspend_irq_lock, swsuspirqflags); + PRINTPREEMPTCOUNT("After irq lock."); +} + +/* + * do_swsusp2_suspend_2 + * Functionality : Steps taken after saving CPU state to save the + * image and powerdown/reboot or recover on failure. + * Key Assumptions : save_image returns zero on success; otherwise we need to + * clean up and exit. The state on exiting this routine + * should be essentially the same as if we have suspended, + * resumed and reached the end of do_swsusp2_resume_2. + * Called From : include/asm/suspend.h:do_swsusp_lowlevel + */ +void do_swsusp2_suspend_2(void) +{ + if (!save_image_part1()) + suspend_power_down(); + + if (!TEST_RESULT_STATE(SUSPEND_ABORT_REQUESTED)) + 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(); + + active_writer->ops.writer.invalidate_image(); + drivers_resume(RESUME_ALL_PHASES); +} + +static inline void lru_check_page(struct page * page) +{ + if (!PageLRU(page)) + printk("Page %p/%p in inactivelist but not marked LRU.\n", + page, page_address(page)); +} + +int suspend_snprintf(char * buffer, int buffer_size, const char *fmt, ...) +{ + int result; + va_list args; + + if (!buffer_size) { + return 0; + } + + va_start(args, fmt); + result = vsnprintf(buffer, buffer_size, fmt, args); + va_end(args); + + if (result > buffer_size) { + return buffer_size; + } + + return result; +} + +static int print_plugin_debug_info(char * buffer, int buffer_size) +{ + struct list_head *plugin; + struct swsusp_plugin_ops *this_plugin = NULL; + int len = 0; + + list_for_each(plugin, &swsusp_plugins) { + this_plugin = list_entry(plugin, struct swsusp_plugin_ops, plugin_list); + if (this_plugin->print_debug_info) { + int result; + result = this_plugin->print_debug_info(buffer + len, buffer_size - len); + len += result; + } + } + + return len; +} + +/* get_debug_info + * Functionality: Store debug info in a buffer. + * Called from: Worker thread or via do_software_suspend. + */ + +#define SNPRINTF(a...) len += suspend_snprintf(buffer + len, buffer_size - len - 1, ## a); + +static int get_suspend_debug_info(char * buffer, int buffer_size) +{ + int len = 0; + + SNPRINTF("Please include the following information in bug reports:\n"); + SNPRINTF("- SWSUSP core : %s\n", swsusp_version); + SNPRINTF("- Kernel Version : %s\n", UTS_RELEASE); + SNPRINTF("- Version spec. : %s\n", SWSUSP_VERSION_SPECIFIC_REVISION_STRING); + SNPRINTF("- Compiler vers. : %d.%d\n", __GNUC__, __GNUC_MINOR__); +#ifdef CONFIG_MODULES + { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + struct module *this_mod; + SNPRINTF("- Modules loaded : "); + this_mod = module_list; + while (this_mod) { + if (this_mod->name) + SNPRINTF("%s ", this_mod->name); + this_mod = this_mod->next; + } +#else + extern int print_module_list_to_buffer(char * buffer, int size); + len+= print_module_list_to_buffer(buffer + len, buffer_size - len - 1); +#endif + } + SNPRINTF("\n"); +#else + SNPRINTF("- No module support.\n"); +#endif + SNPRINTF("- Attempt number : %d\n", nr_suspends); + SNPRINTF("- Pageset sizes : %d and %d (%d low).\n", + pagedir1.lastpageset_size, + pagedir2.lastpageset_size, + pageset2_sizelow); +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + SNPRINTF("- Parameters : %ld %ld %ld %d %d %d\n", + swsusp_result, + swsusp_action, + swsusp_debug_state, + swsusp_default_console_level, + image_size_limit, + max_async_ios); +#else + SNPRINTF("- Parameters : %ld %ld %d %d\n", + swsusp_result, + swsusp_action, + image_size_limit, + max_async_ios); +#endif + SNPRINTF("- Calculations : Image size: %lu. Ram to suspend: %ld.\n", + STORAGE_NEEDED, RAM_TO_SUSPEND); + SNPRINTF("- Limits : %lu pages RAM. Initial boot: %lu.\n", + max_mapnr, orig_mem_free); + SNPRINTF("- Overall expected compression percentage: %d.\n", 100 - expected_compression_ratio()); + len+= print_plugin_debug_info(buffer + len, buffer_size - len - 1); +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + SNPRINTF("- Debugging compiled in.\n"); +#endif +#ifdef CONFIG_PREEMPT + SNPRINTF("- Preemptive kernel.\n"); +#endif +#ifdef CONFIG_SMP + SNPRINTF("- SMP kernel.\n"); +#endif +#ifdef CONFIG_HIGHMEM + SNPRINTF("- Highmem Support.\n"); +#endif + SNPRINTF("- Max ranges used: %d ranges in %d pages.\n", + max_ranges_used, num_range_pages); + + return len; +} + +/* + * display_debug_info + * Functionality : At the end of resuming, displays information that may be + * helpful in debugging software suspend. + * Called From : do_software_suspend + */ +static void display_debug_info(void) +{ + int len; + char * buffer = (char *) get_zeroed_page(GFP_ATOMIC); + + if (!buffer) + printk("Unable to allocate a buffer for debug info!.\n"); + + len = get_suspend_debug_info(buffer, PAGE_SIZE); + printk("%s", buffer); + free_pages((unsigned long) buffer, 0); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +static unsigned long kstat_store = 0; +/* kstat_save + * Save the contents of the kstat array so that + * our labours are hidden from vmstat + */ +static int kstat_save(void) +{ + const int bytes_per_page = PAGE_SIZE - sizeof(unsigned long); + int kstat_pages = (sizeof(kstat) + bytes_per_page - 1) / bytes_per_page; + int i, source_offset = 0, bytes_to_copy; + unsigned long this_page, last_page = 0; + + kstat_store = this_page = get_zeroed_page(GFP_ATOMIC); + for (i = 1; i <= kstat_pages; i++) { + if (!this_page) { + int k; + this_page = kstat_store; + for (k = 1; k < i; k++) { + unsigned long next_page = *((unsigned long *) this_page + ((PAGE_SIZE / sizeof(unsigned long)) - 1)); + free_pages(this_page, 0); + this_page = next_page; + } + kstat_store = 0; + return -ENOMEM; + } + bytes_to_copy = ((sizeof(kstat) - source_offset) >= bytes_per_page) ? + bytes_per_page : (sizeof(kstat) - source_offset); + memcpy((char *) this_page, ((char *) &kstat) + source_offset, bytes_to_copy); + if (i < kstat_pages) { + last_page = this_page; + this_page = get_zeroed_page(GFP_ATOMIC); + *((unsigned long *) last_page + ((PAGE_SIZE / sizeof(unsigned long)) - 1)) = this_page; + source_offset += bytes_per_page; + } + } + return 0; +} + +/* kstat_restore + * Restore a previously saved kstat array + */ +static void kstat_restore(void) +{ + const int bytes_per_page = PAGE_SIZE - sizeof(unsigned long); + int kstat_pages = (sizeof(kstat) + bytes_per_page - 1) / bytes_per_page; + int i, source_offset = 0, bytes_to_copy; + unsigned long this_page = kstat_store, next_page; + + if (!kstat_store) + return; + + for (i = 1; i <= kstat_pages; i++) { + bytes_to_copy = ((sizeof(kstat) - source_offset) >= bytes_per_page) ? + bytes_per_page : (sizeof(kstat) - source_offset); + memcpy(((char *) &kstat) + source_offset, (char *) this_page, bytes_to_copy); + next_page = *((unsigned long *) this_page + ((PAGE_SIZE / sizeof(unsigned long)) - 1)); + source_offset += bytes_per_page; + free_pages(this_page, 0); + this_page = next_page; + } + kstat_store = 0; +} +#else +#define kstat_save() +#define kstat_restore() +#endif + +#define SUSPEND_C +#include + +/* + * do_software_suspend + * Functionality : First level of code for software suspend invocations. + * Stores and restores load averages (to avoid a spike), + * allocates bitmaps, freezes processes and eats memory + * as required before suspending drivers and invoking + * the 'low level' code to save the state to disk. + * By the time we return from do_swsusp_lowlevel, we + * have either failed to save the image or successfully + * suspended and reloaded the image. The difference can + * be discerned by checking SUSPEND_ABORTED. + * Called From : + */ +extern inline int notify_resume(void); +static void display_debug_info(void); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#if defined(CONFIG_MAGIC_SYSRQ) && defined(CONFIG_VT) +extern int sysrq_pressed; +#endif +#endif + +static void do_software_suspend(void) +{ + unsigned long avenrun_save[3]; + int i; + + if (software_suspend_state & 3) { + printk(name_suspend "Software suspend is disabled or already running.\n"); + printk("This may be because you haven't put something along the lines of\n"); + printk("\nresume2=swap:/dev/hda1\n\n"); + printk("In lilo.conf or equivalent. (Where /dev/hda1 is your swap partition).\n"); + + SET_RESULT_STATE(SUSPEND_ABORTED); + return; + } + + /* Suspend always runs on processor 0 */ + ensure_on_processor_zero(); + +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if (TEST_RESULT_STATE(SUSPEND_KEPT_IMAGE)) { + if (TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) { + printk("Image already stored: powering down immediately."); + suspend_power_down(); + return; /* It shouldn't, but just in case */ + } else { + printk("Invalidating previous image.\n"); + active_writer->ops.writer.invalidate_image(); + } + } +#endif + + printk(name_suspend "Initiating a software_suspend cycle.\n"); + BUG_ON(in_interrupt()); + software_suspend_state |= SOFTWARE_SUSPEND_RUNNING; + swsusp_result = 0; + max_ranges_used = 0; + nr_suspends++; + now_resuming = 0; + + PRINTFREEMEM("at start of do_software_suspend"); + + /* + * Running swsusp makes for a very high load average. I'm told that + * sendmail and crond check the load average, so in order for them + * not to be unnecessarily affected by the operation of swsusp, we + * store the avenrun values prior to suspending and restore them + * at the end of the resume cycle. Thus, the operation of swsusp + * should be invisible to them. Thanks to Marcus Gaugusch and Bernard + * Blackham for noticing the problem and suggesting the solution. + */ + + for (i = 0; i < 3; i++) + avenrun_save[i] = avenrun[i]; + + /* Suspend console switch (if necessary) */ + if (pm_prepare_console()) + printk(name_suspend "Can't allocate a console... proceeding\n"); + + PRINTFREEMEM("after preparing suspend_console"); + + beepOK; /* first beep */ + + prepare_status(1, 1, "Attempting to allocate memory for page flags."); + + printlog(SUSPEND_MEMORY, SUSPEND_VERBOSE, "Allocating inusemap\n"); + if (allocatemap(&inusemap, 1)) + goto out; + PRINTFREEMEM("after allocating inusemap"); + + if (allocatemap(&pageset2map, 1)) + goto out; + PRINTFREEMEM("after allocating pageset2 map"); + + set_chain_names(&pagedir1); + set_chain_names(&pagedir2); + + /* Free up memory if necessary */ + printlog(SUSPEND_ANY_SECTION, SUSPEND_VERBOSE, "Preparing image.\n"); + PRINTPREEMPTCOUNT("Before preparing image."); + if (prepare_image() || TEST_RESULT_STATE(SUSPEND_ABORTED)) + goto out; + + PRINTPREEMPTCOUNT("After prepare_image."); + PRINTFREEMEM("after preparing image"); + + if (TEST_ACTION_STATE(SUSPEND_FREEZER_TEST)) + goto out; + + beepOK; /* second beep */ + + /* We don't want suspend to show in the kernel statistics - + * it should be transparent to userspace */ + kstat_save(); + + if (!TEST_RESULT_STATE(SUSPEND_ABORTED)) { + prepare_status(1, 0, "Starting to save the image.."); + save_image(); + PRINTPREEMPTCOUNT("After saving the image."); + beepOK; + } + + display_debug_info(); + + check_shift_keys(1, NULL); + +out: + PRINTFREEMEM("at 'out'"); + + swsusp_state &= ~USE_MEMORY_POOL; + STORAGE_UNSUSPEND + + free_pagedir(&pagedir2); + free_pagedir(&pagedir1); + +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if (!TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) +#endif + active_writer->ops.writer.invalidate_image(); + + empty_swsusp_memory_pool(); + PRINTFREEMEM("after freeing memory pool"); + + free_ranges(); + PRINTFREEMEM("after freeing ranges"); + + freemap(&pageset2map); + PRINTFREEMEM("after freeing pageset2 map"); + + freemap(&inusemap); + PRINTFREEMEM("after freeing inuse map"); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#if defined(CONFIG_MAGIC_SYSRQ) && defined(CONFIG_VT) + sysrq_pressed = 0; +#endif +#endif + /* Restore stats before we restart processes */ + for (i = 0; i < 3; i++) + avenrun[i] = avenrun_save[i]; + + kstat_restore(); + + thaw_processes(); + + if (notify_resume()) + printk(KERN_EMERG "Failed to notify resume chain.\n"); + + MDELAY(1000); + check_shift_keys(1, "About to restore original console. "); + pm_restore_console(); +#ifdef DEFAULT_SUSPEND_CONSOLE + update_screen(fg_console); +#endif + PRINTFREEMEM("at end of do_software_suspend"); + + software_suspend_state &= ~SOFTWARE_SUSPEND_RUNNING; +#ifdef CONFIG_PREEMPT + PRINTPREEMPTCOUNT("Exiting with preempt count"); +#endif +} + +static DECLARE_MUTEX(kswsuspd_access); +static DECLARE_WAIT_QUEUE_HEAD(swsusp_wait); +static DECLARE_COMPLETION(work_complete); +static void * work_data = NULL; + +void start_kswsuspd(void * data) +{ + down(&kswsuspd_access); + init_completion(&work_complete); + work_data = data; + wake_up_interruptible(&swsusp_wait); + wait_for_completion(&work_complete); + up(&kswsuspd_access); +} + +/* + * This is main interface to the outside world. It needs to be + * called from process context. It sets the active flag, + * notifies kswsuspd thread through the wake up and waits + * for ACTIVE flag to be cleared. + */ + +void software_suspend_pending(void) +{ + int action = 1; + + current->flags |= PF_NOFREEZE; + start_kswsuspd(&action); + current->flags &= ~PF_NOFREEZE; +} + +/* + * This is kswsuspd, the kernel daemon which waits to be woken by a userspace + * daemon (via software_suspend_pending). It signals completion (failure or + * the end of a full suspend/resume cycle) by resetting cycle_unfinished. In + * this way, the userspace process blocks until the cycle is complete and a + * script doesn't need to poll the proc entry (there is now no way to tell + * from /proc/sys/kernel/swsusp whether the cycle has completed anyway). + */ + +static int get_suspend_debug_info(char * buffer, int buffer_size); + +static int swsusp_mainloop(void *unused) +{ + printk(name_suspend "kswsuspd starting\n"); /* This will print the current version on boot */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + daemonize(); + strcpy(current->comm, "kswsuspd"); +#else + daemonize("kwsuspd"); +#endif + current->flags |= PF_NOFREEZE; + + sigfillset(¤t->blocked); + software_suspend_state &= ~SOFTWARE_SUSPEND_RUNNING; + + for(;;) { + interruptible_sleep_on(&swsusp_wait); + schedule(); + switch (*((int *) work_data)) { + case 1: /* Suspend */ + swsusp_result = 0; + do_software_suspend(); +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + currentbeep = 100; /* not the original one, so that we know if this is first or subsequent suspend */ +#endif + break; + case 2: /* Temporarily turn on swap (where applicable) for getting debugging info */ + { + struct debug_info_data * did = (struct debug_info_data *) work_data; + did->bytes_used = get_suspend_debug_info(did->buffer, did->buffer_size); + } + } + + complete(&work_complete); + } + + swsusp_cleanup_proc(); + + return(0); +} + +int swsusp_register_plugin(struct swsusp_plugin_ops * plugin) +{ + if (!(num_filters + num_writers)) { + INIT_LIST_HEAD(&swsusp_filters); + INIT_LIST_HEAD(&swsusp_writers); + INIT_LIST_HEAD(&swsusp_plugins); + } + + switch (plugin->type) { + case FILTER_PLUGIN: + { + list_add_tail(&plugin->ops.filter.filter_list, &swsusp_filters); + if (!num_filters) + first_filter = plugin; + num_filters++; + } + break; + case WRITER_PLUGIN: + { + list_add_tail(&plugin->ops.writer.writer_list, &swsusp_writers); + num_writers++; + } + break; + default: + printk("Hmmm. Plugin '%s' has an invalid type. It has been ignored.\n", plugin->name); + return -EINVAL; + } + list_add(&plugin->plugin_list, &swsusp_plugins); + + return 0; +} + +struct swsusp_plugin_ops * get_next_filter(struct swsusp_plugin_ops * filter_sought) +{ + struct list_head *filter; + struct swsusp_plugin_ops * last_filter = NULL, *this_filter = NULL; + + list_for_each(filter, &swsusp_filters) { + this_filter = list_entry(filter, struct swsusp_plugin_ops, ops.filter.filter_list); + if (last_filter == filter_sought) + return this_filter; + last_filter = this_filter; + } + + return active_writer; +} + +int attempt_to_parse_resume_device(int boot_time) +{ + struct list_head *writer; + struct swsusp_plugin_ops * this_writer; + int result = 0; + + if (!num_writers) { + printk(name_suspend "No writers have been registered.\n"); + return 0; + } + + software_suspend_state &= ~SOFTWARE_SUSPEND_RESUME_DEVICE_OK; + software_suspend_state |= SOFTWARE_SUSPEND_DISABLED; + + if (!resume_file[0]) + return -EINVAL; + + list_for_each(writer, &swsusp_writers) { + this_writer = list_entry(writer, struct swsusp_plugin_ops, ops.writer.writer_list); + result = this_writer->ops.writer.parse_image_location(resume_file, boot_time); + switch (result) { + case -EINVAL: /* For this writer, but not a valid configuration */ + printk(name_suspend "Not able to successfully parse this resume device.\n"); + return result; + case 0: /* Not for this writer. Try the next one. */ + break; + case 1: /* For this writer and valid. */ + active_writer = this_writer; + + /* We may not have any filters compiled in */ + if (!first_filter) + first_filter = (struct swsusp_plugin_ops *) this_writer; + software_suspend_state |= SOFTWARE_SUSPEND_RESUME_DEVICE_OK; + software_suspend_state &= ~3; + return 0; + } + } + printk(name_suspend "No matching writer found.\n"); + return -EINVAL; +} + +/* + * Called from init kernel_thread. + * We check if we have an image and if so we try to resume. + * We also start kswsuspd if configuration looks right. + */ + +extern int freeze_processes(int no_progress); +extern int orig_loglevel; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +void software_resume2(void) +{ +#define RESUME2_RET +#else +static int __init software_resume2(void) +{ +#define RESUME2_RET ret + int ret = 0; +#endif + int read_image_result = 0; + orig_loglevel = console_loglevel; + + PRINTPREEMPTCOUNT("at start of software_resume2"); + + if (sizeof(swp_entry_t) != sizeof(long)) { + printk(KERN_WARNING name_suspend "The size of swp_entry_t != size of long. Please report this!\n"); + return RESUME2_RET; + } + + if (!resume_file) + printk(KERN_WARNING name_suspend "You need to use a resume2= command line parameter to tell Software Suspend 2 where to look for an image.\n"); + else + attempt_to_parse_resume_device(1); + + swsusp_init_proc(); + + if (!(software_suspend_state & SOFTWARE_SUSPEND_RESUME_DEVICE_OK)) { + /* Without a usable storage device we can do nothing - even if noresume is given */ + if (!num_writers) + printk(KERN_ALERT name_suspend "No writers have been registered. You must select a method of storing the image when compiling the kernel.\n"); + else + printk(KERN_ALERT name_suspend "Missing or invalid storage location (resume2= parameter). Please correct and rerun lilo (or equivalent) before suspending.\n"); + kernel_thread(swsusp_mainloop, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD); + return RESUME2_RET; + } + + /* We enable the possibility of machine suspend */ + orig_mem_free = nr_free_pages(); + + suspend_task = current->pid; + + printk(name_suspend "Checking for image...\n"); + + PRINTPREEMPTCOUNT("when about to read primary image"); + + read_image_result = read_primary_suspend_image(); /* non fatal error ignored */ + + if (software_suspend_state & SOFTWARE_SUSPEND_NORESUME_SPECIFIED) + printk(KERN_WARNING name_suspend "Resuming disabled as requested.\n"); + + if (read_image_result) { + suspend_task = 0; + software_suspend_state &= ~3; + kernel_thread(swsusp_mainloop, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD); + console_loglevel = orig_loglevel; + return RESUME2_RET; + } + + freeze_processes(1); + + prepare_status(0, 0, "Copying original kernel back (no status - sensitive!)..."); + + software_suspend_state |= SOFTWARE_SUSPEND_RUNNING; + + PRINTPREEMPTCOUNT("Prior to calling do_swsusp_lowlevel."); + + /* Suspend always runs on processor 0 */ + ensure_on_processor_zero(); + + do_swsusp_lowlevel(1); + BUG(); + + return RESUME2_RET; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +late_initcall(software_resume2); +#endif + +/* + * Resume setup: obtain the storage device. + */ + +static int __init resume_setup(char *str) +{ + if (str == NULL) + return 1; + + strncpy(resume_file, str, 255); + return 0; +} + +/* + * Allow the user to set the action parameter from lilo, prior to resuming. + */ +static int __init swsusp_act_setup(char *str) +{ + if(str) + swsusp_action=simple_strtol(str,NULL,0); + swsusp_act_used = 1; + return 0; +} + +/* + * Allow the user to set the debug parameter from lilo, prior to resuming. + */ +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +static int __init swsusp_dbg_setup(char *str) +{ + if(str) + swsusp_debug_state=simple_strtol(str,NULL,0); + swsusp_dbg_used = 1; + return 0; +} + +/* + * Allow the user to set the debug level parameter from lilo, prior to + * resuming. + */ +static int __init swsusp_lvl_setup(char *str) +{ + if(str) + console_loglevel = swsusp_default_console_level= simple_strtol(str,NULL,0); + swsusp_lvl_used = 1; + return 0; +} +#endif + +/* + * Allow the user to specify that we should ignore any image found and invalidate + * the image if necesssary. This is equivalent to running the + * task queue and a sync and then turning off the power. The same + * precautions should be taken: fsck if you're not journalled. + */ +static int __init noresume_setup(char *str) +{ + software_suspend_state |= SOFTWARE_SUSPEND_NORESUME_SPECIFIED; + /* Message printed later */ + return 0; +} + +__setup("resume2=", resume_setup); +__setup("swsusp_act=", swsusp_act_setup); +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +__setup("swsusp_dbg=", swsusp_dbg_setup); +__setup("swsusp_lvl=", swsusp_lvl_setup); +#endif +__setup("noresume2", noresume_setup); + +EXPORT_SYMBOL(software_suspend_pending); diff -ruN post-version-specific/kernel/power/Todo software-suspend-core-2.0/kernel/power/Todo --- post-version-specific/kernel/power/Todo 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/Todo 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,30 @@ +Todo + +20040128 + 2.0 known issues: + ---------------- +- DRI support for 2.4 & 2.6 +- AGP support under 2.6 +- USB support under 2.4 and 2.6 +- SMP support currently 2.4 only +- Incomplete support in other drivers +- No support for discontig memory +- Currently requires PSE extension (/proc/cpuinfo) +- Highmem >4GB not supported +- SMP suffers from lost interrupts during resuming +- 2.6 does not currently flush caches properly before powering down. + +20040107 +- Further cleaning up. + +20040106 +- Fix lost interrupts on SMP. + +20031216 +- Include progress-bar-granularity in all_settings. + +20031202 +- Bounds checking on all_settings. + +20031201 +- Remove /proc/sys/kernel/swsusp diff -ruN post-version-specific/kernel/power/ui.c software-suspend-core-2.0/kernel/power/ui.c --- post-version-specific/kernel/power/ui.c 1970-01-01 12:00:00.000000000 +1200 +++ software-suspend-core-2.0/kernel/power/ui.c 2004-01-30 17:22:58.000000000 +1300 @@ -0,0 +1,1038 @@ +/* + * kernel/power/ui.c + * + * Copyright (C) 1998-2001 Gabor Kuti + * Copyright (C) 1998,2001,2002 Pavel Machek + * Copyright (C) 2002-2003 Florent Chabaud + * Copyright (C) 2002-2003 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * Routines for Software Suspend's user interface. + * + * The user interface includes support for both a 'nice display' and + * a bootsplash screen (bootsplash.org), and for run-time debugging. + * + * The 'nice display' is text based and implements a progress bar and + * (optional) textual progress, as well as an overall description of + * the current action and the display of a header and the code version. + * + * The bootsplash support uses calls to bootsplash's proc interface + * to set the progress bar value. These calls replace the display of + * the text based progress bar only; if the bootsplash is in verbose + * mode, the header, version and description still show. In silent + * mode, the calls are still made but the text is not seen. + * + * Note that this implies a bootsplash picture version of 3 or later. + * Earlier ones require a separate bootsplash option patch. + * + * As well as displaying status information, the user has some control + * over the software while suspending. Hooks in drivers/char/console.c + * and drivers/char/serial.c allow a user on the keyboard or serial + * console to... + * + * Key + * Toggle rebooting R + * Toggle logging all output L + * Toggle pausing between major steps (1) P + * Toggle pausing at minor steps S + * Switch log levels (2) 0-7 + * Cancel the suspend (3) Escape + * Continue when paused Space + * + * (1) Pausing only occurs when the log level is > 1. + * (2) When debugging is not compiled in, only levels 0 & 1 work. + * (3) Can be disabled using /proc/swsusp/enable_escape. + * + * (The following assumes debugging is compiled in)... + * + * Fundamental to the debugging code is the idea that each debug + * message which suspend can display has both a section of code to + * which it belongs and a level of verbosity. When suspend is running, + * it only displays a message if debugging for the section has been + * enabled prior to beginning the cycle and the current loglevel is + * greater than or equal to the level of the message. + * + * Prior to starting a suspend cycle, a user can select which sections + * should display debugging information by setting + * /proc/swsusp/debug_sections. This is a bit vector (values in + * include/linux/suspend-debug.h). The initial log level can be also + * be set using /proc/swsusp/default_console_level. + * The debug sections and level can be changed prior to resuming using + * the kernel command line parameters swsusp_dbg and swsusp_lvl. + * + * In addition to the above ability to control whether messages are + * displayed, messages can be displayed in two ways. The printlog call + * displays a message using printk, with the result that it is also + * logged to syslog in the normal way. + * + * A call to printnolog usually gets the text to the screen using + * vt_console_print and is thus not logged. This is the preferred + * means of displaying highlevel debugging information, because it + * reduces clutter in the logs. (You can easily end up with 1/5645.^M + * 2/5645.^M 3/5645.^M... otherwise). + * If loggging of this output is wanted, the log all output toggle + * can be activated and printk will be used instead of + * vt_console_print. + */ +#define SWSUSP_CONSOLE_C + +#define __KERNEL_SYSCALLS__ + +#include +#include + +static DECLARE_WAIT_QUEUE_HEAD(suspend_wait_for_key); + +#ifdef DEFAULT_SUSPEND_CONSOLE +static int barwidth = 100, barposn = -1, newbarposn = 0; +static int orig_kmsg; +extern int swsusp_default_console_level; +#endif + +#ifdef CONFIG_VT +static int orig_fgconsole; +#endif +int orig_loglevel = 0; + +static char print_buf[1024]; /* Same as printk - should be safe */ + +extern void hide_cursor(int currcons); + + +#if CONFIG_VT_CONSOLE + +//#define USE_DEV_CONSOLE + +/* We want the number of lines & columns for the suspend console, not the current console */ +#undef video_num_columns +#define video_num_columns (vc_cons[suspend_console].d->vc_cols) +#undef video_num_lines +#define video_num_lines (vc_cons[suspend_console].d->vc_rows) + +int suspend_console = 0; +extern void reset_terminal(int currcons, int do_clear); + +/* Forward declaration */ +void force_console_redraw(void); + +#ifdef USE_DEV_CONSOLE + +int console_fd = -1; +struct termios termios; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) +extern asmlinkage ssize_t sys_write(unsigned int fd, const char __user * buf, size_t count); +#define write sys_write +#endif + +#define cond_console_print(chars, count) \ + if (suspend_console == fg_console) { \ + write(console_fd, chars, count); \ + hide_cursor(fg_console); \ + poke_blanked_console(); \ + } + +static void move_cursor_to(unsigned char * xy) +{ + char buf[10]; + + int len = suspend_snprintf(buf, 10, "\233%d;%dH", xy[1], xy[0]); + + cond_console_print(buf, len); +} + +static void clear_display(void) +{ + char buf[4] = "\2332J"; + unsigned char home[2]; /* Initialised to zero, which is what we want */ + + cond_console_print(buf, 3); + move_cursor_to(home); + force_console_redraw(); +} + +#else /* not using /dev/console */ +extern void putconsxy(int currcons, unsigned char *p); +extern void vt_console_print(struct console *co, const char * b, unsigned count); +#define cond_console_print(chars, count) vt_console_print((struct console *) NULL, chars, count) +#define move_cursor_to(posn) putconsxy(suspend_console, posn) +#define clear_display() reset_terminal(suspend_console, 1) +#endif + +/* Your bootsplash progress bar may have a width of (eg) 1024 pixels. That + * doesn't necessarily mean you want the bar updated 1024 times when writing + * the image */ +static int bar_granularity_limit = 0; + +/* We remember the last header that was (or could have been) displayed for + * use during log level switches */ +static char lastheader[512]; +static int lastheader_message_len = 0; + +/* Is the 'nice display' on right now? */ +static int nice_display = 0; +#endif + +/* Remember the last loglevel so we can tell when it changes */ +static int lastloglevel = -1; +static int orig_default_message_loglevel; + +/* ------------------ Splash screen defines -------------------------- */ + +#if defined(CONFIG_PROC_FS) && (defined(CONFIG_BOOTSPLASH) || defined(CONFIG_FBCON_SPLASHSCREEN)) +extern struct display fb_display[MAX_NR_CONSOLES]; +extern int splash_verbose(void); + +/* splash_is_on + * + * Description: Determine whether a VT has a splash screen on. + * Arguments: int consolenr. The VT number of a console to check. + * Returns: Boolean indicating whether the splash screen for + * that console is on right now. + */ +static int splash_is_on(int consolenr) +{ + struct splash_data *info = get_splash_data(consolenr); + + if (info) + return ((info->splash_state & 1) == 1); + return 0; +} + +/* splash_write_proc. + * + * Write to Bootsplash's proc entry. We need this to work when /proc + * hasn't been mounted yet and / can't be mounted. In addition, we + * want it to work despite the fact that bootsplash (under 2.4 at least) + * removes its proc entry when it shouldn't. We therefore use + * our proc.c find_proc_dir_entry routine to get the location of the + * write routine once (boot time & at start of each resume), and keep it. + */ + +extern struct proc_dir_entry * find_proc_dir_entry(const char *name, struct proc_dir_entry *parent); +static void splash_write_proc(const char *buffer, unsigned long count) +{ + static write_proc_t * write_routine; + struct proc_dir_entry * proc_entry; + + if (unlikely(!write_routine)) { + proc_entry = find_proc_dir_entry("splash", &proc_root); + if (proc_entry) + write_routine = proc_entry->write_proc; + } + + if (write_routine) + write_routine(NULL, buffer, count, NULL); +} + +/* fb_splash-set_progress + * + * Description: Set the progress bar position for a splash screen. + * Arguments: int consolenr. The VT number of a console to use. + * unsigned long value, unsigned long maximum: + * The proportion (value/maximum) of the bar to fill. + */ + +static int fb_splash_set_progress(int consolenr, unsigned long value, unsigned long maximum) +{ + char procstring[15]; + int length, bitshift = generic_fls(maximum) - 16; + static unsigned long lastvalue = 0; + unsigned long thisvalue; + + BUG_ON(consolenr >= MAX_NR_CONSOLES); + + if (value > maximum) + value = maximum; + + /* Avoid math problems - we can't do 64 bit math here + * (and don't need it - anyone got screen resolution + * of 65536 pixels or more?) */ + if (bitshift > 0) { + maximum = maximum >> bitshift; + value = value >> bitshift; + } + + thisvalue = value * 65534 / maximum; + + length = sprintf(procstring, "show %lu", thisvalue); + + splash_write_proc(procstring, length); + + /* Ensure redraw when the progress bar goes to a lower value */ + if (thisvalue < lastvalue) + force_console_redraw(); + + lastvalue = thisvalue; + + return 0; +} +#else +#define splash_is_on(consolenr) (0) +#define fb_splash_set_progress(...) do { } while(0) +#define splash_write_proc(...) do { } while(0) +#define splash_verbose() +#endif + +/* abort_suspend + * + * Description: Begin to abort a cycle. If this wasn't at the user's request + * (and we're displaying output), tell the user why and wait for + * them to acknowledge the message. + * Arguments: A parameterised string (imagine this is printk) to display, + * telling the user why we're aborting. + */ + +void abort_suspend(const char *fmt, ...) +{ + va_list args; + int printed_len = 0; + + if (!TEST_RESULT_STATE(SUSPEND_ABORTED)) { + if ((!TEST_RESULT_STATE(SUSPEND_ABORT_REQUESTED)) && (!NO_OUTPUT_OR_PAUSING)) { + int locked = (spin_is_locked(&suspend_irq_lock)); + + if (locked) + swsusp_spin_unlock_irqrestore(&suspend_irq_lock, swsuspirqflags); + va_start(args, fmt); + printed_len = vsnprintf(print_buf, sizeof(print_buf), fmt, args); + va_end(args); + printed_len = sprintf(print_buf + printed_len, + " (Press SPACE to continue)"); + prepare_status(1, 1, print_buf); + beepERR; + /* + * Make sure message seen - wait for shift to be + * released if being pressed + */ + interruptible_sleep_on(&suspend_wait_for_key); + + if (locked) + swsusp_spin_lock_irqsave(&suspend_irq_lock, swsuspirqflags); + } + /* Turn on aborting flag */ + SET_RESULT_STATE(SUSPEND_ABORTED); + } +} + +/* handle_loglevel_change + * + * Description: Update the display when the user changes the log level. + * Returns: Boolean indicating whether the level was changed. + */ + +static int handle_loglevel_change(void) +{ + static int recursive = 0; + + if ((console_loglevel == lastloglevel) || recursive) + return 0; + + recursive = 1; + +#ifdef CONFIG_VT + /* Calculate progress bar width. Note that whether the + * splash screen is on might have changed (this might be + * the first call in a new cycle), so we can't take it + * for granted that the width should be the same as + * last time we came in here */ + if (splash_is_on(suspend_console)) { + /* proc interface ensures bar_granularity_limit >= 0 */ + if (bar_granularity_limit) + barwidth = bar_granularity_limit; + else + barwidth = 100; + } else + barwidth = (video_num_columns - 2 * (video_num_columns / 4) - 2); + + /* Only reset the display if we're switching between nice display + * and displaying debugging output */ + if (console_loglevel > 1) { + if (nice_display || (lastloglevel == -1)) { + nice_display = 0; + clear_display(); +#endif + printnolog(0, console_loglevel, 0, "Switched to console loglevel %d.\n", console_loglevel); +#ifdef CONFIG_VT + if (splash_is_on(suspend_console)) + splash_write_proc("verbose\n", 9); + } + } else if (!nice_display) { + nice_display = 1; + clear_display(); + if (splash_is_on(suspend_console)) + splash_write_proc("silent\n", 8); + } + + /* Get the nice display or last action [re]drawn */ + prepare_status(0, 0, NULL); +#endif + + lastloglevel = console_loglevel; + + recursive = 0; + + return 1; +} + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +/* printnolog. + * + * Description: This function is intended to do the same job as printk, but + * without normally logging what is printed. The point is to be + * able to get debugging info on screen without filling the logs + * with "1/534. ^M 2/534^M. 3/534^M" + * + * It may be called from an interrupt context - can't sleep! + * + * Arguments: int mask: The debugging section(s) this message belongs to. + * int level: The level of verbosity of this message. + * int restartline: Whether to output a \r or \n with this line + * (\n if we're logging all output). + * const char *fmt, ...: Message to be displayed a la printk. + */ +void printnolog(int mask, int level, int restartline, const char *fmt, ...) +{ +#if defined(DEFAULT_SUSPEND_CONSOLE) + va_list args; + int printed_len = 0; + + if (console_loglevel < level) + return; + + if (!CHECKMASK(mask)) + return; + + /* Don't do this if not printing anything - print[no]log get called before + * we prepare the console at resume time */ + handle_loglevel_change(); + + if ((restartline) && (!TEST_ACTION_STATE(SUSPEND_LOGALL))) + printed_len = vsnprintf(print_buf, sizeof(print_buf), "\r", NULL); + + va_start(args, fmt); + printed_len += vsnprintf(print_buf + printed_len, sizeof(print_buf) - printed_len, fmt, args); + va_end(args); + + if TEST_ACTION_STATE(SUSPEND_LOGALL) { + /* If we didn't print anything, don't do the \n anyway! */ + if (!printed_len) + return; + if (restartline) + printk("\n"); + printk(print_buf); + } else + cond_console_print(print_buf, printed_len); +#endif +} + +/* printlog + * + * Description: This function is a wrapper around printk. It makes the display + * conditional and adds the log level of the message to the front, + * so we can change the detail displayed at runtime. + * Arguments: int mask: The debugging section(s) this message belongs to. + * int level: The level of verbosity of this message. + * const char *fmt, ...: Message to be displayed via printk. + */ +void printlog(int mask, int level, const char *fmt, ...) +{ +#if defined(DEFAULT_SUSPEND_CONSOLE) + va_list args; + int printed_len = 0; + + if (console_loglevel < level) + return; + + if (!CHECKMASK(mask)) + return; + + /* Don't do this if not printing anything - print[no]log get called before + * we prepare the console at resume time */ + handle_loglevel_change(); + + va_start(args, fmt); + printed_len += vsnprintf(print_buf + printed_len, sizeof(print_buf) - printed_len, fmt, args); + va_end(args); + + printk(print_buf); +#endif +} +#endif + +/* prepare_status + * Description: Prepare the 'nice display', drawing the header and version, + * along with the current action and perhaps also resetting the + * progress bar. + * Arguments: int printalways: Whether to print the action when debugging + * is on. + * int clearbar: Whether to reset the progress bar. + * const char *fmt, ...: The action to be displayed. + */ +void prepare_status(int printalways, int clearbar, const char *fmt, ...) +{ +#if defined(DEFAULT_SUSPEND_CONSOLE) + unsigned char posn[2]; + va_list args; + + if (NO_OUTPUT_OR_PAUSING) + return; + + if (fmt) { + va_start(args, fmt); + lastheader_message_len = vsnprintf(lastheader, 512, fmt, args); + va_end(args); + } + + handle_loglevel_change(); + + if (console_loglevel >= SUSPEND_ERROR) { + + if (printalways) + printk("\n** %s\n", lastheader); + + /* If nice display is off, there is no progress bar to clear */ + if ((clearbar) && (splash_is_on(suspend_console))) + fb_splash_set_progress(suspend_console, 0, 1); + return; + } + + /* Print version */ + posn[0] = (unsigned char) (0); + posn[1] = (unsigned char) (video_num_lines); + move_cursor_to(posn); + cond_console_print(swsusp_version, strlen(swsusp_version)); + + /* Print header */ + posn[0] = (unsigned char) ((video_num_columns - 29) / 2); + posn[1] = (unsigned char) ((video_num_lines / 3) -4); + move_cursor_to(posn); + + if (now_resuming) { + cond_console_print(console_resume, strlen(console_resume)); + } else + cond_console_print(console_suspend, strlen(console_suspend)); + + /* Print action */ + posn[1] = (unsigned char) (video_num_lines / 3); + posn[0] = (unsigned char) 0; + move_cursor_to(posn); + + /* Clear old message */ + for (barposn = 0; barposn < video_num_columns; barposn++) + cond_console_print(" ", 1); + + posn[0] = (unsigned char) (((video_num_columns - lastheader_message_len) / 2)); + move_cursor_to(posn); + cond_console_print(lastheader, lastheader_message_len); + + if (!splash_is_on(suspend_console)) { + /* Draw left bracket of progress bar. */ + posn[0] = (unsigned char) (video_num_columns / 4); + posn[1]++; + move_cursor_to(posn); + cond_console_print("[", 1); + + /* Draw right bracket of progress bar. */ + posn[0] = (unsigned char) (video_num_columns - (video_num_columns / 4) - 1); + move_cursor_to(posn); + cond_console_print("]", 1); + } + + if (clearbar) { + if (splash_is_on(suspend_console)) + fb_splash_set_progress(suspend_console, 0, 1); + else { + /* Position at start of progress */ + posn[0] = (unsigned char) (video_num_columns / 4 + 1); + move_cursor_to(posn); + + /* Clear bar */ + for (barposn = 0; barposn < barwidth; barposn++) + cond_console_print(" ", 1); + move_cursor_to(posn); + } + } + + barposn = 0; +#endif +} + +/* update_status + * + * Description: Update the progress bar and (if on) in-bar message. + * Arguments: UL value, maximum: Current progress percentage (value/max). + * const char *fmt, ...: Message to be displayed in the middle + * of the progress bar. + * Note that a NULL message does not mean that any previous + * message is erased! For that, you need prepare_status with + * clearbar on. + * Returns: Unsigned long: The next value where status needs to be updated. + * This is to reduce unnecessary calls to update_status. + */ +unsigned long update_status(unsigned long value, unsigned long maximum, const char *fmt, ...) +{ + unsigned long next_update = 0; +#if defined(DEFAULT_SUSPEND_CONSOLE) + int bitshift = generic_fls(maximum) - 16; + unsigned char posn[2]; + va_list args; + int message_len = 0; + + handle_loglevel_change(); + + if (NO_OUTPUT_OR_PAUSING || (!maximum) || (!barwidth)) + return maximum; + + if (value < 0) + value = 0; + + if (value > maximum) + value = maximum; + + /* Try to avoid math problems - we can't do 64 bit math here + * (and shouldn't need it - anyone got screen resolution + * of 65536 pixels or more?) */ + if (bitshift > 0) { + unsigned long temp_maximum = maximum >> bitshift; + unsigned long temp_value = value >> bitshift; + newbarposn = (int) (temp_value * barwidth / temp_maximum); + } else + newbarposn = (int) (value * barwidth / maximum); + + if (newbarposn < barposn) + barposn = 0; + + next_update = ((newbarposn + 1) * maximum / barwidth) + 1; + + if (console_loglevel >= SUSPEND_ERROR) { + if ((splash_is_on(suspend_console)) && (newbarposn != barposn)) { + fb_splash_set_progress(suspend_console, value, maximum); + barposn = newbarposn; + } + return next_update; + } + + /* Update bar */ + if (splash_is_on(suspend_console)) { + if (newbarposn != barposn) + fb_splash_set_progress(suspend_console, value, maximum); + } else { + posn[1] = (unsigned char) ((video_num_lines / 3) + 1); + + /* Clear bar if at start */ + if (!barposn) { + posn[0] = (unsigned char) (video_num_columns / 4 + 1); + move_cursor_to(posn); + for (; barposn < barwidth; barposn++) + cond_console_print(" ", 1); + barposn = 0; + } + posn[0] = (unsigned char) (video_num_columns / 4 + 1 + barposn); + move_cursor_to(posn); + + for (; barposn < newbarposn; barposn++) + cond_console_print("-", 1); + } + + /* Print string in progress bar on loglevel 1 */ + if ((fmt) && (console_loglevel)) { + va_start(args, fmt); + message_len = vsnprintf(print_buf, sizeof(print_buf), " ", NULL); + message_len += vsnprintf(print_buf + message_len, sizeof(print_buf) - message_len, fmt, args); + message_len += vsnprintf(print_buf + message_len, sizeof(print_buf) - message_len, " ", NULL); + va_end(args); + + if (message_len) { + posn[0] = (unsigned char) ((video_num_columns - message_len) / 2); + posn[1] = (unsigned char) ((video_num_lines / 3) + 1); + move_cursor_to(posn); + cond_console_print(print_buf, message_len); + } + } + + barposn = newbarposn; +#else + next_update = maximum; +#endif + return next_update; +} + +/* request_abort_suspend + * + * Description: Handle the user requesting the cancellation of a suspend by + * pressing escape. Note that on a second press, we try a little + * harder, attempting to forcefully thaw processes. This shouldn't + * been needed, and may result in an oops (if we've overwritten + * memory), but has been useful on ocassion. + * Callers: Called from drivers/char/keyboard.c or drivers/char/serial.c + * when the user presses escape. + */ +void request_abort_suspend(void) +{ + if ((now_resuming) || (TEST_RESULT_STATE(SUSPEND_ABORT_REQUESTED))) + return; + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + prepare_status(1, 1, "--- ESCAPE PRESSED AGAIN : TRYING HARDER TO ABORT ---"); + show_state(); + thaw_processes(); + } else { + prepare_status(1, 1, "--- ESCAPE PRESSED : ABORTING PROCESS ---"); + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_ABORT_REQUESTED); + abort_suspend("Escape pressed."); + } +} + +/* check_shift_keys + * + * Description: Potentially pause and wait for the user to tell us to continue. + * We normally only pause when @pause is set. + * Arguments: int pause: Whether we normally pause. + * char * message: The message to display. Not parameterised + * because it's normally a constant. + */ + +void check_shift_keys(int pause, char * message) +{ +#ifdef DEFAULT_SUSPEND_CONSOLE + + if (NO_OUTPUT_OR_PAUSING) + return; + + if (((TEST_ACTION_STATE(SUSPEND_PAUSE) && pause) || (TEST_ACTION_STATE(SUSPEND_SINGLESTEP))) + && (console_loglevel > 1)) { + prepare_status(1, 0, "%s%sPress SPACE to continue.\n", + (TEST_ACTION_STATE(SUSPEND_SINGLESTEP)) ? "(Single step on) " : "", + message ? message : ""); + interruptible_sleep_on(&suspend_wait_for_key); + } +#endif +} + +/* wakeup_suspend + * + * Description: Wake up suspend when a key is pressed on the console + * (serial or local) indicating we should continue. + */ + +void wakeup_suspend(void) +{ + wake_up_interruptible(&suspend_wait_for_key); +} + +extern asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg); + +/* pm_prepare_console + * + * Description: Prepare a console for use, save current settings. + * Returns: Boolean: Whether an error occured. Errors aren't + * treated as fatal, but a warning is printed. + */ +int pm_prepare_console(void) +{ + if (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT)) + /* No output should be produced. */ + return 0; + + orig_loglevel = console_loglevel; + orig_default_message_loglevel = default_message_loglevel; + if (!now_resuming) + console_loglevel = swsusp_default_console_level; + +#ifdef CONFIG_VT_CONSOLE + orig_fgconsole = fg_console; + + if ((orig_fgconsole != DEFAULT_SUSPEND_CONSOLE) && (!splash_is_on(fg_console))) { + suspend_console = DEFAULT_SUSPEND_CONSOLE; + if (vc_allocate(DEFAULT_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(DEFAULT_SUSPEND_CONSOLE); + /* We should have a timeout here */ + if (vt_waitactive(DEFAULT_SUSPEND_CONSOLE)) { + printk("Can't switch virtual consoles."); + return 1; + } + } else + suspend_console = fg_console; + +#ifdef USE_DEV_CONSOLE + console_fd = sys_open("/dev/console", O_RDWR | O_NONBLOCK, 0); + if (console_fd < 0) { + printk("Can't open /dev/console. Error value was %d.\n", console_fd); + return 1; + } + + sys_ioctl(console_fd, TCGETS, (long)&termios); + termios.c_lflag &= ~ICANON; + sys_ioctl(console_fd, TCSETSF, (long)&termios); +#endif + + clear_display(); + orig_kmsg = kmsg_redirect; + kmsg_redirect = suspend_console; + prepare_status(0, 0, ""); + + default_message_loglevel = 1; +#endif + if (splash_is_on(suspend_console)) { + if (console_loglevel > 1) + splash_write_proc("verbose\n", 9); + else + splash_write_proc("silent\n", 8); + } + return 0; +} + +/* suspend_relinquish_console + * + * Description: Close our handle on /dev/console. Must be done + * earlier than pm_restore_console to avoid problems with other + * processes trying to grab it when thawed. + */ + +void suspend_relinquish_console(void) +{ +#if defined(CONFIG_VT_CONSOLE) && defined(USE_DEV_CONSOLE) + clear_display(); + + termios.c_lflag |= ICANON; + sys_ioctl(console_fd, TCSETSF, (long)&termios); + sys_close(console_fd); +#endif +} + +/* pm_restore_console + * + * Description: Restore the settings we saved above. + */ + +void pm_restore_console(void) +{ + if (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT)) + return; + + reset_terminal(suspend_console, 1); + +#ifdef CONFIG_VT_CONSOLE + if (orig_fgconsole != suspend_console) { + set_console(orig_fgconsole); + /* We should have a timeout here */ + if (vt_waitactive(orig_fgconsole)) { + printk("Can't switch virtual consoles."); + return; + } + } + + kmsg_redirect = orig_kmsg; + nice_display = 0; +#endif + + swsusp_default_console_level = console_loglevel; + console_loglevel = orig_loglevel; + default_message_loglevel = orig_default_message_loglevel; + lastloglevel = -1; + return; +} + +#ifdef CONFIG_VT_CONSOLE +extern void unblank_screen(void); +extern int console_blanked; +extern int handle_loglevel_change(void); + +/* force_console_redraw + * + * Description: Force a redraw of the console. Necessary after copying the + * original kernel back and when the progress bar is moved + * backwards. + */ +void force_console_redraw(void) +{ + console_blanked = fg_console + 1; + handle_loglevel_change(); + unblank_screen(); + update_screen(fg_console); +} + +/* post_resume_console_redraw(void) + * + * Description: Redraw the console after copying the original kernel back. + */ + +void post_resume_console_redraw(void) +{ + unblank_screen(); + fb_splash_set_progress(suspend_console, pageset1_size, pageset1_size + pageset2_size); +} +#else +#define post_resume_console_redraw() +#endif + +/* sanity_check_failed() + * Description: Complain when the image doesn't appear to match the booted + * kernel. The user may press C to invalidate the image + * and continue booting, or space to reboot. This works from + * either the serial console or normally attached keyboard. + * + * Note that we come in here from init, while the kernel is + * locked. If we want to get events from the serial console, + * we need to temporarily unlock the kernel. + * + * Arguments: Char *. Pointer to a string explaining why we're moaning. + */ + +int sanity_check_failed(char *reason) +{ + PRINTPREEMPTCOUNT("At entry to sanity check failed"); +#if defined(CONFIG_VT) || defined(CONFIG_SERIAL_CONSOLE) +#ifdef CONFIG_VT + kd_mksound(300,HZ/4); + mdelay(300); + splash_verbose(); +#endif + printk(KERN_EMERG "=== Software Suspend ===\n\n"); + printk(KERN_EMERG "BIG FAT WARNING!! %s\n\n", reason); + printk(KERN_EMERG "If you want to use the current suspend image, reboot and try\n"); + printk(KERN_EMERG "again with the same kernel that you suspended from. If you want\n"); + printk(KERN_EMERG "to forget that image, continue and the image will be erased.\n"); + printk(KERN_EMERG "Press SPACE to reboot or C to continue booting with this kernel\n"); + + software_suspend_state |= SOFTWARE_SUSPEND_SANITY_CHECK_PROMPT; + PRINTPREEMPTCOUNT("prior to interruptible sleep on"); + interruptible_sleep_on(&suspend_wait_for_key); + PRINTPREEMPTCOUNT("post interruptible sleep on"); + software_suspend_state &= ~SOFTWARE_SUSPEND_SANITY_CHECK_PROMPT; +#endif // CONFIG_VT or CONFIG_SERIAL_CONSOLE + return -EPERM; +} + +#if defined(CONFIG_SOFTWARE_SUSPEND2) && defined(CONFIG_PROC_FS) +/* + * User interface specific /proc/swsusp entries. + */ + +static struct swsusp_proc_data proc_params[] = { + { .filename = "beeping", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_BEEP, + } + } + }, + + { .filename = "default_console_level", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &swsusp_default_console_level, + .minimum = 0, +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + .maximum = 7, +#else + .maximum = 1, +#endif + + } + } + }, + + { .filename = "enable_escape", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_CAN_CANCEL, + } + } + }, + + { .filename = "no_output", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_NO_OUTPUT, + } + } + }, + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + { .filename = "debug_sections", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_UL, + .data = { + .ul = { + .variable = &swsusp_debug_state, + .minimum = 0, + .maximum = 2 << 30, + } + } + }, + + { .filename = "log_everything", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_LOGALL, + } + } + }, + + { .filename = "pause_between_steps", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &swsusp_action, + .bit = SUSPEND_PAUSE, + } + } + }, +#endif + +#if defined(CONFIG_FBCON_SPLASHSCREEN) || defined(CONFIG_BOOTSPLASH) + { .filename = "progressbar_granularity_limit", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &bar_granularity_limit, + .minimum = 1, + .maximum = 2000, + } + } + } +#endif +}; + +/* swsusp_console_proc_init + * Description: Boot time initialisation for user interface. + */ +__init void swsusp_console_proc_init(void) +{ + int i, numfiles = sizeof(proc_params) / sizeof(struct swsusp_proc_data); + + for (i=0; i< numfiles; i++) + swsusp_register_procfile(&proc_params[i]); +} + +#endif + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +EXPORT_SYMBOL(printlog); +#endif +EXPORT_SYMBOL(request_abort_suspend); +EXPORT_SYMBOL(wakeup_suspend); +EXPORT_SYMBOL(prepare_status);