1524 lines · 34.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * kernel/power/hibernate.c - Hibernation (a.k.a suspend-to-disk) support. * * Copyright (c) 2003 Patrick Mochel * Copyright (c) 2003 Open Source Development Lab * Copyright (c) 2004 Pavel Machek <pavel@ucw.cz> * Copyright (c) 2009 Rafael J. Wysocki, Novell Inc. * Copyright (C) 2012 Bojan Smojver <bojan@rexursive.com> */ #define pr_fmt(fmt) "PM: hibernation: " fmt #include <crypto/acompress.h>#include <linux/blkdev.h>#include <linux/export.h>#include <linux/suspend.h>#include <linux/reboot.h>#include <linux/string.h>#include <linux/device.h>#include <linux/async.h>#include <linux/delay.h>#include <linux/fs.h>#include <linux/mount.h>#include <linux/pm.h>#include <linux/nmi.h>#include <linux/console.h>#include <linux/cpu.h>#include <linux/freezer.h>#include <linux/gfp.h>#include <linux/syscore_ops.h>#include <linux/ctype.h>#include <linux/ktime.h>#include <linux/security.h>#include <linux/secretmem.h>#include <trace/events/power.h> #include "power.h"  static int nocompress;static int noresume;static int nohibernate;static int resume_wait;static unsigned int resume_delay;static char resume_file[256] = CONFIG_PM_STD_PARTITION;dev_t swsusp_resume_device;sector_t swsusp_resume_block;__visible int in_suspend __nosavedata; static char hibernate_compressor[CRYPTO_MAX_ALG_NAME] = CONFIG_HIBERNATION_DEF_COMP; /* * Compression/decompression algorithm to be used while saving/loading * image to/from disk. This would later be used in 'kernel/power/swap.c' * to allocate comp streams. */char hib_comp_algo[CRYPTO_MAX_ALG_NAME]; enum {	HIBERNATION_INVALID,	HIBERNATION_PLATFORM,	HIBERNATION_SHUTDOWN,	HIBERNATION_REBOOT,#ifdef CONFIG_SUSPEND	HIBERNATION_SUSPEND,#endif	HIBERNATION_TEST_RESUME,	/* keep last */	__HIBERNATION_AFTER_LAST};#define HIBERNATION_MAX (__HIBERNATION_AFTER_LAST-1)#define HIBERNATION_FIRST (HIBERNATION_INVALID + 1) static int hibernation_mode = HIBERNATION_SHUTDOWN; bool freezer_test_done; static const struct platform_hibernation_ops *hibernation_ops; static atomic_t hibernate_atomic = ATOMIC_INIT(1); #ifdef CONFIG_SUSPEND/** * pm_hibernation_mode_is_suspend - Check if hibernation has been set to suspend */bool pm_hibernation_mode_is_suspend(void){	return hibernation_mode == HIBERNATION_SUSPEND;}EXPORT_SYMBOL_GPL(pm_hibernation_mode_is_suspend);#endif bool hibernate_acquire(void){	return atomic_add_unless(&hibernate_atomic, -1, 0);} void hibernate_release(void){	atomic_inc(&hibernate_atomic);} bool hibernation_in_progress(void){	return !atomic_read(&hibernate_atomic);} bool hibernation_available(void){	return nohibernate == 0 &&		!security_locked_down(LOCKDOWN_HIBERNATION) &&		!secretmem_active() && !cxl_mem_active();} /** * hibernation_set_ops - Set the global hibernate operations. * @ops: Hibernation operations to use in subsequent hibernation transitions. */void hibernation_set_ops(const struct platform_hibernation_ops *ops){	unsigned int sleep_flags; 	if (ops && !(ops->begin && ops->end &&  ops->pre_snapshot	    && ops->prepare && ops->finish && ops->enter && ops->pre_restore	    && ops->restore_cleanup && ops->leave)) {		WARN_ON(1);		return;	} 	sleep_flags = lock_system_sleep(); 	hibernation_ops = ops;	if (ops)		hibernation_mode = HIBERNATION_PLATFORM;	else if (hibernation_mode == HIBERNATION_PLATFORM)		hibernation_mode = HIBERNATION_SHUTDOWN; 	unlock_system_sleep(sleep_flags);}EXPORT_SYMBOL_GPL(hibernation_set_ops); static bool entering_platform_hibernation; bool system_entering_hibernation(void){	return entering_platform_hibernation;}EXPORT_SYMBOL(system_entering_hibernation); #ifdef CONFIG_PM_DEBUGstatic unsigned int pm_test_delay = 5;module_param(pm_test_delay, uint, 0644);MODULE_PARM_DESC(pm_test_delay,		 "Number of seconds to wait before resuming from hibernation test");static void hibernation_debug_sleep(void){	pr_info("hibernation debug: Waiting for %d second(s).\n",		pm_test_delay);	mdelay(pm_test_delay * 1000);} static int hibernation_test(int level){	if (pm_test_level == level) {		hibernation_debug_sleep();		return 1;	}	return 0;}#else /* !CONFIG_PM_DEBUG */static int hibernation_test(int level) { return 0; }#endif /* !CONFIG_PM_DEBUG */ /** * platform_begin - Call platform to start hibernation. * @platform_mode: Whether or not to use the platform driver. */static int platform_begin(int platform_mode){	return (platform_mode && hibernation_ops) ?		hibernation_ops->begin(PMSG_FREEZE) : 0;} /** * platform_end - Call platform to finish transition to the working state. * @platform_mode: Whether or not to use the platform driver. */static void platform_end(int platform_mode){	if (platform_mode && hibernation_ops)		hibernation_ops->end();} /** * platform_pre_snapshot - Call platform to prepare the machine for hibernation. * @platform_mode: Whether or not to use the platform driver. * * Use the platform driver to prepare the system for creating a hibernate image, * if so configured, and return an error code if that fails. */ static int platform_pre_snapshot(int platform_mode){	return (platform_mode && hibernation_ops) ?		hibernation_ops->pre_snapshot() : 0;} /** * platform_leave - Call platform to prepare a transition to the working state. * @platform_mode: Whether or not to use the platform driver. * * Use the platform driver prepare to prepare the machine for switching to the * normal mode of operation. * * This routine is called on one CPU with interrupts disabled. */static void platform_leave(int platform_mode){	if (platform_mode && hibernation_ops)		hibernation_ops->leave();} /** * platform_finish - Call platform to switch the system to the working state. * @platform_mode: Whether or not to use the platform driver. * * Use the platform driver to switch the machine to the normal mode of * operation. * * This routine must be called after platform_prepare(). */static void platform_finish(int platform_mode){	if (platform_mode && hibernation_ops)		hibernation_ops->finish();} /** * platform_pre_restore - Prepare for hibernate image restoration. * @platform_mode: Whether or not to use the platform driver. * * Use the platform driver to prepare the system for resume from a hibernation * image. * * If the restore fails after this function has been called, * platform_restore_cleanup() must be called. */static int platform_pre_restore(int platform_mode){	return (platform_mode && hibernation_ops) ?		hibernation_ops->pre_restore() : 0;} /** * platform_restore_cleanup - Switch to the working state after failing restore. * @platform_mode: Whether or not to use the platform driver. * * Use the platform driver to switch the system to the normal mode of operation * after a failing restore. * * If platform_pre_restore() has been called before the failing restore, this * function must be called too, regardless of the result of * platform_pre_restore(). */static void platform_restore_cleanup(int platform_mode){	if (platform_mode && hibernation_ops)		hibernation_ops->restore_cleanup();} /** * platform_recover - Recover from a failure to suspend devices. * @platform_mode: Whether or not to use the platform driver. */static void platform_recover(int platform_mode){	if (platform_mode && hibernation_ops && hibernation_ops->recover)		hibernation_ops->recover();} /** * swsusp_show_speed - Print time elapsed between two events during hibernation. * @start: Starting event. * @stop: Final event. * @nr_pages: Number of memory pages processed between @start and @stop. * @msg: Additional diagnostic message to print. */void swsusp_show_speed(ktime_t start, ktime_t stop,		      unsigned nr_pages, char *msg){	ktime_t diff;	u64 elapsed_centisecs64;	unsigned int centisecs;	unsigned int k;	unsigned int kps; 	diff = ktime_sub(stop, start);	elapsed_centisecs64 = ktime_divns(diff, 10*NSEC_PER_MSEC);	centisecs = elapsed_centisecs64;	if (centisecs == 0)		centisecs = 1;	/* avoid div-by-zero */	k = nr_pages * (PAGE_SIZE / 1024);	kps = (k * 100) / centisecs;	pr_info("%s %u kbytes in %u.%02u seconds (%u.%02u MB/s)\n",		msg, k, centisecs / 100, centisecs % 100, kps / 1000,		(kps % 1000) / 10);} __weak int arch_resume_nosmt(void){	return 0;} /** * create_image - Create a hibernation image. * @platform_mode: Whether or not to use the platform driver. * * Execute device drivers' "late" and "noirq" freeze callbacks, create a * hibernation image and run the drivers' "noirq" and "early" thaw callbacks. * * Control reappears in this routine after the subsequent restore. */static int create_image(int platform_mode){	int error; 	error = dpm_suspend_end(PMSG_FREEZE);	if (error) {		pr_err("Some devices failed to power down, aborting\n");		return error;	} 	error = platform_pre_snapshot(platform_mode);	if (error || hibernation_test(TEST_PLATFORM))		goto Platform_finish; 	error = pm_sleep_disable_secondary_cpus();	if (error || hibernation_test(TEST_CPUS))		goto Enable_cpus; 	local_irq_disable(); 	system_state = SYSTEM_SUSPEND; 	error = syscore_suspend();	if (error) {		pr_err("Some system devices failed to power down, aborting\n");		goto Enable_irqs;	} 	if (hibernation_test(TEST_CORE) || pm_wakeup_pending())		goto Power_up; 	in_suspend = 1;	save_processor_state();	trace_suspend_resume(TPS("machine_suspend"), PM_EVENT_HIBERNATE, true);	error = swsusp_arch_suspend();	/* Restore control flow magically appears here */	restore_processor_state();	trace_suspend_resume(TPS("machine_suspend"), PM_EVENT_HIBERNATE, false);	if (error)		pr_err("Error %d creating image\n", error); 	if (!in_suspend) {		events_check_enabled = false;		clear_or_poison_free_pages();	} 	platform_leave(platform_mode);  Power_up:	syscore_resume();  Enable_irqs:	system_state = SYSTEM_RUNNING;	local_irq_enable();  Enable_cpus:	pm_sleep_enable_secondary_cpus(); 	/* Allow architectures to do nosmt-specific post-resume dances */	if (!in_suspend)		error = arch_resume_nosmt();  Platform_finish:	platform_finish(platform_mode); 	dpm_resume_start(in_suspend ?		(error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE); 	return error;} /** * hibernation_snapshot - Quiesce devices and create a hibernation image. * @platform_mode: If set, use platform driver to prepare for the transition. * * This routine must be called with system_transition_mutex held. */int hibernation_snapshot(int platform_mode){	pm_message_t msg;	int error; 	pm_suspend_clear_flags();	error = platform_begin(platform_mode);	if (error)		goto Close; 	error = freeze_kernel_threads();	if (error)		goto Close; 	if (hibernation_test(TEST_FREEZER)) { 		/*		 * Indicate to the caller that we are returning due to a		 * successful freezer test.		 */		freezer_test_done = true;		goto Thaw;	} 	error = dpm_prepare(PMSG_FREEZE);	if (error)		goto Complete; 	/* Preallocate image memory before shutting down devices. */	error = hibernate_preallocate_memory();	if (error)		goto Complete; 	console_suspend_all();	pm_restrict_gfp_mask(); 	error = dpm_suspend(PMSG_FREEZE); 	if (error || hibernation_test(TEST_DEVICES))		platform_recover(platform_mode);	else		error = create_image(platform_mode); 	/*	 * In the case that we call create_image() above, the control	 * returns here (1) after the image has been created or the	 * image creation has failed and (2) after a successful restore.	 */ 	/* We may need to release the preallocated image pages here. */	if (error || !in_suspend)		swsusp_free(); 	msg = in_suspend ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE;	dpm_resume(msg); 	if (error || !in_suspend)		pm_restore_gfp_mask(); 	console_resume_all();	dpm_complete(msg);  Close:	platform_end(platform_mode);	return error;  Complete:	dpm_complete(PMSG_RECOVER); Thaw:	thaw_kernel_threads();	goto Close;} int __weak hibernate_resume_nonboot_cpu_disable(void){	return suspend_disable_secondary_cpus();} /** * resume_target_kernel - Restore system state from a hibernation image. * @platform_mode: Whether or not to use the platform driver. * * Execute device drivers' "noirq" and "late" freeze callbacks, restore the * contents of highmem that have not been restored yet from the image and run * the low-level code that will restore the remaining contents of memory and * switch to the just restored target kernel. */static int resume_target_kernel(bool platform_mode){	int error; 	error = dpm_suspend_end(PMSG_QUIESCE);	if (error) {		pr_err("Some devices failed to power down, aborting resume\n");		return error;	} 	error = platform_pre_restore(platform_mode);	if (error)		goto Cleanup;

Lines 1–500 of 1524.