| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374 |
- /*
- * kernel/power/tuxonice_highlevel.c
- */
- /** \mainpage TuxOnIce.
- *
- * TuxOnIce provides support for saving and restoring an image of
- * system memory to an arbitrary storage device, either on the local computer,
- * or across some network. The support is entirely OS based, so TuxOnIce
- * works without requiring BIOS, APM or ACPI support. The vast majority of the
- * code is also architecture independent, so it should be very easy to port
- * the code to new architectures. TuxOnIce includes support for SMP, 4G HighMem
- * and preemption. Initramfses and initrds are also supported.
- *
- * TuxOnIce uses a modular design, in which the method of storing the image is
- * completely abstracted from the core code, as are transformations on the data
- * such as compression and/or encryption (multiple 'modules' can be used to
- * provide arbitrary combinations of functionality). The user interface is also
- * modular, so that arbitrarily simple or complex interfaces can be used to
- * provide anything from debugging information through to eye candy.
- *
- * \section Copyright
- *
- * TuxOnIce is released under the GPLv2.
- *
- * Copyright (C) 1998-2001 Gabor Kuti <seasons@fornax.hu><BR>
- * Copyright (C) 1998,2001,2002 Pavel Machek <pavel@suse.cz><BR>
- * Copyright (C) 2002-2003 Florent Chabaud <fchabaud@free.fr><BR>
- * Copyright (C) 2002-2014 Nigel Cunningham (nigel at tuxonice net)<BR>
- *
- * \section Credits
- *
- * Nigel would like to thank the following people for their work:
- *
- * Bernard Blackham <bernard@blackham.com.au><BR>
- * Web page & Wiki administration, some coding. A person without whom
- * TuxOnIce would not be where it is.
- *
- * Michael Frank <mhf@linuxmail.org><BR>
- * Extensive testing and help with improving stability. I was constantly
- * amazed by the quality and quantity of Michael's help.
- *
- * Pavel Machek <pavel@ucw.cz><BR>
- * Modifications, defectiveness pointing, being with Gabor at the very
- * beginning, suspend to swap space, stop all tasks. Port to 2.4.18-ac and
- * 2.5.17. Even though Pavel and I disagree on the direction suspend to
- * disk should take, I appreciate the valuable work he did in helping Gabor
- * get the concept working.
- *
- * ..and of course the myriads of TuxOnIce users who have helped diagnose
- * and fix bugs, made suggestions on how to improve the code, proofread
- * documentation, and donated time and money.
- *
- * Thanks also to corporate sponsors:
- *
- * <B>Redhat.</B>Sometime employer from May 2006 (my fault, not Redhat's!).
- *
- * <B>Cyclades.com.</B> Nigel's employers from Dec 2004 until May 2006, who
- * allowed him to work on TuxOnIce and PM related issues on company time.
- *
- * <B>LinuxFund.org.</B> Sponsored Nigel's work on TuxOnIce for four months Oct
- * 2003 to Jan 2004.
- *
- * <B>LAC Linux.</B> Donated P4 hardware that enabled development and ongoing
- * maintenance of SMP and Highmem support.
- *
- * <B>OSDL.</B> Provided access to various hardware configurations, make
- * occasional small donations to the project.
- */
- #include <linux/suspend.h>
- #include <linux/freezer.h>
- #include <generated/utsrelease.h>
- #include <linux/cpu.h>
- #include <linux/console.h>
- #include <linux/writeback.h>
- #include <linux/uaccess.h> /* for get/set_fs & KERNEL_DS on i386 */
- #include <linux/bio.h>
- #include <linux/kgdb.h>
- #include "tuxonice.h"
- #include "tuxonice_modules.h"
- #include "tuxonice_sysfs.h"
- #include "tuxonice_prepare_image.h"
- #include "tuxonice_io.h"
- #include "tuxonice_ui.h"
- #include "tuxonice_power_off.h"
- #include "tuxonice_storage.h"
- #include "tuxonice_checksum.h"
- #include "tuxonice_builtin.h"
- #include "tuxonice_atomic_copy.h"
- #include "tuxonice_alloc.h"
- #include "tuxonice_cluster.h"
- /*! Pageset metadata. */
- struct pagedir pagedir2 = { 2 };
- EXPORT_SYMBOL_GPL(pagedir2);
- static mm_segment_t oldfs;
- static DEFINE_MUTEX(tuxonice_in_use);
- static int block_dump_save;
- int toi_trace_index;
- /* Binary signature if an image is present */
- char tuxonice_signature[9] = "\xed\xc3\x02\xe9\x98\x56\xe5\x0c";
- EXPORT_SYMBOL_GPL(tuxonice_signature);
- unsigned long boot_kernel_data_buffer;
- #ifdef CONFIG_TOI_FIXUP
- #if 0 /* removed it */
- #endif
- static char *result_strings[] = {
- "Hibernation was aborted",
- "The user requested that we cancel the hibernation",
- "No storage was available",
- "Insufficient storage was available",
- "Freezing filesystems and/or tasks failed",
- "A pre-existing image was used",
- "We would free memory, but image size limit doesn't allow this",
- "Unable to free enough memory to hibernate",
- "Unable to obtain the Power Management Semaphore",
- "A device suspend/resume returned an error",
- "A system device suspend/resume returned an error",
- "The extra pages allowance is too small",
- "We were unable to successfully prepare an image",
- "TuxOnIce module initialisation failed",
- "TuxOnIce module cleanup failed",
- "I/O errors were encountered",
- "Ran out of memory",
- "An error was encountered while reading the image",
- "Platform preparation failed",
- "CPU Hotplugging failed",
- "Architecture specific preparation failed",
- "Pages needed resaving, but we were told to abort if this happens",
- "We can't hibernate at the moment (invalid resume= or filewriter " "target?)",
- "A hibernation preparation notifier chain member cancelled the " "hibernation",
- "Pre-snapshot preparation failed",
- "Pre-restore preparation failed",
- "Failed to disable usermode helpers",
- "Can't resume from alternate image",
- "Header reservation too small",
- "Device Power Management Preparation failed",
- };
- #ifdef CONFIG_TOI_FIXUP
- #endif /* remove it */
- #endif
- /**
- * toi_finish_anything - cleanup after doing anything
- * @hibernate_or_resume: Whether finishing a cycle or attempt at
- * resuming.
- *
- * This is our basic clean-up routine, matching start_anything below. We
- * call cleanup routines, drop module references and restore process fs and
- * cpus allowed masks, together with the global block_dump variable's value.
- **/
- void toi_finish_anything(int hibernate_or_resume)
- {
- hib_log("hibernate_or_resume(%d)\n", hibernate_or_resume);
- toi_running = 0;
- toi_cleanup_modules(hibernate_or_resume);
- toi_put_modules();
- if (hibernate_or_resume) {
- block_dump = block_dump_save;
- set_cpus_allowed_ptr(current, cpu_all_mask);
- toi_alloc_print_debug_stats();
- atomic_inc(&snapshot_device_available);
- unlock_system_sleep();
- }
- set_fs(oldfs);
- mutex_unlock(&tuxonice_in_use);
- }
- /**
- * toi_start_anything - basic initialisation for TuxOnIce
- * @toi_or_resume: Whether starting a cycle or attempt at resuming.
- *
- * Our basic initialisation routine. Take references on modules, use the
- * kernel segment, recheck resume= if no active allocator is set, initialise
- * modules, save and reset block_dump and ensure we're running on CPU0.
- **/
- int toi_start_anything(int hibernate_or_resume)
- {
- mutex_lock(&tuxonice_in_use);
- oldfs = get_fs();
- set_fs(KERNEL_DS);
- toi_trace_index = 0;
- if (hibernate_or_resume) {
- lock_system_sleep();
- if (!atomic_add_unless(&snapshot_device_available, -1, 0))
- goto snapshotdevice_unavailable;
- }
- if (hibernate_or_resume == SYSFS_HIBERNATE)
- toi_print_modules();
- if (toi_get_modules()) {
- pr_warn("TuxOnIce: Get modules failed!\n");
- goto prehibernate_err;
- }
- if (hibernate_or_resume) {
- block_dump_save = block_dump;
- block_dump = 0;
- set_cpus_allowed_ptr(current, cpumask_of(cpumask_first(cpu_online_mask)));
- }
- if (toi_initialise_modules_early(hibernate_or_resume))
- goto early_init_err;
- if (!toiActiveAllocator) {
- hib_log("hibernate_or_resume(0x%08x), resume_file=\"%s\"\n", hibernate_or_resume,
- resume_file);
- toi_attempt_to_parse_resume_device(!hibernate_or_resume);
- }
- if (!toi_initialise_modules_late(hibernate_or_resume)) {
- toi_running = 1; /* For the swsusp code we use :< */
- return 0;
- }
- toi_cleanup_modules(hibernate_or_resume);
- early_init_err:
- if (hibernate_or_resume) {
- block_dump_save = block_dump;
- set_cpus_allowed_ptr(current, cpu_all_mask);
- }
- toi_put_modules();
- prehibernate_err:
- if (hibernate_or_resume)
- atomic_inc(&snapshot_device_available);
- snapshotdevice_unavailable:
- if (hibernate_or_resume)
- mutex_unlock(&pm_mutex);
- set_fs(oldfs);
- mutex_unlock(&tuxonice_in_use);
- return -EBUSY;
- }
- /*
- * Nosave page tracking.
- *
- * Here rather than in prepare_image because we want to do it once only at the
- * start of a cycle.
- */
- /**
- * mark_nosave_pages - set up our Nosave bitmap
- *
- * Build a bitmap of Nosave pages from the list. The bitmap allows faster
- * use when preparing the image.
- **/
- static void mark_nosave_pages(void)
- {
- struct nosave_region *region;
- list_for_each_entry(region, &nosave_regions, list) {
- unsigned long pfn;
- for (pfn = region->start_pfn; pfn < region->end_pfn; pfn++)
- if (pfn_valid(pfn))
- SetPageNosave(pfn_to_page(pfn));
- }
- }
- /**
- * allocate_bitmaps - allocate bitmaps used to record page states
- *
- * Allocate the bitmaps we use to record the various TuxOnIce related
- * page states.
- **/
- static int allocate_bitmaps(void)
- {
- if (toi_alloc_bitmap(&pageset1_map) ||
- toi_alloc_bitmap(&pageset1_copy_map) ||
- toi_alloc_bitmap(&pageset2_map) ||
- toi_alloc_bitmap(&io_map) ||
- toi_alloc_bitmap(&nosave_map) ||
- toi_alloc_bitmap(&free_map) ||
- toi_alloc_bitmap(&compare_map) ||
- toi_alloc_bitmap(&page_resave_map))
- return 1;
- return 0;
- }
- /**
- * free_bitmaps - free the bitmaps used to record page states
- *
- * Free the bitmaps allocated above. It is not an error to call
- * memory_bm_free on a bitmap that isn't currently allocated.
- **/
- static void free_bitmaps(void)
- {
- toi_free_bitmap(&pageset1_map);
- toi_free_bitmap(&pageset1_copy_map);
- toi_free_bitmap(&pageset2_map);
- toi_free_bitmap(&io_map);
- toi_free_bitmap(&nosave_map);
- toi_free_bitmap(&free_map);
- toi_free_bitmap(&compare_map);
- toi_free_bitmap(&page_resave_map);
- }
- /**
- * io_MB_per_second - return the number of MB/s read or written
- * @write: Whether to return the speed at which we wrote.
- *
- * Calculate the number of megabytes per second that were read or written.
- **/
- static int io_MB_per_second(int write)
- {
- return (toi_bkd.toi_io_time[write][1]) ?
- MB((unsigned long)toi_bkd.toi_io_time[write][0]) * HZ /
- toi_bkd.toi_io_time[write][1] : 0;
- }
- #define SNPRINTF(a...) ((len) += scnprintf(((char *) (buffer)) + (len), (count) - (len) - 1, ##a))
- /**
- * get_debug_info - fill a buffer with debugging information
- * @buffer: The buffer to be filled.
- * @count: The size of the buffer, in bytes.
- *
- * Fill a (usually PAGE_SIZEd) buffer with the debugging info that we will
- * either print log or return via sysfs.
- **/
- static int get_toi_debug_info(const char *buffer, int count)
- {
- int len = 0, i, first_result = 1;
- SNPRINTF("TuxOnIce debugging info:\n");
- SNPRINTF("- TuxOnIce core : " TOI_CORE_VERSION "\n");
- SNPRINTF("- Kernel Version : " UTS_RELEASE "\n");
- SNPRINTF("- Compiler vers. : %d.%d\n", __GNUC__, __GNUC_MINOR__);
- SNPRINTF("- Attempt number : %d\n", nr_hibernates);
- SNPRINTF("- Parameters : %ld %ld %ld %d %ld %ld\n",
- toi_result,
- toi_bkd.toi_action,
- toi_bkd.toi_debug_state,
- toi_bkd.toi_default_console_level,
- image_size_limit,
- toi_poweroff_method);
- SNPRINTF("- Overall expected compression percentage: %d.\n",
- 100 - toi_expected_compression_ratio());
- len += toi_print_module_debug_info(((char *)buffer) + len, count - len - 1);
- if (toi_bkd.toi_io_time[0][1]) {
- if ((io_MB_per_second(0) < 5) || (io_MB_per_second(1) < 5)) {
- SNPRINTF("- I/O speed: Write %ld KB/s",
- (KB((unsigned long)toi_bkd.toi_io_time[0][0]) * HZ /
- toi_bkd.toi_io_time[0][1]));
- if (toi_bkd.toi_io_time[1][1])
- SNPRINTF(", Read %ld KB/s", (KB((unsigned long)
- toi_bkd.toi_io_time[1][0]) * HZ /
- toi_bkd.toi_io_time[1][1]));
- } else {
- SNPRINTF("- I/O speed: Write %ld MB/s",
- (MB((unsigned long)toi_bkd.toi_io_time[0][0]) * HZ /
- toi_bkd.toi_io_time[0][1]));
- if (toi_bkd.toi_io_time[1][1])
- SNPRINTF(", Read %ld MB/s", (MB((unsigned long)
- toi_bkd.toi_io_time[1][0]) * HZ /
- toi_bkd.toi_io_time[1][1]));
- }
- SNPRINTF(".\n");
- } else
- SNPRINTF("- No I/O speed stats available.\n");
- SNPRINTF("- Extra pages : %lu used/%lu.\n",
- extra_pd1_pages_used, extra_pd1_pages_allowance);
- for (i = 0; i < min_t(int, TOI_NUM_RESULT_STATES, ARRAY_SIZE(result_strings)); i++)
- if (test_result_state(i)) {
- #ifdef CONFIG_TOI_FIXUP
- SNPRINTF("%s: %s.\n", first_result ?
- "- Result " : " ", result_strings[i]);
- #endif
- first_result = 0;
- }
- #ifdef CONFIG_TOI_FIXUP
- if (first_result)
- SNPRINTF("- Result : %s.\n", nr_hibernates ?
- "Succeeded" : "No hibernation attempts so far");
- #endif
- return len;
- }
- /**
- * do_cleanup - cleanup after attempting to hibernate or resume
- * @get_debug_info: Whether to allocate and return debugging info.
- *
- * Cleanup after attempting to hibernate or resume, possibly getting
- * debugging info as we do so.
- **/
- static void do_cleanup(int get_debug_info, int restarting)
- {
- int i = 0;
- char *buffer = NULL;
- trap_non_toi_io = 0;
- if (get_debug_info)
- toi_prepare_status(DONT_CLEAR_BAR, "Cleaning up...");
- #ifdef CONFIG_TOI_FIXUP
- #if !defined(HIB_TOI_DEBUG) /* turn off the verbose when debug off */
- hib_warn("Turn off debug info\n");
- get_debug_info = 0;
- #endif
- #endif
- #ifdef CONFIG_TOI_ENHANCE
- toi_actual_compression_ratio(); /* keep the actual compressed ratio for reference */
- #endif
- free_checksum_pages();
- if (get_debug_info)
- buffer = (char *)toi_get_zeroed_page(20, TOI_ATOMIC_GFP);
- if (buffer)
- i = get_toi_debug_info(buffer, PAGE_SIZE);
- toi_free_extra_pagedir_memory();
- pagedir1.size = 0;
- pagedir2.size = 0;
- set_highmem_size(pagedir1, 0);
- set_highmem_size(pagedir2, 0);
- if (boot_kernel_data_buffer) {
- if (!test_toi_state(TOI_BOOT_KERNEL))
- toi_free_page(37, boot_kernel_data_buffer);
- boot_kernel_data_buffer = 0;
- }
- if (test_toi_state(TOI_DEVICE_HOTPLUG_LOCKED)) {
- unlock_device_hotplug();
- clear_toi_state(TOI_DEVICE_HOTPLUG_LOCKED);
- }
- clear_toi_state(TOI_BOOT_KERNEL);
- if (current->flags & PF_SUSPEND_TASK)
- thaw_processes();
- if (!restarting)
- toi_stop_other_threads();
- if (test_action_state(TOI_KEEP_IMAGE) && !test_result_state(TOI_ABORTED)) {
- toi_message(TOI_ANY_SECTION, TOI_LOW, 1,
- "TuxOnIce: Not invalidating the image due to Keep Image being enabled.");
- set_result_state(TOI_KEPT_IMAGE);
- } else
- if (toiActiveAllocator)
- toiActiveAllocator->remove_image();
- free_bitmaps();
- usermodehelper_enable();
- if (test_toi_state(TOI_NOTIFIERS_PREPARE)) {
- pm_notifier_call_chain(PM_POST_HIBERNATION);
- clear_toi_state(TOI_NOTIFIERS_PREPARE);
- }
- if (buffer && i) {
- /* Printk can only handle 1023 bytes, including
- * its level mangling. */
- for (i = 0; i < 3; i++)
- pr_err("%s", buffer + (1023 * i));
- toi_free_page(20, (unsigned long)buffer);
- }
- if (!restarting)
- toi_cleanup_console();
- free_attention_list();
- if (!restarting)
- toi_deactivate_storage(0);
- clear_toi_state(TOI_IGNORE_LOGLEVEL);
- clear_toi_state(TOI_TRYING_TO_RESUME);
- clear_toi_state(TOI_NOW_RESUMING);
- }
- /**
- * check_still_keeping_image - we kept an image; check whether to reuse it.
- *
- * We enter this routine when we have kept an image. If the user has said they
- * want to still keep it, all we need to do is powerdown. If powering down
- * means hibernating to ram and the power doesn't run out, we'll return 1.
- * If we do power off properly or the battery runs out, we'll resume via the
- * normal paths.
- *
- * If the user has said they want to remove the previously kept image, we
- * remove it, and return 0. We'll then store a new image.
- **/
- static int check_still_keeping_image(void)
- {
- if (test_action_state(TOI_KEEP_IMAGE)) {
- pr_warn("Image already stored: powering down " "immediately.");
- do_toi_step(STEP_HIBERNATE_POWERDOWN);
- return 1; /* Just in case we're using S3 */
- }
- pr_warn("Invalidating previous image.\n");
- toiActiveAllocator->remove_image();
- return 0;
- }
- /**
- * toi_init - prepare to hibernate to disk
- *
- * Initialise variables & data structures, in preparation for
- * hibernating to disk.
- **/
- static int toi_init(int restarting)
- {
- int result, i, j;
- #ifdef CONFIG_TOI_FIXUP
- if (test_result_state(TOI_ABORTED))
- return 1;
- #endif
- toi_result = 0;
- pr_warn("Initiating a hibernation cycle.\n");
- nr_hibernates++;
- for (i = 0; i < 2; i++)
- for (j = 0; j < 2; j++)
- toi_bkd.toi_io_time[i][j] = 0;
- if (!test_toi_state(TOI_CAN_HIBERNATE) || allocate_bitmaps())
- return 1;
- mark_nosave_pages();
- if (!restarting)
- toi_prepare_console();
- result = pm_notifier_call_chain(PM_HIBERNATION_PREPARE);
- if (result) {
- set_result_state(TOI_NOTIFIERS_PREPARE_FAILED);
- return 1;
- }
- set_toi_state(TOI_NOTIFIERS_PREPARE);
- if (!restarting) {
- #ifdef CONFIG_TOI_FIXUP
- int num_threaded;
- num_threaded = toi_start_other_threads();
- pr_err("Starting other threads (%d).", num_threaded);
- #else
- pr_err("Starting other threads.");
- toi_start_other_threads();
- #endif
- }
- result = usermodehelper_disable();
- if (result) {
- pr_err("TuxOnIce: Failed to disable usermode " "helpers\n");
- set_result_state(TOI_USERMODE_HELPERS_ERR);
- return 1;
- }
- boot_kernel_data_buffer = toi_get_zeroed_page(37, TOI_ATOMIC_GFP);
- if (!boot_kernel_data_buffer) {
- pr_err("TuxOnIce: Failed to allocate " "boot_kernel_data_buffer.\n");
- set_result_state(TOI_OUT_OF_MEMORY);
- return 1;
- }
- return 0;
- }
- /**
- * can_hibernate - perform basic 'Can we hibernate?' tests
- *
- * Perform basic tests that must pass if we're going to be able to hibernate:
- * Can we get the pm_mutex? Is resume= valid (we need to know where to write
- * the image header).
- **/
- static int can_hibernate(void)
- {
- if (!test_toi_state(TOI_CAN_HIBERNATE))
- toi_attempt_to_parse_resume_device(0);
- if (!test_toi_state(TOI_CAN_HIBERNATE)) {
- pr_warn("TuxOnIce: Hibernation disabled. maybe no resume=swap:/dev/hda1 in lilo.conf or equivalent.");
- set_abort_result(TOI_CANT_SUSPEND);
- return 0;
- }
- if (strlen(alt_resume_param)) {
- attempt_to_parse_alt_resume_param();
- if (!strlen(alt_resume_param)) {
- pr_warn("Alternate resume parameter now " "invalid. Aborting.\n");
- set_abort_result(TOI_CANT_USE_ALT_RESUME);
- return 0;
- }
- }
- hib_log("passed!\n");
- return 1;
- }
- /**
- * do_post_image_write - having written an image, figure out what to do next
- *
- * After writing an image, we might load an alternate image or power down.
- * Powering down might involve hibernating to ram, in which case we also
- * need to handle reloading pageset2.
- **/
- static int do_post_image_write(void)
- {
- /* If switching images fails, do normal powerdown */
- if (alt_resume_param[0])
- do_toi_step(STEP_RESUME_ALT_IMAGE);
- toi_power_down();
- barrier();
- mb();
- return 0;
- }
- /**
- * __save_image - do the hard work of saving the image
- *
- * High level routine for getting the image saved. The key assumptions made
- * are that processes have been frozen and sufficient memory is available.
- *
- * We also exit through here at resume time, coming back from toi_hibernate
- * after the atomic restore. This is the reason for the toi_in_hibernate
- * test.
- **/
- static int __save_image(void)
- {
- int temp_result, did_copy = 0;
- toi_prepare_status(DONT_CLEAR_BAR, "Starting to save the image..");
- toi_message(TOI_ANY_SECTION, TOI_LOW, 1,
- " - Final values: %lu and %lu.", pagedir1.size, pagedir2.size);
- toi_cond_pause(1, "About to write pagedir2.");
- temp_result = write_pageset(&pagedir2);
- if (temp_result == -1 || test_result_state(TOI_ABORTED))
- return 1;
- toi_cond_pause(1, "About to copy pageset 1.");
- if (test_result_state(TOI_ABORTED))
- return 1;
- toi_deactivate_storage(1);
- toi_prepare_status(DONT_CLEAR_BAR, "Doing atomic copy/restore.");
- toi_in_hibernate = 1;
- if (toi_go_atomic(PMSG_FREEZE, 1))
- goto Failed;
- hib_log("calling toi_hibernate()\n");
- temp_result = toi_hibernate();
- #ifdef CONFIG_KGDB
- if (test_action_state(TOI_POST_RESUME_BREAKPOINT))
- kgdb_breakpoint();
- #endif
- if (!temp_result)
- did_copy = 1;
- hib_log("calling toi_end_atomic() toi_in_hibernate(%d) temp_result(%d)\n", toi_in_hibernate,
- temp_result);
- /* We return here at resume time too! */
- toi_end_atomic(ATOMIC_ALL_STEPS, toi_in_hibernate, temp_result);
- Failed:
- if (toi_activate_storage(1))
- panic("Failed to reactivate our storage.");
- /* Resume time? */
- if (!toi_in_hibernate) {
- hib_log("last resume here ...\n");
- copyback_post();
- return 0;
- }
- /* Nope. Hibernating. So, see if we can save the image... */
- if (temp_result || test_result_state(TOI_ABORTED)) {
- if (did_copy)
- goto abort_reloading_pagedir_two;
- else
- return 1;
- }
- hib_log("@line:%d\n", __LINE__);
- toi_update_status(pagedir2.size, pagedir1.size + pagedir2.size, NULL);
- if (test_result_state(TOI_ABORTED))
- goto abort_reloading_pagedir_two;
- toi_cond_pause(1, "About to write pageset1.");
- toi_message(TOI_ANY_SECTION, TOI_LOW, 1, "-- Writing pageset1");
- temp_result = write_pageset(&pagedir1);
- /* We didn't overwrite any memory, so no reread needs to be done. */
- if (test_action_state(TOI_TEST_FILTER_SPEED) || test_action_state(TOI_TEST_BIO))
- return 1;
- if (temp_result == 1 || test_result_state(TOI_ABORTED))
- goto abort_reloading_pagedir_two;
- toi_cond_pause(1, "About to write header.");
- if (test_result_state(TOI_ABORTED))
- goto abort_reloading_pagedir_two;
- temp_result = write_image_header();
- if (!temp_result && !test_result_state(TOI_ABORTED))
- return 0;
- abort_reloading_pagedir_two:
- temp_result = read_pageset2(1);
- /* If that failed, we're sunk. Panic! */
- if (temp_result)
- panic("Attempt to reload pagedir 2 while aborting " "a hibernate failed.");
- hib_log("passed!\n");
- return 1;
- }
- static void map_ps2_pages(int enable)
- {
- unsigned long pfn = 0;
- memory_bm_position_reset(pageset2_map);
- pfn = memory_bm_next_pfn(pageset2_map, 0);
- while (pfn != BM_END_OF_MAP) {
- struct page *page = pfn_to_page(pfn);
- kernel_map_pages(page, 1, enable);
- pfn = memory_bm_next_pfn(pageset2_map, 0);
- }
- }
- /**
- * do_save_image - save the image and handle the result
- *
- * Save the prepared image. If we fail or we're in the path returning
- * from the atomic restore, cleanup.
- **/
- static int do_save_image(void)
- {
- int result;
- map_ps2_pages(0);
- result = __save_image();
- map_ps2_pages(1);
- return result;
- }
- /**
- * do_prepare_image - try to prepare an image
- *
- * Seek to initialise and prepare an image to be saved. On failure,
- * cleanup.
- **/
- static int do_prepare_image(void)
- {
- int restarting = test_result_state(TOI_EXTRA_PAGES_ALLOW_TOO_SMALL);
- if (!restarting && toi_activate_storage(0))
- return 1;
- hib_log("step 1 @line:%d\n", __LINE__);
- /*
- * If kept image and still keeping image and hibernating to RAM, we will
- * return 1 after hibernating and resuming (provided the power doesn't
- * run out. In that case, we skip directly to cleaning up and exiting.
- */
- if (!can_hibernate() || (test_result_state(TOI_KEPT_IMAGE) && check_still_keeping_image()))
- return 1;
- hib_log("step 2 @line:%d\n", __LINE__);
- if (toi_init(restarting) || toi_prepare_image() || test_result_state(TOI_ABORTED))
- return 1;
- hib_log("step 3 @line:%d\n", __LINE__);
- trap_non_toi_io = 1;
- return 0;
- }
- /**
- * do_check_can_resume - find out whether an image has been stored
- *
- * Read whether an image exists. We use the same routine as the
- * image_exists sysfs entry, and just look to see whether the
- * first character in the resulting buffer is a '1'.
- **/
- int do_check_can_resume(void)
- {
- int result = -1;
- if (toi_activate_storage(0))
- return -1;
- if (!test_toi_state(TOI_RESUME_DEVICE_OK))
- toi_attempt_to_parse_resume_device(1);
- if (toiActiveAllocator)
- result = toiActiveAllocator->image_exists(1);
- toi_deactivate_storage(0);
- return result;
- }
- EXPORT_SYMBOL_GPL(do_check_can_resume);
- /**
- * do_load_atomic_copy - load the first part of an image, if it exists
- *
- * Check whether we have an image. If one exists, do sanity checking
- * (possibly invalidating the image or even rebooting if the user
- * requests that) before loading it into memory in preparation for the
- * atomic restore.
- *
- * If and only if we have an image loaded and ready to restore, we return 1.
- **/
- static int do_load_atomic_copy(void)
- {
- int read_image_result = 0;
- if (sizeof(swp_entry_t) != sizeof(long)) {
- pr_warn("TuxOnIce: The size of swp_entry_t != size of long. Please report this!");
- return 1;
- }
- if (!resume_file[0])
- pr_warn("TuxOnIce: use resume= command line parameter to tell TuxOnIce where to look for an image");
- toi_activate_storage(0);
- if (!(test_toi_state(TOI_RESUME_DEVICE_OK)) && !toi_attempt_to_parse_resume_device(0)) {
- /*
- * Without a usable storage device we can do nothing -
- * even if noresume is given
- */
- pr_err("TuxOnIce: No/wrong storage allocators : %d", toiNumAllocators);
- toi_deactivate_storage(0);
- return 1;
- }
- if (allocate_bitmaps())
- return 1;
- read_image_result = read_pageset1(); /* non fatal error ignored */
- if (test_toi_state(TOI_NORESUME_SPECIFIED))
- clear_toi_state(TOI_NORESUME_SPECIFIED);
- toi_deactivate_storage(0);
- if (read_image_result)
- return 1;
- return 0;
- }
- /**
- * prepare_restore_load_alt_image - save & restore alt image variables
- *
- * Save and restore the pageset1 maps, when loading an alternate image.
- **/
- static void prepare_restore_load_alt_image(int prepare)
- {
- static struct memory_bitmap *pageset1_map_save, *pageset1_copy_map_save;
- if (prepare) {
- pageset1_map_save = pageset1_map;
- pageset1_map = NULL;
- pageset1_copy_map_save = pageset1_copy_map;
- pageset1_copy_map = NULL;
- set_toi_state(TOI_LOADING_ALT_IMAGE);
- toi_reset_alt_image_pageset2_pfn();
- } else {
- toi_free_bitmap(&pageset1_map);
- pageset1_map = pageset1_map_save;
- toi_free_bitmap(&pageset1_copy_map);
- pageset1_copy_map = pageset1_copy_map_save;
- clear_toi_state(TOI_NOW_RESUMING);
- clear_toi_state(TOI_LOADING_ALT_IMAGE);
- }
- }
- /**
- * do_toi_step - perform a step in hibernating or resuming
- *
- * Perform a step in hibernating or resuming an image. This abstraction
- * is in preparation for implementing cluster support, and perhaps replacing
- * uswsusp too (haven't looked whether that's possible yet).
- **/
- int do_toi_step(int step)
- {
- switch (step) {
- case STEP_HIBERNATE_PREPARE_IMAGE:
- return do_prepare_image();
- case STEP_HIBERNATE_SAVE_IMAGE:
- return do_save_image();
- case STEP_HIBERNATE_POWERDOWN:
- return do_post_image_write();
- case STEP_RESUME_CAN_RESUME:
- return do_check_can_resume();
- case STEP_RESUME_LOAD_PS1:
- return do_load_atomic_copy();
- case STEP_RESUME_DO_RESTORE:
- /*
- * If we succeed, this doesn't return.
- * Instead, we return from do_save_image() in the
- * hibernated kernel.
- */
- return toi_atomic_restore();
- case STEP_RESUME_ALT_IMAGE:
- pr_warn("Trying to resume alternate image.\n");
- toi_in_hibernate = 0;
- save_restore_alt_param(SAVE, NOQUIET);
- prepare_restore_load_alt_image(1);
- if (!do_check_can_resume()) {
- pr_warn("Nothing to resume from.\n");
- goto out;
- }
- if (!do_load_atomic_copy())
- toi_atomic_restore();
- pr_warn("Failed to load image.\n");
- out:
- prepare_restore_load_alt_image(0);
- save_restore_alt_param(RESTORE, NOQUIET);
- break;
- case STEP_CLEANUP:
- do_cleanup(1, 0);
- break;
- case STEP_QUIET_CLEANUP:
- do_cleanup(0, 0);
- break;
- }
- return 0;
- }
- EXPORT_SYMBOL_GPL(do_toi_step);
- /* -- Functions for kickstarting a hibernate or resume --- */
- /**
- * toi_try_resume - try to do the steps in resuming
- *
- * Check if we have an image and if so try to resume. Clear the status
- * flags too.
- **/
- void toi_try_resume(void)
- {
- #ifdef CONFIG_TOI_FIXUP
- int num_threaded;
- #endif
- hib_log("entering...\n");
- set_toi_state(TOI_TRYING_TO_RESUME);
- resume_attempted = 1;
- current->flags |= PF_MEMALLOC;
- #ifdef CONFIG_TOI_FIXUP
- get_online_cpus(); /* to protect against hotplug interference */
- num_threaded = toi_start_other_threads();
- pr_err("[resume] Starting other threads (%d).", num_threaded);
- if (do_toi_step(STEP_RESUME_CAN_RESUME) && !do_toi_step(STEP_RESUME_LOAD_PS1)) {
- put_online_cpus(); /* to protect against hotplug interference */
- do_toi_step(STEP_RESUME_DO_RESTORE);
- } else {
- put_online_cpus(); /* to protect against hotplug interference */
- }
- #else
- toi_start_other_threads();
- if (do_toi_step(STEP_RESUME_CAN_RESUME) && !do_toi_step(STEP_RESUME_LOAD_PS1))
- do_toi_step(STEP_RESUME_DO_RESTORE);
- #endif
- toi_stop_other_threads();
- do_cleanup(0, 0);
- current->flags &= ~PF_MEMALLOC;
- clear_toi_state(TOI_IGNORE_LOGLEVEL);
- clear_toi_state(TOI_TRYING_TO_RESUME);
- clear_toi_state(TOI_NOW_RESUMING);
- }
- /**
- * toi_sys_power_disk_try_resume - wrapper calling toi_try_resume
- *
- * Wrapper for when __toi_try_resume is called from swsusp resume path,
- * rather than from echo > /sys/power/tuxonice/do_resume.
- **/
- static void toi_sys_power_disk_try_resume(void)
- {
- resume_attempted = 1;
- hib_log("step 1 @line:%d\n", __LINE__);
- /*
- * There's a comment in kernel/power/disk.c that indicates
- * we should be able to use mutex_lock_nested below. That
- * doesn't seem to cut it, though, so let's just turn lockdep
- * off for now.
- */
- lockdep_off();
- if (toi_start_anything(SYSFS_RESUMING))
- goto out;
- toi_try_resume();
- /*
- * For initramfs, we have to clear the boot time
- * flag after trying to resume
- */
- clear_toi_state(TOI_BOOT_TIME);
- toi_finish_anything(SYSFS_RESUMING);
- out:
- lockdep_on();
- }
- /**
- * toi_try_hibernate - try to start a hibernation cycle
- *
- * Start a hibernation cycle, coming in from either
- * echo > /sys/power/tuxonice/do_suspend
- *
- * or
- *
- * echo disk > /sys/power/state
- *
- * In the later case, we come in without pm_sem taken; in the
- * former, it has been taken.
- **/
- int toi_try_hibernate(void)
- {
- int result = 0, sys_power_disk = 0, retries = 0;
- if (!mutex_is_locked(&tuxonice_in_use)) {
- /* Came in via /sys/power/disk */
- if (toi_start_anything(SYSFS_HIBERNATING))
- return -EBUSY;
- sys_power_disk = 1;
- }
- current->flags |= PF_MEMALLOC;
- if (test_toi_state(TOI_CLUSTER_MODE)) {
- toi_initiate_cluster_hibernate();
- goto out;
- }
- prepare:
- result = do_toi_step(STEP_HIBERNATE_PREPARE_IMAGE);
- hib_log("after calling do_toi_step(STEP_HIBERNATE_PREPARE_IMAGE), result(%d)\n", result);
- if (result)
- goto out;
- if (test_action_state(TOI_FREEZER_TEST))
- goto out_restore_gfp_mask;
- result = do_toi_step(STEP_HIBERNATE_SAVE_IMAGE);
- if (test_result_state(TOI_EXTRA_PAGES_ALLOW_TOO_SMALL)) {
- if (retries < 2) {
- hib_log("failed and calling do_cleanup(0, 1)\n");
- do_cleanup(0, 1);
- retries++;
- clear_result_state(TOI_ABORTED);
- extra_pd1_pages_allowance = extra_pd1_pages_used + 500;
- pr_warn("Automatically adjusting the extra pages allowance to %ld and restarting",
- extra_pd1_pages_allowance);
- pm_restore_gfp_mask();
- goto prepare;
- }
- pr_warn("Adjusted extra pages allowance twice and still couldn't hibernate successfully. Giving up.");
- }
- /* This code runs at resume time too! */
- if (!result && toi_in_hibernate)
- result = do_toi_step(STEP_HIBERNATE_POWERDOWN);
- out_restore_gfp_mask:
- pm_restore_gfp_mask();
- out:
- do_cleanup(1, 0);
- current->flags &= ~PF_MEMALLOC;
- if (sys_power_disk)
- toi_finish_anything(SYSFS_HIBERNATING);
- return result;
- }
- /*
- * channel_no: If !0, -c <channel_no> is added to args (userui).
- */
- int toi_launch_userspace_program(char *command, int channel_no, int wait, int debug)
- {
- int retval;
- static char *envp[4] = {
- "HOME=/",
- "TERM=linux",
- "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
- NULL
- };
- char *argv[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
- };
- char *channel = NULL;
- int arg = 0, size;
- char test_read[255];
- char *orig_posn = command;
- if (!strlen(orig_posn))
- return 1;
- if (channel_no) {
- channel = toi_kzalloc(4, 6, GFP_KERNEL);
- if (!channel) {
- pr_warn("Failed to allocate memory in preparing to launch userspace program.");
- return 1;
- }
- }
- /* Up to 6 args supported */
- while (arg < 6) {
- if (sscanf(orig_posn, "%254s", test_read) != 1) {
- pr_warn("Failed to sscanf from userspace program command");
- break;
- }
- size = strlen(test_read);
- if (!(size))
- break;
- argv[arg] = toi_kzalloc(5, size + 1, TOI_ATOMIC_GFP);
- strcpy(argv[arg], test_read);
- orig_posn += size + 1;
- *test_read = 0;
- arg++;
- }
- if (channel_no) {
- sprintf(channel, "-c%d", channel_no);
- argv[arg] = channel;
- } else
- arg--;
- if (debug) {
- argv[++arg] = toi_kzalloc(5, 8, TOI_ATOMIC_GFP);
- strcpy(argv[arg], "--debug");
- }
- retval = call_usermodehelper(argv[0], argv, envp, wait);
- /*
- * If the program reports an error, retval = 256. Don't complain
- * about that here.
- */
- if (retval && retval != 256)
- pr_err("Failed to launch userspace program '%s': Error %d\n", command, retval);
- {
- int i;
- for (i = 0; i < arg; i++)
- if (argv[i] && argv[i] != channel)
- toi_kfree(5, argv[i], sizeof(*argv[i]));
- }
- toi_kfree(4, channel, sizeof(*channel));
- return retval;
- }
- #ifdef CONFIG_TOI_ENHANCE
- int toi_abort_hibernate(void)
- {
- if (test_result_state(TOI_ABORTED))
- return 0;
- set_result_state(TOI_ABORTED);
- return 0;
- }
- EXPORT_SYMBOL_GPL(toi_abort_hibernate);
- int toi_hibernate_fatalerror(void)
- {
- if (test_result_state(TOI_ARCH_PREPARE_FAILED))
- return 1;
- else
- return 0;
- }
- EXPORT_SYMBOL_GPL(toi_hibernate_fatalerror);
- #endif
- /*
- * This array contains entries that are automatically registered at
- * boot. Modules and the console code register their own entries separately.
- */
- static struct toi_sysfs_data sysfs_params[] = {
- SYSFS_LONG("extra_pages_allowance", SYSFS_RW,
- &extra_pd1_pages_allowance, 0, LONG_MAX, 0),
- SYSFS_CUSTOM("image_exists", SYSFS_RW, image_exists_read,
- image_exists_write, SYSFS_NEEDS_SM_FOR_BOTH, NULL),
- SYSFS_STRING("resume", SYSFS_RW, resume_file, 255,
- SYSFS_NEEDS_SM_FOR_WRITE,
- attempt_to_parse_resume_device2),
- SYSFS_STRING("alt_resume_param", SYSFS_RW, alt_resume_param, 255,
- SYSFS_NEEDS_SM_FOR_WRITE,
- attempt_to_parse_alt_resume_param),
- SYSFS_CUSTOM("debug_info", SYSFS_READONLY, get_toi_debug_info, NULL, 0,
- NULL),
- SYSFS_BIT("ignore_rootfs", SYSFS_RW, &toi_bkd.toi_action,
- TOI_IGNORE_ROOTFS, 0),
- SYSFS_LONG("image_size_limit", SYSFS_RW, &image_size_limit, -2,
- INT_MAX, 0),
- SYSFS_UL("last_result", SYSFS_RW, &toi_result, 0, 0, 0),
- SYSFS_BIT("no_multithreaded_io", SYSFS_RW, &toi_bkd.toi_action,
- TOI_NO_MULTITHREADED_IO, 0),
- SYSFS_BIT("no_flusher_thread", SYSFS_RW, &toi_bkd.toi_action,
- TOI_NO_FLUSHER_THREAD, 0),
- SYSFS_BIT("full_pageset2", SYSFS_RW, &toi_bkd.toi_action,
- TOI_PAGESET2_FULL, 0),
- SYSFS_BIT("reboot", SYSFS_RW, &toi_bkd.toi_action, TOI_REBOOT, 0),
- SYSFS_BIT("replace_swsusp", SYSFS_RW, &toi_bkd.toi_action,
- TOI_REPLACE_SWSUSP, 0),
- SYSFS_STRING("resume_commandline", SYSFS_RW,
- toi_bkd.toi_nosave_commandline, COMMAND_LINE_SIZE, 0,
- NULL),
- SYSFS_STRING("version", SYSFS_READONLY, TOI_CORE_VERSION, 0, 0, NULL),
- SYSFS_BIT("freezer_test", SYSFS_RW, &toi_bkd.toi_action,
- TOI_FREEZER_TEST, 0),
- SYSFS_BIT("test_bio", SYSFS_RW, &toi_bkd.toi_action, TOI_TEST_BIO, 0),
- SYSFS_BIT("test_filter_speed", SYSFS_RW, &toi_bkd.toi_action,
- TOI_TEST_FILTER_SPEED, 0),
- SYSFS_BIT("no_pageset2", SYSFS_RW, &toi_bkd.toi_action,
- TOI_NO_PAGESET2, 0),
- SYSFS_BIT("no_pageset2_if_unneeded", SYSFS_RW, &toi_bkd.toi_action,
- TOI_NO_PS2_IF_UNNEEDED, 0),
- SYSFS_STRING("binary_signature", SYSFS_READONLY,
- tuxonice_signature, 9, 0, NULL),
- SYSFS_INT("max_workers", SYSFS_RW, &toi_max_workers, 0, nr_cpumask_bits, 0,
- NULL),
- #ifdef CONFIG_KGDB
- SYSFS_BIT("post_resume_breakpoint", SYSFS_RW, &toi_bkd.toi_action,
- TOI_POST_RESUME_BREAKPOINT, 0),
- #endif
- SYSFS_BIT("no_readahead", SYSFS_RW, &toi_bkd.toi_action,
- TOI_NO_READAHEAD, 0),
- SYSFS_BIT("trace_debug_on", SYSFS_RW, &toi_bkd.toi_action,
- TOI_TRACE_DEBUG_ON, 0),
- #ifdef CONFIG_TOI_KEEP_IMAGE
- SYSFS_BIT("keep_image", SYSFS_RW, &toi_bkd.toi_action, TOI_KEEP_IMAGE,
- 0),
- #endif
- };
- static struct toi_core_fns my_fns = {
- .get_nonconflicting_page = __toi_get_nonconflicting_page,
- .post_context_save = __toi_post_context_save,
- .try_hibernate = toi_try_hibernate,
- .try_resume = toi_sys_power_disk_try_resume,
- };
- /**
- * core_load - initialisation of TuxOnIce core
- *
- * Initialise the core, beginning with sysfs. Checksum and so on are part of
- * the core, but have their own initialisation routines because they either
- * aren't compiled in all the time or have their own subdirectories.
- **/
- static __init int core_load(void)
- {
- int i, numfiles = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data);
- pr_warn("TuxOnIce " TOI_CORE_VERSION " (http://tuxonice.net)\n");
- if (!hibernation_available()) {
- pr_warn("TuxOnIce disabled due to request for hibernation to be disabled in this kernel");
- return 1;
- }
- if (toi_sysfs_init())
- return 1;
- for (i = 0; i < numfiles; i++)
- toi_register_sysfs_file(tuxonice_kobj, &sysfs_params[i]);
- toi_core_fns = &my_fns;
- if (toi_alloc_init())
- return 1;
- if (toi_checksum_init())
- return 1;
- if (toi_usm_init())
- return 1;
- if (toi_ui_init())
- return 1;
- if (toi_poweroff_init())
- return 1;
- if (toi_cluster_init())
- return 1;
- return 0;
- }
- #ifdef MODULE
- /**
- * core_unload: Prepare to unload the core code.
- **/
- static __exit void core_unload(void)
- {
- int i, numfiles = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data);
- toi_alloc_exit();
- toi_checksum_exit();
- toi_poweroff_exit();
- toi_ui_exit();
- toi_usm_exit();
- toi_cluster_exit();
- for (i = 0; i < numfiles; i++)
- toi_unregister_sysfs_file(tuxonice_kobj, &sysfs_params[i]);
- toi_core_fns = NULL;
- toi_sysfs_exit();
- }
- MODULE_LICENSE("GPL");
- module_init(core_load);
- module_exit(core_unload);
- #else
- late_initcall(core_load);
- #endif
|