diff -ruN post-version-specific/Documentation/power/internals.txt software-suspend-core-2.0.0.96/Documentation/power/internals.txt --- post-version-specific/Documentation/power/internals.txt 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/Documentation/power/internals.txt 2004-07-08 14:10:50.000000000 +1000 @@ -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/Documentation/power/todo.txt software-suspend-core-2.0.0.96/Documentation/power/todo.txt --- post-version-specific/Documentation/power/todo.txt 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/Documentation/power/todo.txt 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,28 @@ +Suspend2 todo list + +20040128 + 2.0 known issues: + ---------------- +- DRI support for 2.4 & 2.6 +- USB support under 2.4 and 2.6 +- Incomplete support in other drivers +- No support for discontig memory +- Currently requires PSE extension (/proc/cpuinfo) +- Highmem >4GB not supported +- SMP suffers from lost interrupts during resuming +- 2.6 does not currently flush caches properly before powering down. + +20040107 +- Further cleaning up. + +20040106 +- Fix lost interrupts on SMP. + +20031216 +- Include progress-bar-granularity in all_settings. + +20031202 +- Bounds checking on all_settings. + +20031201 +- Remove /proc/sys/kernel/swsusp diff -ruN post-version-specific/include/linux/suspend-common.h software-suspend-core-2.0.0.96/include/linux/suspend-common.h --- post-version-specific/include/linux/suspend-common.h 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/include/linux/suspend-common.h 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,589 @@ +/* + * 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 + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +extern inline void do_flush_tlb_all_local(void); +#define FLUSH_LOCAL_TLB() do_flush_tlb_all_local() +#else +#define FLUSH_LOCAL_TLB() local_flush_tlb() +#endif + +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; + int io_time[2][2]; +}; + +extern struct tq_struct suspend_tq; +extern int suspend_default_console_level; +extern spinlock_t suspend_irq_lock __nosavedata; +extern unsigned long suspendirqflags __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 size1low; + 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_suspend(int amount_needed, int free_flags); + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +extern int suspend_memory_pool_level(void); +extern int suspend_amount_grabbed; +#define PRINTFREEMEM(desn) printlog(SUSPEND_MEMORY, SUSPEND_MEDIUM, \ + "Free memory %s: %d+%d.\n", desn, \ + nr_free_pages() + suspend_amount_grabbed, \ + suspend_memory_pool_level()) +#else /* CONFIG_SOFTWARE_SUSPEND_DEBUG */ +#define PRINTFREEMEM(desn) do { } while(0) +#endif /* CONFIG_SOFTWARE_SUSPEND_DEBUG */ + +#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) +#else /* No preempt */ +#define PRINTPREEMPTCOUNT(desn) do { } while(0) +#endif + +/* Local variables that should not be affected by save */ +#define pageset1_size (pagedir1.pageset_size) +#define pageset2_size (pagedir2.pageset_size) + +/* + * XXX: We try to keep some more pages free so that I/O operations succeed + * without paging. Might this be more? + */ +#ifdef CONFIG_HIGHMEM +#define MIN_FREE_RAM (highstart_pfn >> 7) +#else +#define MIN_FREE_RAM (max_mapnr >> 7) +#endif + +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 suspend_early_boot_message(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(USE_ECR) \ + ((pageset1_size + pageset2_size) * \ + (USE_ECR ? expected_compression_ratio() : 100) / 100) + +#define HEADER_STORAGE_NEEDED \ + (num_range_pages + 1 + \ + (int) header_storage_for_plugins()) + +#define STORAGE_NEEDED(USE_ECR) \ + (MAIN_STORAGE_NEEDED(USE_ECR) + HEADER_STORAGE_NEEDED) + +#define RAM_TO_SUSPEND (1 + MAX((pageset1_size - pageset2_sizelow), 0) + \ + MIN_FREE_RAM + memory_for_plugins()) + +/* --------- 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(void); +void absolutise_chain(struct rangechain * chain); +int get_rangepages_list(void); +void put_rangepages_list(void); +unsigned long * get_rangepages_list_entry(int index); +int relocate_rangepages(void); + +extern struct range * first_range_page, * last_range_page; + +#define RANGE_RELATIVE(x) (struct range *) ((((unsigned long) x) & \ + (PAGE_SIZE - 1)) | \ + ((*RANGEPAGELINK(x) & (PAGE_SIZE - 1)) << PAGE_SHIFT)) +#define RANGE_ABSOLUTE(entry) (struct range *) \ + ((((unsigned long) (entry)) & (PAGE_SIZE - 1)) | \ + (unsigned long) get_rangepages_list_entry(((unsigned long) (entry)) >> PAGE_SHIFT)) + +extern unsigned long * in_use_map; +extern unsigned long * pageset2_map; +extern unsigned long * checksum_map; + +#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), &in_use_map[PAGEINDEX(page)]) +#define SetPageInUse(page) \ + set_bit(PAGEBIT(page), &in_use_map[PAGEINDEX(page)]) +#define ClearPageInUse(page) \ + clear_bit(PAGEBIT(page), &in_use_map[PAGEINDEX(page)]) + +#define PagePageset2(page) \ + test_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)]) +#define SetPagePageset2(page) \ + set_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)]) +#define TestAndSetPagePageset2(page) \ + test_and_set_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)]) +#define TestAndClearPagePageset2(page) \ + test_and_clear_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)]) +#define ClearPagePageset2(page) \ + clear_bit(PAGEBIT(page), &pageset2_map[PAGEINDEX(page)]) + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS +#define PageChecksumIgnore(page) \ + test_bit(PAGEBIT(page), &checksum_map[PAGEINDEX(page)]) +#define SetPageChecksumIgnore(page) \ + set_bit(PAGEBIT(page), &checksum_map[PAGEINDEX(page)]) +#define ClearPageChecksumIgnore(page) \ + clear_bit(PAGEBIT(page), &checksum_map[PAGEINDEX(page)]) +#else +#define PageChecksumIgnore(page) (0) +#define SetPageChecksumIgnore(page) do { } while(0) +#define ClearPageChecksumIgnore(page) do { } while(0) +#endif +/* + * #defs that are dependant on CONFIG_SOFTWARE_SUSPEND_DEBUG + * + */ +#if defined(CONFIG_SOFTWARE_SUSPEND_DEBUG) +#define MDELAY(a) do { if (TEST_ACTION_STATE(SUSPEND_SLOW)) mdelay(a); } \ + while (0) + +#else // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + +#define MDELAY(a) do { } while (0) + +#endif // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + +#define FILTER_PLUGIN 1 +#define WRITER_PLUGIN 2 + +struct suspend_filter_ops { + int (*expected_compression) (void); + struct list_head filter_list; +}; + +struct suspend_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, int only_writer); + + /* Determine whether image exists that we can restore */ + int (*image_exists) (void); + + /* Mark the image as having tried to resume */ + void (*mark_resume_attempted) (void); + + /* Destroy image if one exists */ + int (*invalidate_image) (void); + + /* Wait on I/O */ + int (*wait_on_io) (int flush_all); + + struct list_head writer_list; +}; + +#define SUSPEND_ASYNC 0 +#define SUSPEND_SYNC 1 + +struct suspend_plugin_ops { + /* Functions common to filters and writers */ + int type; + char * name; + int disabled; + struct list_head plugin_list; + unsigned long (*memory_needed) (void); + unsigned long (*storage_needed) (void); + int (*print_debug_info) (char * buffer, int size); + int (*save_config_info) (char * buffer); + void (*load_config_info) (char * buffer, int len); + + /* Writing the image proper */ + int (*write_init) (int stream_number); + int (*write_chunk) (struct page * buffer_page); + int (*write_cleanup) (void); + + /* Reading the image proper */ + int (*read_init) (int stream_number); + int (*read_chunk) (struct page * buffer_page, int sync); + int (*read_cleanup) (void); + + /* Initialise & cleanup - general routines called + * at the start and end of a cycle. */ + int (*initialise) (void); + void (*cleanup) (void); + + /* Reset plugin if image exists but reading aborted */ + void (*noresume_reset) (void); + + /* Set list of devices not to be suspended/resumed */ + void (*dpm_set_devices) (void); + + union { + struct suspend_filter_ops filter; + struct suspend_writer_ops writer; + } ops; +}; + +extern struct suspend_plugin_ops * active_writer; +extern struct list_head suspend_filters, suspend_writers, suspend_plugins; +extern struct suspend_plugin_ops * get_next_filter(struct suspend_plugin_ops *); +extern int suspend_register_plugin(struct suspend_plugin_ops * plugin); + +struct suspend_proc_data { + char * filename; + int permissions; + int type; + union { + struct { + unsigned long * bit_vector; + int bit; + } bit; + struct { + int * variable; + int minimum; + int maximum; + } integer; + struct { + unsigned long * variable; + unsigned long minimum; + unsigned long maximum; + } ul; + struct { + char * variable; + int max_length; + /* Routine triggered by write after new value stored */ + int (* write_proc) (void); + } 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 * suspend_register_procfile( + struct suspend_proc_data * suspend_proc_data); +void suspend_unregister_procfile(struct suspend_proc_data * suspend_proc_data); + +struct debug_info_data +{ + int action; + char * buffer; + int buffer_size; + int bytes_used; +}; + +struct reload_data +{ + int pageset; + int pagenumber; + struct page * page_address; + char * base_version; + char * compared_version; + struct reload_data * next; +}; + +#define MAX_FREEMEM_SLOTS 24 +enum { + SUSPEND_FREE_BASE, + SUSPEND_FREE_CONSOLE_ALLOC, + SUSPEND_FREE_DRAIN_PCP, + SUSPEND_FREE_IN_USE_MAP, + SUSPEND_FREE_PS2_MAP, + SUSPEND_FREE_CHECKSUM_MAP, + SUSPEND_FREE_RELOAD_PAGES, + SUSPEND_FREE_INIT_PLUGINS, + SUSPEND_FREE_MEM_POOL, + SUSPEND_FREE_FREEZER, + SUSPEND_FREE_EAT_MEMORY, + SUSPEND_FREE_SYNC, + SUSPEND_FREE_GRABBED_MEMORY, + SUSPEND_FREE_RANGE_PAGES, + SUSPEND_FREE_EXTRA_PD1, + SUSPEND_FREE_WRITER_STORAGE, + SUSPEND_FREE_HEADER_STORAGE, + SUSPEND_FREE_CHECKSUM_PAGES, + SUSPEND_FREE_KSTAT, + SUSPEND_FREE_DEBUG_INFO, + SUSPEND_FREE_INVALIDATE_IMAGE, + SUSPEND_FREE_IO, + SUSPEND_FREE_IO_INFO, + SUSPEND_FREE_START_ONE +}; +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +extern void suspend_store_free_mem(int slot, int side); +#else +#define suspend_store_free_mem(a, b) do { } while(0) +#endif +extern int suspend_free_mem_values[MAX_FREEMEM_SLOTS][2]; + +#endif /* #ifndef SWSUSP_COMMON_H */ diff -ruN post-version-specific/include/linux/suspend-debug.h software-suspend-core-2.0.0.96/include/linux/suspend-debug.h --- post-version-specific/include/linux/suspend-debug.h 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/include/linux/suspend-debug.h 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,123 @@ + +#ifndef _LINUX_SWSUSP_DEBUG_H +#define _LINUX_SWSUSP_DEBUG_H + +/* Solely for comparison with the version specific patch revision */ +#define SWSUSP_CORE_REVISION 0x201 + +#define SWSUSP_CORE_VERSION "2.0.0.96" +#define name_suspend "Software Suspend " SWSUSP_CORE_VERSION ": " + +/* Same length to ensure one overwrites the other */ +#define console_suspend " S U S P E N D T O D I S K " +#define console_resume "R E S U M E F R O M D I S K" + +extern unsigned long suspend_action; +extern unsigned long suspend_result; +extern unsigned long suspend_debug_state; + +#define TEST_RESULT_STATE(bit) (test_bit(bit, &suspend_result)) +#define SET_RESULT_STATE(bit) (test_and_set_bit(bit, &suspend_result)) +#define CLEAR_RESULT_STATE(bit) (test_and_clear_bit(bit, &suspend_result)) + +#define TEST_ACTION_STATE(bit) (test_bit(bit, &suspend_action)) +#define SET_ACTION_STATE(bit) (test_and_set_bit(bit, &suspend_action)) +#define CLEAR_ACTION_STATE(bit) (test_and_clear_bit(bit, &suspend_action)) + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +#define TEST_DEBUG_STATE(bit) (suspend_debug_state) && \ + (test_bit(bit, &suspend_debug_state)) +#define SET_DEBUG_STATE(bit) (test_and_set_bit(bit, &suspend_debug_state)) +#define CLEAR_DEBUG_STATE(bit) (test_and_clear_bit(bit, &suspend_debug_state)) +#else +#define TEST_DEBUG_STATE(bit) (0) +#define SET_DEBUG_STATE(bit) (0) +#define CLEAR_DEBUG_STATE(bit) (0) +#endif + +/* first status register - this is suspend's return code. */ +#define SUSPEND_ABORTED 0 +#define SUSPEND_ABORT_REQUESTED 1 +#define SUSPEND_NOSTORAGE_AVAILABLE 2 +#define SUSPEND_INSUFFICIENT_STORAGE 3 +#define SUSPEND_FREEZING_FAILED 4 +#define SUSPEND_UNEXPECTED_ALLOC 5 +#define SUSPEND_KEPT_IMAGE 6 +#define SUSPEND_WOULD_EAT_MEMORY 7 +#define SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY 8 + +/* second status register */ +#define SUSPEND_REBOOT 0 +#define SUSPEND_NO_OUTPUT 1 +#define SUSPEND_PAUSE 2 +#define SUSPEND_SLOW 3 +#define SUSPEND_NOPAGESET2 7 +#define SUSPEND_LOGALL 8 +/* Set to disable compression when compiled in */ +#define SUSPEND_NO_COMPRESSION 9 +//#define SUSPEND_ENABLE_KDB 10 +#define SUSPEND_CAN_CANCEL 11 +#define SUSPEND_KEEP_IMAGE 13 +#define SUSPEND_FREEZER_TEST 14 +#define SUSPEND_FREEZER_TEST_SHOWALL 15 +#define SUSPEND_SINGLESTEP 16 +#define SUSPEND_PAUSE_NEAR_PAGESET_END 17 + +/* 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 +#define SUSPEND_NOSAVE 15 +#define SUSPEND_INTEGRITY 16 +/* 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) + +#define NO_OUTPUT_OR_PAUSING (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT)) +#define CHECKMASK(mask) (((!mask) || (TEST_DEBUG_STATE(mask))) \ + && (!NO_OUTPUT_OR_PAUSING)) + +void __printnolog(int restartline, const char *fmt, ...); +void __printlog(const char *fmt, ...); + +#define printlog(mask, level, f, a...) do { \ + if (CHECKMASK(mask) && (console_loglevel >= level)) \ + __printlog(f, ##a); \ +} while(0) + +#define printnolog(mask, level, f, a...) do { \ + if (CHECKMASK(mask) && (console_loglevel >= level)) \ + __printnolog(f, ##a); \ +} while(0) + +#else // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + +#define printlog(...) do { } while(0) +#define printnolog(...) do { } while(0) +#define NO_OUTPUT_OR_PAUSING (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT)) +#define CHECKMASK(mask) (0) + +#endif // #ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + +#endif diff -ruN post-version-specific/include/linux/suspend.h software-suspend-core-2.0.0.96/include/linux/suspend.h --- post-version-specific/include/linux/suspend.h 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/include/linux/suspend.h 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,69 @@ +#ifndef _LINUX_SWSUSP_H +#define _LINUX_SWSUSP_H + +#ifdef CONFIG_PM + +#include +#include +#include + +#include + +extern unsigned long software_suspend_state; +struct page * get_suspend_pool_page(unsigned int gfp_mask, unsigned int order); +void free_suspend_pool_pages(struct page *page, unsigned int order); +extern char idletimeout; +extern void schedule_suspend_message(int message_number); +extern atomic_t suspend_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 +#define SOFTWARE_SUSPEND_FREEZE_NEW_ACTIVITY 128 +#define SOFTWARE_SUSPEND_FREEZE_UNREFRIGERATED 256 +#define SOFTWARE_SUSPEND_BLOCK_PAGE_ALLOCATIONS 512 +#define SOFTWARE_SUSPEND_USE_MEMORY_POOL 1024 +#define SOFTWARE_SUSPEND_STAGE2_CONTINUE 2048 +#define SOFTWARE_SUSPEND_FREEZE_SMP 4096 +#define SOFTWARE_SUSPEND_PAGESET2_NOT_LOADED 8192 +#define SOFTWARE_SUSPEND_CONTINUE_REQ 16384 +#define SOFTWARE_SUSPEND_RESUMED_BEFORE 32768 +extern int suspend_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 struct buffer_head * suspend_swap_bh; +extern inline void cleanup_finished_suspend_io(void); +extern void __suspend_activity_start(int, const char *, const char *); +extern atomic_t suspend_num_doing_io; +#define SWSUSP_CANHOLD 1 +#define SWSUSP_WAIT_ON_ME 2 +extern unsigned int suspend_task; + +#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 suspend_task (0) +#define software_suspend_state (0) +#endif + +#endif /* _LINUX_SWSUSP_H */ diff -ruN post-version-specific/include/linux/suspend-utility.h software-suspend-core-2.0.0.96/include/linux/suspend-utility.h --- post-version-specific/include/linux/suspend-utility.h 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/include/linux/suspend-utility.h 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,23 @@ +/* + * include/linux/suspend-utility.h + * + * Copyright (C) 2004 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * Routines that only suspend uses at the moment, but which might move + * when we merge because they're generic. + */ + +#include +#include +#include +#include +extern unsigned long max_mapnr; +extern struct page *mem_map; + +extern int suspend_snprintf(char * buffer, int buffer_size, const char *fmt, ...); +extern struct proc_dir_entry * find_proc_dir_entry(const char *name, struct proc_dir_entry *parent); +extern void clear_map(unsigned long * pagemap); +extern int allocatemap(unsigned long ** pagemap, int setnosave); +extern int freemap(unsigned long ** pagemap); diff -ruN post-version-specific/kernel/power/block_io.h software-suspend-core-2.0.0.96/kernel/power/block_io.h --- post-version-specific/kernel/power/block_io.h 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/block_io.h 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,70 @@ +/* + * block_io.h + * + * Copyright 2004 Nigel Cunningham + * + * Distributed under GPLv2. + * + * This file contains declarations for functions exported from + * block_io.c, which contains low level io functions. + */ + +#include + +#define MAX_READAHEAD (int) sizeof(unsigned long) * 8 +#ifdef MAX_READAHEAD +extern unsigned long suspend_readahead_flags[]; +extern spinlock_t suspend_readahead_flags_lock; +extern void suspend_wait_on_readahead(int readahead); +#endif + +// Forward Declarations + +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; + struct submit_params * next; +}; + + +// Lowlevel I/O functions +// - Get/set block size. +int suspend_get_block_size(DEVICE_BLOCK_TYPE bdev); +int suspend_set_block_size(DEVICE_BLOCK_TYPE bdev, int size); + +// - Manage statistics for I/O structures. +void __inline reset_io_stats(void); +void suspend_check_io_stats(void); + +// - Submit & cleanup I/O. +void cleanup_completed_io(void); +void finish_all_io(void); + +// - Synchronous (async + wait on completion) +extern void bdev_page_io(int rw, DEVICE_BLOCK_TYPE bdev, long pos, + struct page * page); + +extern int max_async_ios; +#define REAL_MAX_ASYNC ((max_async_ios ? max_async_ios : 32)) + +extern void do_suspend_io(int rw, + struct submit_params * submit_info, int syncio); + +/* 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) } +#else +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)) } +#endif diff -ruN post-version-specific/kernel/power/Changelog software-suspend-core-2.0.0.96/kernel/power/Changelog --- post-version-specific/kernel/power/Changelog 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/Changelog 2004-07-08 14:10:50.000000000 +1000 @@ -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.0.96/kernel/power/gzipcompress.c --- post-version-specific/kernel/power/gzipcompress.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/gzipcompress.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,532 @@ +/* + * 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 suspend. + * Compression is implemented using the zlib library. + * + */ + +#include + +/* Forward declaration for the ops structure we export */ +struct suspend_plugin_ops gzip_compression_ops; + +/* The next driver in the pipeline */ +static struct suspend_plugin_ops * next_driver; + +/* Zlib routines we use to compress/decompress the data */ +extern int zlib_compress(unsigned char *data_in, unsigned char *cpage_out, + u32 *sourcelen, u32 *dstlen); +extern void zlib_decompress(unsigned char *data_in, unsigned char *cpage_out, + u32 srclen, u32 destlen); + +/* Buffers */ +static void *compression_workspace = NULL; +static char *local_buffer = NULL; + +/* Configuration data we pass to zlib */ +static z_stream strm; + +/* Stats we save */ +static __nosavedata unsigned long bytes_in = 0, bytes_out = 0; + +/* Expected compression is used to reduce the amount of storage allocated */ +static int expected_gzip_compression = 0; + +/* ---- Zlib memory management ---- */ + +/* allocate_zlib_compression_space + * + * Description: Allocate space for zlib to use in compressing our data. + * Each call must have a matching call to free_zlib_memory. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ +static inline int allocate_zlib_compression_space(void) +{ + BUG_ON(compression_workspace); + + compression_workspace = vmalloc_32(zlib_deflate_workspacesize()); + if (!compression_workspace) { + printk(KERN_WARNING + "Failed to allocate %d bytes for deflate workspace\n", + zlib_deflate_workspacesize()); + return -ENOMEM; + } + + return 0; +} + +/* allocate_zlib_decompression_space + * + * Description: Allocate space for zlib to use in decompressing our data. + * Each call must have a matching call to free_zlib_memory. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ +static inline int allocate_zlib_decompression_space(void) +{ + BUG_ON(compression_workspace); + + compression_workspace = vmalloc_32(zlib_inflate_workspacesize()); + if (!compression_workspace) { + printk(KERN_WARNING + "Failed to allocate %d bytes for inflate workspace\n", + zlib_inflate_workspacesize()); + return -ENOMEM; + } + + return 0; +} + +/* free_zlib_memory + * + * Description: Frees memory allocated by either allocation routine (above). + */ +static inline void free_zlib_memory(void) +{ + if (!compression_workspace) + return; + + vfree(compression_workspace); + compression_workspace = NULL; +} + +/* ---- Local buffer management ---- */ + +/* allocate_local_buffer + * + * Description: Allocates a page of memory for buffering output. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ +static int allocate_local_buffer(void) +{ + if (local_buffer) + return 0; + + local_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + + if (!local_buffer) { + printk(KERN_ERR + "Failed to allocate a page for compression " + "driver's buffer.\n"); + return -ENOMEM; + } + + return 0; +} + +/* free_local_buffer + * + * Description: Frees memory allocated for buffering output. + */ +static inline void free_local_buffer(void) +{ + if (local_buffer) + free_pages((unsigned long) local_buffer, 0); + local_buffer = NULL; +} + +/* ---- Functions exported via operations struct ---- */ + +/* gzip_write_init + * + * Description: Allocate buffers and prepares zlib for deflating a new stream + * of data. + * Arguments: Stream_number: Ignored. + * Returns: Int. Zero if successful, otherwise an appropriate error number. + */ + +static int gzip_write_init(int stream_number) +{ + int result; + + next_driver = get_next_filter(&gzip_compression_ops); + + if (!next_driver) { + printk("GZip Compression Driver: Argh! No one wants my output!"); + return -ECHILD; + } + + if ((result = allocate_zlib_compression_space())) + return result; + + if ((result = allocate_local_buffer())) + return result; + + strm.total_in = 0; + strm.total_out = 0; + strm.workspace = compression_workspace; + strm.next_out = (char *) local_buffer; + strm.avail_out = PAGE_SIZE; + result = zlib_deflateInit(&strm, Z_BEST_SPEED); + + if (Z_OK != result) { + printk(KERN_ERR name_suspend "Failed to initialise zlib.\n"); + return -EPERM; + } + + /* + * Reset the statistics iif we are about to write the first part of + * the image + */ + if (stream_number == 2) + bytes_in = bytes_out = 0; + + return 0; +} + +/* gzip_write_chunk() + * + * Description: Compress a page of data, buffering output and passing on + * filled pages to the next plugin in the pipeline. + * Arguments: Buffer_start: Pointer to a buffer of size PAGE_SIZE, + * containing data to be compressed. + * Returns: 0 on success. Otherwise the error is that returned by later + * plugins, -ECHILD if we have a broken pipeline or -EPERM if + * zlib errs. + */ + +static int gzip_write_chunk(struct page * buffer_page) +{ + int ret; + char * buffer_start = kmap(buffer_page); + + /* Work to do */ + strm.next_in = buffer_start; + strm.avail_in = PAGE_SIZE; + while (strm.avail_in) { + ret = zlib_deflate(&strm, Z_PARTIAL_FLUSH); + if (ret != Z_OK) { + printk("Zlib failed to compress our data. " + "Result code was %d.\n", ret); + kunmap(buffer_page); + return -EPERM; + } + + if (!strm.avail_out) { + + if ((ret = next_driver->write_chunk( + virt_to_page(local_buffer)))) { + kunmap(buffer_page); + return ret; + } + strm.next_out = local_buffer; + strm.avail_out = PAGE_SIZE; + } + } + kunmap(buffer_page); + + return 0; +} + +/* gzip_write_cleanup() + * + * Description: Flush remaining data, update statistics and free allocated + * space. + * Returns: Zero. Never fails. Okay. Zlib might fail... but it shouldn't. + */ + +static int gzip_write_cleanup(void) +{ + int ret = 0, finished = 0; + + while (!finished) { + if (strm.avail_out) { + ret = zlib_deflate(&strm, Z_FINISH); + + if (ret == Z_STREAM_END) { + ret = zlib_deflateEnd(&strm); + finished = 1; + } + + if ((ret != Z_OK) && (ret != Z_STREAM_END)) { + zlib_deflateEnd(&strm); + printk("Failed to finish compressing data. " + "Result %d received.\n", ret); + return -EPERM; + } + } + + if ((!strm.avail_out) || (finished)) { + if ((ret = next_driver->write_chunk( + virt_to_page(local_buffer)))) + return ret; + strm.next_out = local_buffer; + strm.avail_out = PAGE_SIZE; + } + } + + bytes_in+= strm.total_in; + bytes_out+= strm.total_out; + + free_zlib_memory(); + free_local_buffer(); + + return 0; +} + +/* gzip_read_init + * + * Description: Prepare to read a new stream of data. + * Arguments: Stream_number: Not used. + * Returns: Int. Zero if successful, otherwise an appropriate error number. + */ + +static int gzip_read_init(int stream_number) +{ + int result; + + next_driver = get_next_filter(&gzip_compression_ops); + + if (!next_driver) { + printk("GZip Compression Driver: Argh! " + "No one wants to feed me data!"); + return -ECHILD; + } + + if ((result = allocate_zlib_decompression_space())) + return result; + + if ((result = allocate_local_buffer())) + return result; + + strm.total_in = 0; + strm.total_out = 0; + strm.workspace = compression_workspace; + strm.avail_in = 0; + if ((result = zlib_inflateInit(&strm)) != Z_OK) { + printk(KERN_ERR name_suspend "Failed to initialise zlib.\n"); + return -EPERM; + } + + return 0; +} + +/* gzip_read_chunk() + * + * Description: Retrieve data from later plugins and decompress it until the + * input buffer is filled. + * Arguments: Buffer_start: Pointer to a buffer of size PAGE_SIZE. + * Sync: Whether the previous plugin (or core) wants its + * data synchronously. + * Returns: Zero if successful. Error condition from me or from downstream + * on failure. + */ + +static int gzip_read_chunk(struct page * buffer_page, int sync) +{ + int ret; + char * buffer_start = kmap(buffer_page); + + /* + * All our reads must be synchronous - we can't decompress + * data that hasn't been read yet. + */ + + /* Work to do */ + strm.next_out = buffer_start; + strm.avail_out = PAGE_SIZE; + while (strm.avail_out) { + if (!strm.avail_in) { + if ((ret = next_driver->read_chunk( + virt_to_page(local_buffer), + SUSPEND_SYNC)) < 0) { + kunmap(buffer_page); + return ret; + } + strm.next_in = local_buffer; + strm.avail_in = PAGE_SIZE; + } + + ret = zlib_inflate(&strm, Z_PARTIAL_FLUSH); + + if ((ret == Z_BUF_ERROR) && (!strm.avail_in)) { + continue; + } + + if ((ret != Z_OK) && (ret != Z_STREAM_END)) { + printk("Zlib failed to decompress our data. " + "Result code was %d.\n", ret); + kunmap(buffer_page); + return -EPERM; + } + } + kunmap(buffer_page); + + return 0; +} + +/* read_cleanup() + * + * Description: Clean up after reading part or all of a stream of data. + * Returns: int: Always zero. Never fails. + */ + +static int gzip_read_cleanup(void) +{ + zlib_inflateEnd(&strm); + + free_zlib_memory(); + free_local_buffer(); + return 0; +} + +/* gzip_print_debug_stats + * + * Description: Print information to be recorded for debugging purposes into a + * buffer. + * Arguments: buffer: Pointer to a buffer into which the debug info will be + * printed. + * size: Size of the buffer. + * Returns: Number of characters written to the buffer. + */ + +static int gzip_print_debug_stats(char * buffer, int size) +{ + int pages_in = bytes_in >> PAGE_SHIFT; + int pages_out = bytes_out >> PAGE_SHIFT; + int len; + + //Output the compression ratio achieved. + len = suspend_snprintf(buffer, size, "- GZIP compressor enabled.\n"); + if (pages_in) + len+= suspend_snprintf(buffer+len, size - len, + " Compressed %ld bytes into %ld.\n " + "Image compressed by %d percent.\n", + bytes_in, bytes_out, (pages_in - pages_out) * 100 / pages_in); + return len; +} + +/* compression_memory_needed + * + * Description: Tell the caller how much memory we need to operate during + * suspend/resume. + * Returns: Unsigned long. Maximum number of bytes of memory required for + * operation. + */ + +static unsigned long gzip_memory_needed(void) +{ + return PAGE_SIZE + MAX( zlib_deflate_workspacesize(), + zlib_inflate_workspacesize()); +} + +/* gzip_save_config_info + * + * Description: Save information needed when reloading the image at resume time. + * Arguments: Buffer: Pointer to a buffer of size PAGE_SIZE. + * Returns: Number of bytes used for saving our data. + */ + +static int gzip_save_config_info(char * buffer) +{ + *((unsigned long *) buffer) = bytes_in; + *((unsigned long *) (buffer + sizeof(unsigned long))) = bytes_out; + *((int *) (buffer + 2 * sizeof(unsigned long))) = + gzip_compression_ops.disabled; + return 2 * sizeof(unsigned long) + sizeof(int); +} + +/* gzip_load_config_info + * + * Description: Reload information needed for decompressing the image at + * resume time. + * Arguments: Buffer: Pointer to the start of the data. + * Size: Number of bytes that were saved. + */ + +static void gzip_load_config_info(char * buffer, int size) +{ + BUG_ON(size != 2 * sizeof(unsigned long) + sizeof(int)); + + bytes_in = *((unsigned long *) buffer); + bytes_out = *((unsigned long *) (buffer + sizeof(unsigned long))); + gzip_compression_ops.disabled = + *((int *) (buffer + 2 * 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: The value set by the user via our proc entry. + */ + +static int gzip_get_expected_compression(void) +{ + return 100 - expected_gzip_compression; +} + +/* + * data for our proc entries. + */ + +struct suspend_proc_data expected_compression_proc_data = { + .filename = "expected_gzip_compression", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &expected_gzip_compression, + .minimum = 0, + .maximum = 99, + } + } +}; + +struct suspend_proc_data disable_compression_proc_data = { + .filename = "disable_gzip_compression", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &gzip_compression_ops.disabled, + .minimum = 0, + .maximum = 1, + } + } +}; + +/* + * Ops structure. + */ + +struct suspend_plugin_ops gzip_compression_ops = { + .type = FILTER_PLUGIN, + .name = "Zlib Page Compressor", + .memory_needed = gzip_memory_needed, + .print_debug_info = gzip_print_debug_stats, + .save_config_info = gzip_save_config_info, + .load_config_info = gzip_load_config_info, + .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 = suspend_register_plugin(&gzip_compression_ops))) { + suspend_register_procfile(&expected_compression_proc_data); + suspend_register_procfile(&disable_compression_proc_data); + } + return result; +} + +__initcall(gzip_load); + diff -ruN post-version-specific/kernel/power/io.c software-suspend-core-2.0.0.96/kernel/power/io.c --- post-version-specific/kernel/power/io.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/io.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,1159 @@ +/* + * 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 suspend. + * + */ + +#define SWSUSP_IO_C + +#include + +extern unsigned long orig_mem_free; +extern int suspend_act_used; +extern int suspend_lvl_used; +extern int suspend_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); + +int suspend_io_time[2][2]; + +/* cleanup_finished_suspend_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_suspend_io(void) +{ + active_writer->ops.writer.wait_on_io(0); +} + +/* fill_suspend_header() + * + * Description: Fill the suspend header structure. + * Arguments: struct suspend_header: Header data structure to be filled. + */ + +static __inline__ void fill_suspend_header(struct suspend_header *sh) +{ + int i; + + memset((char *)sh, 0, sizeof(*sh)); + + sh->version_code = LINUX_VERSION_CODE; + sh->num_physpages = num_physpages; + sh->orig_mem_free = orig_mem_free; + strncpy(sh->machine, system_utsname.machine, 65); + strncpy(sh->version, system_utsname.version, 65); + sh->num_cpus = NUM_CPUS; + sh->page_size = PAGE_SIZE; + sh->pagedir = pagedir1; + sh->pagedir.origranges.first = pagedir1.origranges.first; + sh->pagedir.destranges.first = pagedir1.destranges.first; + sh->pagedir.allocdranges.first = pagedir1.allocdranges.first; + sh->unused_ranges = unused_ranges; + sh->num_range_pages = num_range_pages; + sh->pageset_2_size = pagedir2.pageset_size; + sh->param0 = suspend_result; + sh->param1 = suspend_action; +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + sh->param2 = suspend_debug_state; +#endif + sh->param3 = console_loglevel; + for (i = 0; i < 4; i++) + sh->io_time[i/2][i%2] = + suspend_io_time[i/2][i%2]; +} + +/* 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 suspend_plugin_ops * this_filter, * first_filter = get_next_filter(NULL); + + PRINTFREEMEM("at start of write pageset"); + + size = pagedir->pageset_size; + if (!size) + return 0; + + if (whichtowrite == 1) { + prepare_status(1, 0, "Writing kernel & process data..."); + base = pagedir2.pageset_size; + } else { + prepare_status(1, 1, "Writing caches..."); + } + + start_time = jiffies; + + /* Initialise page transformers */ + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, + ops.filter.filter_list); + if (this_filter->disabled) + continue; + if (this_filter->write_init) + this_filter->write_init(whichtowrite); + } + + PRINTFREEMEM("after initialising page transformers"); + + /* Initialise writer */ + active_writer->write_init(whichtowrite); + PRINTFREEMEM("after initialising writer"); + + 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)); + if ((i == (size - 5)) && + TEST_ACTION_STATE(SUSPEND_PAUSE_NEAR_PAGESET_END)) + check_shift_keys(1, "Five more pages to write."); + + /* Write */ + ret = first_filter->write_chunk(pbe.address); + + if (ret) { + printk("Write chunk returned %d.\n", ret); + abort_suspend("Failed to write a chunk of the " + "image."); + error = -1; + goto write_pageset_free_buffers; + } + + /* Interactivity */ + check_shift_keys(0, NULL); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + abort_suspend("Aborting as requested."); + error = -1; + goto write_pageset_free_buffers; + } + + /* Prepare next */ + get_next_pbe(&pbe); + } + + update_status(base+size, barmax, " %d/%d MB ", + MB(base+size), MB(barmax)); + printlog(SUSPEND_IO, SUSPEND_LOW, "|\n"); + PRINTFREEMEM("after writing data"); + +write_pageset_free_buffers: + + /* Flush data and cleanup */ + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, + ops.filter.filter_list); + if (this_filter->disabled) + continue; + if (this_filter->write_cleanup) + this_filter->write_cleanup(); + } + PRINTFREEMEM("after cleaning up transformers"); + active_writer->write_cleanup(); + PRINTFREEMEM("after cleaning up writer"); + + /* Statistics */ + end_time = jiffies; + + if ((end_time - start_time) && (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + printnolog(SUSPEND_IO, SUSPEND_LOW, 0, + "Time to write data: %d pages in %d jiffies => " + "MB written per second: %lu.\n", + size, + (end_time - start_time), + (MB((unsigned long) size) * HZ / (end_time - start_time))); + suspend_io_time[0][0] += size, + suspend_io_time[0][1] += (end_time - start_time); + } + + PRINTFREEMEM("at end of write pageset"); + + /* Sanity checking */ + if (nr_free_pages() != origfree) { + abort_suspend("Number of free pages at start and end of write " + "pageset don't match! (%d != %d)", + origfree, nr_free_pages()); + } + + suspend_store_free_mem(SUSPEND_FREE_IO, 0); + return error; +} + +/* read_pageset() + * + * Description: Read a pageset from disk. + * Arguments: pagedir: Pointer to the pagedir to be saved. + * whichtowrite: Controls what debugging output is printed. + * overwrittenpagesonly: Whether to read the whole pageset or + * only part. + * Returns: Zero on success or -1 on failure. + */ + +int read_pageset(struct pagedir * pagedir, int whichtoread, + int overwrittenpagesonly) +{ + int nextupdate = 0, result = 0, base = 0; + int start_time, end_time, finish_at = pagedir->pageset_size; + int barmax = pagedir1.pageset_size + pagedir2.pageset_size; + long i; + struct pbe pbe; + struct list_head *filter; + struct suspend_plugin_ops * this_filter, * first_filter = get_next_filter(NULL); + + PRINTFREEMEM("at start of read pageset"); + + if (whichtoread == 1) { + prepare_status(1, 1, "Reading kernel & process data..."); + } else { + prepare_status(1, 0, "Reading caches..."); + if (overwrittenpagesonly) + barmax = finish_at = MIN(pageset1_size, pageset2_size); + else { + base = pagedir1.pageset_size; + } + } + + start_time=jiffies; + + /* Initialise page transformers */ + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, + ops.filter.filter_list); + if (this_filter->disabled) + continue; + if (this_filter->read_init && + this_filter->read_init(whichtoread)) { + abort_suspend("Failed to initialise a filter."); + result = 1; + goto read_pageset_free_buffers; + } + } + + /* Initialise writer */ + if (active_writer->read_init(whichtoread)) { + abort_suspend("Failed to initialise the writer."); + result = 1; + goto read_pageset_free_buffers; + } + + get_first_pbe(&pbe, pagedir); + + printnolog(SUSPEND_IO, SUSPEND_LOW, 0, + "Attempting to read %d pages.\n", finish_at); + + /* Read the pages */ + for (i=0; i< finish_at; i++) { + /* 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)); + if ((i == (finish_at - 5)) && + TEST_ACTION_STATE(SUSPEND_PAUSE_NEAR_PAGESET_END)) + check_shift_keys(1, "Five more pages to read."); + + + result = first_filter->read_chunk(pbe.address, SUSPEND_ASYNC); + + if (result) { + abort_suspend("Failed to read chunk %d/%d of the image.", + i, finish_at); + goto read_pageset_free_buffers; + } + + /* Interactivity*/ + check_shift_keys(0, NULL); + + /* Prepare next */ + get_next_pbe(&pbe); + } + + update_status(base+finish_at, barmax, " %d/%d MB ", + MB(base+finish_at), MB(barmax)); + printlog(SUSPEND_IO, SUSPEND_LOW, "|\n"); + +read_pageset_free_buffers: + + if (active_writer->read_cleanup()) { + abort_suspend("Failed to cleanup the writer."); + result = 1; + } + + /* Finish I/O, flush data and cleanup reads. */ + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, + ops.filter.filter_list); + if (this_filter->disabled) + continue; + if (this_filter->read_cleanup && + this_filter->read_cleanup()) { + abort_suspend("Failed to cleanup a filter."); + result = 1; + } + } + + /* Statistics */ + end_time=jiffies; + if ((end_time - start_time) && (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + printnolog(SUSPEND_IO, SUSPEND_LOW, 0, + "Time to read data: %d pages in %d jiffies => " + "MB read per second: %lu.\n", + finish_at, + (end_time - start_time), + (MB((unsigned long) finish_at) * HZ / + (end_time - start_time))); + suspend_io_time[1][0] += finish_at, + suspend_io_time[1][1] += (end_time - start_time); + } + + PRINTFREEMEM("at end of read pageset"); + + suspend_store_free_mem(SUSPEND_FREE_IO, 1); + return result; +} + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS +/* suspend_reread_pages() + * + * Description: Reread pages from an image for diagnosing differences. + * Arguments: page_list: A list containing information on pages + * to be reloaded, sorted by pageset and + * page index. + * Returns: Zero on success or -1 on failure. + */ + +int suspend_reread_pages(struct reload_data * page_list) +{ + int result = 0, whichtoread; + long i; + struct pbe pbe; + struct list_head *filter; + struct suspend_plugin_ops * this_filter, * first_filter = get_next_filter(NULL); + + if (!page_list) + return 0; + + PRINTFREEMEM("at start of read pageset"); + + for (whichtoread = page_list->pageset; whichtoread <= 2; whichtoread++) { + struct pagedir * pagedir; + + switch (whichtoread) { + case 1: + pagedir = &pagedir1; + break; + case 2: + pagedir = &pagedir2; + break; + default: + goto out; + } + + printnolog(SUSPEND_IO, SUSPEND_LOW, 0, + "Reread pages from pagedir %d.\n", whichtoread); + + /* Initialise page transformers */ + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, + ops.filter.filter_list); + if (this_filter->disabled) + continue; + if (this_filter->read_init && + this_filter->read_init(whichtoread)) { + abort_suspend("Failed to initialise a filter."); + return 1; + } + } + + /* Initialise writer */ + if (active_writer->read_init(whichtoread)) { + abort_suspend("Failed to initialise the writer."); + result = 1; + goto reread_free_buffers; + } + + get_first_pbe(&pbe, pagedir); + + /* Read the pages */ + for (i=0; i< pagedir->pageset_size; i++) { + /* Read */ + result = first_filter->read_chunk( + virt_to_page(page_list->base_version), + SUSPEND_SYNC); + + if (result) { + abort_suspend("Failed to read a chunk of the image."); + goto reread_free_buffers; + } + + /* Interactivity*/ + check_shift_keys(0, NULL); + + /* Prepare next */ + get_next_pbe(&pbe); + + /* Got the one we're after? */ + if (i == page_list->pagenumber) + page_list = page_list->next; + + if (page_list->pageset != whichtoread) + break; + } + +reread_free_buffers: + + /* Cleanup reads from this pageset. */ + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, + ops.filter.filter_list); + if (this_filter->disabled) + continue; + if (this_filter->read_cleanup && + this_filter->read_cleanup()) { + abort_suspend("Failed to cleanup a filter."); + result = 1; + } + } + + if (active_writer->read_cleanup()) { + abort_suspend("Failed to cleanup the writer."); + result = 1; + } + } +out: + printk("\n"); + + return result; +} +#endif + +/* 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 suspend_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, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_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 suspend_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, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_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; + char * header_buffer = NULL; + + /* First, relativise all range information */ + if (get_rangepages_list()) + return -1; + + if (unused_ranges) + unused_ranges = RANGE_RELATIVE(unused_ranges); + + relativise_chain(&pagedir1.origranges); + relativise_chain(&pagedir1.destranges); + relativise_chain(&pagedir1.allocdranges); + + if ((ret = active_writer->ops.writer.prepare_save_ranges())) { + abort_suspend("Active writer's prepare_save_ranges " + "function failed."); + goto write_image_header_abort1; + } + + relativise_ranges(); + + /* Now prepare to write the header */ + if ((ret = active_writer->ops.writer.write_header_init())) { + abort_suspend("Active writer's write_header_init" + " function failed."); + goto write_image_header_abort2; + } + + /* Get a buffer */ + header_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + if (!header_buffer) { + abort_suspend("Out of memory when trying to get page " + "for header!"); + goto write_image_header_abort3; + } + + /* Write the meta data */ + fill_suspend_header((struct suspend_header *) header_buffer); + active_writer->ops.writer.write_header_chunk(header_buffer, + sizeof(struct suspend_header)); + + /* Write plugin configurations */ + if ((ret = write_plugin_configs())) { + abort_suspend("Failed to write plugin configs."); + goto write_image_header_abort3; + } + + /* Write range pages */ + printlog(SUSPEND_IO, SUSPEND_LOW, name_suspend "Writing %d range pages.\n", + num_range_pages); + + for (i=1; i<=num_range_pages; i++) { + unsigned long * this_range_page = get_rangepages_list_entry(i); + /* Status update */ + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 1, "%d/%d: %p.\n", + i, num_range_pages, this_range_page); + 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."); + goto write_image_header_abort3; + } + + /* Write one range page */ + active_writer->ops.writer.write_header_chunk( + (char *) this_range_page, PAGE_SIZE); + + if (ret) { + abort_suspend("Failed writing a page. " + "Error number was %d.", ret); + goto write_image_header_abort3; + } + } + + update_status(total - 1, total, NULL); + + /* Flush data and let writer cleanup */ + if (active_writer->ops.writer.write_header_cleanup()) { + abort_suspend("Failed to cleanup writing header."); + goto write_image_header_abort2; + } + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + goto write_image_header_abort2; + + 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(); + + put_rangepages_list(); + + if (active_writer->ops.writer.post_load_ranges) + active_writer->ops.writer.post_load_ranges(); + +write_image_header_abort1: + if (get_rangepages_list()) + panic("Unable to allocate rangepageslist."); + + absolutise_chain(&pagedir1.origranges); + absolutise_chain(&pagedir1.destranges); + absolutise_chain(&pagedir1.allocdranges); + + put_rangepages_list(); + + free_pages((unsigned long) header_buffer, 0); + return -1; +} + +extern int suspend_early_boot_message(char *reason); + +/* sanity_check() + * + * Description: Perform a few checks, seeking to ensure that the kernel being + * booted matches the one suspended. They need to match so we can + * be _sure_ things will work. It is not absolutely impossible for + * resuming from a different kernel to work, just not assured. + * Arguments: Struct suspend_header. The header which was saved at suspend + * time. + */ +static int sanity_check(struct suspend_header *sh) +{ + if (sh->version_code != LINUX_VERSION_CODE) + return suspend_early_boot_message("Incorrect kernel version"); + + if (sh->num_physpages != num_physpages) + return suspend_early_boot_message("Incorrect memory size"); + + if (strncmp(sh->machine, system_utsname.machine, 65)) + return suspend_early_boot_message("Incorrect machine type"); + + if (strncmp(sh->version, system_utsname.version, 65)) + return suspend_early_boot_message("Incorrect version"); + + if (sh->num_cpus != NUM_CPUS) + return suspend_early_boot_message("Incorrect number of cpus"); + + if (sh->page_size != PAGE_SIZE) + return suspend_early_boot_message("Incorrect PAGE_SIZE"); + + return 0; +} + +/* noresume_reset_plugins + * + * Description: When we read the start of an image, plugins (and especially the + * active writer) might need to reset data structures if we decide + * to invalidate the image rather than resuming from it. + */ + +static void noresume_reset_plugins(void) +{ + struct suspend_plugin_ops * this_plugin; + struct list_head *plugin; + + list_for_each(plugin, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_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; + 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; + } + + /* Check whether we've resumed before */ + if (software_suspend_state & SOFTWARE_SUSPEND_RESUMED_BEFORE){ + suspend_early_boot_message(NULL); + if (!(software_suspend_state & SOFTWARE_SUSPEND_CONTINUE_REQ)) { + active_writer->ops.writer.invalidate_image(); + result = -EINVAL; + noresume_reset_plugins(); + goto out; + } + } + + software_suspend_state &= ~SOFTWARE_SUSPEND_CONTINUE_REQ; + + /* + * Prepare the active writer for reading the image header. The + * activate writer might read its own configuration or set up + * a network connection here. + * + * NB: This call may never return because there might be a signature + * for a different image such that we warn the user and they choose + * to reboot. (If the device ids look erroneous (2.4 vs 2.6) or the + * location of the image might be unavailable if it was stored on a + * network connection. + */ + 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 the image size limit because it's not used + * during resume and will be restored with the image anyway. + */ + + orig_mem_free = suspend_header->orig_mem_free; + memcpy((char *) &pagedir_resume, + (char *) &suspend_header->pagedir, sizeof(pagedir_resume)); + unused_ranges = suspend_header->unused_ranges; + num_range_pages = suspend_header->num_range_pages; + suspend_result = suspend_header->param0; + if (!suspend_act_used) + suspend_action = suspend_header->param1; +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + if (!suspend_dbg_used) + suspend_debug_state = suspend_header->param2; +#endif + if (!suspend_lvl_used) + console_loglevel = suspend_header->param3; + pagedir1.pageset_size = pagedir_resume.pageset_size; + pagedir2.pageset_size = suspend_header->pageset_2_size; + for (i = 0; i < 4; i++) + suspend_io_time[i/2][i%2] = + suspend_header->io_time[i/2][i%2]; + + /* 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."); + + for (i=0; i < num_range_pages; i++) { + /* Get a page into which we will load the data */ + struct range * this_range_page = + (struct range *) get_zeroed_page(GFP_ATOMIC); + if (!this_range_page) { + abort_suspend("Unable to allocate a pagedir."); + result = -ENOMEM; + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + /* Link to previous page */ + if (i == 0) + first_range_page = this_range_page; + else + *RANGEPAGELINK(last_range_page) = + (i | (unsigned long) this_range_page); + + /* Read this page */ + if ((result = active_writer->ops.writer.read_header_chunk( + (char *) this_range_page, PAGE_SIZE)) < 0) { + printk("Active writer's read_header_chunk routine " + "returned %d.\n", result); + free_page((unsigned long) this_range_page); + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + last_range_page = this_range_page; + } + + /* Set the last page's link to its index */ + *RANGEPAGELINK(last_range_page) = i; + + /* Clean up after reading the header */ + if ((result = active_writer->ops.writer.read_header_cleanup())) { + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + /* Okay. + * + * Now we need to move the range pages to a place such that they won't + * get overwritten while being used when copying the original kernel + * back. To achieve this, we need to absolutise them where they are + * now, prepare a bitmap of pages that collide and then relativise the + * range pages again. Having done that, we can relocate the range + * pages so that they don't collide with the image being restored, + * and absolutise them in that location. + */ + + if (get_rangepages_list()) { + result = -ENOMEM; + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + /* Absolutise ranges so they can be used for building the map of + * pages that will be overwritten. */ + absolutise_ranges(); + absolutise_chain(&pagedir_resume.origranges); + + /* Mark the pages used by the current and original kernels */ + warmup_collision_cache(); + + /* Prepare to move the pages so they don't conflict */ + relativise_chain(&pagedir_resume.origranges); + relativise_ranges(); + + /* Relocate the pages */ + relocate_rangepages(); + + /* Make sure the rangepages list is correct */ + put_rangepages_list(); + get_rangepages_list(); + + /* Absolutise in final place */ + absolutise_ranges(); + + /* Done. + * + * Now we can absolutise all the pointers to the range chains. + */ + + set_chain_names(&pagedir_resume); + + absolutise_chain(&pagedir_resume.origranges); + + /* + * We don't want the original destination ranges (the locations where + * the atomic copy of pageset1 was stored at suspend time); we release + * the chain's elements before getting new ones. (The kernel running + * right now could be using pages that were free when we suspended). + */ + + absolutise_chain(&pagedir_resume.destranges); + + absolutise_chain(&pagedir_resume.allocdranges); + + if (unused_ranges) + unused_ranges = RANGE_ABSOLUTE(unused_ranges); + + put_rangepages_list(); + + /* + * The active writer should be using chains to record where it stored + * the data. Give it a chance to absolutise them. + */ + if (active_writer->ops.writer.post_load_ranges) + active_writer->ops.writer.post_load_ranges(); + + /* + * Get the addresses of pages into which we will load the kernel to + * be copied back + */ + put_range_chain(&pagedir_resume.destranges); + + if (get_pageset1_load_addresses()) { + result = -ENOMEM; + noresume_reset_plugins(); + goto outfreeingrangepages; + } + + 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)) { + prepare_status(1, 1, "Failed to read pageset 1."); + 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; + + if (active_writer->ops.writer.mark_resume_attempted) + active_writer->ops.writer.mark_resume_attempted(); + +out: + free_pages((unsigned long) header_buffer, 0); + return result; +outfreeingrangepages: + //FIXME Test i post loop and reset memory structures. + { + int j; + struct range * this_range_page = first_range_page; + struct range * next_range_page; + for (j = 0; j < i; j++) { + next_range_page = (struct range *) + (((unsigned long) + *RANGEPAGELINK(this_range_page)) & PAGE_MASK); + free_page((unsigned long) this_range_page); + this_range_page = next_range_page; + } + } + goto out; +} + +/* read_primary_suspend_image() + * + * Description: Attempt to read the header and pageset1 of a suspend image. + * Handle the outcome, complaining where appropriate. + */ +int read_primary_suspend_image(void) +{ + int error; + + error = __read_primary_suspend_image(); + + switch (error) { + case 0: + case -ENODATA: + case -EINVAL: /* non fatal error */ + software_suspend_state &= ~3; + MDELAY(1000); + return error; + case -EIO: + printk(KERN_CRIT name_suspend "I/O error\n"); + break; + case -ENOENT: + printk(KERN_CRIT name_suspend "No such file or directory\n"); + break; + case -EPERM: + printk(KERN_CRIT name_suspend "Sanity check error\n"); + break; + default: + printk(KERN_CRIT name_suspend "Error %d resuming\n", error); + break; + } + abort_suspend("Error %d in read_primary_suspend_image",error); + return error; +} + +/* read_secondary_pagedir() + * + * Description: Read in part or all of pageset2 of an image, depending upon + * whether we are suspending and have only overwritten a portion + * with pageset1 pages, or are resuming and need to read them + * all. + * Arguments: Int. Boolean. Read only pages which would have been + * overwritten by pageset1? + * Returns: Int. Zero if no error, otherwise the error value. + */ +int read_secondary_pagedir(int overwrittenpagesonly) +{ + int result = 0; + + if (!pageset2_size) + return 0; + + 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.0.96/kernel/power/lzf/lzf_c.c --- post-version-specific/kernel/power/lzf/lzf_c.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/lzf/lzf_c.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,220 @@ +/* + * 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. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License version 2 (the "GPL"), in which case the + * provisions of the GPL are applicable instead of the above. If you wish to + * allow the use of your version of this file only under the terms of the + * GPL and not to allow others to use your version of this file under the + * BSD license, indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by the GPL. If + * you do not delete the provisions above, a recipient may use your version + * of this file under either the BSD or the GPL. + */ + +#define HSIZE (1 << (HLOG)) + +/* + * don't play with this unless you benchmark! + * decompression is not dependent on the hash function + * the hashing function might seem strange, just believe me + * it works ;) + */ +#define FRST(p) (((p[0]) << 8) + p[1]) +#define NEXT(v,p) (((v) << 8) + p[2]) +#define IDX(h) ((((h ^ (h << 5)) >> (3*8 - HLOG)) + h*3) & (HSIZE - 1)) +/* + * IDX works because it is very similar to a multiplicative hash, e.g. + * (h * 57321 >> (3*8 - HLOG)) + * the next one is also quite good, albeit slow ;) + * (int)(cos(h & 0xffffff) * 1e6) + */ + +#if 0 +/* original lzv-like hash function */ +# define FRST(p) (p[0] << 5) ^ p[1] +# define NEXT(v,p) ((v) << 5) ^ p[2] +# define IDX(h) ((h) & (HSIZE - 1)) +#endif + +#define MAX_LIT (1 << 5) +#define MAX_OFF (1 << 13) +#define MAX_REF ((1 << 8) + (1 << 3)) + +/* + * compressed format + * + * 000LLLLL ; 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.0.96/kernel/power/lzf/lzf_d.c --- post-version-specific/kernel/power/lzf/lzf_d.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/lzf/lzf_d.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,98 @@ +/* + * 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. + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License version 2 (the "GPL"), in which case the + * provisions of the GPL are applicable instead of the above. If you wish to + * allow the use of your version of this file only under the terms of the + * GPL and not to allow others to use your version of this file under the + * BSD license, indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by the GPL. If + * you do not delete the provisions above, a recipient may use your version + * of this file under either the BSD or the GPL. + */ + +unsigned int +lzf_decompress (const void *const in_data, unsigned int in_len, + void *out_data, unsigned int out_len) +{ + u8 const *ip = in_data; + u8 *op = out_data; + u8 const *const in_end = ip + in_len; + u8 *const out_end = op + out_len; + + do + { + unsigned int ctrl = *ip++; + + if (ctrl < (1 << 5)) /* literal run */ + { + ctrl++; + + if (op + ctrl > out_end) + return 0; + +#if USE_MEMCPY + memcpy (op, ip, ctrl); + op += ctrl; + ip += ctrl; +#else + do + *op++ = *ip++; + while (--ctrl); +#endif + } + else /* back reference */ + { + unsigned int len = ctrl >> 5; + + u8 *ref = op - ((ctrl & 0x1f) << 8) - 1; + + if (len == 7) + len += *ip++; + + ref -= *ip++; + + if (op + len + 2 > out_end) + return 0; + + if (ref < (u8 *)out_data) + return 0; + + *op++ = *ref++; + *op++ = *ref++; + + do + *op++ = *ref++; + while (--len); + } + } + while (op < out_end && ip < in_end); + + return op - (u8 *)out_data; +} + diff -ruN post-version-specific/kernel/power/lzfcompress.c software-suspend-core-2.0.0.96/kernel/power/lzfcompress.c --- post-version-specific/kernel/power/lzfcompress.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/lzfcompress.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,526 @@ +/* + * 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 suspend, + * using LZH compression. + * + */ + +#include + +static int expected_lzf_compression = 0; + +/* + * size of hashtable is (1 << HLOG) * sizeof (char *) + * decompression is independent of the hash table size + * the difference between 15 and 14 is very small + * for small blocks (and 14 is also faster). + * For a low-memory configuration, use HLOG == 13; + * For best compression, use 15 or 16. + */ +#ifndef HLOG +# define HLOG 14 +#endif + +/* + * sacrifice some compression quality in favour of compression speed. + * (roughly 1-2% worse compression for large blocks and + * 9-10% for small, redundant, blocks and >>20% better speed in both cases) + * In short: enable this for binary data, disable this for text data. + */ +#ifndef ULTRA_FAST +# define ULTRA_FAST 1 +#endif + +#define STRICT_ALIGN 0 +#define USE_MEMCPY 1 +#define INIT_HTAB 0 + +#include "lzf/lzf_c.c" +#include "lzf/lzf_d.c" + +static struct suspend_plugin_ops lzf_compression_ops; +static struct suspend_plugin_ops * next_driver; + +static void *compression_workspace = NULL; +static u8 *local_buffer = NULL; +static struct page * local_buffer_page = NULL; +static u8 *page_buffer = NULL; +static struct page * page_buffer_page = NULL; +static unsigned int bufofs; + +static __nosavedata unsigned long bytes_in = 0, bytes_out = 0; + +/* allocate_compression_space + * + * Description: Allocate space for use in [de]compressing our data. + * Each call must have a matching call to free_memory. + * Returns: Int: Zero if successful, -ENONEM otherwise. + */ + +static inline int allocate_compression_space(void) +{ + BUG_ON(compression_workspace); + + compression_workspace = vmalloc_32((1< 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_page)) < 0) + return ret; + } + memcpy (local_buffer + bufofs, buffer, len); + bufofs += len; + return 0; +} + +/* lzf_write_chunk() + * + * Description: Compress a page of data, buffering output and passing on + * filled pages to the next plugin in the pipeline. + * Arguments: Buffer_page: Pointer to a buffer of size PAGE_SIZE, + * containing data to be compressed. + * Returns: 0 on success. Otherwise the error is that returned by later + * plugins, -ECHILD if we have a broken pipeline or -EPERM if + * zlib errs. + */ + +static int lzf_write_chunk(struct page * buffer_page) +{ + int ret; + u16 len; + char * buffer_start = kmap(buffer_page); + + bytes_in += PAGE_SIZE; + + len = lzf_compress(buffer_start, PAGE_SIZE, page_buffer, + PAGE_SIZE - 3, compression_workspace); + + if ((ret = lzf_write((u8 *)&len, 2)) >= 0) { + if (len) // some compression + ret = lzf_write(page_buffer, len); + else + ret = lzf_write(buffer_start, PAGE_SIZE); + } + kunmap(buffer_page); + return ret; +} + +/* write_cleanup() + * + * Description: Write unflushed data and free workspace. + * Returns: Result of writing last page. + */ + +static int lzf_write_cleanup(void) +{ + int ret; + + ret = next_driver->write_chunk(local_buffer_page); + + free_memory(); + free_local_buffer(); + + return ret; +} + +/* read_init() + * + * Description: Prepare to read a new stream of data. + * Arguments: int: Section of image about to be read. + * Returns: int: Zero on success, error number otherwise. + */ + +static int lzf_read_init(int stream_number) +{ + int result; + + next_driver = get_next_filter(&lzf_compression_ops); + + if (!next_driver) { + printk("LZF Compression Driver: Argh! No one wants " + "to feed me data!"); + return -ECHILD; + } + + if ((result = allocate_local_buffer())) + return result; + + bufofs = PAGE_SIZE; + + return 0; +} + +/* lzf_read() + * + * Description: Read data into compression buffer. + * Arguments: u8 *: Address of the buffer. + * unsigned int: Length + * Returns: int: Result of reading the image chunk. + */ + +static int lzf_read (u8 * buffer, unsigned int len) +{ + int ret; + + while (len + bufofs > PAGE_SIZE) { + unsigned int chunk = PAGE_SIZE - bufofs; + memcpy(buffer, local_buffer + bufofs, chunk); + buffer += chunk; + len -= chunk; + bufofs = 0; + if ((ret = next_driver->read_chunk( + local_buffer_page, SUSPEND_SYNC)) < 0) { + return ret; + } + } + memcpy (buffer, local_buffer + bufofs, len); + bufofs += len; + return 0; +} + +/* lzf_read_chunk() + * + * Description: Retrieve data from later plugins and decompress it until the + * input buffer is filled. + * Arguments: Buffer_start: Pointer to a buffer of size PAGE_SIZE. + * Sync: Whether the previous plugin (or core) wants its + * data synchronously. + * Returns: Zero if successful. Error condition from me or from downstream + * on failure. + */ + +static int lzf_read_chunk(struct page * buffer_page, int sync) +{ + int ret; + u16 len; + char * buffer_start = kmap(buffer_page); + + /* + * All our reads must be synchronous - we can't decompress + * data that hasn't been read yet. + */ + + if ((ret = lzf_read ((u8 *)&len, 2)) >= 0) { + if (len == 0) { // uncompressed + ret = lzf_read(buffer_start, PAGE_SIZE); + } else { // compressed + if ((ret = lzf_read(page_buffer, len)) >= 0) { + ret = lzf_decompress(page_buffer, len, buffer_start, PAGE_SIZE); + if (ret != PAGE_SIZE) + ret = -EPERM; // why EPERM?? + else + ret = 0; + } + } + } + kunmap(buffer_page); + return ret; +} + +/* read_cleanup() + * + * Description: Clean up after reading part or all of a stream of data. + * Returns: int: Always zero. Never fails. + */ + +static int lzf_read_cleanup(void) +{ + free_local_buffer(); + return 0; +} + +/* lzf_print_debug_stats + * + * Description: Print information to be recorded for debugging purposes into a + * buffer. + * Arguments: buffer: Pointer to a buffer into which the debug info will be + * printed. + * size: Size of the buffer. + * Returns: Number of characters written to the buffer. + */ + +static int lzf_print_debug_stats(char * buffer, int size) +{ + int pages_in = bytes_in >> PAGE_SHIFT, + pages_out = bytes_out >> PAGE_SHIFT; + int len; + + /* Output the compression ratio achieved. */ + len = suspend_snprintf(buffer, size, "- LZF Compressor enabled.\n"); + if (pages_in) + len+= suspend_snprintf(buffer+len, size - len, + " Compressed %ld bytes into %ld (%d percent compression).\n", + bytes_in, bytes_out, (pages_in - pages_out) * 100 / pages_in); + return len; +} + +/* compression_memory_needed + * + * Description: Tell the caller how much memory we need to operate during + * suspend/resume. + * Returns: Unsigned long. Maximum number of bytes of memory required for + * operation. + */ + +static unsigned long lzf_memory_needed(void) +{ + return PAGE_SIZE * 2 + (1< + * + * 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 * suspend_memory_pool[MAX_POOL_ORDER + 1] = + { NULL, NULL, NULL }; +static int suspend_pool_level_limit[MAX_POOL_ORDER + 1] = { 0, 1, 1 }; +static int suspend_pool_level[MAX_POOL_ORDER + 1] = { 0, 0, 0 }; +static spinlock_t suspend_memory_pool_lock = SPIN_LOCK_UNLOCKED; + +/* suspend_memory_pool_level() + * + * Description: Returns the number of pages currently in the pool. + * Returns: Int. Number of pages in the pool. + */ +int suspend_memory_pool_level(void) +{ + int order, sum = 0; + + for (order = 0; order <= MAX_POOL_ORDER; order++) + sum+= suspend_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; + + spin_lock_irqsave(&suspend_memory_pool_lock, flags); + printlog(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, "Memory pool:\n"); + for (order = 0; order <= MAX_POOL_ORDER; order++) { + printlog(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, + "- Order %d:\n", order); + next = suspend_memory_pool[order]; + count = 0; + while (next) { + printlog(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, + "[%p] ", next); + count++; + next = (unsigned long *) *next; + } + if (count) + printlog(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, + "(%d entries)\n", count); + else + printlog(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, + "(empty)\n"); + } + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); +} + +/* fill_suspend_memory_pool() + * + * Description: Fill the memory pool from the main free memory pool in the + * first instance, or grabbed pages if that fails. + * We allocate @sizesought order 0 pages, plus 1 each + * of the higher order allocations. + * Arguments: int. Number of order zero pages requested. + * Returns: int. Number of order zero pages obtained. + */ +int fill_suspend_memory_pool(int sizesought) +{ + int i = 0, j, order, orig_state = + software_suspend_state & SOFTWARE_SUSPEND_USE_MEMORY_POOL; + unsigned long *this = NULL; + unsigned long flags; + + spin_lock_irqsave(&suspend_memory_pool_lock, flags); + + /* Pool must not be active for this to work */ + software_suspend_state &= ~SOFTWARE_SUSPEND_USE_MEMORY_POOL; + + suspend_pool_level_limit[0] = sizesought; + + for (order = MAX_POOL_ORDER; order >= 0; order--) { + for (i = suspend_pool_level[order]; + i < suspend_pool_level_limit[order]; i++) { + this = (unsigned long *) get_grabbed_pages(order); + if (!this) { + printk(name_suspend "Error. %d pages wanted of" + " order %d for suspend memory pool," + " got %d.\n", + suspend_pool_level_limit[order], + order, i - 1); + break; + } + for (j = 0; j < (1 << order); j++) + SetPageChecksumIgnore(virt_to_page(this + j)); + *this = (unsigned long) suspend_memory_pool[order]; + suspend_memory_pool[order] = this; + suspend_pool_level[order]++; + } + } + + software_suspend_state |= orig_state; + + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); + + display_memory_pool_pages(); + + return 0; +} + +/* empty_suspend_memory_pool() + * + * Description: Drain our memory pool. + */ +void empty_suspend_memory_pool(void) +{ + unsigned long flags; + int order; + + display_memory_pool_pages(); + spin_lock_irqsave(&suspend_memory_pool_lock, flags); + + /* Pool must not be active for this to work */ + software_suspend_state &= ~SOFTWARE_SUSPEND_USE_MEMORY_POOL; + + for (order = 0; order <= MAX_POOL_ORDER; order++) { + while (suspend_memory_pool[order]) { + unsigned long next = *suspend_memory_pool[order]; + if (page_count(virt_to_page((unsigned long) + suspend_memory_pool[order])) != 1) + printk("Error freeing page %p from memory" + " pool. Page count is %d (should be" + " 1).\n", + suspend_memory_pool[order], + page_count(virt_to_page((unsigned long) + suspend_memory_pool[order]))); + *suspend_memory_pool[order] = 0; + free_pages((unsigned long) suspend_memory_pool[order], + order); + suspend_memory_pool[order] = (unsigned long *) next; + suspend_pool_level[order]--; + } + } + + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); +} + +/* get_suspend_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. + * We're giving normal zone pages regardless. + * order: The number of pages (1 << order) wanted. + * Returns: struct page *: Pointer (possibly NULL) to pages allocated. + */ +struct page * get_suspend_pool_page(unsigned int gfp_mask, unsigned int order) +{ + unsigned long * this = NULL, flags; + struct page * page; + static int printederror = 0; + static unsigned long * orig_value; + int i; + + if (order > 0) { + if (order <= MAX_POOL_ORDER) { + spin_lock_irqsave(&suspend_memory_pool_lock, flags); + if (!suspend_pool_level[order]) { + printk("No order %d allocation available.\n", + order); + spin_unlock_irqrestore( + &suspend_memory_pool_lock, + flags); + display_memory_pool_pages(); + return NULL; + } + goto check_and_return; + } + + if (!printederror) { + printederror = 1; + printk("Swsusp memory pool:" + " Rejecting allocation of order %d.\n", order); + } + display_memory_pool_pages(); + return NULL; + } else + orig_value = suspend_memory_pool[order]; + +try_again: + if ((!suspend_memory_pool[order]) && (!(gfp_mask & __GFP_WAIT))) { + display_memory_pool_pages(); + return NULL; + } + + while(!suspend_memory_pool[order]) { + active_writer->ops.writer.wait_on_io(0); + schedule(); + } + + spin_lock_irqsave(&suspend_memory_pool_lock, flags); + if (!suspend_memory_pool[order]) { + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); + goto try_again; + } +check_and_return: + this = suspend_memory_pool[order]; + suspend_memory_pool[order] = (unsigned long *) *this; + *this = 0; + suspend_pool_level[order]--; + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); + + page = virt_to_page(this); + for (i = 0; i < (1 << order); i++) { + ClearPageChecksumIgnore(page + i); + clear_page(this + i * (PAGE_SIZE / sizeof(unsigned long))); + } + + 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(); + if (suspend_pool_level[order] && !suspend_memory_pool[order]) { + printk("Original value was %p.\n", orig_value); + BUG(); + } + + display_memory_pool_pages(); + + return page; +} + +/* free_suspend_pool_pages() + * + * Description: Our equivalent to __free_pages. Put freed pages into the pool. + * HighMem pages do still get freed to the normal pool because they + * aren't going to affect the consistency of our image - worse case, + * we write a few free pages. + * Arguments: Struct page *: First page to be freed. + * Unsigned int: Size of allocation being freed. + */ +void free_suspend_pool_pages(struct page *page, unsigned int order) +{ + unsigned long *new_head, flags; + int i; + static unsigned long * orig_value; + + printlog(SUSPEND_MEM_POOL, SUSPEND_VERBOSE, "Freeing page %p (%p), order %d.\n", + page_address(page), page, order); + + for (i = 0; i < (1 << order); i++) { + clear_page(page_address(page + i)); + set_page_count(page + i, 0); + SetPageChecksumIgnore(page + i); + } + + spin_lock_irqsave(&suspend_memory_pool_lock, flags); + + if (order && (order <= MAX_POOL_ORDER) + && (suspend_pool_level[order] < suspend_pool_level_limit[order])) { + new_head = (unsigned long *) page_address(page); + set_page_count(page, 1); + *new_head = (unsigned long) suspend_memory_pool[order]; + suspend_memory_pool[order] = new_head; + suspend_pool_level[order]++; + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); + display_memory_pool_pages(); + return; + } + + if (order <= MAX_POOL_ORDER) + orig_value = suspend_memory_pool[order]; + + 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) suspend_memory_pool[0]; + suspend_memory_pool[0] = new_head; + suspend_pool_level[0]++; + } + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); + + display_memory_pool_pages(); +} + +int suspend_page_in_pool(struct page * page) +{ + unsigned long flags, *next, *address; + int order; + + if (PageHighMem(page)) + return 0; + + address = (unsigned long *) page_address(page); + + spin_lock_irqsave(&suspend_memory_pool_lock, flags); + for (order = 0; order <= MAX_POOL_ORDER; order++) { + next = suspend_memory_pool[order]; + while (next) { + if (next == address) { + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); + return 1; + } + next = (unsigned long *) *next; + } + } + spin_unlock_irqrestore(&suspend_memory_pool_lock, flags); + return 0; +} diff -ruN post-version-specific/kernel/power/nfswriter.c software-suspend-core-2.0.0.96/kernel/power/nfswriter.c --- post-version-specific/kernel/power/nfswriter.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/nfswriter.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,259 @@ +/* + * NFSwriter.c + * + * Copyright 2004 Nigel Cunningham + * + * Distributed under GPLv2. + * + * This file encapsulates functions for storing an image on + * an NFS share. + * + */ + +#include + +static int nfswriter_mount_share(void) +{ + void *data = nfs_root_data(); + + sys_mkdir("/proc/swsusp/nfsroot", 0700); + + if (data && sys_mount("/dev/root","/proc/swsusp/nfsroot","nfs",root_mountflags,data) == 0) + return 1; + + return 0; +} + +static int nfswriter_unmount_share(void) +{ + sys_rmdir("/proc/swsusp/nfsroot"); +} + +static unsigned long nfswriter_storage_allocated(void) +{ + return 0; +} + +static long nfswriter_storage_available(void) +{ + return (long) (0); +} + +static int nfswriter_release_storage(void) +{ +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if ((TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) && (now_resuming)) + return 0; +#endif + + /* Invalidate the image */ + return 0; +} + +static long nfswriter_allocate_header_space(unsigned long space_requested) +{ +} + +static int nfswriter_allocate_storage(unsigned long space_requested) +{ +} + +static int nfswriter_write_header_init(void) +{ + return 0; +} + +static int nfswriter_write_header_chunk(char * buffer, int buffer_size) +{ + return 0; +} + +static int nfswriter_write_header_cleanup(void) +{ + return 0; +} + +/* ------------------------- HEADER READING ------------------------- */ + +static int nfswriter_read_header_init(void) +{ + return 0; +} + +static int nfswriter_read_header_chunk(char * buffer, int buffer_size) +{ + return buffer_size; +} + +static int nfswriter_read_header_cleanup(void) +{ + return 0; +} + +static int nfswriter_prepare_save_ranges(void) +{ + return 0; +} + +static int nfswriter_post_load_ranges(void) +{ + return 0; +} + +static int nfswriter_write_init(int stream_number) +{ + return 0; +} + +static int nfswriter_write_chunk(char * buffer) +{ + return 0; +} + +static int nfswriter_write_cleanup(void) +{ + return 0; +} + +static int nfswriter_read_init(int stream_number) +{ + return 0; +} + +static int nfswriter_read_chunk(char * buffer, int sync) +{ + return 0; +} + +static int nfswriter_read_cleanup(void) +{ + return 0; +} + +static int nfswriter_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 nfswriter_memory_needed(void) +{ + return 0; +} + +/* + * Storage needed + * + * Returns amount of space in the header required + * for the nfswriter's data. + * + */ +static unsigned long nfswriter_storage_needed(void) +{ + return 0; +} + +/* + * Wait on I/O + * + */ + +static int nfswriter_wait_on_io(int flush_all) +{ + return 0; +} + +/* + * Image_exists + * + */ + +static int nfswriter_image_exists(void) +{ + return 0; +} + +/* + * Parse Image Location + * + */ + +static int nfswriter_parse_image_location(char * commandline, int boot_time, + int only_writer) +{ + int posn = 0; + + if (strncmp(commandline, "nfs:", 5)) { + if (!only_writer) { + printk(name_suspend "Commandline doesn't begin with 'nfs:'" + "... nfswriter ignoring.\n"); + return 0; + } + + } else + posn+= 5; + + /* + * posn now points to the start of the parameters we need to parse. + */ + + return 1; +} + +struct suspend_plugin_ops nfswriterops = { + .type = WRITER_PLUGIN, + .name = "NFS Writer", + .memory_needed = nfswriter_memory_needed, + .storage_needed = nfswriter_storage_needed, + .write_init = nfswriter_write_init, + .write_chunk = nfswriter_write_chunk, + .write_cleanup = nfswriter_write_cleanup, + .read_init = nfswriter_read_init, + .read_chunk = nfswriter_read_chunk, + .read_cleanup = nfswriter_read_cleanup, + .ops = { + .writer = { + .storage_available = nfswriter_storage_available, + .storage_allocated = nfswriter_storage_allocated, + .release_storage = nfswriter_release_storage, + .allocate_header_space = + nfswriter_allocate_header_space, + .allocate_storage = nfswriter_allocate_storage, + .image_exists = nfswriter_image_exists, + .write_header_init = nfswriter_write_header_init, + .write_header_chunk = nfswriter_write_header_chunk, + .write_header_cleanup = + nfswriter_write_header_cleanup, + .read_header_init = nfswriter_read_header_init, + .read_header_chunk = nfswriter_read_header_chunk, + .read_header_cleanup = + nfswriter_read_header_cleanup, + .prepare_save_ranges = + nfswriter_prepare_save_ranges, + .post_load_ranges = nfswriter_post_load_ranges, + .invalidate_image = nfswriter_invalidate_image, + .wait_on_io = nfswriter_wait_on_io, + .parse_image_location = + nfswriter_parse_image_location, + } + } +}; + +/* ---- Registration ---- */ + +static __init int nfswriter_load(void) +{ + printk("Software Suspend NFS Writer v0.1\n"); + return suspend_register_plugin(&nfswriterops); +} + +__initcall(nfswriter_load); + diff -ruN post-version-specific/kernel/power/nulltransformer.c software-suspend-core-2.0.0.96/kernel/power/nulltransformer.c --- post-version-specific/kernel/power/nulltransformer.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/nulltransformer.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,183 @@ +/* + * 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 suspend_plugin_ops nulltransformer_ops; +struct suspend_plugin_ops * next_driver; + +/* 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) +{ + next_driver = get_next_filter(&nulltransformer_ops); + + if (!next_driver) { + printk("Null Pagetransformer: Argh! No one wants my output!"); + return -ECHILD; + } + + 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(struct page * buffer) +{ + return next_driver->write_chunk(buffer); +} + +/* 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) +{ + next_driver = get_next_filter(&nulltransformer_ops); + + if (!next_driver) { + printk("Null Pagetransformer: Argh! " + "No one wants to feed me data!"); + return -ECHILD; + } + + 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(struct page * buffer, int sync) +{ + return next_driver->read_chunk(buffer, 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/suspend/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 suspend_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 suspend_register_plugin(&nulltransformer_ops); +} + +__initcall(nulltrans_load); + diff -ruN post-version-specific/kernel/power/nullwriter.c software-suspend-core-2.0.0.96/kernel/power/nullwriter.c --- post-version-specific/kernel/power/nullwriter.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/nullwriter.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,275 @@ +/* + * Nullwriter.c + * + * Copyright 2004 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(struct page * buffer_page) +{ + 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(struct page * buffer_page, 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, + int only_writer) +{ + int posn = 0; + + if (strncmp(commandline, "null:", 5)) { + if (!only_writer) { + printk(name_suspend "Commandline doesn't begin with 'null:'" + "... nullwriter ignoring.\n"); + return 0; + } + + } else + posn+= 5; + + /* + * posn now points to the start of the parameters we need to parse. + */ + + return 1; +} + +struct suspend_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 suspend_register_plugin(&nullwriterops); +} + +__initcall(nullwriter_load); + diff -ruN post-version-specific/kernel/power/pagedir.c software-suspend-core-2.0.0.96/kernel/power/pagedir.c --- post-version-specific/kernel/power/pagedir.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/pagedir.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,432 @@ +/* + * 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); +} + +/* 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; +} + +/* + * -------------------------------------------------------------------------------------- + * + * Local Page Flags routines. + * + * Rather than using the rare and precious flags in struct page, we allocate + * our own bitmaps dynamically. + * + */ + +/* ------------------------------------------------------------------------- */ + +/* copy_pageset1 + * + * Description: Make the atomic copy of pageset1. We can't use copy_page (as we + * once did) because we can't be sure what side effects it has. On + * my Duron, with 3DNOW, kernel_fpu_begin increments preempt + * count, making our preempt count at resume time 4 instead of 3. + */ +extern char __nosavedata swsusp_pg_dir[PAGE_SIZE] + __attribute__ ((aligned (PAGE_SIZE))); + +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 = KMAP_ATOMIC(pbe.origaddress); + unsigned long * copypage = page_address(pbe.address); + + for (loop=0; loop < (PAGE_SIZE / sizeof(unsigned long)); loop++) + *(copypage + loop) = *(origpage + loop); + KUNMAP_ATOMIC(pbe.origaddress); + get_next_pbe(&pbe); + } + +} + +/* free_pagedir + * + * Description: Free a previously allocated pagedir. + * Arguments: struct pagedir *: Pointer to the pagedir being freed. + */ +void free_pagedir(struct pagedir * p) +{ + PRINTFREEMEM("at start of free_pagedir"); + + if (p->allocdranges.first) { + /* Free allocated pages */ + struct range * rangepointer; + unsigned long pagenumber; + range_for_each(&p->allocdranges, rangepointer, pagenumber) { + ClearPageNosave(mem_map+pagenumber); + free_page((unsigned long) page_address(mem_map+pagenumber)); + } + } + + suspend_store_free_mem(SUSPEND_FREE_EXTRA_PD1, 1); + + /* For pagedir 2, destranges == origranges */ + if (p->pagedir_num == 2) + p->destranges.first = NULL; + + put_range_chain(&p->origranges); + put_range_chain(&p->destranges); + put_range_chain(&p->allocdranges); + + PRINTFREEMEM("at end of free_pagedir"); + 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; + int j, order; + + prepare_status(0, 0, "Preparing page directory."); + + PRINTFREEMEM("at start of allocate_extra_pagedir_memory"); + + if (num_to_alloc < 1) + num_to_alloc = 0; + + if (num_to_alloc) { + int num_added = 0, numnosaveallocated=0; +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + int origallocd = p->allocdranges.size; +#endif + + PRINTFREEMEM("prior to attempt"); + + order = generic_fls(num_to_alloc); + if (order >= MAX_ORDER) + order = MAX_ORDER - 1; + + while (num_added < num_to_alloc) { + struct page * newpage; + unsigned long virt; + + while ((1 << order) > (num_to_alloc - num_added)) + order--; + + virt = get_grabbed_pages(order); + while ((!virt) && (order > 0)) { + order--; + virt = get_grabbed_pages(order); + } + + if (!virt) { + p->pageset_size += num_added; + 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 - num_added); + PRINTFREEMEM("at abort of " + "allocate_extra_pagedir_memory"); + suspend_store_free_mem(SUSPEND_FREE_EXTRA_PD1, 0); + return 1; + } + + newpage = virt_to_page(virt); + for (j = 0; j < (1 << order); j++) { + SetPageNosave(newpage + j); + /* Pages will be freed one at a time. */ + set_page_count(newpage + j, 1); + add_to_range_chain(&p->allocdranges, newpage - mem_map + j); + numnosaveallocated++; + } + num_added+= (1 << order); + } + 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; + + suspend_store_free_mem(SUSPEND_FREE_EXTRA_PD1, 0); + PRINTFREEMEM("at end of allocate_extra_pagedir_memory"); + return 0; +} + +/* mark_page_pageset2 + * + * Description: This looks a bit pointless, but it seems to be 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. + */ + +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) { + if (get_rangepages_list()) + return; + + for (i = 1; i <= num_range_pages; i++) { + struct page * page; + page = virt_to_page(get_rangepages_list_entry(i)); + // Must be assigned by the time recalc stats is called + if (PagePageset2(page)) { + printlog(SUSPEND_PAGESETS, SUSPEND_ERROR, + "Pagedir[%d] was marked as pageset2 -" + " unmarking.\n", i); + ClearPagePageset2(page); + numpageset2--; + } + } + put_rangepages_list(); + } + + /* Finally, ensure that Slab pages are not Pageset2. */ + + for (i = 0; i < max_mapnr; i++) { + if (PageSlab(mem_map+i)) { + if (TestAndClearPagePageset2(mem_map+i)) { + printk("Found page %d is slab page " + "but marked pageset 2.\n", i); + numpageset2--; + } + } +#if 0 + if (PageHighMem(mem_map+i)) + if (!TestAndSetPagePageset2(mem_map+i)) + numpageset2++; +#endif + } +} + +/* warmup_collision_cache + * + * Description: Mark the pages which are used by the original kernel. + */ +void warmup_collision_cache(void) { + int i; + struct range * rangepointer = NULL; + unsigned long pagenumber; + + /* Allocatemap doesn't get deallocated because it's forgotten when we + * copy PageDir1 back. It doesn't matter if it collides because it is + * not used during the copy back itself. + */ + allocatemap(&in_use_map, 0); + 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); +} + +/* get_pageset1_load_addresses + * + * Description: We check here that pagedir & pages it points to won't collide + * with pages where we're going to restore from the loaded pages + * later. + * Returns: Zero on success, one if couldn't find enough pages (shouldn't + * happen). + */ + +int get_pageset1_load_addresses(void) +{ + int i, nrdone = 0, result = 0; + void **eaten_memory = NULL, **this; + struct page * pageaddr = NULL; + + /* + * Because we're trying to make this work when we're saving as much + * memory as possible we need to remember the pages we reject here + * and then free them when we're done. + */ + + for(i=0; i < pagedir_resume.pageset_size; i++) { + while ((this = (void *) get_zeroed_page(GFP_ATOMIC))) { + memset(this, 0, PAGE_SIZE); + pageaddr = virt_to_page(this); + if (!PageInUse(pageaddr)) { + break; + } + *this = eaten_memory; + eaten_memory = this; + } + if (!this) { + abort_suspend("Error: Ran out of memory seeking locations for reloading data."); + result = 1; + break; + } + add_to_range_chain(&pagedir_resume.destranges, pageaddr - mem_map); + nrdone++; + } + + /* Free unwanted memory */ + while(eaten_memory) { + this = eaten_memory; + eaten_memory = *eaten_memory; + free_page((unsigned long) this); + } + + return result; +} + +/* set_chain_names + * + * Description: Set the chain names for a pagedir. (For debugging). + * Arguments: struct pagedir: The pagedir on which we want to set the names. + */ + +void set_chain_names(struct pagedir * p) +{ + p->origranges.name = "original addresses"; + p->destranges.name = "destination addresses"; + p->allocdranges.name = "allocated addresses"; +} diff -ruN post-version-specific/kernel/power/prepare_image.c software-suspend-core-2.0.0.96/kernel/power/prepare_image.c --- post-version-specific/kernel/power/prepare_image.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/prepare_image.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,812 @@ +/* + * 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 pageset1_sizelow, pageset2_sizelow; +extern unsigned long orig_mem_free; +extern void mark_pages_for_pageset2(void); +extern int image_size_limit; +extern int fill_suspend_memory_pool(int sizesought); + +int suspend_amount_grabbed = 0; +static int arefrozen = 0, numnosave = 0; + +static 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; + 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 { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,5) + page = list_entry(curr, struct page, lru); +#else + page = list_entry(curr, struct page, list); +#endif + for(loop=0; loop < (1 << order); loop++) + ClearPageInUse(page+loop); + } + + curr = curr->next; + if (curr == head) + break; + } + } while(order--); + spin_unlock_irqrestore(&zone->lock, flags); + } +} + +static int size_of_free_region(struct page * page) +{ + struct page * posn = page; + + while (((posn-mem_map) < max_mapnr) && (!PageInUse(posn))) + posn++; + return (posn - page); +} + +/* + * 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 suspend_plugin_ops * this_plugin; + unsigned long bytes = 0; + + list_for_each(plugin, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_plugin_ops, plugin_list); + if (this_plugin->disabled) + continue; + 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 suspend_plugin_ops * this_filter; + unsigned long ratio = 100; + + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, ops.filter.filter_list); + if (this_filter->disabled) + continue; + if (this_filter->ops.filter.expected_compression) + ratio = ratio * this_filter->ops.filter.expected_compression() / 100; + } + + return (int) ratio; +} + +/* + * memory_for_plugins + * + * Returns the amount of memory requested by plugins for + * doing their work during the cycle. + */ + +unsigned long memory_for_plugins(void) +{ + unsigned long bytes = 0; + struct list_head *plugin; + struct suspend_plugin_ops * this_plugin; + + list_for_each(plugin, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_plugin_ops, plugin_list); + if (this_plugin->disabled) + continue; + if (this_plugin->memory_needed) + bytes += this_plugin->memory_needed(); + } + + return ((bytes + PAGE_SIZE - 1) >> PAGE_SHIFT); +} + +static void display_reserved_pages(void) +{ + int loop; + int rangemin = -1; + + for (loop = 0; loop < max_mapnr; loop++) { + if (PageReserved(mem_map+loop)) { + if (rangemin == -1) + rangemin = loop; + } else { + if (rangemin > -1) { + printk("Reserved pages from %p to %p.\n", + page_address(mem_map+rangemin), + ((char *) page_address(mem_map + loop)) - 1); + rangemin = -1; + } + } + } + + if (rangemin > -1) + printk("Reserved pages from %p to %p.\n", + page_address(mem_map+rangemin), + ((char *) page_address(mem_map + max_mapnr)) - 1); +} + +void display_nosave_pages(void) +{ + int loop; + int rangemin = -1; + + if (!CHECKMASK(SUSPEND_NOSAVE)) + return; + + display_reserved_pages(); + + for (loop = 0; loop < max_mapnr; loop++) { + if (PageNosave(mem_map+loop)) { + if (rangemin == -1) + rangemin = loop; + } else { + if (rangemin > -1) { + printk("Nosave pages from %p to %p.\n", + page_address(mem_map+rangemin), + ((char *) page_address(mem_map + loop)) - 1); + rangemin = -1; + } + } + } + + if (rangemin > -1) + printk("Nosave pages from %p to %p.\n", + page_address(mem_map+rangemin), + ((char *) page_address(mem_map + max_mapnr)) - 1); +} + +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.size1low = 0; + result.size2 = 0; + result.size2low = 0; + result.needmorespace = 0; + + numnosave = 0; + + put_range_chain(&pagedir1.origranges); + put_range_chain(&pagedir1.destranges); + put_range_chain(&pagedir2.origranges); + pagedir2.destranges.first = NULL; + pagedir2.destranges.size = 0; + + generate_free_page_map(); + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) { + result.size1 = -1; + result.size1low = -1; + result.size2 = -1; + result.size2low = -1; + result.needmorespace = 0; + return result; + } + + if (max_mapnr != num_physpages) { + abort_suspend("Max_mapnr is not equal to num_physpages."); + result.size1 = -1; + result.size1low = -1; + result.size2 = -1; + result.size2low = -1; + result.needmorespace = 0; + return result; + } + /* + * Pages not to be saved are marked Nosave irrespective of being reserved + */ + for (loop = 0; loop < max_mapnr; loop++) { + if (PageNosave(mem_map+loop)) { + numnosave++; + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + continue; + } + + if (!PageReserved(mem_map+loop)) { + if ((chunk_size=size_of_free_region(mem_map+loop))!=0) { + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + numfree += chunk_size; + loop += chunk_size - 1; + continue; + } + } else { +#ifdef CONFIG_HIGHMEM + if (loop >= highstart_pfn) { + /* HighMem pages may be marked Reserved. We ignore them. */ + numnosave++; + if (currentrange) { + append_to_range_chain(currentrange, rangemin, loop - 1); + rangemin = loop; + currentrange = 0; + } + continue; + } +#endif + }; + + usepagedir2 = !!PagePageset2(mem_map+loop); + + if (currentrange != (1 + usepagedir2)) { + if (currentrange) + append_to_range_chain(currentrange, rangemin, loop - 1); + currentrange = usepagedir2 + 1; + rangemin = loop; + ranges++; + } + + if (usepagedir2) { + result.size2++; + if (!PageHighMem(mem_map+loop)) + result.size2low++; + } else { + result.size1++; + if (!PageHighMem(mem_map+loop)) + result.size1low++; + } + } + + if (currentrange) + append_to_range_chain(currentrange, rangemin, loop - 1); + + if ((pagedir1.pageset_size) && (result.size1 > pagedir1.pageset_size)) + result.needmorespace = 1; + if ((pagedir2.pageset_size) && (result.size2 > pagedir2.pageset_size)) + result.needmorespace = 1; + 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); + } + + 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(int use_image_size_limit) +{ + + int max1 = MAX( (int) (RAM_TO_SUSPEND - nr_free_pages() - + nr_free_highpages() - suspend_amount_grabbed), + ((int) (STORAGE_NEEDED(1) - + active_writer->ops.writer.storage_available()))); + if (use_image_size_limit) + return MAX( max1, + (image_size_limit > 0) ? + (STORAGE_NEEDED(1) - (image_size_limit << 8)) : 0); + return max1; +} + +#define EATEN_ENOUGH_MEMORY() (amount_needed(1) < 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(%d). Sets:%d(%d),%d(%d). Header:%d. Nosave:%d-%d-%d=%d. Storage:%d/%lu(%lu). Needed:%d|%d|%d\n", + + /* Free */ + nr_free_pages(), suspend_amount_grabbed, suspend_memory_pool_level(), + nr_free_pages() + suspend_amount_grabbed + suspend_memory_pool_level(), + nr_free_pages() - nr_free_highpages(), + + /* Sets */ + pageset1_size, pageset1_sizelow, + pageset2_size, pageset2_sizelow, + + /* Header */ + num_range_pages, + + /* Nosave */ + numnosave, pagedir1.allocdranges.size, suspend_amount_grabbed, + numnosave - pagedir1.allocdranges.size - suspend_amount_grabbed, + + /* Storage - converted to pages for comparison */ + storage_allocated, + STORAGE_NEEDED(1), + storage_available, + + /* Needed */ + RAM_TO_SUSPEND - nr_free_pages() - nr_free_highpages() - suspend_amount_grabbed, + STORAGE_NEEDED(1) - storage_available, + (image_size_limit > 0) ? (STORAGE_NEEDED(1) - (image_size_limit << 8)) : 0); +#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 suspend_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(); + pageset1_sizelow = result.size1low; + pageset2_sizelow = result.size2low; + pageset2_size = result.size2; + pageset1_size = result.size1; + storage_available = active_writer->ops.writer.storage_available(); + suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0); + return result; +} + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS +extern void suspend_allocate_checksum_pages(void); +extern void suspend_calculate_checksums(void); +#endif + +static int update_image(void) +{ + struct pageset_sizes_result result; + int iteration = 0, orig_num_range_pages; + + result = recalculate_stats(); + + suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0); + + do { + iteration++; + + orig_num_range_pages = num_range_pages; + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW,"-- Iteration %d.\n", iteration); + + if (allocate_extra_pagedir_memory(&pagedir1, pageset1_size, pageset2_sizelow)) { + 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(1))) { + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, + "Still need to get more storage space for the image proper.\n"); + suspend_store_free_mem(SUSPEND_FREE_WRITER_STORAGE, 0); + return 1; + } + + suspend_store_free_mem(SUSPEND_FREE_WRITER_STORAGE, 0); + + 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; + } + + /* + * Allocate remaining storage space, if possible, up to the + * maximum we know we'll need. It's okay to allocate the + * maximum if the writer is the swapwriter, but + * we don't want to grab all available space on an NFS share. + * We therefore ignore the expected compression ratio here, + * thereby trying to allocate the maximum image size we could + * need (assuming compression doesn't expand the image), but + * don't complain if we can't get the full amount we're after. + */ + + active_writer->ops.writer.allocate_storage( + MAX((long)(active_writer->ops.writer.storage_available() - + active_writer->ops.writer.storage_allocated()), + (long)(HEADER_STORAGE_NEEDED + MAIN_STORAGE_NEEDED(1)))); + + suspend_store_free_mem(SUSPEND_FREE_WRITER_STORAGE, 0); + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS + printlog(SUSPEND_ANY_SECTION, SUSPEND_MEDIUM,"-- Allocate checksum pages.\n"); + + suspend_allocate_checksum_pages(); +#endif + + 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(1))) + && (!TEST_RESULT_STATE(SUSPEND_ABORTED))); + + printlog(SUSPEND_ANY_SECTION, SUSPEND_MEDIUM,"-- Exit loop.\n"); + + return (amount_needed(0) > 0); +} + +static spinlock_t suspend_grabbed_memory_lock = SPIN_LOCK_UNLOCKED; + +struct eaten_memory_t +{ + void * next; +}; + +struct eaten_memory_t *eaten_memory[MAX_ORDER]; + +static void grab_free_memory(void) +{ + int order, k; + unsigned long flags; + + spin_lock_irqsave(&suspend_grabbed_memory_lock, flags); + /* + * First, quickly eat all memory that's already free. + */ + + for (order = MAX_ORDER - 1; order > -1; order--) { + struct eaten_memory_t *prev = eaten_memory[order]; + eaten_memory[order] = (struct eaten_memory_t *) __get_free_pages(EAT_MEMORY_FLAGS, order); + while (eaten_memory[order]) { + struct page * page = virt_to_page(eaten_memory[order]); + eaten_memory[order]->next = prev; + prev = eaten_memory[order]; + suspend_amount_grabbed += (1 << order); + for (k=0; k < (1 << order); k++) + SetPageNosave(page + k); + eaten_memory[order] = (struct eaten_memory_t *) __get_free_pages(EAT_MEMORY_FLAGS, order); + } + eaten_memory[order] = prev; + } + + spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags); +} + +static void free_grabbed_memory(void) +{ + struct eaten_memory_t *next = NULL, *this = NULL; + int j, num_freed = 0, order; + unsigned long flags; + + spin_lock_irqsave(&suspend_grabbed_memory_lock, flags); + + /* Free all eaten pages immediately */ + for (order = MAX_ORDER - 1; order > -1; order--) { + this=eaten_memory[order]; + while(this) { + struct page * page = virt_to_page(this); + next = this->next; + for (j=0; j < (1 << order); j++) + ClearPageNosave(page + j); + free_pages((unsigned long) this, order); + num_freed+= (1 << order); + this = next; + } + eaten_memory[order] = NULL; + } + suspend_amount_grabbed -= num_freed; + BUG_ON(suspend_amount_grabbed); + spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags); +} + +unsigned long get_grabbed_pages(int order) +{ + unsigned long this = (unsigned long) eaten_memory[order]; + int alternative, j; + unsigned long flags; + + /* Get grabbed lowmem pages for suspend's use */ + spin_lock_irqsave(&suspend_grabbed_memory_lock, flags); + +try_again: + if (this) { + struct page * page = virt_to_page(this); + eaten_memory[order] = eaten_memory[order]->next; + for (j=0; j < (1 << order); j++) { + ClearPageNosave(page + j); + clear_page(page_address(page + j)); + } + suspend_amount_grabbed -= (1 << order); + spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags); + check_shift_keys(0, NULL); + return this; + } + + alternative = order+1; + while ((!eaten_memory[alternative]) && (alternative < MAX_ORDER)) + alternative++; + + /* Maybe we didn't eat any memory - try normal get */ + if (alternative == MAX_ORDER) { + this = __get_free_pages(EAT_MEMORY_FLAGS, order); + if (this) + for (j=0; j < (1 << order); j++) + clear_page((char *) this + j * PAGE_SIZE); + spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags); + check_shift_keys(0, NULL); + return this; + } + + { + struct page * page = virt_to_page(eaten_memory[alternative]); + unsigned long virt = (unsigned long) eaten_memory[alternative]; + eaten_memory[alternative] = eaten_memory[alternative]->next; + for (j=0; j < (1 << (alternative)); j++) { + ClearPageNosave(page + j); + clear_page(page_address(page + j)); + } + free_pages(virt, alternative); + suspend_amount_grabbed -= (1 << alternative); + } + + /* Get the chunk we want to return. May fail if something grabs + * the memory before us. */ + this = __get_free_pages(EAT_MEMORY_FLAGS, order); + if (!this) + goto try_again; + + for (j=0; j < (1 << order); j++) + clear_page(this); + + spin_unlock_irqrestore(&suspend_grabbed_memory_lock, flags); + + /* Grab the rest */ + grab_free_memory(); + + check_shift_keys(0, NULL); + return this; +} + +extern int freeze_processes(int no_progress); + +static int attempt_to_freeze(void) +{ + int result; + + /* Stop processes before checking again */ + thaw_processes(); + prepare_status(1, 1, "Freezing processes"); + result = freeze_processes(0); + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, "- Freeze_processes returned %d.\n", + result); + + if (result) { + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_FREEZING_FAILED); + } else + arefrozen = 1; + + return result; +} + +static int eat_memory(void) +{ + int orig_memory_still_to_eat, last_amount_needed = 0, times_criteria_met = 0; + int free_flags = 0; + + /* + * Note that if we have enough storage space and enough free memory, we may + * exit without eating anything. We give up when the last 10 iterations ate + * no extra pages because we're not going to get much more anyway, but + * the few pages we get will take a lot of time. + * + * We freeze processes before beginning, and then unfreeze them if we + * need to eat memory until we think we have enough. If our attempts + * to freeze fail, we give up and abort. + */ + + /* ----------- Stage 1: Freeze Processes ------------- */ + + + prepare_status(0, 1, "Eating memory."); + + recalculate_stats(); + display_stats(); + + orig_memory_still_to_eat = amount_needed(1); + last_amount_needed = orig_memory_still_to_eat; + + switch (image_size_limit) { + case -1: /* Don't eat any memory */ + if (orig_memory_still_to_eat) { + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_WOULD_EAT_MEMORY); + } + break; + case -2: /* Free caches only */ + free_flags = GFP_NOIO | __GFP_HIGHMEM; + break; + default: + free_flags = GFP_ATOMIC | __GFP_HIGHMEM; + } + + /* ----------- Stage 2: Eat memory ------------- */ + + while ((!EATEN_ENOUGH_MEMORY()) && (!TEST_RESULT_STATE(SUSPEND_ABORTED)) && (times_criteria_met < 10)) { + if (orig_memory_still_to_eat) + update_status(orig_memory_still_to_eat - amount_needed(1), orig_memory_still_to_eat, " Image size %d ", MB(STORAGE_NEEDED(1))); + + if ((last_amount_needed - amount_needed(1)) < 10) + times_criteria_met++; + else + times_criteria_met = 0; + last_amount_needed = amount_needed(1); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + try_to_free_pages_suspend(amount_needed(1), free_flags); +#else + shrink_all_memory(amount_needed(1)); +#endif + 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(1)); + + /* Blank out image size display */ + update_status(100, 100, " "); + + /* Include image size limit when checking what to report */ + if (amount_needed(1) > 0) + SET_RESULT_STATE(SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY); + + /* But don't include it when deciding whether to abort (soft limit) */ + if ((amount_needed(0) > 0)) { + printk("Unable to free sufficient memory to suspend. Still need %d pages.\n", + amount_needed(1)); + SET_RESULT_STATE(SUSPEND_ABORTED); + } + + check_shift_keys(1, "Memory eating completed."); + return 0; +} + + +int prepare_image(void) +{ + int result = 1, sizesought; + + arefrozen = 0; + + sizesought = 100 + memory_for_plugins(); + + PRINTFREEMEM("prior to filling the memory pool"); + + if (fill_suspend_memory_pool(sizesought)) + return 1; + + PRINTFREEMEM("after filling the memory pool"); + suspend_store_free_mem(SUSPEND_FREE_MEM_POOL, 0); + + if (attempt_to_freeze()) + return 1; + + PRINTFREEMEM("after freezing processes"); + suspend_store_free_mem(SUSPEND_FREE_FREEZER, 0); + + if (!active_writer->ops.writer.storage_available()) { + printk(KERN_ERR "You need some storage available to be able to suspend.\n"); + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_NOSTORAGE_AVAILABLE); + return 1; + } + + do { + if (eat_memory() || TEST_RESULT_STATE(SUSPEND_ABORTED)) + break; + + PRINTFREEMEM("after eating memory"); + suspend_store_free_mem(SUSPEND_FREE_EAT_MEMORY, 0); + + /* Top up */ + if (fill_suspend_memory_pool(sizesought)) + continue; + + PRINTFREEMEM("after refilling memory pool"); + suspend_store_free_mem(SUSPEND_FREE_MEM_POOL, 0); + + do_suspend_sync(); + + PRINTFREEMEM("after syncing"); + suspend_store_free_mem(SUSPEND_FREE_SYNC, 0); + + result = update_image(); + PRINTFREEMEM("after updating the image"); + + } while ((result) && (!TEST_RESULT_STATE(SUSPEND_ABORTED)) && + (!TEST_RESULT_STATE(SUSPEND_UNABLE_TO_FREE_ENOUGH_MEMORY))); + + PRINTFREEMEM("after preparing image"); + + /* Release memory that has been eaten */ + free_grabbed_memory(); + + PRINTFREEMEM("after freeing grabbed memory"); + suspend_store_free_mem(SUSPEND_FREE_GRABBED_MEMORY, 1); + + software_suspend_state |= SOFTWARE_SUSPEND_USE_MEMORY_POOL; + + check_shift_keys(1, "Image preparation complete."); + + return result; +} diff -ruN post-version-specific/kernel/power/proc.c software-suspend-core-2.0.0.96/kernel/power/proc.c --- post-version-specific/kernel/power/proc.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/proc.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,590 @@ +/* + * /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/suspend the only tuning interface + * 2: Initial version of this file + * 3: Removed kernel debugger parameter. + * Added checkpage parameter (for checking checksum of a page over time). + * 4: Added entry for maximum granularity in splash screen progress bar. + * (Progress bar is slow, but the right setting will vary with disk & + * processor speed and the user's tastes). + * 5: Added enable_escape to control ability to cancel aborting by pressing + * ESC key. + * 6: Removed checksumming and checkpage parameter. Made all debugging proc + * entries dependant upon debugging being compiled in. + * Meaning of some flags also changed in this version. + * 7: Added header_locations entry to simplify getting the resume= parameter for + * swapfiles easy and swapfile entry for automatically doing swapon/off from + * swapfiles as well as partitions. + * 8: Added option for marking process pages as pageset 2 (processes_pageset2). + * 9: Added option for keep image mode. + * Enumeration patch from Michael Frank applied. + * 10: Various corrections to when options are disabled/enabled; + * Added option for specifying expected compression. + * 11: Added option for freezer testing. Debug only. + * 12: Removed test entries no_async_[read|write], processes_pageset2 and + * NoPageset2. + * 13: Make default_console_level available when debugging disabled, but limited + * to 0 or 1. + * 14: Rewrite to allow for dynamic registration of proc entries and smooth the + * transition to kobjects in 2.6. + * 15: Add setting resume2 parameter without rebooting (still need to run lilo + * though!). + * 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 suspend_proc_version = 15; +static int proc_initialised = 0; + +#include +#include +#include + +static struct list_head suspend_proc_entries; +static struct proc_dir_entry *suspend_dir, *compat_entry, *compat_parent; + +extern void suspend_console_proc_init(void); +extern char resume_file[256]; /* For resume= kernel option */ + +static char suspend_core_version[] = SWSUSP_CORE_VERSION; + +/* + * suspend_write_compat_proc. + * + * This entry allows all of the settings to be set at once. + * It was originally for compatibility with pre- /proc/suspend + * versions, but has been retained because it makes saving and + * restoring the configuration simpler. + */ +static int suspend_write_compat_proc(struct file *file, const char * buffer, + unsigned long count, void * data) +{ + char * 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: + suspend_result = nextval; + break; + case 1: + suspend_action = nextval; + break; + case 2: +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + suspend_debug_state = nextval; +#endif + break; + case 3: + suspend_default_console_level = nextval; +#ifndef CONFIG_SOFTWARE_SUSPEND_DEBUG + if (suspend_default_console_level > 1) + suspend_default_console_level = 1; +#endif + break; + case 4: + image_size_limit = nextval; + break; + case 5: + max_async_ios = nextval; + if (max_async_ios > 256) + max_async_ios = 256; + break; + + } + buf++; + while (*buf == ' ') + buf++; + } + buf[count] = 0; + free_pages((unsigned long) buf, 0); + return count; +} + +/* + * suspend_read_compat_proc. + * + * Like it's _write_ sibling, this entry allows all of the settings + * to be read at once. + * It too was originally for compatibility with pre- /proc/suspend + * versions, but has been retained because it makes saving and + * restoring the configuration simpler. + */ +static int suspend_read_compat_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int len = 0; + + len = sprintf(page, "%ld %ld %ld %d %d %d\n", + suspend_result, + suspend_action, + suspend_debug_state, + suspend_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/suspend/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); + +extern void start_ksuspendd(void * data); +extern int initialise_suspend_plugins(void); +extern void cleanup_suspend_plugins(void); + +static int resume2_write_proc(void) +{ + mm_segment_t oldfs; + + oldfs = get_fs(); set_fs(KERNEL_DS); + initialise_suspend_plugins(); + attempt_to_parse_resume_device(0); + cleanup_suspend_plugins(); + set_fs(oldfs); + return 0; +} + +extern int debuginfo_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data); +/* + * generic_read_proc + * + * Generic handling for reading the contents of bits, integers, + * unsigned longs and strings. + */ +static int generic_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int len = 0; + struct suspend_proc_data * proc_data = (struct suspend_proc_data *) data; + + switch (proc_data->type) { + case SWSUSP_PROC_DATA_CUSTOM: + printk("Error! /proc/suspend/%s marked as having custom" + " routines, but the generic read routine has" + " been invoked.\n", + proc_data->filename); + break; + case 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 suspend_proc_data * proc_data = (struct suspend_proc_data *) data; + char * my_buf = (char *) get_zeroed_page(GFP_ATOMIC); + int result = count; + + if (!my_buf) + return -ENOMEM; + + if (count > PAGE_SIZE) + count = PAGE_SIZE; + + if (copy_from_user(my_buf, buffer, count)) + return -EFAULT; + + my_buf[count] = 0; + + switch (proc_data->type) { + case SWSUSP_PROC_DATA_CUSTOM: + printk("Error! /proc/suspend/%s marked as having custom" + " routines, but the generic write routine has" + " been invoked.\n", + proc_data->filename); + break; + case 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_strtol(my_buf, NULL, 0); + if (((*variable) < minimum)) + *variable = minimum; + + if (((*variable) > maximum)) + *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); + + if ((*variable) < minimum) + *variable = minimum; + + if ((*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 suspend_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 = suspend_read_compat_proc, + .write_proc = suspend_write_compat_proc, + } + } + }, + + { .filename = "async_io_limit", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &max_async_ios, + .minimum = 1, + .maximum = 256, + } + } + }, + + { .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 = -2, + .maximum = 32767, + } + } + }, + + { .filename = "interface_version", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &suspend_proc_version, + } + } + }, + + { .filename = "last_result", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_UL, + .data = { + .ul = { + .variable = &suspend_result, + } + } + }, + + { .filename = "reboot", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &suspend_action, + .bit = SUSPEND_REBOOT, + } + } + }, + + { .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_STRING, + .data = { + .string = { + .variable = suspend_core_version, + } + } + }, + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + { .filename = "freezer_test", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &suspend_action, + .bit = SUSPEND_FREEZER_TEST, + } + } + }, + + { .filename = "slow", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &suspend_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 = &suspend_action, + .bit = SUSPEND_KEEP_IMAGE, + } + } + }, +#endif +}; + +/* + * suspend_initialise_proc + * + * Initialise the /proc/suspend tree. + * + */ + +int suspend_initialise_proc(void) +{ + suspend_dir = proc_mkdir("swsusp", NULL); + + if (suspend_dir == NULL) { + printk(KERN_ERR "Failed to create /proc/swsusp.\n"); + return 1; + } + + INIT_LIST_HEAD(&suspend_proc_entries); + + proc_initialised = 1; + + return 0; +} + +/* + * suspend_register_procfile + * + * Helper for registering a new /proc/suspend entry. + */ + +struct proc_dir_entry * suspend_register_procfile( + struct suspend_proc_data * suspend_proc_data) +{ + struct proc_dir_entry * new_entry; + + if ((!proc_initialised) && (suspend_initialise_proc())) + return NULL; + + new_entry = create_proc_entry( + suspend_proc_data->filename, + suspend_proc_data->permissions, + suspend_dir); + if (new_entry) { + list_add_tail(&suspend_proc_data->proc_data_list, &suspend_proc_entries); + if (suspend_proc_data->type) { + new_entry->read_proc = generic_read_proc; + new_entry->write_proc = generic_write_proc; + } else { + new_entry->read_proc = suspend_proc_data->data.special.read_proc; + new_entry->write_proc = suspend_proc_data->data.special.write_proc; + } + new_entry->data = suspend_proc_data; + } + return new_entry; +} + +/* + * suspend_unregister_procfile + * + * Helper for removing unwanted /proc/suspend entries. + * + */ +void suspend_unregister_procfile(struct suspend_proc_data * suspend_proc_data) +{ + remove_proc_entry( + suspend_proc_data->filename, + suspend_dir); + list_del(&suspend_proc_data->proc_data_list); +} + +/* + * suspend_init_proc + * + * Initialise proc file support at boot time. This is called + * by suspend2.c::software_resume2. + */ +int suspend_init_proc(void) +{ + int i; + int numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data); + + if ((!proc_initialised) && suspend_initialise_proc()) + return 1; + + for (i=0; i< numfiles; i++) + suspend_register_procfile(&proc_params[i]); + + suspend_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 = suspend_read_compat_proc; + compat_entry->write_proc = suspend_write_compat_proc; + } + } + + return 0; +} diff -ruN post-version-specific/kernel/power/process.c software-suspend-core-2.0.0.96/kernel/power/process.c --- post-version-specific/kernel/power/process.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/process.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,598 @@ +/* + * 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 + +char idletimeout; +atomic_t suspend_num_active = { 0 }; +atomic_t __nosavedata suspend_cpu_counter = { 0 }; + +unsigned long software_suspend_state = SOFTWARE_SUSPEND_DISABLED; +unsigned int suspend_task = 0; + +unsigned long suspend_action = 0; +unsigned long suspend_result = 0; + +/* Timeouts when freezing */ +#define FREEZER_TOTAL_TIMEOUT (5 * HZ) +#define FREEZER_CHECK_TIMEOUT (HZ) + + +/* Locks */ +spinlock_t suspend_irq_lock = SPIN_LOCK_UNLOCKED; +unsigned long suspendirqflags; + +int now_resuming = 0; + +int suspend_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); + 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]; + + while (sync_buffers(NODEV, 1)); + 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(&suspend_cpu_counter); + while(software_suspend_state & SOFTWARE_SUSPEND_FREEZE_SMP) { + cpu_relax(); + barrier(); + } + FLUSH_LOCAL_TLB(); + atomic_dec(&suspend_cpu_counter); +} +#endif + +/* + * to_be_frozen + * + * Description: Determine whether a process should be frozen yet. + * Parameters: struct task_struct * The process to consider. + * int Which group of processes to consider. + * Returns: int 0 if don't freeze yet, otherwise do. + */ +static int to_be_frozen(struct task_struct * p, int type_being_frozen) { + + if ((p == current) || +#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; + if ((!(p->mm)) && (type_being_frozen < 3)) + return 0; + if ((p->flags & PF_SYNCTHREAD) && (type_being_frozen == 1)) + return 0; + return 1; +} + +/* + * num_to_be_frozen + * + * Description: Determine how many processes of our type are still to be + * frozen. As a side effect, update the progress bar too. + * Parameters: int Which type we are trying to freeze. + * int Whether we are displaying our progress. + */ +static int num_to_be_frozen(int type_being_frozen, int no_progress) { + + struct task_struct FOR_EACH_THREAD_TASK_STRUCTS; + int todo_this_type = 0, todo_all_types = 0, total_todo = 0; + int total_threads = 0; + + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (to_be_frozen(p, type_being_frozen)) { + todo_this_type++; + todo_all_types++; + total_todo++; + } else if (to_be_frozen(p, 3)) { + todo_all_types++; + total_todo++; + } else if (p->flags & PF_FROZEN) + total_todo++; + total_threads++; + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + + if (!no_progress) + update_status( total_todo - todo_all_types, + total_todo, + "%d/%d", + total_todo - todo_all_types, + total_todo); + return todo_this_type; +} + +/** + * 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)) { + spin_lock_irqsave(PROCESS_SIG_MASK(current), flags); + RECALC_SIGPENDING; + 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). + */ + if ((flag) && (current->flags & PF_FREEZE)) { + + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, + "\n%s (%d) refrigerated and sigpending recalculated.", + current->comm, current->pid); + spin_lock_irqsave(PROCESS_SIG_MASK(current), flags); + RECALC_SIGPENDING; + spin_unlock_irqrestore(PROCESS_SIG_MASK(current), flags); + } else + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, + "\n%s (%d) refrigerated.", + current->comm, current->pid); + + if (software_suspend_state & + (SOFTWARE_SUSPEND_FREEZE_NEW_ACTIVITY | + SOFTWARE_SUSPEND_FREEZE_UNREFRIGERATED)) { + save = current->state; + current->flags |= PF_FROZEN; + while (current->flags & PF_FROZEN) { + current->state = TASK_STOPPED; + schedule(); + if (flag) { + spin_lock_irqsave( + PROCESS_SIG_MASK(current), flags); + RECALC_SIGPENDING; + spin_unlock_irqrestore( + PROCESS_SIG_MASK(current), flags); + } + } + current->state = save; + } else + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 0, + "No longer freezing processes. Dropping out.\n"); + current->flags &= ~PF_FREEZE; +} + +/* + * freeze_threads + * + * Freeze a set of threads having particular attributes. + * + * Types: + * 1: User threads not syncing. + * 2: Remaining user threads. + * 3: Kernel threads. + */ +extern void show_task(struct task_struct * p); + +static int freeze_threads(int type, int no_progress) +{ + struct task_struct FOR_EACH_THREAD_TASK_STRUCTS; + unsigned long start_time = jiffies; + int result = 0, still_to_do; + + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, + "\n STARTING TO FREEZE TYPE %d THREADS.\n", + type); + + do { + int numsignalled = 0; + + /* + * Pause the other processors so we can safely + * change threads' flags + */ + software_suspend_state |= SOFTWARE_SUSPEND_FREEZE_SMP; + smp_call_function(smp_pause, NULL, 0, 0); + + while (atomic_read(&suspend_cpu_counter) < (NUM_CPUS - 1)) { + cpu_relax(); + barrier(); + } + + if (TEST_RESULT_STATE(SUSPEND_ABORTED)) + return 1; + + /* + * Signal the processes. + * + * We signal them every time through. Otherwise pdflush - + * and maybe other processes - might never enter the + * fridge. + */ + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + unsigned long flags; + if (!to_be_frozen(p, type)) + continue; + + numsignalled++; + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "\n %s: pid %d", + p->comm, p->pid); + p->flags |= PF_FREEZE; + spin_lock_irqsave(PROCESS_SIG_MASK(p), flags); + WAKE_UP(p); + spin_unlock_irqrestore(PROCESS_SIG_MASK(p), flags); + } FOR_EACH_THREAD_END + + if (numsignalled) + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "\n Number of threads signalled this iteration is %d.\n", + numsignalled); + + /* + * Let the processes run. + */ + software_suspend_state &= ~SOFTWARE_SUSPEND_FREEZE_SMP; + + while (atomic_read(&suspend_cpu_counter)) { + cpu_relax(); + barrier(); + } + + read_unlock(&tasklist_lock); + + /* + * Sleep. + */ + set_task_state(current, TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + + still_to_do = num_to_be_frozen(type, no_progress); + } while(still_to_do && (!TEST_RESULT_STATE(SUSPEND_ABORTED))); + + /* + * Did we time out? See if we failed to freeze processes as well. + * + */ + if ((time_after(jiffies, start_time + TIMEOUT)) && (still_to_do)) { + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (!to_be_frozen(p, type)) + continue; + + if (!result) { + printk(KERN_ERR name_suspend + "Stopping tasks failed.\n"); + printk(KERN_ERR "Tasks that refused to be refrigerated" + " and haven't since exited:\n"); + result = 1; + } + + if (p->flags & PF_FREEZE) { + printk(" - %s (#%d) signalled but " + "didn't enter refrigerator.\n", + p->comm, p->pid); + show_task(p); + } else + printk(" - %s (#%d) wasn't " + "signalled.\n", + p->comm, p->pid); + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + } else + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, + "\n\nSuccessfully froze processes of type %d.\n", + type); + return result; +} + +/* + * freeze_processes - Freeze processes prior to saving an image of memory. + * + * Return value: 0 = success, else # of processes that we failed to stop. + */ +extern int sync_old_buffers(void); +#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 showidlelist, result = 0, num_type[3]; + struct task_struct FOR_EACH_THREAD_TASK_STRUCTS; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + unsigned long iorequestflags = 0; +#endif + + suspend_task = current->pid; + + showidlelist = 1; + + num_type[0] = num_type[1] = num_type[2] = 0; + + software_suspend_state |= SOFTWARE_SUSPEND_FREEZE_NEW_ACTIVITY; + suspend_result = 0; /* Might be called from pm_disk or suspend - + ensure reset */ + + read_lock(&tasklist_lock); + FOR_EACH_THREAD_START { + if (p->mm) { + 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); + num_type[1]++; + } else + num_type[2]++; + } else { + if (p->flags & PF_NOFREEZE) + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, + "%s (%d) is NO_FREEZE.\n", + p->comm, p->pid); + else + num_type[2]++; + } + } FOR_EACH_THREAD_END + read_unlock(&tasklist_lock); + printnolog(SUSPEND_FREEZER, SUSPEND_MEDIUM, 0, "\n"); + + /* First, freeze all userspace, non syncing threads. */ + if (freeze_threads(1, no_progress) || (TEST_RESULT_STATE(SUSPEND_ABORTED))) + goto aborting; + + /* Now freeze processes that were syncing and are still running */ + if (freeze_threads(2, no_progress) || (TEST_RESULT_STATE(SUSPEND_ABORTED))) + goto aborting; + + /* Now do our own sync, just in case one wasn't running already */ + if (!no_progress) + prepare_status(1, 1, + "Freezing processes: Syncing remaining I/O."); + + do_suspend_sync(); + + if (!no_progress) { + check_shift_keys(1, "Syncing finished."); + + prepare_status(1, 1, + "Freezing processes: Freezing remaining tasks."); + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + spin_lock_irqsave(&io_request_lock, iorequestflags); +#endif + + /* Freeze kernel threads */ + if (freeze_threads(3, no_progress) || (TEST_RESULT_STATE(SUSPEND_ABORTED))) + goto aborting; + +out: + printnolog(SUSPEND_FREEZER, SUSPEND_VERBOSE, 1, + "Left freezer loop.\n"); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + spin_unlock_irqrestore(&io_request_lock, iorequestflags); +#endif + + if (!no_progress) + check_shift_keys(1, "Freezing processes completed."); + + software_suspend_state &= ~SOFTWARE_SUSPEND_FREEZE_SMP; + + while (atomic_read(&suspend_cpu_counter)) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + + return result; +aborting: + result = -1; + goto out; +} + +void thaw_processes(void) +{ + struct task_struct FOR_EACH_THREAD_TASK_STRUCTS; + printnolog(SUSPEND_FREEZER, SUSPEND_LOW, 1, "Thawing tasks\n"); + + suspend_task = 0; + software_suspend_state &= ~(SOFTWARE_SUSPEND_FREEZE_SMP | + SOFTWARE_SUSPEND_FREEZE_NEW_ACTIVITY | + SOFTWARE_SUSPEND_FREEZE_UNREFRIGERATED); + + /* + * Pause the other processors so we can safely + * change threads' flags + */ + + software_suspend_state |= SOFTWARE_SUSPEND_FREEZE_SMP; + smp_call_function(smp_pause, NULL, 0, 0); + + while ((atomic_read(&suspend_cpu_counter) < (NUM_CPUS - 1)) && + (TEST_RESULT_STATE(SUSPEND_ABORTED))) { + 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); + + software_suspend_state &= ~(SOFTWARE_SUSPEND_FREEZE_SMP | + SOFTWARE_SUSPEND_FREEZE_NEW_ACTIVITY | + SOFTWARE_SUSPEND_FREEZE_UNREFRIGERATED); + + while (atomic_read(&suspend_cpu_counter) && + (TEST_RESULT_STATE(SUSPEND_ABORTED))) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#include +/* + * The following code is based on the reboot notifier code in kernel/sys.c. + * It is used to notify drivers when a suspend cycle finishes, so that + * timers which have been stopped can be restarted. + */ + + +/* + * Notifier list for kernel code which wants to be called + * at resume. This is used to restart timers which were + * stopped during suspend. + */ + +static struct notifier_block *resume_notifier_list; +rwlock_t suspend_notifier_lock = RW_LOCK_UNLOCKED; + +/** + * register_resume_notifier - Register function to be called at resume time + * @nb: Info about notifier function to be called + * + * Registers a function with the list of functions + * to be called at resume time. + * + * Currently always returns zero, as notifier_chain_register + * always returns zero. + */ + +int register_resume_notifier(struct notifier_block * nb) +{ + return notifier_chain_register(&resume_notifier_list, nb); +} + +/** + * unregister_resume_notifier - Unregister previously registered resume + * notifier + * @nb: Hook to be unregistered + * + * Unregisters a previously registered resume + * notifier function. + * + * Returns zero on success, or %-ENOENT on failure. + */ + +int unregister_resume_notifier(struct notifier_block * nb) +{ + return notifier_chain_unregister(&resume_notifier_list, nb); +} + +inline int notify_resume(void) +{ + return notifier_call_chain(&resume_notifier_list, 0, NULL); +} +#endif + +EXPORT_SYMBOL(suspend_num_active); +EXPORT_SYMBOL(idletimeout); +EXPORT_SYMBOL(refrigerator); +EXPORT_SYMBOL(suspend_task); +EXPORT_SYMBOL(suspend_action); +EXPORT_SYMBOL(software_suspend_state); +EXPORT_SYMBOL(show_task); diff -ruN post-version-specific/kernel/power/range.c software-suspend-core-2.0.0.96/kernel/power/range.c --- post-version-specific/kernel/power/range.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/range.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,778 @@ +/* Suspend2 routines for manipulating ranges. + * + * (C) 2003-2004 Nigel Cunningham. + * + * Distributed under GPLv2. + * + * These encapsulate the manipulation of ranges. I learnt after writing this + * code that ranges are more commonly called extents. They work like this: + * + * A lot of the data that 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 + +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; + void **eaten_memory = NULL, **this; + + for (j = 0; j < number_requested; j++) { + if (now_resuming) { + struct page * pageaddr; + /* Make sure page doesn't collide when we're resuming */ + while ((this = (void **) get_zeroed_page(GFP_ATOMIC))) { + pageaddr = virt_to_page(this); + if (!PageInUse(pageaddr)) + break; + *this = eaten_memory; + eaten_memory = this; + } + // Free unwanted memory + while(eaten_memory) { + this = eaten_memory; + eaten_memory = *eaten_memory; + free_page((unsigned long) this); + } + } else + this = (void *) get_grabbed_pages(0); + + if (!this) { + printk("Failed to allocate a new range page.\n"); + return 1; + } + + num_range_pages++; + if (!first_range_page) + first_range_page = (struct range *) this; + if (last_range_page) + *RANGEPAGELINK(last_range_page) |= (unsigned long) this; + *RANGEPAGELINK(this) = num_range_pages; + last_range_page = (struct range *) this; + ranges = (struct range *) this; + for (i = 0; i < RANGES_PER_PAGE; i++) + (ranges+i)->next = (ranges+i+1); + (ranges + i - 1)->next = unused_ranges; + unused_ranges = ranges; + nr_unused_ranges += i; + } + return 0; +} + + +/* + * Free ranges. + * + * Frees pages allocated by add_range_pages() + * + * Checks that all ranges allocated have been freed and 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++) { + 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 suspend, which is single threaded + */ + +static struct range * get_range(void) +{ + struct range * result; + + if ((!unused_ranges) && (add_range_pages(1))) + return NULL; + + result = unused_ranges; + unused_ranges = unused_ranges->next; + nr_unused_ranges--; + ranges_allocated++; + if (ranges_allocated > max_ranges_used) + max_ranges_used++; + result->minimum = result->maximum = 0; + result->next = NULL; + return result; +} + +/* + * put_range. + * + * Returns a range to the pool of unused pages and + * decrements the usage 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; + + 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; + + 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(SUSPEND_RANGES, debuglevel, 0, "Chain %p\n", chain); + else + printnolog(SUSPEND_RANGES, debuglevel, 0, "%s\n", chain->name); + + while (this) { + /* + * 'This' is printed separately so it is displayed if an oops + * results. + */ + switch (printmethod) { + case 0: + printnolog(SUSPEND_RANGES, debuglevel, 0, "(%p) ", + this); + printnolog(SUSPEND_RANGES, debuglevel, 0, "%lx-%lx; ", + this->minimum, this->maximum); + break; + case 1: + printnolog(SUSPEND_RANGES, debuglevel, 0, "(%p)", + this); + printnolog(SUSPEND_RANGES, debuglevel, 0, "%lu-%lu; ", + this->minimum, this->maximum); + break; + case 2: + printnolog(SUSPEND_RANGES, debuglevel, 0, "(%p)", + this); + printnolog(SUSPEND_RANGES, debuglevel, 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(SUSPEND_RANGES, debuglevel, 0, "\n"); + } + + if ((count%4)) + printnolog(SUSPEND_RANGES, debuglevel, 0, "\n"); + + printnolog(SUSPEND_RANGES, debuglevel, 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(SUSPEND_RANGES, debuglevel, 0, "\n"); +} + +/* + * add_to_range_chain. + * + * Takes a value to be stored and a pointer + * to a chain and adds the value to the range + * chain, merging with an existing range or + * adding a new entry as necessary. Ranges + * are stored in increasing order. + * + * Values should be consecutive, and so may + * need to be transformed first. (eg for + * pages, would want to call with page-mem_map). + * + * Important optimisation: + * We store in the chain info the location of + * the last range accessed or added (and its + * previous). If the next value is outside this + * range by one, we start from the previous + * entry instead of the start of the chain. + * In cases of heavy fragmentation, this saves + * a lot of time searching. + * + * Returns: + * 0 if successful + * 1 if the value is already included. + * 2 if unable to allocate memory. + * 3 if fall out bottom (shouldn't happen). + */ + +int add_to_range_chain(struct rangechain * chain, unsigned long value) +{ + struct range * this, * prev = NULL, * prevtoprev = NULL; + int usedoptimisation = 0; + + if (!chain->first) { /* Empty */ + chain->last = chain->first = get_range(); + if (!chain->first) { + printk("Error unable to allocate the first range for " + "the chain.\n"); + return 2; + } + chain->allocs++; + chain->first->maximum = value; + chain->first->minimum = value; + chain->size++; + return 0; + } + + this = chain->first; + + if (chain->lastaccessed && chain->prevtolastaccessed && + chain->prevtoprev) { + if ((value + 1) == chain->lastaccessed->minimum) { + prev = chain->prevtoprev; + this = chain->prevtolastaccessed; + usedoptimisation = 1; + } else if (((value - 1) == chain->lastaccessed->maximum)) { + prev = chain->prevtolastaccessed; + this = chain->lastaccessed; + usedoptimisation = 1; + } + } + + while (this) { + /* Need new entry prior to this? */ + if ((value + 1) < this->minimum) { + struct range * new = get_range(); + if (!new) { + 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; + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = new; + } + chain->size++; + return 0; + } + + if ((this->minimum <= value) && (this->maximum >= value)) { + if (chain->name) + printk("%s:", chain->name); + else + printk("%p:", chain); + printk("Trying to add a value (%ld/0x%lx) already " + "included in chain.\n", + value, value); + print_chain(SUSPEND_ERROR, chain, 0); + check_shift_keys(1, NULL); + return 1; + } + if ((value + 1) == this->minimum) { + this->minimum = value; + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = this; + } + chain->size++; + return 0; + } + if ((value - 1) == this->maximum) { + if ((this->next) && + (this->next->minimum == value + 1)) { + struct range * oldnext = this->next; + this->maximum = this->next->maximum; + this->next = this->next->next; + if ((chain->last) == oldnext) + chain->last = this; + put_range(oldnext); + /* Invalidate optimisation info */ + chain->lastaccessed = NULL; + chain->frees++; + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = this; + } + chain->size++; + return 0; + } + this->maximum = value; + if (!usedoptimisation) { + chain->prevtoprev = prevtoprev; + chain->prevtolastaccessed = prev; + chain->lastaccessed = this; + } + chain->size++; + return 0; + } + if (!this->next) { + struct range * new = get_range(); + if (!new) { + printk("Error unable to append a new range to " + "the chain.\n"); + return 2; + } + chain->allocs++; + new->minimum = value; + new->maximum = value; + new->next = NULL; + this->next = new; + chain->last = new; + if (!usedoptimisation) { + chain->prevtoprev = prev; + chain->prevtolastaccessed = this; + chain->lastaccessed = new; + } + chain->size++; + return 0; + } + prevtoprev = prev; + prev = this; + this = this->next; + } + printk("\nFell out the bottom of add_to_range_chain. This shouldn't " + "happen!\n"); + SET_RESULT_STATE(SUSPEND_ABORTED); + return 3; +} + +/* append_range + * Used where we know a range is to be added to the end of the list + * and does not need merging with the current last range. + * (count_data_pages only at the moment) + */ + +int append_range_to_range_chain(struct rangechain * chain, + unsigned long minimum, unsigned long maximum) +{ + struct range * newrange = NULL; + + newrange = get_range(); + if (!newrange) { + printk("Error unable to append a new range to the chain.\n"); + return 2; + } + + chain->allocs++; + chain->size+= (maximum - minimum + 1); + newrange->minimum = minimum; + newrange->maximum = maximum; + newrange->next = NULL; + + if (chain->last) { + chain->last->next = newrange; + chain->last = newrange; + } else + chain->last = chain->first = newrange; + + /* No need to reset optimisation info since added to end */ + return 0; +} + +int append_to_range_chain(int chain, unsigned long min, unsigned long max) +{ + int result = 0; + + switch (chain) { + case 0: + return 0; + case 1: + result = append_range_to_range_chain( + &pagedir1.origranges, min, max); + break; + case 2: + result = append_range_to_range_chain( + &pagedir2.origranges, min, max); + if (!result) + result = append_range_to_range_chain( + &pagedir1.destranges, min, max); + } + return result; +} + +/* + * Prepare rangesets for save by translating addresses to relative indices. + */ +void relativise_ranges(void) +{ + struct range * this_range_page = first_range_page; + int i; + + while (this_range_page) { + struct range * this_range = this_range_page; + for (i = 0; i < RANGES_PER_PAGE; i++) { + if (this_range->next) { +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + struct range * orig = this_range->next; +#endif + this_range->next = + RANGE_RELATIVE(this_range->next); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, + "Relativised range %d on this page is %p. Absolutised range is %p.\n", + i, this_range->next, orig); + } + this_range++; + } + this_range_page = (struct range *) + ((*RANGEPAGELINK(this_range_page)) & PAGE_MASK); + } +} + +/* Convert ->next pointers for ranges back to absolute values. + * The issue is finding out what page the absolute value is now at. + * If we use an array of values, we gain speed, but then we need to + * be able to allocate contiguous pages. Fortunately, this is done + * prior to loading pagesets, so we can just allocate the pages + * needed, set up our array and use it and then discard the data + * before we exit. + */ + +void absolutise_ranges() +{ + struct range * this_range_page = first_range_page; + int i; + + while (this_range_page) { + struct range * this_range = this_range_page; + for (i = 0; i < RANGES_PER_PAGE; i++) { + if (this_range->next) { +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + struct range * orig = this_range->next; +#endif + this_range->next = + RANGE_ABSOLUTE(this_range->next); + printnolog(SUSPEND_RANGES, SUSPEND_VERBOSE, 0, + "Relativised range %d on this page is %p. Absolutised range is %p.\n", + i, orig, this_range->next); + } + this_range++; + } + this_range_page = (struct range *) + ((*RANGEPAGELINK(this_range_page)) & PAGE_MASK); + } +} + +void absolutise_chain(struct rangechain * chain) +{ + if (chain->first) + chain->first = RANGE_ABSOLUTE(chain->first); + if (chain->last) + chain->last = RANGE_ABSOLUTE(chain->last); + if (chain->lastaccessed) + chain->lastaccessed = RANGE_ABSOLUTE(chain->lastaccessed); + if (chain->prevtolastaccessed) + chain->prevtolastaccessed = + RANGE_ABSOLUTE(chain->prevtolastaccessed); + if (chain->prevtoprev) + chain->prevtoprev = + RANGE_ABSOLUTE(chain->prevtoprev); +} + +void relativise_chain(struct rangechain * chain) +{ + if (chain->first) + chain->first = RANGE_RELATIVE(chain->first); + if (chain->last) + chain->last = RANGE_RELATIVE(chain->last); + if (chain->lastaccessed) + chain->lastaccessed = RANGE_RELATIVE(chain->lastaccessed); + if (chain->prevtolastaccessed) + chain->prevtolastaccessed = + RANGE_RELATIVE(chain->prevtolastaccessed); + if (chain->prevtoprev) + chain->prevtoprev = RANGE_RELATIVE(chain->prevtoprev); +} + +/* + * Each page in the rangepages lists starts with a pointer to + * the next page containing the list. This lets us only use + * order zero allocations + */ +#define POINTERS_PER_PAGE ((PAGE_SIZE / sizeof(void *)) - 1) +static unsigned long * range_pagelist = NULL; + +unsigned long * get_rangepages_list_entry(int index) +{ + int pagenum, offset, i; + unsigned long * current_list_page = range_pagelist; + + BUG_ON(index > num_range_pages); + + pagenum = index / POINTERS_PER_PAGE; + offset = index - (pagenum * POINTERS_PER_PAGE); + + for (i = 0; i < pagenum; i++) + current_list_page = *((unsigned long **) current_list_page); + + return (unsigned long *) current_list_page[offset + 1]; +} + +int get_rangepages_list(void) +{ + struct range * this_range_page = first_range_page; + int i, j, pages_needed, num_in_this_page; + unsigned long * current_list_page = (unsigned long *) first_range_page; + unsigned long * prev_list_page = NULL; + + pages_needed = + ((num_range_pages + POINTERS_PER_PAGE - 1) / POINTERS_PER_PAGE); + + for (i = 0; i < pages_needed; i++) { + int page_start = i * POINTERS_PER_PAGE; + current_list_page = + (unsigned long *) get_grabbed_pages(0); + if (!current_list_page) { + abort_suspend("Unable to allocate memory for a range pages list."); + printk("Number of range pages is %d.\n", num_range_pages); + return -ENOMEM; + } + + if (!prev_list_page) + range_pagelist = current_list_page; + else { + *prev_list_page = (unsigned long) current_list_page; + prev_list_page = current_list_page; + } + + num_in_this_page = num_range_pages - page_start; + if (num_in_this_page > POINTERS_PER_PAGE) + num_in_this_page = POINTERS_PER_PAGE; + + for (j = 1; j <= num_in_this_page; j++) { + current_list_page[page_start+j+1] = (unsigned long) this_range_page; + + this_range_page = (struct range *) (((unsigned long) + (*RANGEPAGELINK(this_range_page))) & PAGE_MASK); + } + } + + return 0; +} + +void put_rangepages_list(void) +{ + unsigned long * last; + + while (range_pagelist) { + last = range_pagelist; + range_pagelist = *((unsigned long **) range_pagelist); + free_pages((unsigned long) last, 0); + } +} + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS +int PageRangePage(char * seeking) +{ + int i; + + for (i = 1; i <= num_range_pages; i++) + if (get_rangepages_list_entry(i) == + (unsigned long *) seeking) + return 1; + + return 0; +} +#endif + +int relocate_rangepages() +{ + void **eaten_memory = NULL; + void **c = eaten_memory, *m = NULL, *f; + int oom = 0, i, numeaten = 0; + unsigned long * prev_page = NULL; + + for (i = 1; i <= num_range_pages; i++) { + int this_collides = 0; + unsigned long * this_page = get_rangepages_list_entry(i); + + this_collides = PageInUse(virt_to_page(this_page)); + + if (!this_collides) { + prev_page = this_page; + continue; + } + + while ((m = (void *) get_zeroed_page(GFP_ATOMIC))) { + memset(m, 0, PAGE_SIZE); + if (!PageInUse(virt_to_page(m))) { + copy_page(m, (void *) this_page); + free_page((unsigned long) this_page); + if (i == 1) + first_range_page = m; + else + *RANGEPAGELINK(prev_page) = + (i | (unsigned long) m); + prev_page = m; + break; + } + numeaten++; + eaten_memory = m; + *eaten_memory = c; + c = eaten_memory; + } + + if (!m) { + printk("\nRan out of memory trying to relocate " + "rangepages (tried %d pages).\n", numeaten); + oom = 1; + break; + } + } + + c = eaten_memory; + while(c) { + f = c; + c = *c; + if (f) + free_pages((unsigned long) f, 0); + } + eaten_memory = NULL; + + if (oom) + return -ENOMEM; + else + return 0; +} + diff -ruN post-version-specific/kernel/power/suspend2.c software-suspend-core-2.0.0.96/kernel/power/suspend2.c --- post-version-specific/kernel/power/suspend2.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/suspend2.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,2004 @@ +/* + * kernel/power/suspend2.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 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/suspend.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 +#include +#endif + +#if (SWSUSP_VERSION_SPECIFIC_REVISION != SWSUSP_CORE_REVISION) +#error The core and version specific patch revisions of Software Suspend are +#error incompatible. You can find your core version from +#error include/linux/suspend-debug.h and your version specific revision from +#error include/linux/suspend-version-specific.h. +#endif + +#include + +unsigned int nr_suspends = 0; + +/* Variables to be preserved over suspend */ +int pageset1_sizelow = 0, pageset2_sizelow = 0; +struct pagedir __nosavedata pagedir_resume; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +static int pm_suspend_state = 0; +#endif + +struct list_head suspend_filters, suspend_writers, suspend_plugins; +int num_filters = 0, num_writers = 0; +struct suspend_plugin_ops * active_writer = NULL; + +int suspend_act_used = 0; +int suspend_lvl_used = 0; +int suspend_dbg_used = 0; + +/* For resume2= kernel option */ +char resume_file[256] = CONFIG_SOFTWARE_SUSPEND_DEFAULT_RESUME2; + +unsigned long orig_mem_free = 0; + +static void drivers_resume(int); +extern void do_suspend2_lowlevel(int resume); +extern unsigned long header_storage_for_plugins(void); +extern int pm_prepare_console(void); +extern void pm_restore_console(void); +extern void suspend_relinquish_console(void); +extern int suspend_io_time[2][2]; +void empty_suspend_memory_pool(void); +int read_primary_suspend_image(void); +extern void display_nosave_pages(void); +int initialise_suspend_plugins(void); +void cleanup_suspend_plugins(void); +int attempt_to_parse_resume_device(int boot_time); + +unsigned long * in_use_map = NULL; +unsigned long * pageset2_map = NULL; +unsigned long * checksum_map = 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}; + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS + +#define CHECKSUMS_PER_PAGE ((PAGE_SIZE - sizeof(void *)) / sizeof(unsigned long)) +#define NEXT_CHECKSUM_PAGE(page) *((unsigned long *) (((char *) (page)) + PAGE_SIZE - sizeof(void *))) +static int checksum_pages; +static unsigned long * first_checksum_page, *last_checksum_page; +static struct reload_data * first_reload_data, * last_reload_data; + +static void suspend_print_differences(void); +void suspend_calculate_checksums(void); +void suspend_check_checksums(void); +#endif + +unsigned long suspend_debug_state = 0; +char * debug_info_buffer; + +int image_size_limit = 0; +int max_async_ios = 32; + +/* 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 suspend_init_proc(void); +extern int suspend_cleanup_proc(void); + +/* process.c */ +extern atomic_t suspend_cpu_counter; + +/* include/asm-i386/suspend.h */ +extern void smp_suspend2_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 +} + +#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) + +#ifdef CONFIG_BLK_DEV_HD + if(flags & RESUME_PHASE2) + 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 /* Kernel version >= 2.5.0 */ + if (flags & RESUME_PHASE1) { + device_flush_retain_state(); + 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 + 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 + int result; + + result = device_suspend(3); + return result; +#endif +} + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + +int suspend_free_mem_values[MAX_FREEMEM_SLOTS][2]; +/* These should match the enumerated type in suspend-common.h */ +static char * suspend_free_mem_descns[MAX_FREEMEM_SLOTS] = { + "Start/End ", /* 0 */ + "Console Allocn ", + "Drain pcp ", + "InUse map ", + "PS2 map ", + "Checksum map ", /* 5 */ + "Reload pages ", + "Init plugins ", + "Memory pool ", + "Freezer ", + "Eat Memory ", /* 10 */ + "Syncing ", + "Grabbed Memory ", + "Range Pages ", + "Extra PD1 pages", + "Writer storage ", /* 15 */ + "Header storage ", + "Checksum pages ", + "KStat data ", + "Debug Info ", + "Remove Image ", /* 20 */ + "I/O ", + "I/O info ", + "Start one ", +}; + +/* store_free_mem + */ + + +void suspend_store_free_mem(int slot, int side) +{ + static int last_free_mem; + int this_free_mem = nr_free_pages() + suspend_amount_grabbed + + suspend_memory_pool_level(); + int i; + + BUG_ON(slot >= MAX_FREEMEM_SLOTS); + + printnolog(SUSPEND_MEMORY, SUSPEND_VERBOSE, 0, + "Last free mem was %d. Is now %d. ", + last_free_mem, this_free_mem); + + if (slot == 0) { + if (!side) + for (i = 1; i < MAX_FREEMEM_SLOTS; i++) { + suspend_free_mem_values[i][0] = 0; + suspend_free_mem_values[i][1] = 0; + } + suspend_free_mem_values[slot][side] = this_free_mem; + } else + suspend_free_mem_values[slot][side] += this_free_mem - last_free_mem; + last_free_mem = this_free_mem; + printnolog(SUSPEND_MEMORY, SUSPEND_VERBOSE, 0, + "%s value %d now %d.\n", + suspend_free_mem_descns[slot], + side, + suspend_free_mem_values[slot][side]); +} + +/* + * display_free_mem + */ +static void display_free_mem(void) +{ + int i; + + + if (!TEST_DEBUG_STATE(SUSPEND_MEMORY)) + return; + + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "Start: %7d End: %7d.\n", + suspend_free_mem_values[0][0], + suspend_free_mem_values[0][1]); + + for (i = 1; i < MAX_FREEMEM_SLOTS; i++) + if (suspend_free_mem_values[i][0] + suspend_free_mem_values[i][1]) + printnolog(SUSPEND_MEMORY, SUSPEND_HIGH, 0, + "%s %7d %7d.\n", + suspend_free_mem_descns[i], + suspend_free_mem_values[i][0], + suspend_free_mem_values[i][1]); +} +#endif + +/* + * save_image + * Result code (int): Zero on success, non zero on failure. + * Functionality : High level routine which performs the steps necessary + * to prepare and save the image after preparatory steps + * have been taken. + * Key Assumptions : Processes frozen, sufficient memory available, drivers + * suspended. + * Called from : do_suspend2_suspend_2 + */ +extern struct pageset_sizes_result recalculate_stats(void); +extern 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) { + prepare_status(1, 1, + "Couldn't get enough free pages, on %ld pages short", + 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; + + /* Suspend devices we're not going to use in writing the image */ + if (active_writer && active_writer->dpm_set_devices) + active_writer->dpm_set_devices(); + drivers_suspend(); + + 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; + + check_shift_keys(1, "About to copy pageset 1."); + + PRINTPREEMPTCOUNT("Prior to lowlevel suspend."); + + do_suspend2_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; + + PRINTPREEMPTCOUNT("Prior to copying pageset 1."); + + /* Set the progress bar to what we want at resume time, so that + * it looks smoother then */ + update_status(pageset1_size, pageset1_size + pageset2_size, NULL); + + copy_pageset1(); + + update_status(pageset2_size, pageset1_size + pageset2_size, NULL); + + /* + * ---- FROM HERE ON, NEED TO REREAD PAGESET2 IF ABORTING!!! ----- + * + */ + + spin_unlock_irqrestore(&suspend_irq_lock, suspendirqflags); + +#ifdef CONFIG_PREEMPT + preempt_enable(); +#endif + +#ifdef CONFIG_X86 + kernel_fpu_end(); +#endif + + PRINTPREEMPTCOUNT("Prior to resuming drivers."); + + /* + * Other processors have waited for me to make the atomic copy of the + * kernel + */ + + software_suspend_state &= ~SOFTWARE_SUSPEND_FREEZE_SMP; + + while (atomic_read(&suspend_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; + + 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 that failed, we're sunk. Panic! */ + 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_suspend2_suspend_2 + */ + +extern asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd, + void * arg); +extern void apm_power_off(void); + +void suspend_power_down(void) +{ + C_A_D = 0; + /* No delay should be needed - ide_disk_suspend purges cache now */ + prepare_status(1, 0, "Ready to power down."); +#if defined(CONFIG_VT) + 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) + device_flush_retain_state(); + drivers_suspend(); +#endif + sys_reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, + LINUX_REBOOT_CMD_POWER_OFF, NULL); + + prepare_status(1, 0, "Probably not capable for powerdown."); + machine_halt(); + prepare_status(1, 0, "System is now halted."); + while (1) + cpu_relax(); + /* NOTREACHED */ +} + +/* + * do_suspend2_resume_1 + * Functionality : Preparatory steps for copying the original kernel back. + * Called From : include/asm/suspend.h:do_suspend2_lowlevel + */ + +void do_suspend2_resume_1(void) +{ + if (active_writer && active_writer->dpm_set_devices) + active_writer->dpm_set_devices(); + drivers_suspend(); + + /* Get other cpus ready to restore their original contexts */ + + software_suspend_state |= SOFTWARE_SUSPEND_FREEZE_SMP; + + smp_call_function(smp_suspend2_lowlevel, NULL, 0, 0); + + while (atomic_read(&suspend_cpu_counter) < (NUM_CPUS - 1)) { + cpu_relax(); + barrier(); + } + + barrier(); + mb(); + /* Done to disable interrupts */ + spin_lock_irqsave(&suspend_irq_lock, suspendirqflags); + + printlog(SUSPEND_ANY_SECTION, SUSPEND_LOW, + name_suspend "About to copy pageset1 back...\n"); + + MDELAY(2000); +} + +/* + * do_suspend2_resume_2 + * Functionality : Steps taken after copying back the original kernel at + * resume. + * Key Assumptions : Will be able to read back secondary pagedir (if + * applicable). + * Called From : include/asm/suspend.h:do_suspend2_lowlevel + */ + +extern void post_resume_console_redraw(void); +extern int suspend_reread_pages(struct reload_data * page_list); + +void do_suspend2_resume_2(void) +{ + tainted |= 4; /* Taint the kernel because we have suspended */ + now_resuming = 1; + software_suspend_state |= SOFTWARE_SUSPEND_PAGESET2_NOT_LOADED; + + PRINTPREEMPTCOUNT("In resume_2 after copy back."); + +#ifdef CONFIG_PREEMPT + preempt_enable(); +#endif + + spin_unlock_irqrestore(&suspend_irq_lock, suspendirqflags); + +#ifdef DEFAULT_SUSPEND_CONSOLE + post_resume_console_redraw(); +#endif + + check_shift_keys(1, "About to reload secondary pagedir."); + + read_secondary_pagedir(0); + software_suspend_state &= ~SOFTWARE_SUSPEND_PAGESET2_NOT_LOADED; + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS + if (CHECKMASK(SUSPEND_INTEGRITY)) { + suspend_reread_pages(first_reload_data); + suspend_print_differences(); + } +#endif + + prepare_status(0, 0, "Cleaning up..."); + + software_suspend_state &= ~SOFTWARE_SUSPEND_USE_MEMORY_POOL; +} + +/* + * do_suspend2_suspend_1 + * Functionality : Steps taken prior to saving CPU state and the image + * itself. + * Called From : include/asm/suspend.h:do_suspend2_lowlevel + */ + +void do_suspend2_suspend_1(void) +{ + /* Save other cpu contexts */ + + software_suspend_state |= SOFTWARE_SUSPEND_FREEZE_SMP; + + smp_call_function(smp_suspend2_lowlevel, NULL, 0, 0); + + while ((atomic_read(&suspend_cpu_counter) < (NUM_CPUS - 1)) && + (!TEST_RESULT_STATE(SUSPEND_ABORTED))) { + cpu_relax(); + barrier(); + check_shift_keys(0, ""); + } + + mb(); + barrier(); + +#ifdef CONFIG_PREEMPT + preempt_disable(); +#endif + spin_lock_irqsave(&suspend_irq_lock, suspendirqflags); +} + +/* + * do_suspend2_suspend_2 + * Functionality : Steps taken after saving CPU state to save the + * image and powerdown/reboot or recover on failure. + * Key Assumptions : save_image returns zero on success; otherwise we need to + * clean up and exit. The state on exiting this routine + * should be essentially the same as if we have suspended, + * resumed and reached the end of do_suspend2_resume_2. + * Called From : include/asm/suspend.h:do_suspend2_lowlevel + */ +void do_suspend2_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); + + barrier(); + mb(); +} + +static inline void lru_check_page(struct page * page) +{ + if (!PageLRU(page)) + printk("Page %p/%p in inactivelist but not marked LRU.\n", + page, page_address(page)); +} + +/* + * print_plugin_debug_info + * Functionality : Get debugging info from plugins into a buffer. + */ +static int print_plugin_debug_info(char * buffer, int buffer_size) +{ + struct list_head *plugin; + struct suspend_plugin_ops *this_plugin = NULL; + int len = 0; + + list_for_each(plugin, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_plugin_ops, + plugin_list); + if (this_plugin->disabled) + continue; + if (this_plugin->print_debug_info) { + int result; + result = this_plugin->print_debug_info(buffer + len, + buffer_size - len); + len += result; + } + } + + return len; +} + +/* get_debug_info + * Functionality: Store debug info in a buffer. + * Called from: Worker thread or via software_suspend_pending. + */ + +#define SNPRINTF(a...) len += suspend_snprintf(debug_info_buffer + len, \ + PAGE_SIZE - len - 1, ## a); + +static int get_suspend_debug_info(void) +{ + int len = 0; + if (!debug_info_buffer) { + debug_info_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + if (!debug_info_buffer) { + printk("Error! Unable to allocate buffer for" + "software suspend debug info.\n"); + return 0; + } + } + + SNPRINTF("Please include the following information in bug reports:\n"); + SNPRINTF("- SWSUSP core : %s\n", SWSUSP_CORE_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 + SNPRINTF("- Modules loaded : "); + { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + struct module *this_mod; + this_mod = module_list; + while (this_mod) { + if (this_mod->name) + SNPRINTF("%s ", this_mod->name); + this_mod = this_mod->next; + } +#else + extern int print_module_list_to_buffer(char * buffer, int size); + len+= print_module_list_to_buffer(debug_info_buffer + len, + PAGE_SIZE - len - 1); +#endif + } + SNPRINTF("\n"); +#else + SNPRINTF("- No module support.\n"); +#endif + SNPRINTF("- Attempt number : %d\n", nr_suspends); + if (num_range_pages) + SNPRINTF("- Pageset sizes : %d 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", + suspend_result, + suspend_action, + suspend_debug_state, + suspend_default_console_level, + image_size_limit, + max_async_ios); +#else + SNPRINTF("- Parameters : %ld %ld %d %d\n", + suspend_result, + suspend_action, + image_size_limit, + max_async_ios); +#endif + if (num_range_pages) + SNPRINTF("- Calculations : Image size: %lu. " + "Ram to suspend: %ld.\n", + STORAGE_NEEDED(1), RAM_TO_SUSPEND); + SNPRINTF("- Limits : %lu pages RAM. Initial boot: %lu.\n", + max_mapnr, orig_mem_free); + SNPRINTF("- Overall expected compression percentage: %d.\n", + 100 - expected_compression_ratio()); + len+= print_plugin_debug_info(debug_info_buffer + len, + PAGE_SIZE - len - 1); +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + SNPRINTF("- Debugging compiled in.\n"); +#endif +#ifdef CONFIG_PREEMPT + SNPRINTF("- Preemptive kernel.\n"); +#endif +#ifdef CONFIG_SMP + SNPRINTF("- SMP kernel.\n"); +#endif +#ifdef CONFIG_HIGHMEM + SNPRINTF("- Highmem Support.\n"); +#endif + if (num_range_pages) + SNPRINTF("- Max ranges used: %d ranges in %d pages.\n", + max_ranges_used, num_range_pages); + if (suspend_io_time[0][1] && suspend_io_time[1][1]) + SNPRINTF("- I/O speed: Write %d MB/s, Read %d MB/s.\n", + (MB((unsigned long) suspend_io_time[0][0]) * HZ / + suspend_io_time[0][1]), + (MB((unsigned long) suspend_io_time[1][0]) * HZ / + suspend_io_time[1][1])) + else if (num_range_pages) + SNPRINTF("- Suspend cancelled. No I/O speed stats.\n"); + + return len; +} + +/* + * debuginfo_read_proc + * Functionality : Displays information that may be helpful in debugging + * software suspend. + */ +int debuginfo_read_proc(char * page, char ** start, off_t off, int count, + int *eof, void *data) +{ + int info_len, result; + + initialise_suspend_plugins(); + info_len = get_suspend_debug_info(); + cleanup_suspend_plugins(); + + memcpy(page, debug_info_buffer + off, count); + if ((off + count) >= info_len) { + *eof = 1; + result = info_len - off; + if (result < 0) + result = 0; + } else + result = count; + + free_pages((unsigned long) debug_info_buffer, 0); + debug_info_buffer = NULL; + return result; +} + +#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; + 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 = ((sizeof(kstat) - source_offset) >= bytes_per_page) ? + bytes_per_page : (sizeof(kstat) - source_offset); + memcpy((char *) this_page, ((char *) &kstat) + source_offset, + bytes); + 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; + unsigned long this_page = kstat_store, next_page; + + if (!kstat_store) + return; + + for (i = 1; i <= kstat_pages; i++) { + bytes = ((sizeof(kstat) - source_offset) >= bytes_per_page) ? + bytes_per_page : (sizeof(kstat) - source_offset); + memcpy(((char *) &kstat) + source_offset, + (char *) this_page, bytes); + 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 + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS +/* + * Checksumming + */ + +unsigned long suspend_page_checksum(struct page * page) +{ + unsigned long * virt; + int i; + unsigned long value = 0; + + virt = (unsigned long *) kmap(page); + for (i = 0; i < (PAGE_SIZE / sizeof(unsigned long)); i++) + value += *(virt + i); + kunmap(page); + return value; +} + +extern void get_first_pbe(struct pbe * pbe, struct pagedir * pagedir); +extern void get_next_pbe(struct pbe * pbe); + +void suspend_calculate_checksums(void) +{ + struct pbe pbe; + int i = 0, page_index = 0, whichpagedir = 1; + unsigned long * current_checksum_page = first_checksum_page; + + if (!first_checksum_page) { + prepare_status(1, 0, "Unable to checksum at this point."); + return; + } + + prepare_status(1, 0, "Calculating checksums... "); + + get_first_pbe(&pbe, &pagedir1); + + do { + *(current_checksum_page + page_index) = + suspend_page_checksum(pbe.origaddress); + i++; + page_index++; + if (page_index == CHECKSUMS_PER_PAGE) { + page_index = 0; + current_checksum_page = (unsigned long *) + NEXT_CHECKSUM_PAGE(current_checksum_page); + } + if (whichpagedir == 1) { + if (pagedir1.pageset_size == i) { + if (software_suspend_state & + SOFTWARE_SUSPEND_PAGESET2_NOT_LOADED) + goto out; + get_first_pbe(&pbe, &pagedir2); + whichpagedir = 2; + i = 0; + } + } else { + if (pagedir2.pageset_size == i) + goto out; + } + get_next_pbe(&pbe); + } while(1); + +out: + prepare_status(1, 0, "Checksums done."); +} + +int suspend_page_in_pool(struct page * page); + +void suspend_check_checksums(void) +{ + struct pbe pbe; + int i = 0, page_index = 0, whichpagedir = 1; + unsigned long * current_checksum_page = first_checksum_page; + unsigned long sum_now; + struct reload_data * next_reload_data = first_reload_data; + + if (!first_checksum_page) { + prepare_status(1, 0, "Unable to checksum at this point."); + return; + } + + prepare_status(1, 0, "Checking checksums... "); + + get_first_pbe(&pbe, &pagedir1); + + do { + if (PageChecksumIgnore(pbe.origaddress)) + goto skip; + + sum_now = suspend_page_checksum(pbe.origaddress); + if (sum_now != *(current_checksum_page + page_index)) { + if (next_reload_data) { + char * virt; + next_reload_data->pageset = whichpagedir; + next_reload_data->pagenumber = i; + next_reload_data->page_address = pbe.origaddress; + virt = kmap(pbe.origaddress); + memcpy(next_reload_data->compared_version, + virt, PAGE_SIZE); + kunmap(pbe.origaddress); + next_reload_data = next_reload_data->next; + } + } +skip: + i++; + page_index++; + if (page_index == CHECKSUMS_PER_PAGE) { + page_index = 0; + current_checksum_page = (unsigned long *) + NEXT_CHECKSUM_PAGE(current_checksum_page); + } + if (whichpagedir == 1) { + if (pagedir1.pageset_size == i) { + if (software_suspend_state & + SOFTWARE_SUSPEND_PAGESET2_NOT_LOADED) + goto out; + get_first_pbe(&pbe, &pagedir2); + whichpagedir = 2; + i = 0; + } + } else { + if (pagedir2.pageset_size == i) + return; + } + get_next_pbe(&pbe); + + } while(1); + +out: + prepare_status(1, 0, "Differencing done."); +} + +/* + * free_reload_data. + * + * Reload data begins on a page boundary. + */ +static void suspend_free_reload_data(void) +{ + struct reload_data * this_data = first_reload_data; + struct reload_data *prev_reload_data = NULL; + + while (this_data) { + if (this_data->compared_version) { + ClearPageNosave(virt_to_page(this_data->compared_version)); + free_pages((unsigned long) this_data->compared_version, 0); + } + + if (this_data->base_version) { + ClearPageNosave(virt_to_page(this_data->base_version)); + free_pages((unsigned long) this_data->base_version, 0); + } + + if (!(((unsigned long) this_data) & PAGE_MASK)) + prev_reload_data->next = this_data->next; + else + prev_reload_data = this_data; + this_data = this_data->next; + } + + this_data = first_reload_data; + while (this_data) { + prev_reload_data = this_data; + this_data = this_data->next; + free_pages((unsigned long) prev_reload_data, 0); + } + + first_reload_data = last_reload_data = NULL; + +} + +static void suspend_free_checksum_pages(void) +{ + unsigned long * next_checksum_page; + + while(first_checksum_page) { + next_checksum_page = + (unsigned long *) NEXT_CHECKSUM_PAGE(first_checksum_page); + free_pages((unsigned long) first_checksum_page, 0); + first_checksum_page = next_checksum_page; + } + last_checksum_page = NULL; + checksum_pages = 0; + suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_PAGES, 1); +} + +#define PRINTABLE(a) (((a) < 32 || (a) > 122) ? '.' : (a)) +extern int PageRangePage(char * seeking); + +static void local_print_location( + unsigned char * real, + unsigned char * original, + unsigned char * resumetime) +{ + int i; + + for (i = 0; i < 8; i++) + if (*(original + i) != *(resumetime + i)) + break; + if (i == 8) + return; + + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, "%p: ", real); + + for (i = 0; i < 8; i++) + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, + "%2x ", *(original + i)); + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, " "); + for (i = 0; i < 8; i++) + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, + "%c", PRINTABLE(*(original + i))); + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, " "); + + for (i = 0; i < 8; i++) + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, + "%2x ", *(resumetime + i)); + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, " "); + for (i = 0; i < 8; i++) + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, + "%c", PRINTABLE(*(resumetime + i))); + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, "\n"); + if (suspend_page_in_pool(virt_to_page(real))) + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, + "[In memory pool]"); + if (PageNosave(virt_to_page(real))) + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, + "[NoSave]"); + if (PageRangePage(real)) + printlog(SUSPEND_INTEGRITY, SUSPEND_HIGH, + "[RangePage]"); +#ifdef CONFIG_KDB + { + int did_print = 0; + + for (i = 0; i < 8; i++) { + static const char *last_sym = NULL; + if (*(original + i) != *(resumetime + i)) { + kdb_symtab_t symtab; + + kdbnearsym((unsigned long) real + i, + &symtab); + + if ((!symtab.sym_name) || + (symtab.sym_name == last_sym)) + continue; + + last_sym = symtab.sym_name; + + printlog(SUSPEND_INTEGRITY, SUSPEND_LOW, + "%p = %s\n", + symtab.sym_start, + symtab.sym_name); + did_print = 1; + } + } + + if (did_print) + printlog(SUSPEND_INTEGRITY, SUSPEND_LOW, + "\n"); + } +#endif +} + + +static void suspend_print_differences(void) +{ + struct reload_data * this_data = first_reload_data; + int i; + + if (get_rangepages_list()) + return; + + while (this_data) { + if (this_data->pageset && + this_data->pagenumber) { + printlog(SUSPEND_INTEGRITY, SUSPEND_MEDIUM, + "Pagedir %d. Page %d. Address %p." + " Base %p. Copy %p.\n", + this_data->pageset, + this_data->pagenumber, + page_address(this_data->page_address), + this_data->base_version, + this_data->compared_version); + for (i= 0; i < (PAGE_SIZE / 8); i++) { + local_print_location( + page_address(this_data->page_address) + i * 8, + this_data->base_version + i * 8, + this_data->compared_version + i * 8); + check_shift_keys(0, NULL); + } + check_shift_keys(1, NULL); + } else + return; + this_data = this_data->next; + } + + put_rangepages_list(); +} + +static int suspend_allocate_reload_data(int pages) +{ + struct reload_data * this_data; + unsigned long data_start; + int i; + + for (i = 1; i <= pages; i++) { + data_start = get_grabbed_pages(0); + + if (!data_start) + return -ENOMEM; + + SetPageChecksumIgnore(virt_to_page(data_start)); + this_data = (struct reload_data *) data_start; + + while (data_start == + ((((unsigned long) (this_data + 1)) - 1) & PAGE_MASK)) { + struct page * page; + unsigned long virt; + + virt = get_grabbed_pages(0); + if (!virt) { + printk("Couldn't get a page in which to store " + "a changed page.\n"); + return -ENOMEM; + } + page = virt_to_page(virt); + + this_data->compared_version = (char *) virt; + SetPageNosave(page); + SetPageChecksumIgnore(page); + + virt = get_grabbed_pages(0); + if (!virt) { + printk("Couldn't get a page in which to store " + "a baseline page.\n"); + return -ENOMEM; + } + page = virt_to_page(virt); + + this_data->base_version = (char *) virt; + SetPageNosave(page); + SetPageChecksumIgnore(page); + + if (last_reload_data) + last_reload_data->next = this_data; + else + first_reload_data = this_data; + + last_reload_data = this_data; + + this_data++; + } + + check_shift_keys(0, NULL); + } + + return 0; +} + +void suspend_allocate_checksum_pages(void) +{ + int pages_required = + (pageset1_size + pageset2_size) / CHECKSUMS_PER_PAGE; + unsigned long this_page; + + while (checksum_pages <= pages_required) { + this_page = get_grabbed_pages(0); + if (!first_checksum_page) + first_checksum_page = + (unsigned long *) this_page; + else + NEXT_CHECKSUM_PAGE(last_checksum_page) = this_page; + + last_checksum_page = (unsigned long *) this_page; + SetPageChecksumIgnore(virt_to_page(this_page)); + checksum_pages++; + } + suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_PAGES, 0); +} +#endif + +#define SUSPEND_C +#include + +static int get_suspend_debug_info(void); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +extern inline int notify_resume(void); +#if defined(CONFIG_MAGIC_SYSRQ) && defined(CONFIG_VT) +extern int sysrq_pressed; +#endif +#endif + +static int can_suspend(void) +{ + if (software_suspend_state & 2) { + printk(name_suspend "Software suspend is already running.\n"); + return 0; + } + + if (software_suspend_state & 1) { + printk(name_suspend "Software suspend is disabled.\n" + "This may be because you haven't put something along the " + "lines of\n\nresume2=swap:/dev/hda1\n\n" + "in lilo.conf or equivalent. (Where /dev/hda1 is your " + "swap partition).\n"); + SET_RESULT_STATE(SUSPEND_ABORTED); + return 0; + } + + BUG_ON(in_interrupt()); + + return 1; +} + +static int allocate_bitmaps(void) +{ + printlog(SUSPEND_MEMORY, SUSPEND_VERBOSE, "Allocating in_use_map\n"); + if (allocatemap(&in_use_map, 1)) + return 1; + + PRINTFREEMEM("after allocating in_use_map"); + suspend_store_free_mem(3, 0); + + if (allocatemap(&pageset2_map, 1)) + return 1; + + PRINTFREEMEM("after allocating pageset2 map"); + suspend_store_free_mem(4, 0); + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS + /* Allocate even if not used to save having extra tests elsewhere. */ + if (allocatemap(&checksum_map, 0)) + return 1; + PRINTFREEMEM("after allocating checksum map"); + suspend_store_free_mem(5, 0); +#endif + + return 0; +} + +#if defined(CONFIG_SOFTWARE_SUSPEND_DEBUG) && LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) +void show_pcp_lists(void) +{ + int cpu, temperature; + struct zone *zone; + + for_each_zone(zone) { + printk("%s per-cpu:", zone->name); + + if (!zone->present_pages) { + printk(" empty\n"); + continue; + } else + printk("\n"); + + for (cpu = 0; cpu < NR_CPUS; ++cpu) { + struct per_cpu_pageset *pageset; + + if (!cpu_possible(cpu)) + continue; + + pageset = zone->pageset + cpu; + + for (temperature = 0; temperature < 2; temperature++) + printk("cpu %d %s: low %d, high %d, batch %d, count %d.\n", + cpu, + temperature ? "cold" : "hot", + pageset->pcp[temperature].low, + pageset->pcp[temperature].high, + pageset->pcp[temperature].batch, + pageset->pcp[temperature].count); + } + } +} +#endif + +/* + * software_suspend_pending + * Functionality : First level of code for software suspend invocations. + * Stores and restores load averages (to avoid a spike), + * allocates bitmaps, freezes processes and eats memory + * as required before suspending drivers and invoking + * the 'low level' code to save the state to disk. + * By the time we return from do_suspend2_lowlevel, we + * have either failed to save the image or successfully + * suspended and reloaded the image. The difference can + * be discerned by checking SUSPEND_ABORTED. + * Called From : + */ + +void software_suspend_pending(void) +{ + unsigned long avenrun_save[3]; + int i; + mm_segment_t oldfs; + + if (!can_suspend()) + return; + + /* Suspend always runs on processor 0 */ + ensure_on_processor_zero(); + + display_nosave_pages(); + +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if (TEST_RESULT_STATE(SUSPEND_KEPT_IMAGE)) { + if (TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE)) { + printk("Image already stored:" + " powering down immediately."); + suspend_power_down(); + return; /* It 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"); + software_suspend_state |= SOFTWARE_SUSPEND_RUNNING; + oldfs = get_fs(); set_fs(KERNEL_DS); + + suspend_result = 0; + max_ranges_used = 0; + nr_suspends++; + now_resuming = 0; + + suspend_io_time[0][0] = suspend_io_time[0][1] = suspend_io_time[1][0] = + suspend_io_time[1][1] = 0; + + PRINTFREEMEM("at start of software_suspend_pending"); + suspend_store_free_mem(SUSPEND_FREE_BASE, 0); + + /* + * Running suspend makes for a very high load average. I'm told that + * sendmail and crond check the load average, so in order for them + * not to be unnecessarily affected by the operation of suspend, we + * store the avenrun values prior to suspending and restore them + * at the end of the resume cycle. Thus, the operation of suspend + * should be invisible to them. Thanks to Marcus Gaugusch and Bernard + * Blackham for noticing the problem and suggesting the solution. + */ + + 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"); + suspend_store_free_mem(SUSPEND_FREE_CONSOLE_ALLOC, 0); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + disable_pcp_lists(); + + PRINTFREEMEM("after draining local pages"); + suspend_store_free_mem(SUSPEND_FREE_DRAIN_PCP, 0); + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + if (TEST_DEBUG_STATE(SUSPEND_FREEZER)) + show_pcp_lists(); +#endif +#endif + + if (allocate_bitmaps()) + goto out; + + PRINTFREEMEM("after allocating bitmaps"); + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS + if (CHECKMASK(SUSPEND_INTEGRITY)) { + suspend_allocate_reload_data(1); + PRINTFREEMEM("after allocating reload data"); + suspend_store_free_mem(SUSPEND_FREE_RELOAD_PAGES, 0); + } +#endif + + display_nosave_pages(); + + set_chain_names(&pagedir1); + set_chain_names(&pagedir2); + + if (initialise_suspend_plugins()) + goto out; + + PRINTFREEMEM("after initialising plugins"); + suspend_store_free_mem(SUSPEND_FREE_INIT_PLUGINS, 0); + + /* Free up memory if necessary */ + 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; + + /* We don't want suspend to show in the kernel statistics - + * it should be transparent to userspace */ + kstat_save(); + + PRINTFREEMEM("after saving kernel stats"); + suspend_store_free_mem(SUSPEND_FREE_KSTAT, 0); + + display_nosave_pages(); + + if (!TEST_RESULT_STATE(SUSPEND_ABORTED)) { + prepare_status(1, 0, "Starting to save the image.."); + save_image(); + PRINTPREEMPTCOUNT("After saving the image."); + } + +out: + PRINTFREEMEM("at 'out'"); + + i = get_suspend_debug_info(); + + PRINTPREEMPTCOUNT("After storing debug info."); + suspend_store_free_mem(SUSPEND_FREE_DEBUG_INFO, 0); + + software_suspend_state &= ~SOFTWARE_SUSPEND_USE_MEMORY_POOL; + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS + suspend_free_checksum_pages(); + PRINTFREEMEM("after freeing checksum pages"); + suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_PAGES, 1); + + if (CHECKMASK(SUSPEND_INTEGRITY)) { + suspend_free_reload_data(); + PRINTFREEMEM("after freeing reload data"); + suspend_store_free_mem(SUSPEND_FREE_RELOAD_PAGES, 1); + } +#endif + + free_pagedir(&pagedir2); + PRINTFREEMEM("after freeing pagedir 1"); + free_pagedir(&pagedir1); + PRINTFREEMEM("after freeing pagedir 2"); + +#ifdef CONFIG_SOFTWARE_SUSPEND_KEEP_IMAGE + if (TEST_ACTION_STATE(SUSPEND_KEEP_IMAGE) && + !TEST_ACTION_STATE(SUSPEND_ABORTED)) { + 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 + active_writer->ops.writer.invalidate_image(); + + drivers_resume(RESUME_ALL_PHASES); + + empty_suspend_memory_pool(); + PRINTFREEMEM("after freeing memory pool"); + suspend_store_free_mem(SUSPEND_FREE_MEM_POOL, 1); + + free_ranges(); + suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 1); + PRINTFREEMEM("after freeing ranges"); + + freemap(&checksum_map); + PRINTFREEMEM("after freeing checksum map"); + suspend_store_free_mem(SUSPEND_FREE_CHECKSUM_MAP, 1); + + freemap(&pageset2_map); + PRINTFREEMEM("after freeing pageset2 map"); + suspend_store_free_mem(SUSPEND_FREE_PS2_MAP, 1); + + freemap(&in_use_map); + PRINTFREEMEM("after freeing inuse map"); + suspend_store_free_mem(SUSPEND_FREE_IN_USE_MAP, 1); + +#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]; + + if (debug_info_buffer) { + /* Printk can only handle 1023 bytes, including + * its level mangling. */ + for (i = 0; i < 3; i++) + printk("%s", debug_info_buffer + (1023 * i)); + free_pages((unsigned long) debug_info_buffer, 0); + debug_info_buffer = NULL; + } + PRINTFREEMEM("after freeing debug info buffer"); + suspend_store_free_mem(SUSPEND_FREE_DEBUG_INFO, 1); + + cleanup_suspend_plugins(); + + PRINTFREEMEM("after cleaning up suspend plugins"); + suspend_store_free_mem(SUSPEND_FREE_INIT_PLUGINS, 1); + + kstat_restore(); + + PRINTFREEMEM("after restoring kstats"); + suspend_store_free_mem(SUSPEND_FREE_KSTAT, 1); + + display_nosave_pages(); + + thaw_processes(); + + PRINTFREEMEM("after thawing processes"); + suspend_store_free_mem(SUSPEND_FREE_FREEZER, 1); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) + enable_pcp_lists(); + suspend_store_free_mem(SUSPEND_FREE_DRAIN_PCP, 1); + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + if (TEST_DEBUG_STATE(SUSPEND_FREEZER)) + show_pcp_lists(); +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + if (notify_resume()) + printk(KERN_EMERG "Failed to notify resume chain.\n"); +#endif + + MDELAY(1000); + + check_shift_keys(1, "About to restore original console."); + suspend_store_free_mem(SUSPEND_FREE_FREEZER, 1); + + pm_restore_console(); + suspend_store_free_mem(SUSPEND_FREE_CONSOLE_ALLOC, 1); + + suspend_store_free_mem(SUSPEND_FREE_BASE, 1); +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + display_free_mem(); +#endif + + software_suspend_state &= ~SOFTWARE_SUSPEND_RUNNING; + PRINTFREEMEM("at end of software_suspend_pending"); +#ifdef CONFIG_PREEMPT + PRINTPREEMPTCOUNT("Exiting with preempt count"); +#endif + set_fs(oldfs); +} + +int suspend_register_plugin(struct suspend_plugin_ops * plugin) +{ + if (!(num_filters + num_writers)) { + INIT_LIST_HEAD(&suspend_filters); + INIT_LIST_HEAD(&suspend_writers); + INIT_LIST_HEAD(&suspend_plugins); + } + + switch (plugin->type) { + case FILTER_PLUGIN: + list_add_tail(&plugin->ops.filter.filter_list, + &suspend_filters); + num_filters++; + break; + + case WRITER_PLUGIN: + list_add_tail(&plugin->ops.writer.writer_list, + &suspend_writers); + num_writers++; + break; + + default: + printk("Hmmm. Plugin '%s' has an invalid type." + " It has been ignored.\n", plugin->name); + return -EINVAL; + } + list_add(&plugin->plugin_list, &suspend_plugins); + + return 0; +} + +int initialise_suspend_plugins(void) +{ + struct list_head *plugin; + struct suspend_plugin_ops * this_plugin; + int result; + + list_for_each(plugin, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_plugin_ops, + plugin_list); + if (this_plugin->disabled) + continue; + printlog(SUSPEND_MEMORY, SUSPEND_MEDIUM, + "Initialising plugin %s.\n", + this_plugin->name); + if (this_plugin->initialise) + if ((result = this_plugin->initialise())) + return result; + PRINTFREEMEM("after initialising plugin"); + } + + return 0; +} + +void cleanup_suspend_plugins(void) +{ + struct list_head *plugin; + struct suspend_plugin_ops * this_plugin; + + list_for_each(plugin, &suspend_plugins) { + this_plugin = list_entry(plugin, struct suspend_plugin_ops, + plugin_list); + if (this_plugin->disabled) + continue; + printlog(SUSPEND_MEMORY, SUSPEND_MEDIUM, + "Cleaning up plugin %s.\n", + this_plugin->name); + if (this_plugin->cleanup) + this_plugin->cleanup(); + PRINTFREEMEM("after cleaning up plugin"); + } +} + +struct suspend_plugin_ops * +get_next_filter(struct suspend_plugin_ops * filter_sought) +{ + struct list_head *filter; + struct suspend_plugin_ops * last_filter = NULL, *this_filter = NULL; + + list_for_each(filter, &suspend_filters) { + this_filter = list_entry(filter, struct suspend_plugin_ops, + ops.filter.filter_list); + if (this_filter->disabled) + continue; + if ((last_filter == filter_sought) || (!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 suspend_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, &suspend_writers) { + this_writer = list_entry(writer, struct suspend_plugin_ops, + ops.writer.writer_list); + + /* + * Not sure why you'd want to disable a writer, but + * we should honour the flag if we're providing it + */ + if (this_writer->disabled) + continue; + + result = this_writer->ops.writer.parse_image_location( + resume_file, boot_time, (num_writers == 1)); + + switch (result) { + case -EINVAL: + /* + * For this writer, but not a valid + * configuration + */ + + printk(name_suspend + "Not able to successfully parse this " + "resume device. Suspending disabled.\n"); + 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 */ + + software_suspend_state |= + SOFTWARE_SUSPEND_RESUME_DEVICE_OK; + software_suspend_state &= ~3; + printk(name_suspend "Suspending enabled.\n"); + return 0; + } + } + printk(name_suspend "No matching writer found. Suspending disabled.\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 ksuspendd 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; + + /* Suspend always runs on processor 0 */ + ensure_on_processor_zero(); + + 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; + } + + software_suspend_state |= SOFTWARE_SUSPEND_RUNNING; + + if (!resume_file[0]) + printk(KERN_WARNING name_suspend + "You need to use a resume2= command line parameter to " + "tell Software Suspend 2 where to look for an image.\n"); + else + attempt_to_parse_resume_device(1); + + suspend_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"); + software_suspend_state &= ~SOFTWARE_SUSPEND_RUNNING; + 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; + console_loglevel = orig_loglevel; + return RESUME2_RET; + } + + freeze_processes(1); + + prepare_status(0, 0, + "Copying original kernel back (no status - sensitive!)..."); + + PRINTPREEMPTCOUNT("Prior to calling do_suspend2_lowlevel."); + + do_suspend2_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 suspend_act_setup(char *str) +{ + if(str) + suspend_action=simple_strtol(str,NULL,0); + suspend_act_used = 1; + return 0; +} + +/* + * Allow the user to set the debug parameter from lilo, prior to resuming. + */ +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +static int __init suspend_dbg_setup(char *str) +{ + if(str) + suspend_debug_state=simple_strtol(str,NULL,0); + suspend_dbg_used = 1; + return 0; +} + +/* + * Allow the user to set the debug level parameter from lilo, prior to + * resuming. + */ +static int __init suspend_lvl_setup(char *str) +{ + if(str) + console_loglevel = + suspend_default_console_level = + simple_strtol(str,NULL,0); + suspend_lvl_used = 1; + 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("suspend_act=", suspend_act_setup); +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +__setup("suspend_dbg=", suspend_dbg_setup); +__setup("suspend_lvl=", suspend_lvl_setup); +#endif +__setup("noresume2", noresume_setup); + +EXPORT_SYMBOL(software_suspend_pending); diff -ruN post-version-specific/kernel/power/swapwriter.c software-suspend-core-2.0.0.96/kernel/power/swapwriter.c --- post-version-specific/kernel/power/swapwriter.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/swapwriter.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,2086 @@ +/* + * Swapwriter.c + * + * Copyright 2004 Nigel Cunningham + * + * Distributed under GPLv2. + * + * This file encapsulates functions for usage of swap space as a + * backing store. + */ + +#include +#include "block_io.h" +#include + +#define SIGNATURE_VER 6 + +/* --- Struct of pages stored on disk */ + +struct suspend_plugin_ops swapwriterops; + +struct swaplink { + char dummy[PAGE_SIZE - sizeof(swp_entry_t)]; + swp_entry_t next; +}; + +union diskpage { + union swap_header swh; /* swh.magic is the only member used */ + struct swaplink link; + struct suspend_header sh; +}; + +union p_diskpage { + union diskpage *pointer; + char *ptr; + unsigned long address; +}; + +#define SIGNATURE_LENGTH 10 + +// - Manage swap signature. +static int prepare_signature(struct submit_params * first_header_page, + char * current_header); +static int parse_signature(char * signature, int restore); + +// Higher Level +static struct page * readahead_page[256]; +static int readahead_index = -1; +unsigned long suspend_readahead_flags[(256 / 8 / sizeof(unsigned long)) + 1]; +spinlock_t suspend_readahead_flags_lock = SPIN_LOCK_UNLOCKED; + +static char * swapwriter_buffer = NULL; +static int swapwriter_buffer_posn = 0; +static int swapwriter_page_index = 0; +static unsigned long * header_link = NULL; +#define BYTES_PER_HEADER_PAGE (PAGE_SIZE - sizeof(swp_entry_t)) + +/* + * --------------------------------------------------------------- + * + * Internal Data Structures + * + * --------------------------------------------------------------- + */ + +/* header_data contains data that is needed to reload pagedir1, and + * is therefore saved in the suspend header. + * + * Pagedir2 swap comes before pagedir1 swap (save order), and the first swap + * entry for pagedir1 to use is set when pagedir2 is written (when we know how + * much swap it used). Since this first entry is almost certainly not at the + * start of a range, the firstoffset variable below tells us where to start in + * the range. All of this means we don't have to worry about getting different + * compression ratios for the kernel and cache (when compressing the image). + * We can simply allocate one pool of swap (size determined using expected + * compression ratio) and use it without worrying whether one pageset + * compresses better and the other worse (this is what happens). As long as the + * user gets the expected compression right, it will work. + */ + +struct { + /* Range chains for swap & blocks */ + struct rangechain swapranges; + struct rangechain block_chain[MAX_SWAPFILES]; + + /* Location of start of pagedir 1 */ + struct range * pd1start_block_range; + unsigned long pd1start_block_offset; + int pd1start_chain; + + /* Devices used for swap */ + DEVICE_ID_TYPE swapdevs[MAX_SWAPFILES]; + char blocksizes[MAX_SWAPFILES]; + + /* Asynchronous I/O limit */ + int max_async_ios; +} header_data; + + +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; + +/* For swapfile automatically swapon/off'd. */ +static char swapfilename[256] = ""; +extern asmlinkage long sys_swapon(const char * specialfile, int swap_flags); + +int suspend_swapon_status = 0; + +/* Must be silent - might be called from cat /proc/suspend/debug_info + * Returns 0 if was off, -EBUSY if was on, error value otherwise. + */ +static int enable_swapfile(void) +{ + int activateswapresult = -EINVAL; + + if (suspend_swapon_status) + return 0; + + if (swapfilename[0]) { + /* Attempt to swap on with maximum priority */ + activateswapresult = sys_swapon(swapfilename, 0xFFFF); + if ((activateswapresult) && (activateswapresult != -EBUSY)) + printk(name_suspend + "The swapfile/partition specified by " + "/proc/suspend/swapfile (%s) could not" + " be turned on (error %d). Attempting " + "to continue.\n", + swapfilename, activateswapresult); + if (!activateswapresult) + suspend_swapon_status = 1; + } + return activateswapresult; +} + +extern asmlinkage long sys_swapoff(const char * specialfile); +/* Returns 0 if was on, -EINVAL if was off, error value otherwise */ +static int disable_swapfile(void) +{ + int result = -EINVAL; + + if (!suspend_swapon_status) + return 0; + + if (swapfilename[0]) { + result = sys_swapoff(swapfilename); + if (result == -EINVAL) + return 0; /* Wasn't on */ + if (!result) + suspend_swapon_status = 0; + } + + return result; +} + +static int manage_swapfile(int enable) +{ + static int result; + mm_segment_t oldfs; + + oldfs = get_fs(); set_fs(KERNEL_DS); + if (enable) + result = enable_swapfile(); + else + result = disable_swapfile(); + set_fs(oldfs); + + return result; +} + +/* + * --------------------------------------------------------------- + * + * Current state. + * + * --------------------------------------------------------------- + */ + +/* Which pagedir are we saving/reloading? Needed so we can know whether to + * remember the last swap entry used at the end of writing pageset2, and + * get that location when saving or reloading pageset1.*/ +static int current_stream = 0; + +/* Pointer to current swap entry being loaded/saved. */ +static struct range * currentblockrange = NULL; +static unsigned long currentblockoffset = 0; +static int currentblockchain = 0; +static int currentblocksperpage = 0; + +/* Header Page Information */ +static int header_pages_allocated = 0; +static struct submit_params * first_header_submit_info = NULL, + * last_header_submit_info = NULL, * current_header_submit_info = NULL; + +/* + * --------------------------------------------------------------- + * + * User Specified Parameters + * + * --------------------------------------------------------------- + */ + +static int resume_firstblock = 0; +static int resume_firstblocksize = PAGE_SIZE; +static DEVICE_ID_TYPE resume_device = 0; +static DEVICE_BLOCK_TYPE resume_block_device = DEVICE_BLOCK_NONE; + +/* + * --------------------------------------------------------------- + * + * Disk I/O routines + * + * --------------------------------------------------------------- + */ +extern char swapfilename[]; + +extern int expected_compression; + +struct sysinfo swapinfo; + +#define MARK_SWAP_SUSPEND 0 +#define MARK_SWAP_RESUME 1 + +static int get_phys_params(swp_entry_t entry) +{ + 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); + + 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, swapfile %x, block %d.\n", + entry.val, + swapfilenum, + header_data.swapdevs[swapfilenum], + offset); + add_to_range_chain(&header_data.block_chain[swapfilenum], + offset); + } else if (swapf) { + int i, j; + unsigned int block_num = offset + << (PAGE_SHIFT - swapf->i_sb->s_blocksize_bits); + block_size = swapf->i_sb->s_blocksize; + for (i=0, j=0; j< PAGE_SIZE ; i++, j += block_size) + if (!(blocks[i] = bmap(swapf,block_num++))) { + printk("Invalid block for swap file. " + "Trying to map block %x on device %x.\n", + SWAP_DEVICE_ID(swapfilenum), + 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 = -1; + 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; +} + +/* + * + */ + +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; + /* + * We are now using the highest bit of the char to indicate + * whether we have attempted to resume from this image before. + */ + software_suspend_state &= ~SOFTWARE_SUSPEND_RESUMED_BEFORE; + if (((int) *headerblocksize_ptr) & 0x80) + software_suspend_state |= SOFTWARE_SUSPEND_RESUMED_BEFORE; + headerblocksize = 512 * (((int) *headerblocksize_ptr) & 0xf); + headerblock = *headerblock_ptr; + } + + if ((restore) && (type > 5)) { + /* We only reset our own signatures */ + if (type & 1) + memcpy(header,"SWAPSPACE2",10); + else + memcpy(header,"SWAP-SPACE",10); + } + + return type; +} + +/* + * prepare_signature + */ + +static int prepare_signature(struct submit_params * header_page_info, + char * current_header) +{ + int current_type = parse_signature(current_header, 0); + 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); + /* prev is the first/last swap page of the resume area */ + *headerblock_ptr = (unsigned long) header_page_info->blocks[0]; + 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; +} + +extern int signature_check(char * header, int fix); + +static int free_swap_pages_for_header(void) +{ + if (!first_header_submit_info) + return 1; + + PRINTFREEMEM("at start of free_swap_pages_for_header"); + + while (first_header_submit_info) { + struct submit_params * next = first_header_submit_info->next; + if (first_header_submit_info->swap_address.val) + swap_free(first_header_submit_info->swap_address); + kfree(first_header_submit_info); + first_header_submit_info = next; + } + + printnolog(SUSPEND_SWAP, SUSPEND_LOW, 1, + " Freed %d swap pages in free_swap_pages_for_header.\n", + header_pages_allocated); + first_header_submit_info = last_header_submit_info = NULL; + header_pages_allocated = 0; + PRINTFREEMEM("at end of free_swap_pages_for_header"); + suspend_store_free_mem(SUSPEND_FREE_HEADER_STORAGE, 1); + return 0; +} + +static void get_main_pool_phys_params(void) +{ + struct range * rangepointer = NULL; + unsigned long address; + int i; + + for (i = 0; i < MAX_SWAPFILES; i++) + if (header_data.block_chain[i].first) + put_range_chain(&header_data.block_chain[i]); + + range_for_each(&header_data.swapranges, rangepointer, address) + get_phys_params(range_val_to_swap_entry(address)); +} + +extern void put_range(struct range * range); + +static unsigned long swapwriter_storage_allocated(void) +{ + return (header_data.swapranges.size + header_pages_allocated); +} + +static long swapwriter_storage_available(void) +{ + si_swapinfo(&swapinfo); + return (swapinfo.freeswap + (long) swapwriter_storage_allocated()); +} + +static int swapwriter_initialise(void) +{ + manage_swapfile(1); + return 0; +} + +static void swapwriter_cleanup(void) +{ + manage_swapfile(0); +} + +static void swapwriter_dpm_set_devices(void) +{ + int i; + + /* Set our device(s) as remaining on. */ + for (i = 0; i < MAX_SWAPFILES; i++) { + if (UNUSED_SWAP_ENTRY(i)) + continue; + + device_set_retain_state(SWAP_DEVICE_BDEV(i)->bd_disk->driverfs_dev); + } +} + +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) { +#if 0 + int swapfilenum; + unsigned long offset; +#endif + entry = range_val_to_swap_entry(rangevalue); +#if 0 + /* Remove - just for debugging swap freeing */ + swapfilenum = SWP_TYPE(entry); + offset = SWP_OFFSET(entry); + if (!swap_info[swapfilenum].swap_map[offset]) + printk("Entry: %lx. Swapfilenum:%d. Offset:%lx. Map entry: %p.\n", + rangevalue, swapfilenum, offset, + &(swap_info[swapfilenum].swap_map[offset])); + printnolog(SUSPEND_SWAP, SUSPEND_VERBOSE, 1, + "(Free %d %lx)", i, + entry.val); +#endif + 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); + + return 0; +} + +static long swapwriter_allocate_header_space(unsigned long space_requested) +{ + /* space_requested was going to be in bytes... not yet */ + int i, ret = 0; + + PRINTFREEMEM("at start of allocate_header_space"); + + for (i=(header_pages_allocated+1); i<=space_requested; i++) { + struct submit_params * new_submit_param; + + /* Get a submit structure */ + new_submit_param = kmalloc(sizeof(struct submit_params), GFP_ATOMIC); + + if (!new_submit_param) { + header_pages_allocated = i - 1; + goto out; + } + + memset(new_submit_param, 0, sizeof(struct submit_params)); + + if (last_header_submit_info) { + last_header_submit_info->next = new_submit_param; + last_header_submit_info = new_submit_param; + } else + last_header_submit_info = first_header_submit_info = + new_submit_param; + + /* Get swap entry */ + new_submit_param->swap_address = get_swap_page(); + + if ((!new_submit_param->swap_address.val) && + (header_data.swapranges.first)) { + /* + * Steal one from pageset swap chain. If, as a result, + * it is too small, more swap will be allocated or + * memory eaten. + */ + + new_submit_param->swap_address = + range_val_to_swap_entry( + header_data.swapranges.first->minimum); + if (header_data.swapranges.first->minimum < + header_data.swapranges.first->maximum) + header_data.swapranges.first->minimum++; + else { + struct range * oldfirst = + header_data.swapranges.first; + header_data.swapranges.first = oldfirst->next; + header_data.swapranges.frees++; + header_data.swapranges.prevtoprev = + header_data.swapranges.prevtolastaccessed = + header_data.swapranges.lastaccessed = NULL; + if (header_data.swapranges.last == oldfirst) + header_data.swapranges.last = NULL; + put_range(oldfirst); + } + + header_data.swapranges.size--; + + /* + * Recalculate block chains for main pool. + * We don't assume blocks are at start of a chain and + * don't know how many blocks per swap entry. + */ + get_main_pool_phys_params(); + } + if (!new_submit_param->swap_address.val) { + free_swap_pages_for_header(); + printk("Unable to allocate swap page for header.\n"); + ret = -ENOMEM; + goto out; + } + if (get_header_params(new_submit_param)) { + ret = -EFAULT; + goto out; + } + printnolog(SUSPEND_SWAP, SUSPEND_MEDIUM, 0, + " Got header page %d/%d. Dev is %x. Block is %lu. " + "Blocksperpage is %d.\n", + i+1, space_requested+1, + new_submit_param->dev, + new_submit_param->blocks[0], + new_submit_param->blocks_used); + } + header_pages_allocated = space_requested; + printnolog(SUSPEND_SWAP, SUSPEND_LOW, 1, + " Have %d swap pages in swapwriter::" + "allocate_header_space.\n", + header_pages_allocated); +out: + PRINTFREEMEM("at end of swapwriter::allocate_header_space"); + suspend_store_free_mem(SUSPEND_FREE_HEADER_STORAGE, 0); + return ret; +} + +static int swapwriter_allocate_storage(unsigned long space_requested) +{ + int i, swapcount = 0, result = 0; + int lastsize = header_data.swapranges.size; + int numwanted = (int) (space_requested); + int pages_to_get = numwanted - header_data.swapranges.size; + + if (numwanted < 1) + return 0; + + 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(); + + /* Any memory we allocate will be for range pages */ + suspend_store_free_mem(SUSPEND_FREE_RANGE_PAGES, 0); + return result; +} + +static int swapwriter_write_header_chunk(char * buffer, int buffer_size); + +static int 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; + + swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + header_link = + (unsigned long *) (swapwriter_buffer + BYTES_PER_HEADER_PAGE); + swapwriter_page_index = 1; + + current_header_submit_info = first_header_submit_info; + + /* Info needed to bootstrap goes at the start of the header. + * First we save the 'header_data' struct, including the number + * of header pages. Then we save the structs containing data needed + * for reading the header pages back. + * Note that even if header pages take more than one page, when we + * read back the info, we will have restored the location of the + * next header page by the time we go to use it. + */ + swapwriter_write_header_chunk((char *) &header_data, + sizeof(header_data)); + + return 0; +} + +static int swapwriter_write_header_chunk(char * buffer, int buffer_size) +{ + int bytes_left = buffer_size; + + /* + * We buffer the writes until a page is full and to use the last + * sizeof(swp_entry_t) bytes for links between pages. This is + * totally transparent to the caller. + * + * Note also that buffer_size can be > PAGE_SIZE. + */ + 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, + swapwriter_page_index, + dest_start, dest_start + bytes_left - 1); + memcpy(dest_start, source_start, bytes_left); + swapwriter_buffer_posn += bytes_left; + return 0; + } + + /* A page is full */ + 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, + swapwriter_page_index, + dest_start, dest_start + dest_capacity - 1); + memcpy(dest_start, source_start, dest_capacity); + bytes_left -= dest_capacity; + + if (swapwriter_page_index == header_pages_allocated) + *header_link = 0; + else { + next_header_page = + SWP_ENTRY(SWP_TYPE( + current_header_submit_info->next->swap_address), + current_header_submit_info->next->blocks[0]); + + *header_link = next_header_page.val; + + 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", + swapwriter_page_index, header_pages_allocated, + BDEV_TO_DEVICE_ID( + current_header_submit_info->dev), + current_header_submit_info->blocks[0], + current_header_submit_info->blocks_used); + + current_header_submit_info->page = + virt_to_page(swapwriter_buffer); + check_shift_keys(0, NULL); + do_suspend_io(WRITE, current_header_submit_info, 0); + + swapwriter_buffer_posn = 0; + swapwriter_page_index++; + current_header_submit_info = current_header_submit_info->next; + } + + return 0; +} + +static int swapwriter_write_header_cleanup(void) +{ + /* Write any unsaved data */ + if (swapwriter_buffer_posn) { + *header_link = 0; + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Writing header page %d/%d. " + "Dev is %x. Block is %lu. Blocksperpage is %d.\n", + swapwriter_page_index, header_pages_allocated, + BDEV_TO_DEVICE_ID( + current_header_submit_info->dev), + current_header_submit_info->blocks[0], + current_header_submit_info->blocks_used); + + current_header_submit_info->page = + virt_to_page(swapwriter_buffer); + do_suspend_io(WRITE, + current_header_submit_info, 0); + + swapwriter_buffer_posn = 0; + swapwriter_page_index++; + current_header_submit_info = current_header_submit_info->next; + } + + /* Adjust swap header */ + bdev_page_io(READ, resume_block_device, resume_firstblock, + virt_to_page(swapwriter_buffer)); + + prepare_signature(first_header_submit_info, + ((union swap_header *) swapwriter_buffer)->magic.magic); + + 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; + + swapwriter_page_index = 1; + + swapwriter_buffer = (char *) get_zeroed_page(GFP_ATOMIC); + + if (!header_device) { + printk("read_header_init called when we haven't " + "verified there is an image!\n"); + return -EINVAL; + } + + /* + * If the header is not on the resume_device, get the resume device first. + */ + if (header_device != resume_device) { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + header_block_device = OPEN_BY_DEVNUM(header_device, FMODE_READ); + + if (IS_ERR(header_block_device)) { + if (suspend_early_boot_message( + "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; + } + + if (set_blocksize(header_block_device, PAGE_SIZE) < 0) { + if (suspend_early_boot_message("Failed to set the blocksize" + "for a swap device.")) + do { } while(0); + 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 (suspend_early_boot_message("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), + swapwriter_page_index, + swapwriter_buffer, swapwriter_buffer + sizeof(header_data) - 1); + memcpy(&header_data, swapwriter_buffer, sizeof(header_data)); + + /* Restore device info */ + for (i = 0; i < MAX_SWAPFILES; i++) { + 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; + /* Mark as used so the device doesn't get suspended. */ + swap_info[i].swap_file = (struct SWAP_FILE_STRUCT *) 0xffffff; + continue; + } + + if (thisdevice == header_device) { + printnolog(SUSPEND_IO, SUSPEND_VERBOSE, 0, + "Resume header device %x", thisdevice); + RESUME_BDEV(i) = header_block_device; + /* Mark as used so the device doesn't get suspended. */ + swap_info[i].swap_file = (struct SWAP_FILE_STRUCT *) 0xffffff; + continue; + } + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,99) + RESUME_BDEV(i) = open_by_devnum(thisdevice, FMODE_READ); + set_blocksize(RESUME_BDEV(i), PAGE_SIZE); +#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 + swap_info[i].swap_file = (struct SWAP_FILE_STRUCT *) 0xffffff; + } + + max_async_ios = header_data.max_async_ios; + + swapwriter_buffer_posn = sizeof(header_data); + + return 0; +} + +static int swapwriter_read_header_chunk(char * buffer, int buffer_size) +{ + int bytes_left = buffer_size, ret = 0; + + check_shift_keys(0, ""); + + /* Read a chunk of the header */ + while ((bytes_left) && (!ret)) { + swp_entry_t next = + ((union p_diskpage) swapwriter_buffer).pointer->link.next; + 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, + swapwriter_page_index, + source_start, source_start + bytes_left - 1, + dest_start, dest_start + bytes_left - 1); + memcpy(dest_start, source_start, bytes_left); + swapwriter_buffer_posn += bytes_left; + return buffer_size; + } + + /* Next to read the next page */ + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Retrieving %d bytes from page %d, %p-%p to %p-%p.\n", + source_capacity, + swapwriter_page_index, + source_start, source_start + source_capacity - 1, + dest_start, dest_start + source_capacity - 1); + memcpy(dest_start, source_start, source_capacity); + bytes_left -= source_capacity; + + 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); + + swapwriter_page_index++; + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Reading header page %d. Dev is %x. Block is %lu.\n", + swapwriter_page_index, BDEV_TO_DEVICE_ID(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; + + 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; + + if (get_rangepages_list()) + return -ENOMEM; + + absolutise_chain(&header_data.swapranges); + + for (i = 0; i < MAX_SWAPFILES; i++) + absolutise_chain(&header_data.block_chain[i]); + + header_data.pd1start_block_range = + RANGE_ABSOLUTE(header_data.pd1start_block_range); + + put_rangepages_list(); + + return 0; +} + +static int swapwriter_write_init(int stream_number) +{ + if (stream_number == 1) { + currentblockrange = header_data.pd1start_block_range; + currentblockoffset = header_data.pd1start_block_offset; + currentblockchain = header_data.pd1start_chain; + } else + for (currentblockchain = 0; currentblockchain < MAX_SWAPFILES; + currentblockchain++) + if (header_data.block_chain[currentblockchain].first) { + currentblockrange = + header_data. + block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + break; + } + + BUG_ON(!currentblockrange); + + currentblocksperpage = PAGE_SIZE / + suspend_get_block_size(SWAP_DEVICE_BDEV(currentblockchain)); + + printnolog(SUSPEND_IO, SUSPEND_HIGH, 0, + "Stream %d beginning from position: chain %d. " + "range %p, block %ld.\n", + stream_number, + currentblockchain, currentblockrange, currentblockoffset); + + swapwriter_buffer_posn = 0; + + swapwriter_page_index = 1; + current_stream = stream_number; + + reset_io_stats(); + + return 0; +} + +static int swapwriter_write_chunk(struct page * buffer_page) +{ + int i; + struct submit_params submit_params; + + if (currentblockchain == MAX_SWAPFILES) { + 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); + } + return -ENOSPC; + } + + if (!currentblockrange) { + do { + currentblockchain++; + } while ((currentblockchain < MAX_SWAPFILES) && + (!header_data.block_chain[currentblockchain].first)); + + /* We can validly not have a new blockrange. We + * might be compressing data and the user was + * too optimistic in setting the compression + * ratio or we're just copying the pageset. */ + + if (currentblockchain == MAX_SWAPFILES) { + printk("Argh. Ran out of block chains.\n"); + return -ENOSPC; + } + + currentblockrange = + header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + currentblocksperpage = PAGE_SIZE / + suspend_get_block_size(SWAP_DEVICE_BDEV(currentblockchain)); + } + + submit_params.readahead_index = -1; + submit_params.page = buffer_page; + submit_params.dev = SWAP_DEVICE_BDEV(currentblockchain); + submit_params.blocks_used = currentblocksperpage; + + /* Get the blocks */ + for (i = 0; i < currentblocksperpage; i++) { + submit_params.blocks[i] = currentblockoffset; + GET_RANGE_NEXT(currentblockrange, currentblockoffset); + } + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Writing page %d. Dev is %x. Block is %lu. " + "Blocksperpage is %d.\n", + swapwriter_page_index, + submit_params.dev, + submit_params.blocks[0], + currentblocksperpage); + + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "page:%d. bdev:%x. blocks (%d):", + swapwriter_page_index, + BDEV_TO_DEVICE_ID(submit_params.dev), + submit_params.blocks_used); + + for (i = 0; i < currentblocksperpage; i++) + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, + "0x%lx%s", + submit_params.blocks[i], + ((i+1) < currentblocksperpage) ? "," : "\n"); + + check_shift_keys(0, NULL); + + do_suspend_io(WRITE, &submit_params, 0); + + swapwriter_buffer_posn = 0; + swapwriter_page_index++; + + return 0; +} + +static int swapwriter_write_cleanup(void) +{ + if (current_stream == 2) { + header_data.pd1start_block_range = currentblockrange; + header_data.pd1start_block_offset = currentblockoffset; + header_data.pd1start_chain = currentblockchain; + } + + finish_all_io(); + + suspend_check_io_stats(); + + return 0; +} + +static int swapwriter_read_init(int stream_number) +{ + int i; + + if (stream_number == 1) { + currentblockrange = header_data.pd1start_block_range; + currentblockoffset = header_data.pd1start_block_offset; + currentblockchain = header_data.pd1start_chain; + } else { + currentblockrange = NULL; + currentblockoffset = 0; + currentblockchain = 0; + for (currentblockchain = 0; currentblockchain < MAX_SWAPFILES; + currentblockchain++) + if (header_data.block_chain[currentblockchain].first) { + currentblockrange = + header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; + break; + } + + if (!currentblockrange){ + printk("Error! Can't find any block chain data.\n"); + for (i = 0; i < MAX_SWAPFILES; i++) { + if (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("First entry in chain at %p.\n", + header_data.block_chain[i].first); + } + BUG_ON(1); + } + } + printlog(SUSPEND_IO, SUSPEND_MEDIUM, + "Stream %d beginning from position: chain %d. " + "range %p, block %ld.\n", + stream_number, + currentblockchain, currentblockrange, currentblockoffset); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + currentblocksperpage = PAGE_SIZE / + suspend_get_block_size(SWAP_DEVICE_BDEV(currentblockchain)); +#else + currentblocksperpage = 1; +#endif + + swapwriter_page_index = 1; + + reset_io_stats(); + + readahead_index = -1; + + for (i = 0; i <= (max_async_ios / 8 / sizeof(unsigned long)); i++) + suspend_readahead_flags[i] = 0; + + for (i = 0; i < max_async_ios; i++) + readahead_page[i] = 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; + + if (currentblockchain == MAX_SWAPFILES) { + /* Readahead might ask us to read too many blocks */ + printk("Currentblockchain == MAX_SWAPFILES. Begin_read_chunk returning -ENODATA.\n"); + return -ENODATA; + } + + if (!currentblockrange) { + do { + currentblockchain++; + } while ((!header_data.block_chain[currentblockchain].first) && + (currentblockchain < MAX_SWAPFILES)); + + /* We can validly not have a new blockrange. We + * might have allocated exactly the right amount + * of swap for the image and be reading the last + * block now. + */ + + if (currentblockchain == MAX_SWAPFILES) { + prepare_status(1, 0, + "Currentblockchain == MAX_SWAPFILES and " + "more data to be read. " + "Begin_read_chunk returning -ENOSPC."); + return -ENOSPC; + } + + currentblockrange = + header_data.block_chain[currentblockchain].first; + currentblockoffset = currentblockrange->minimum; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + currentblocksperpage = PAGE_SIZE / + suspend_get_block_size(SWAP_DEVICE_BDEV(currentblockchain)); +#endif + } + + submit_params.readahead_index = readahead_index; + submit_params.page = page; + submit_params.dev = SWAP_DEVICE_BDEV(currentblockchain); + submit_params.blocks_used = currentblocksperpage; + + /* Get the blocks */ + for (i = 0; i < currentblocksperpage; i++) { + submit_params.blocks[i] = currentblockoffset; + GET_RANGE_NEXT(currentblockrange, currentblockoffset); + } + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Reading page %d. Dev is %x. Block is %lu. " + "Blocksperpage is %d. Page is %p(%lx). Readahead index is %d.", + swapwriter_page_index, + submit_params.dev, + submit_params.blocks[0], + currentblocksperpage, + page, page_address(page), + readahead_index); + + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 1, + "page:%d. bdev:%x. blocks (%d):", + swapwriter_page_index, + BDEV_TO_DEVICE_ID(submit_params.dev), + submit_params.blocks_used); + + for (i = 0; i < currentblocksperpage; i++) + printnolog(SUSPEND_PAGESETS, SUSPEND_VERBOSE, 0, + "0x%lx%s", + submit_params.blocks[i], + ((i+1) < currentblocksperpage) ? "," : "\n"); + + check_shift_keys(0, NULL); + + do_suspend_io(READ, &submit_params, sync); + + swapwriter_buffer_posn = 0; + swapwriter_page_index++; + + check_shift_keys(0, NULL); + + return 0; +} + +/* Note that we ignore the sync parameter. We are implementing + * read ahead, and will always wait until our readhead buffer has + * been read before returning. + */ + +static int swapwriter_read_chunk(struct page * buffer_page, int sync) +{ + static int last_result; + int i; + unsigned long flags, * virt; + + printlog(SUSPEND_IO, SUSPEND_HIGH, + "At entrance to swapwriter_read_chunk.\n"); + + if (sync == SUSPEND_ASYNC) + return swapwriter_begin_read_chunk(buffer_page, -1, sync); + + /* Start the first one, plus read-ahead */ + if (readahead_index == -1) { + for (i = 0; i < max_async_ios; i++) { + if ((last_result = + swapwriter_begin_read_chunk(readahead_page[i], i, + SUSPEND_ASYNC))) { + if ((last_result = -ENODATA)) { + printk("No more readahead possible.\n"); + break; + } + + printk("Begin read chunk for page %d returned %d.\n", + i, last_result); + return last_result; + } + printlog(SUSPEND_IO, SUSPEND_HIGH, + "Began readahead %d.\n", i); + } + readahead_index++; + } + + suspend_wait_on_readahead(readahead_index); + + virt = KMAP_ATOMIC(buffer_page); + printlog(SUSPEND_IO, SUSPEND_HIGH, "Returned result of readahead %d," + " Copying data from %p to %p.\n", readahead_index, + page_address(readahead_page[readahead_index]), + virt); + + memcpy(virt, page_address(readahead_page[readahead_index]), + PAGE_SIZE); + KUNMAP_ATOMIC(buffer_page); + + /* Start a new readahead? */ + if (!last_result) { + int index = readahead_index/(8 * sizeof(unsigned long)); + int bit = readahead_index - index * 8 * sizeof(unsigned long); + + spin_lock_irqsave(&suspend_readahead_flags_lock, flags); + clear_bit(bit, &suspend_readahead_flags[index]); + spin_unlock_irqrestore(&suspend_readahead_flags_lock, flags); + + printlog(SUSPEND_IO, SUSPEND_HIGH, "\nBeginning new readahead %d.\n", + readahead_index); + + last_result = swapwriter_begin_read_chunk( + readahead_page[readahead_index], + readahead_index, SUSPEND_ASYNC); + if (last_result) + printk("Begin read chunk for page %d returned %d.\n", + readahead_index, last_result); + } + + readahead_index++; + if (readahead_index == max_async_ios) + readahead_index = 0; + + return 0; +} + +static int swapwriter_read_cleanup(void) +{ + finish_all_io(); + suspend_check_io_stats(); + { + int i; + for (i = 0; i < max_async_ios; 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; + } + + suspend_store_free_mem(SUSPEND_FREE_INVALIDATE_IMAGE, 0); + + /* + * If nr_suspends == 0, we must be booting, so no swap pages + * will be recorded as used yet. + */ + + if (nr_suspends > 0) + swapwriter_release_storage(); + + /* + * We don't do a sanity check here: we want to restore the swap + * whatever version of kernel made the suspend image. + * + * We need to write swap, but swap may not be enabled so + * we write the device directly + */ + + 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: + finish_all_io(); + free_pages(cur.address, 0); + suspend_store_free_mem(SUSPEND_FREE_INVALIDATE_IMAGE, 1); + return 0; +} + +/* + * workspace_size + * + * Description: + * Returns the number of bytes of RAM needed for this + * code to do its work. (Used when calculating whether + * we have enough memory to be able to suspend & resume). + * + */ +static unsigned long swapwriter_memory_needed(void) +{ + return (REAL_MAX_ASYNC * PAGE_SIZE); +} + +/* Print debug info + * + * Description: + */ + +static int swapwriter_print_debug_stats(char * buffer, int size) +{ + int len = 0; + struct sysinfo sysinfo; + + if (active_writer != &swapwriterops) { + len = suspend_snprintf(buffer, size, "- Swapwriter inactive.\n"); + return len; + } + + len = suspend_snprintf(buffer, size, "- Swapwriter active.\n"); + if (swapfilename[0]) + len+= suspend_snprintf(buffer+len, size-len, + " Attempting to automatically swapon: %s.\n", swapfilename); + + si_swapinfo(&sysinfo); + + len+= suspend_snprintf(buffer+len, size-len, " Swap available for image: %ld.\n", + sysinfo.freeswap); + + 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."); + + 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)); + finish_all_io(); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + + if (signature_found < 2) { + printk(KERN_ERR name_suspend "This is normal swap space.\n" ); + return 0; /* non fatal error */ + } else if (signature_found == -1) { + printk(KERN_ERR name_suspend "Unable to find a signature. Could you have moved a swap file?\n"); + return 0; + } else if (signature_found < 6) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) + if ((!(software_suspend_state & SOFTWARE_SUSPEND_NORESUME_SPECIFIED)) + && suspend_early_boot_message("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)) && + suspend_early_boot_message("Found a different style suspend image signature.")) + software_suspend_state |= SOFTWARE_SUSPEND_NORESUME_SPECIFIED; + } + + return 1; +} + +/* + * Mark resume attempted. + * + * Record that we tried to resume from this image. + */ + +static void swapwriter_mark_resume_attempted(void) +{ + union p_diskpage diskpage; + int signature_found; + + if (!resume_device) { + printk("Not even trying to record attempt at resuming" + " because resume_device is not set.\n"); + return; + } + + diskpage.address = get_zeroed_page(GFP_ATOMIC); + + /* FIXME: Make sure bdev_page_io handles wrong parameters */ + bdev_page_io(READ, resume_block_device, resume_firstblock, virt_to_page(diskpage.ptr)); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + + switch (signature_found) { + case 12: + case 13: + diskpage.pointer->swh.magic.magic[5] |= 0x80; + break; + } + + bdev_page_io(WRITE, resume_block_device, resume_firstblock, + virt_to_page(diskpage.ptr)); + finish_all_io(); + free_pages(diskpage.address, 0); + return; +} + +/* + * Parse Image Location + * + * Attempt to parse a resume2= parameter. + * Swap Writer accepts: + * resume2=swap:DEVNAME[:FIRSTBLOCK][@BLOCKSIZE] + * + * Where: + * DEVNAME is convertable to a 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, int only_writer) +{ + char *thischar, *colon = NULL, *at_symbol = NULL; + union p_diskpage diskpage; + int signature_found; + + if (strncmp(commandline, "swap:", 5)) { + if (!only_writer) { + printk(name_suspend "Swapwriter: Image location doesn't begin with 'swap:'\n"); + return 0; + } + } else + 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) + suspend_early_boot_message("Failed to translate the device name into a device id.\n"); + else + printk(name_suspend "Failed to translate \"%s\" into a device id.\n", commandline); + goto invalid; + } + + resume_block_device = OPEN_BY_DEVNUM(resume_device, FMODE_READ); + + if (IS_ERR(resume_block_device)) { + printk("Open by devnum returned %p given %x.\n", resume_block_device, resume_device); + if (boot_time) + suspend_early_boot_message("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.\n"); + goto invalid; + } +#else + resume_block_device = resume_device = name_to_kdev_t(commandline); + + if (!resume_block_device) { + if (boot_time) + suspend_early_boot_message("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.\n"); + goto invalid; + } + +#endif + + if (colon) + *colon = ':'; + if (at_symbol) + *at_symbol = '@'; + + if ((suspend_get_block_size(resume_block_device) != resume_firstblocksize) && + (suspend_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)); + finish_all_io(); + signature_found = parse_signature(diskpage.pointer->swh.magic.magic, 0); + free_page((unsigned long) diskpage.address); + + if (signature_found != -1) { + printk(KERN_ERR name_suspend "Swap space signature found.\n" ); + return 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; +} + +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 suspend_proc_data swapfilename_proc_data = { + .filename = "swapfilename", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_STRING, + .data = { + .string = { + .variable = swapfilename, + .max_length = 255, + } + } +}; + +struct suspend_proc_data headerlocations_proc_data = { + .filename = "headerlocations", + .permissions = PROC_READONLY, + .type = SWSUSP_PROC_DATA_CUSTOM, + .data = { + .special = { + .read_proc = header_locations_read_proc, + } + } +}; + +struct suspend_plugin_ops swapwriterops = { + .type = WRITER_PLUGIN, + .name = "Swap Writer", + .memory_needed = swapwriter_memory_needed, + .print_debug_info = swapwriter_print_debug_stats, + .storage_needed = swapwriter_storage_needed, + .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, + .initialise = swapwriter_initialise, + .cleanup = swapwriter_cleanup, + .dpm_set_devices = swapwriter_dpm_set_devices, + .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, + .mark_resume_attempted = swapwriter_mark_resume_attempted, + .write_header_init = swapwriter_write_header_init, + .write_header_chunk = swapwriter_write_header_chunk, + .write_header_cleanup = swapwriter_write_header_cleanup, + .read_header_init = swapwriter_read_header_init, + .read_header_chunk = swapwriter_read_header_chunk, + .read_header_cleanup = swapwriter_read_header_cleanup, + .prepare_save_ranges = swapwriter_prepare_save_ranges, + .post_load_ranges = swapwriter_post_load_ranges, + .invalidate_image = swapwriter_invalidate_image, + .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 = suspend_register_plugin(&swapwriterops))) { + suspend_register_procfile(&swapfilename_proc_data); + suspend_register_procfile(&headerlocations_proc_data); + } + return result; +} + +__initcall(swapwriter_load); + diff -ruN post-version-specific/kernel/power/ui.c software-suspend-core-2.0.0.96/kernel/power/ui.c --- post-version-specific/kernel/power/ui.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/ui.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,1289 @@ +/* + * 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-2004 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/suspend/enable_escape. + * + * (The following assumes debugging is compiled in)... + * + * Fundamental to the debugging code is the idea that each debug + * message which suspend can display has both a section of code to + * which it belongs and a level of verbosity. When suspend is running, + * it only displays a message if debugging for the section has been + * enabled prior to beginning the cycle and the current loglevel is + * greater than or equal to the level of the message. + * + * Prior to starting a suspend cycle, a user can select which sections + * should display debugging information by setting + * /proc/suspend/debug_sections. This is a bit vector (values in + * include/linux/suspend-debug.h). The initial log level can be also + * be set using /proc/suspend/default_console_level. + * The debug sections and level can be changed prior to resuming using + * the kernel command line parameters suspend_dbg and suspend_lvl. + * + * In addition to the above ability to control whether messages are + * displayed, messages can be displayed in two ways. The printlog call + * displays a message using printk, with the result that it is also + * logged to syslog in the normal way. + * + * A call to 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 + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) +#include +#else +extern asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type, + unsigned long flags, void * data); +extern asmlinkage long sys_umount(char * name, int flags); +extern asmlinkage long sys_mkdir(const char *name, int mode); +#endif + +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 suspend_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 + +/* + * 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[fg_console].d->vc_cols) +#undef video_num_lines +#define video_num_lines (vc_cons[fg_console].d->vc_rows) + +extern void reset_terminal(int currcons, int do_clear); + +/* Forward declaration */ +void force_console_redraw(void); + +int suspend_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 + +#ifdef CONFIG_DEVFS_FS +static int mounted_devfs = 0; +#endif + +#define cond_console_print(chars, count) \ + if (suspend_console_fd > -1) { \ + write(suspend_console_fd, chars, count); \ + acquire_console_sem(); \ + hide_cursor(fg_console); \ + poke_blanked_console(); \ + release_console_sem(); \ + } + +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] = { 0, 0 }; + + cond_console_print(buf, 3); + move_cursor_to(home); + //force_console_redraw(); +} + +/* 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; +#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 (in_interrupt()) + return; + + if (unlikely(!write_routine)) { + proc_entry = find_proc_dir_entry("splash", &proc_root); + if (proc_entry) + write_routine = proc_entry->write_proc; + } + + if (write_routine) + write_routine(NULL, buffer, count, NULL); +} + +/* fb_splash-set_progress + * + * Description: Set the progress bar position for a splash screen. + * Arguments: int consolenr. The VT number of a console to use. + * unsigned long value, unsigned long maximum: + * The proportion (value/maximum) of the bar to fill. + */ + +static int fb_splash_set_progress(int consolenr, unsigned long value, + unsigned long maximum) +{ + char procstring[15]; + int length, bitshift = generic_fls(maximum) - 16; + static unsigned long lastvalue = 0; + unsigned long thisvalue; + + BUG_ON(consolenr >= MAX_NR_CONSOLES); + + if (in_interrupt()) + return 0; + + if (value > maximum) + value = maximum; + + /* Avoid math problems - we can't do 64 bit math here + * (and don't need it - anyone got screen resolution + * of 65536 pixels or more?) */ + if (bitshift > 0) { + maximum = maximum >> bitshift; + value = value >> bitshift; + } + + thisvalue = value * 65534 / maximum; + + length = sprintf(procstring, "show %lu", thisvalue); + + splash_write_proc(procstring, length); + + /* Ensure redraw when the progress bar goes to a lower value */ + if (thisvalue < lastvalue) + force_console_redraw(); + + lastvalue = thisvalue; + + return 0; +} +#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) + spin_unlock_irqrestore(&suspend_irq_lock, + suspendirqflags); + 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); + /* + * Make sure message seen - wait for shift to be + * released if being pressed + */ + interruptible_sleep_on(&suspend_wait_for_key); + + if (locked) + spin_lock_irqsave(&suspend_irq_lock, + suspendirqflags); + } + /* 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; + + if (lastloglevel == -1) { + lastloglevel = console_loglevel; + 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(fg_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 (lastloglevel < 2) { + clear_display(); + if (splash_is_on(fg_console)) + splash_write_proc("verbose\n", 9); + } +#endif + printnolog(0, console_loglevel, 0, + "Switched to console loglevel %d.\n", + console_loglevel); +#ifdef CONFIG_VT + } else if (lastloglevel > 1) { + clear_display(); + if (splash_is_on(fg_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, ...) +void __printnolog(int restartline, const char *fmt, ...) +{ +#if defined(DEFAULT_SUSPEND_CONSOLE) + va_list args; + int printed_len = 0; + + /* 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, ...) +void __printlog(const char *fmt, ...) +{ +#if defined(DEFAULT_SUSPEND_CONSOLE) + va_list args; + int printed_len = 0; + + /* 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) || in_interrupt()) + 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(fg_console))) + fb_splash_set_progress(fg_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_CORE_VERSION, strlen(SWSUSP_CORE_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(fg_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) { + /* 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); + } + } + + acquire_console_sem(); + hide_cursor(fg_console); + release_console_sem(); + + if ((clearbar) && (splash_is_on(fg_console))) + fb_splash_set_progress(fg_console, 0, 1); + + barposn = 0; + + MDELAY(2000); +#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(fg_console)) && + (newbarposn != barposn)) { + fb_splash_set_progress(fg_console, + value, maximum); + barposn = newbarposn; + } + return next_update; + } + + /* Update bar */ + if (splash_is_on(fg_console)) { + if (newbarposn != barposn) + fb_splash_set_progress(fg_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; + acquire_console_sem(); + hide_cursor(fg_console); + release_console_sem(); + +#else + next_update = maximum; +#endif + return next_update; +} + +static struct waiting_message +{ + int message; + struct waiting_message * next; +} * waiting_messages = NULL; + +/* display_suspend_message + * + * Description: Display a message as a result of the user pressing a key + * and the key being processed in an interrupt handler. + */ + +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS +extern void suspend_calculate_checksums(void); +extern void suspend_check_checksums(void); +#endif + +int display_suspend_messages(void) +{ + int did_work = ((waiting_messages != NULL) || (console_loglevel != lastloglevel)); + + if (console_loglevel != lastloglevel) + handle_loglevel_change(); + + while (waiting_messages) { + struct waiting_message * this_message = waiting_messages; + + switch(waiting_messages->message) { + case 1: + prepare_status(1, 0, "Pausing %s.", + TEST_ACTION_STATE(SUSPEND_PAUSE) ? + "enabled" : "disabled"); + break; + case 2: + prepare_status(1, 0, "Rebooting %s.", + TEST_ACTION_STATE(SUSPEND_REBOOT) ? + "enabled" : "disabled"); + break; + case 3: + prepare_status(1, 0, "Single step %s.", + TEST_ACTION_STATE(SUSPEND_SINGLESTEP) ? + "enabled" : "disabled"); + break; + case 4: + prepare_status(1, 0, "Logging all output %s.", + TEST_ACTION_STATE(SUSPEND_LOGALL) ? + "enabled" : "disabled"); + break; + case 5: + prepare_status(1, 1, + "--- ESCAPE PRESSED AGAIN :" + " TRYING HARDER TO ABORT ---"); + break; + case 6: + prepare_status(1, 1, "--- ESCAPE PRESSED :" + " ABORTING PROCESS ---"); + break; + case 7: + prepare_status(1, 0, "Slowing down %s.", + TEST_ACTION_STATE(SUSPEND_SLOW) ? + "enabled" : "disabled"); + break; + case 8: + prepare_status(1, 0, "Log level changed to %d.", + console_loglevel); + break; +#ifdef CONFIG_SOFTWARE_SUSPEND_VARIATION_ANALYSIS + case 10: + /* Grab image checksums */ + if (CHECKMASK(SUSPEND_INTEGRITY)) + suspend_calculate_checksums(); + break; + case 11: + /* Calculate differences for pageset 1 only */ + if (CHECKMASK(SUSPEND_INTEGRITY)) + suspend_check_checksums(); + break; +#endif + case 20: + prepare_status(1, 0, "General messages %s.", + TEST_DEBUG_STATE(SUSPEND_ANY_SECTION) ? + "enabled" : "disabled"); + break; + case 21: + prepare_status(1, 0, "Freezer messages %s.", + TEST_DEBUG_STATE(SUSPEND_FREEZER) ? + "enabled" : "disabled"); + break; + case 22: + prepare_status(1, 0, "Eat memory messages %s.", + TEST_DEBUG_STATE(SUSPEND_EAT_MEMORY) ? + "enabled" : "disabled"); + break; + case 23: + prepare_status(1, 0, "Pageset messages %s.", + TEST_DEBUG_STATE(SUSPEND_PAGESETS) ? + "enabled" : "disabled"); + break; + case 24: + prepare_status(1, 0, "IO messages %s.", + TEST_DEBUG_STATE(SUSPEND_IO) ? + "enabled" : "disabled"); + break; + case 25: + prepare_status(1, 0, "Bmap messages %s.", + TEST_DEBUG_STATE(SUSPEND_BMAP) ? + "enabled" : "disabled"); + break; + case 26: + prepare_status(1, 0, "Swap messages %s.", + TEST_DEBUG_STATE(SUSPEND_SWAP) ? + "enabled" : "disabled"); + break; + case 27: + prepare_status(1, 0, "Memory messages %s.", + TEST_DEBUG_STATE(SUSPEND_MEMORY) ? + "enabled" : "disabled"); + break; + case 28: + prepare_status(1, 0, "Range messages %s.", + TEST_DEBUG_STATE(SUSPEND_RANGES) ? + "enabled" : "disabled"); + break; + case 29: + prepare_status(1, 0, "Memory pool messages %s.", + TEST_DEBUG_STATE(SUSPEND_MEM_POOL) ? + "enabled" : "disabled"); + break; + case 30: + prepare_status(1, 0, "Nosave messages %s.", + TEST_DEBUG_STATE(SUSPEND_NOSAVE) ? + "enabled" : "disabled"); + break; + case 31: + prepare_status(1, 0, "Integrity messages %s.", + TEST_DEBUG_STATE(SUSPEND_INTEGRITY) ? + "enabled" : "disabled"); + break; + } + + waiting_messages = this_message->next; + kfree(this_message); + } + return did_work; +} + +/* schedule_suspend_message + * + * Description: + * + */ + +void schedule_suspend_message(int message_number) +{ + struct waiting_message * new_message = + kmalloc(sizeof(struct waiting_message), GFP_ATOMIC); + + if (!new_message) { + printk("Argh. Unable to allocate memory for " + "scheduling the display of a message.\n"); + return; + } + + new_message->message = message_number; + new_message->next = waiting_messages; + + waiting_messages = new_message; + return; +} + +/* 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); +} + +/* 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)) { + schedule_suspend_message(5); + show_state(); + thaw_processes(); + wakeup_suspend(); + } else { + schedule_suspend_message(6); + SET_RESULT_STATE(SUSPEND_ABORTED); + SET_RESULT_STATE(SUSPEND_ABORT_REQUESTED); + wakeup_suspend(); + } +} + +/* 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 +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + int displayed_message = 0; +#endif + + if (NO_OUTPUT_OR_PAUSING) + return; + + do { +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + if (((TEST_ACTION_STATE(SUSPEND_PAUSE) && pause) || + (TEST_ACTION_STATE(SUSPEND_SINGLESTEP)))) { + if (!displayed_message) { + prepare_status(1, 0, + "%s Press SPACE to continue.%s", + message ? message : "", + (TEST_ACTION_STATE(SUSPEND_SINGLESTEP)) ? + " Single step on." : ""); + displayed_message = 1; + } + interruptible_sleep_on(&suspend_wait_for_key); + } +#endif + } while (display_suspend_messages()); +#endif +} + +extern asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, + unsigned long arg); + +/* + * + */ + +static void suspend_get_dev_console(void) +{ + if (suspend_console_fd > -1) + return; + + suspend_console_fd = sys_open("/dev/console", O_RDWR | O_NONBLOCK, 0); + if (suspend_console_fd < 0) { + sys_mkdir("/dev", 0700); +#ifdef CONFIG_DEVFS_FS + sys_mount("devfs", "/dev", "devfs", 0, NULL); + mounted_devfs = 1; +#endif + suspend_console_fd = sys_open("/dev/console", O_RDWR | O_NONBLOCK, 0); + } + if (suspend_console_fd < 0) { + printk("Can't open /dev/console. Error value was %d.\n", + suspend_console_fd); + suspend_console_fd = -1; + return; + } + + sys_ioctl(suspend_console_fd, TCGETS, (long)&termios); + termios.c_lflag &= ~ICANON; + sys_ioctl(suspend_console_fd, TCSETSF, (long)&termios); +} + +/* 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 = suspend_default_console_level; + +#ifdef CONFIG_VT_CONSOLE + orig_fgconsole = fg_console; + + if ((orig_fgconsole != DEFAULT_SUSPEND_CONSOLE) && + (!splash_is_on(fg_console))) { + int wait_result, alloc_result; + + acquire_console_sem(); + + alloc_result = vc_allocate(DEFAULT_SUSPEND_CONSOLE); + + release_console_sem(); + + /* If we can't have a free VC for now, too bad.*/ + if (alloc_result) + return 1; + + set_console(DEFAULT_SUSPEND_CONSOLE); + + /* We should have a timeout here */ + wait_result = vt_waitactive(DEFAULT_SUSPEND_CONSOLE); + + if (wait_result) { + printk("Can't switch virtual consoles."); + return 1; + } + } + + suspend_get_dev_console(); + + clear_display(); + orig_kmsg = kmsg_redirect; + kmsg_redirect = fg_console; + prepare_status(0, 0, ""); + + default_message_loglevel = 1; +#endif + if (splash_is_on(fg_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. + */ + +/* pm_restore_console + * + * Description: Restore the settings we saved above. + */ + +void pm_restore_console(void) +{ + if (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT)) + return; + +#ifdef CONFIG_VT_CONSOLE + reset_terminal(fg_console, 1); + termios.c_lflag |= ICANON; + sys_ioctl(suspend_console_fd, TCSETSF, (long)&termios); + sys_close(suspend_console_fd); + suspend_console_fd = -1; + +#ifdef CONFIG_DEVFS_FS + if (mounted_devfs) + sys_umount("/dev", 0); +#endif + + if (orig_fgconsole != fg_console) { + set_console(orig_fgconsole); + /* We should have a timeout here */ + if (vt_waitactive(orig_fgconsole)) { + printk("Can't switch virtual consoles."); + return; + } + } + + acquire_console_sem(); + update_screen(fg_console); + release_console_sem(); + + kmsg_redirect = orig_kmsg; +#endif + + suspend_default_console_level = console_loglevel; + console_loglevel = orig_loglevel; + default_message_loglevel = orig_default_message_loglevel; + lastloglevel = -1; + return; +} + +#ifdef CONFIG_VT_CONSOLE +/* force_console_redraw + * + * Description: Force a redraw of the console. Necessary after copying the + * original kernel back and when the progress bar is moved + * backwards. + */ +extern int console_blanked; + +void force_console_redraw(void) +{ + + console_blanked = fg_console + 1; + //handle_loglevel_change(); + acquire_console_sem(); + unblank_screen(); + update_screen(fg_console); + release_console_sem(); +} + +/* post_resume_console_redraw(void) + * + * Description: Redraw the console after copying the original kernel back. + */ + +void post_resume_console_redraw(void) +{ + if (splash_is_on(fg_console)) { + if (console_loglevel < 2) + splash_write_proc("silent\n", 8); + else + splash_write_proc("verbose\n", 9); + } +} +#endif + +/* suspend_early_boot_message() + * 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 suspend_early_boot_message(char *warning_reason) +{ + PRINTPREEMPTCOUNT("At entry to suspend_early_boot_message"); +#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"); + if (warning_reason) { + printk(KERN_EMERG "BIG FAT WARNING!! %s\n\n", warning_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"); + } else { + printk(KERN_EMERG "BIG FAT WARNING!!\n\n"); + printk(KERN_EMERG "You have tried to resume from this image before.\n"); + printk(KERN_EMERG "If it failed once, it will probably fail again.\n"); + printk(KERN_EMERG "Would you like to remove the image and boot normally?\n"); + printk(KERN_EMERG "This will be equivalent to entering noresume2 on the\n"); + printk(KERN_EMERG "kernel command line.\n\n"); + printk(KERN_EMERG "Press SPACE to remove the image or C to continue resuming.\n"); + } + + software_suspend_state |= SOFTWARE_SUSPEND_SANITY_CHECK_PROMPT; + PRINTPREEMPTCOUNT("prior to interruptible sleep on"); + interruptible_sleep_on(&suspend_wait_for_key); + if ((warning_reason) && (!(software_suspend_state & SOFTWARE_SUSPEND_CONTINUE_REQ))) + machine_restart(NULL); + 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/suspend entries. + */ + +static struct suspend_proc_data proc_params[] = { + { .filename = "default_console_level", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_INTEGER, + .data = { + .integer = { + .variable = &suspend_default_console_level, + .minimum = 0, +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + .maximum = 7, +#else + .maximum = 1, +#endif + + } + } + }, + + { .filename = "enable_escape", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &suspend_action, + .bit = SUSPEND_CAN_CANCEL, + } + } + }, + + { .filename = "no_output", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &suspend_action, + .bit = SUSPEND_NO_OUTPUT, + } + } + }, + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG + { .filename = "debug_sections", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_UL, + .data = { + .ul = { + .variable = &suspend_debug_state, + .minimum = 0, + .maximum = 2 << 30, + } + } + }, + + { .filename = "log_everything", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &suspend_action, + .bit = SUSPEND_LOGALL, + } + } + }, + + { .filename = "pause_between_steps", + .permissions = PROC_RW, + .type = SWSUSP_PROC_DATA_BIT, + .data = { + .bit = { + .bit_vector = &suspend_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 +}; + +/* suspend_console_proc_init + * Description: Boot time initialisation for user interface. + */ +__init void suspend_console_proc_init(void) +{ + int i, numfiles = sizeof(proc_params) / sizeof(struct suspend_proc_data); + + for (i=0; i< numfiles; i++) + suspend_register_procfile(&proc_params[i]); +} + +#endif + +#ifdef CONFIG_SOFTWARE_SUSPEND_DEBUG +EXPORT_SYMBOL(__printlog); +#endif +EXPORT_SYMBOL(request_abort_suspend); +EXPORT_SYMBOL(wakeup_suspend); +EXPORT_SYMBOL(prepare_status); diff -ruN post-version-specific/kernel/power/utility.c software-suspend-core-2.0.0.96/kernel/power/utility.c --- post-version-specific/kernel/power/utility.c 1970-01-01 10:00:00.000000000 +1000 +++ software-suspend-core-2.0.0.96/kernel/power/utility.c 2004-07-08 14:10:50.000000000 +1000 @@ -0,0 +1,140 @@ +/* + * kernel/power/utility.c + * + * Copyright (C) 2004 Nigel Cunningham + * + * This file is released under the GPLv2. + * + * Routines that only suspend uses at the moment, but which might move + * when we merge because they're generic. + */ + +#include + +/* + * suspend_snprintf + * + * Functionality : Print a string with parameters to a buffer of a + * limited size. Unlike vsnprintf, we return the number + * of bytes actually put in the buffer, not the number + * that would have been put in if it was big enough. + */ +int suspend_snprintf(char * buffer, int buffer_size, const char *fmt, ...) +{ + int result; + va_list args; + + if (!buffer_size) { + return 0; + } + + va_start(args, fmt); + result = vsnprintf(buffer, buffer_size, fmt, args); + va_end(args); + + if (result > buffer_size) { + return buffer_size; + } + + return result; +} + +/* + * find_proc_dir_entry. + * + * Based on remove_proc_entry. + * This will go shortly, once user space utilities + * are updated to look at /proc/suspend/all_settings. + */ + +struct proc_dir_entry * find_proc_dir_entry(const char *name, struct proc_dir_entry *parent) +{ + struct proc_dir_entry **p; + int len; + + len = strlen(name); + for (p = &parent->subdir; *p; p=&(*p)->next ) { + if (proc_match(len, name, *p)) { + return *p; + } + } + return NULL; +} + +/* ------------- Dynamically Allocated Page Flags --------------- */ + +#define BITS_PER_PAGE (PAGE_SIZE * 8) +#define PAGES_PER_BITMAP ((max_mapnr + BITS_PER_PAGE - 1) / BITS_PER_PAGE) +#define BITMAP_ORDER (get_bitmask_order((PAGES_PER_BITMAP) - 1)) + +/* clear_map + * + * Description: Clear an array used to store local page flags. + * Arguments: unsigned long *: The pagemap to be cleared. + */ + +void clear_map(unsigned long * pagemap) +{ + int 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) { + printk("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; + } +} +