/* * kernel/power/tuxonice_atomic_copy.c * * Copyright 2004-2015 Nigel Cunningham (nigel at nigelcunningham com au) * * Distributed under GPLv2. * * Routines for doing the atomic save/restore. */ #include #include #include #include #include #include #include #include #include "tuxonice.h" #include "tuxonice_storage.h" #include "tuxonice_power_off.h" #include "tuxonice_ui.h" #include "tuxonice_io.h" #include "tuxonice_prepare_image.h" #include "tuxonice_pageflags.h" #include "tuxonice_checksum.h" #include "tuxonice_builtin.h" #include "tuxonice_atomic_copy.h" #include "tuxonice_alloc.h" #include "tuxonice_modules.h" unsigned long extra_pd1_pages_used; /** * free_pbe_list - free page backup entries used by the atomic copy code. * @list: List to free. * @highmem: Whether the list is in highmem. * * Normally, this function isn't used. If, however, we need to abort before * doing the atomic copy, we use this to free the pbes previously allocated. **/ static void free_pbe_list(struct pbe **list, int highmem) { while (*list) { int i; struct pbe *free_pbe, *next_page = NULL; struct page *page; if (highmem) { page = (struct page *) *list; free_pbe = (struct pbe *) kmap(page); } else { page = virt_to_page(*list); free_pbe = *list; } for (i = 0; i < PBES_PER_PAGE; i++) { if (!free_pbe) break; if (highmem) toi__free_page(29, free_pbe->address); else toi_free_page(29, (unsigned long) free_pbe->address); free_pbe = free_pbe->next; } if (highmem) { if (free_pbe) next_page = free_pbe; kunmap(page); } else { if (free_pbe) next_page = free_pbe; } toi__free_page(29, page); *list = (struct pbe *) next_page; }; } /** * copyback_post - post atomic-restore actions * * After doing the atomic restore, we have a few more things to do: * 1) We want to retain some values across the restore, so we now copy * these from the nosave variables to the normal ones. * 2) Set the status flags. * 3) Resume devices. * 4) Tell userui so it can redraw & restore settings. * 5) Reread the page cache. **/ void copyback_post(void) { struct toi_boot_kernel_data *bkd = (struct toi_boot_kernel_data *) boot_kernel_data_buffer; if (toi_activate_storage(1)) panic("Failed to reactivate our storage."); toi_post_atomic_restore_modules(bkd); toi_cond_pause(1, "About to reload secondary pagedir."); if (read_pageset2(0)) panic("Unable to successfully reread the page cache."); /* * If the user wants to sleep again after resuming from full-off, * it's most likely to be in order to suspend to ram, so we'll * do this check after loading pageset2, to give them the fastest * wakeup when they are ready to use the computer again. */ toi_check_resleep(); if (test_action_state(TOI_INCREMENTAL_IMAGE)) toi_reset_dirtiness(1); } /** * toi_copy_pageset1 - do the atomic copy of pageset1 * * 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 old Duron, with * 3DNOW, kernel_fpu_begin increments preempt count, making our preempt * count at resume time 4 instead of 3. * * We don't want to call kmap_atomic unconditionally because it has the side * effect of incrementing the preempt count, which will leave it one too high * post resume (the page containing the preempt count will be copied after * its incremented. This is essentially the same problem. **/ void toi_copy_pageset1(void) { int i; unsigned long source_index, dest_index; memory_bm_position_reset(pageset1_map); memory_bm_position_reset(pageset1_copy_map); source_index = memory_bm_next_pfn(pageset1_map, 0); dest_index = memory_bm_next_pfn(pageset1_copy_map, 0); for (i = 0; i < pagedir1.size; i++) { unsigned long *origvirt, *copyvirt; struct page *origpage, *copypage; int loop = (PAGE_SIZE / sizeof(unsigned long)) - 1, was_present1, was_present2; origpage = pfn_to_page(source_index); copypage = pfn_to_page(dest_index); origvirt = PageHighMem(origpage) ? kmap_atomic(origpage) : page_address(origpage); copyvirt = PageHighMem(copypage) ? kmap_atomic(copypage) : page_address(copypage); was_present1 = kernel_page_present(origpage); if (!was_present1) kernel_map_pages(origpage, 1, 1); was_present2 = kernel_page_present(copypage); if (!was_present2) kernel_map_pages(copypage, 1, 1); while (loop >= 0) { *(copyvirt + loop) = *(origvirt + loop); loop--; } if (!was_present1) kernel_map_pages(origpage, 1, 0); if (!was_present2) kernel_map_pages(copypage, 1, 0); if (PageHighMem(origpage)) kunmap_atomic(origvirt); if (PageHighMem(copypage)) kunmap_atomic(copyvirt); source_index = memory_bm_next_pfn(pageset1_map, 0); dest_index = memory_bm_next_pfn(pageset1_copy_map, 0); } } /** * __toi_post_context_save - steps after saving the cpu context * * Steps taken after saving the CPU state to make the actual * atomic copy. * * Called from swsusp_save in snapshot.c via toi_post_context_save. **/ int __toi_post_context_save(void) { unsigned long old_ps1_size = pagedir1.size; check_checksums(); free_checksum_pages(); toi_recalculate_image_contents(1); extra_pd1_pages_used = pagedir1.size > old_ps1_size ? pagedir1.size - old_ps1_size : 0; if (extra_pd1_pages_used > extra_pd1_pages_allowance) { printk(KERN_INFO "Pageset1 has grown by %lu pages. " "extra_pages_allowance is currently only %lu.\n", pagedir1.size - old_ps1_size, extra_pd1_pages_allowance); /* * Highlevel code will see this, clear the state and * retry if we haven't already done so twice. */ if (any_to_free(1)) { set_abort_result(TOI_EXTRA_PAGES_ALLOW_TOO_SMALL); return 1; } if (try_allocate_extra_memory()) { printk(KERN_INFO "Failed to allocate the extra memory" " needed. Restarting the process."); set_abort_result(TOI_EXTRA_PAGES_ALLOW_TOO_SMALL); return 1; } printk(KERN_INFO "However it looks like there's enough" " free ram and storage to handle this, so " " continuing anyway."); /* * What if try_allocate_extra_memory above calls * toi_allocate_extra_pagedir_memory and it allocs a new * slab page via toi_kzalloc which should be in ps1? So... */ toi_recalculate_image_contents(1); } if (!test_action_state(TOI_TEST_FILTER_SPEED) && !test_action_state(TOI_TEST_BIO)) toi_copy_pageset1(); return 0; } /** * toi_hibernate - high level code for doing the atomic copy * * High-level code which prepares to do the atomic copy. Loosely based * on the swsusp version, but with the following twists: * - We set toi_running so the swsusp code uses our code paths. * - We give better feedback regarding what goes wrong if there is a * problem. * - We use an extra function to call the assembly, just in case this code * is in a module (return address). **/ int toi_hibernate(void) { int error; error = toi_lowlevel_builtin(); if (!error) { struct toi_boot_kernel_data *bkd = (struct toi_boot_kernel_data *) boot_kernel_data_buffer; /* * The boot kernel's data may be larger (newer version) or * smaller (older version) than ours. Copy the minimum * of the two sizes, so that we don't overwrite valid values * from pre-atomic copy. */ memcpy(&toi_bkd, (char *) boot_kernel_data_buffer, min_t(int, sizeof(struct toi_boot_kernel_data), bkd->size)); } return error; } /** * toi_atomic_restore - prepare to do the atomic restore * * Get ready to do the atomic restore. This part gets us into the same * state we are in prior to do calling do_toi_lowlevel while * hibernating: hot-unplugging secondary cpus and freeze processes, * before starting the thread that will do the restore. **/ int toi_atomic_restore(void) { int error; toi_prepare_status(DONT_CLEAR_BAR, "Atomic restore."); memcpy(&toi_bkd.toi_nosave_commandline, saved_command_line, strlen(saved_command_line)); toi_pre_atomic_restore_modules(&toi_bkd); if (add_boot_kernel_data_pbe()) goto Failed; toi_prepare_status(DONT_CLEAR_BAR, "Doing atomic copy/restore."); if (toi_go_atomic(PMSG_QUIESCE, 0)) goto Failed; /* We'll ignore saved state, but this gets preempt count (etc) right */ save_processor_state(); error = swsusp_arch_resume(); /* * Code below is only ever reached in case of failure. Otherwise * execution continues at place where swsusp_arch_suspend was called. * * We don't know whether it's safe to continue (this shouldn't happen), * so lets err on the side of caution. */ BUG(); Failed: free_pbe_list(&restore_pblist, 0); #ifdef CONFIG_HIGHMEM free_pbe_list(&restore_highmem_pblist, 1); #endif return 1; } /** * toi_go_atomic - do the actual atomic copy/restore * @state: The state to use for dpm_suspend_start & power_down calls. * @suspend_time: Whether we're suspending or resuming. **/ int toi_go_atomic(pm_message_t state, int suspend_time) { if (suspend_time) { if (platform_begin(1)) { set_abort_result(TOI_PLATFORM_PREP_FAILED); toi_end_atomic(ATOMIC_STEP_PLATFORM_END, suspend_time, 3); return 1; } if (dpm_prepare(PMSG_FREEZE)) { set_abort_result(TOI_DPM_PREPARE_FAILED); dpm_complete(PMSG_RECOVER); toi_end_atomic(ATOMIC_STEP_PLATFORM_END, suspend_time, 3); return 1; } } suspend_console(); pm_restrict_gfp_mask(); if (suspend_time) { if (dpm_suspend(state)) { set_abort_result(TOI_DPM_SUSPEND_FAILED); toi_end_atomic(ATOMIC_STEP_DEVICE_RESUME, suspend_time, 3); return 1; } } else { if (dpm_suspend_start(state)) { set_abort_result(TOI_DPM_SUSPEND_FAILED); toi_end_atomic(ATOMIC_STEP_DEVICE_RESUME, suspend_time, 3); return 1; } } /* At this point, dpm_suspend_start() has been called, but *not* * dpm_suspend_noirq(). We *must* dpm_suspend_noirq() now. * Otherwise, drivers for some devices (e.g. interrupt controllers) * become desynchronized with the actual state of the hardware * at resume time, and evil weirdness ensues. */ if (dpm_suspend_end(state)) { set_abort_result(TOI_DEVICE_REFUSED); toi_end_atomic(ATOMIC_STEP_DEVICE_RESUME, suspend_time, 1); return 1; } if (suspend_time) { if (platform_pre_snapshot(1)) set_abort_result(TOI_PRE_SNAPSHOT_FAILED); } else { if (platform_pre_restore(1)) set_abort_result(TOI_PRE_RESTORE_FAILED); } if (test_result_state(TOI_ABORTED)) { toi_end_atomic(ATOMIC_STEP_PLATFORM_FINISH, suspend_time, 1); return 1; } if (disable_nonboot_cpus()) { set_abort_result(TOI_CPU_HOTPLUG_FAILED); toi_end_atomic(ATOMIC_STEP_CPU_HOTPLUG, suspend_time, 1); return 1; } local_irq_disable(); if (syscore_suspend()) { set_abort_result(TOI_SYSCORE_REFUSED); toi_end_atomic(ATOMIC_STEP_IRQS, suspend_time, 1); return 1; } if (suspend_time && pm_wakeup_pending()) { set_abort_result(TOI_WAKEUP_EVENT); toi_end_atomic(ATOMIC_STEP_SYSCORE_RESUME, suspend_time, 1); return 1; } return 0; } /** * toi_end_atomic - post atomic copy/restore routines * @stage: What step to start at. * @suspend_time: Whether we're suspending or resuming. * @error: Whether we're recovering from an error. **/ void toi_end_atomic(int stage, int suspend_time, int error) { pm_message_t msg = suspend_time ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE; switch (stage) { case ATOMIC_ALL_STEPS: if (!suspend_time) { events_check_enabled = false; } platform_leave(1); case ATOMIC_STEP_SYSCORE_RESUME: syscore_resume(); case ATOMIC_STEP_IRQS: local_irq_enable(); case ATOMIC_STEP_CPU_HOTPLUG: enable_nonboot_cpus(); case ATOMIC_STEP_PLATFORM_FINISH: if (!suspend_time && error & 2) platform_restore_cleanup(1); else platform_finish(1); dpm_resume_start(msg); case ATOMIC_STEP_DEVICE_RESUME: if (suspend_time && (error & 2)) platform_recover(1); dpm_resume(msg); if (!toi_in_suspend()) { dpm_resume_end(PMSG_RECOVER); } if (error || !toi_in_suspend()) { pm_restore_gfp_mask(); } resume_console(); case ATOMIC_STEP_DPM_COMPLETE: dpm_complete(msg); case ATOMIC_STEP_PLATFORM_END: platform_end(1); toi_prepare_status(DONT_CLEAR_BAR, "Post atomic."); } }