1161 lines · 28.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * kernel/power/main.c - PM subsystem core functionality. * * Copyright (c) 2003 Patrick Mochel * Copyright (c) 2003 Open Source Development Lab */ #include <linux/acpi.h>#include <linux/export.h>#include <linux/init.h>#include <linux/kobject.h>#include <linux/string.h>#include <linux/pm-trace.h>#include <linux/workqueue.h>#include <linux/debugfs.h>#include <linux/seq_file.h>#include <linux/suspend.h>#include <linux/syscalls.h>#include <linux/pm_runtime.h>#include <linux/atomic.h>#include <linux/wait.h> #include "power.h" #ifdef CONFIG_PM_SLEEP/* * The following functions are used by the suspend/hibernate code to temporarily * change gfp_allowed_mask in order to avoid using I/O during memory allocations * while devices are suspended.  To avoid races with the suspend/hibernate code, * they should always be called with system_transition_mutex held * (gfp_allowed_mask also should only be modified with system_transition_mutex * held, unless the suspend/hibernate code is guaranteed not to run in parallel * with that modification). */static unsigned int saved_gfp_count;static gfp_t saved_gfp_mask; void pm_restore_gfp_mask(void){	WARN_ON(!mutex_is_locked(&system_transition_mutex)); 	if (!saved_gfp_count || --saved_gfp_count)		return; 	gfp_allowed_mask = saved_gfp_mask;	saved_gfp_mask = 0; 	pm_pr_dbg("GFP mask restored\n");} void pm_restrict_gfp_mask(void){	WARN_ON(!mutex_is_locked(&system_transition_mutex)); 	if (saved_gfp_count++) {		WARN_ON((saved_gfp_mask & ~(__GFP_IO | __GFP_FS)) != gfp_allowed_mask);		return;	} 	saved_gfp_mask = gfp_allowed_mask;	gfp_allowed_mask &= ~(__GFP_IO | __GFP_FS); 	pm_pr_dbg("GFP mask restricted\n");} unsigned int lock_system_sleep(void){	unsigned int flags = current->flags;	current->flags |= PF_NOFREEZE;	mutex_lock(&system_transition_mutex);	return flags;}EXPORT_SYMBOL_GPL(lock_system_sleep); void unlock_system_sleep(unsigned int flags){	if (!(flags & PF_NOFREEZE))		current->flags &= ~PF_NOFREEZE;	mutex_unlock(&system_transition_mutex);}EXPORT_SYMBOL_GPL(unlock_system_sleep); void ksys_sync_helper(void){	ktime_t start;	long elapsed_msecs; 	start = ktime_get();	ksys_sync();	elapsed_msecs = ktime_to_ms(ktime_sub(ktime_get(), start));	pr_info("Filesystems sync: %ld.%03ld seconds\n",		elapsed_msecs / MSEC_PER_SEC, elapsed_msecs % MSEC_PER_SEC);}EXPORT_SYMBOL_GPL(ksys_sync_helper); #if defined(CONFIG_SUSPEND) || defined(CONFIG_HIBERNATION)/* Wakeup events handling resolution while syncing file systems in jiffies */#define PM_FS_SYNC_WAKEUP_RESOLUTION	5 static atomic_t pm_fs_sync_count = ATOMIC_INIT(0);static struct workqueue_struct *pm_fs_sync_wq;static DECLARE_WAIT_QUEUE_HEAD(pm_fs_sync_wait); static bool pm_fs_sync_completed(void){	return atomic_read(&pm_fs_sync_count) == 0;} static void pm_fs_sync_work_fn(struct work_struct *work){	ksys_sync_helper(); 	if (atomic_dec_and_test(&pm_fs_sync_count))		wake_up(&pm_fs_sync_wait);}static DECLARE_WORK(pm_fs_sync_work, pm_fs_sync_work_fn); /** * pm_sleep_fs_sync() - Sync file systems in an interruptible way * * Return: 0 on successful file system sync, or -EBUSY if the file system sync * was aborted. */int pm_sleep_fs_sync(void){	pm_wakeup_clear(0); 	/*	 * Take back-to-back sleeps into account by queuing a subsequent fs sync	 * only if the previous fs sync is running or is not queued. Multiple fs	 * syncs increase the likelihood of saving the latest files immediately	 * before sleep.	 */	if (!work_pending(&pm_fs_sync_work)) {		atomic_inc(&pm_fs_sync_count);		queue_work(pm_fs_sync_wq, &pm_fs_sync_work);	} 	while (!pm_fs_sync_completed()) {		if (pm_wakeup_pending())			return -EBUSY; 		wait_event_timeout(pm_fs_sync_wait, pm_fs_sync_completed(),				   PM_FS_SYNC_WAKEUP_RESOLUTION);	} 	return 0;}#endif /* CONFIG_SUSPEND || CONFIG_HIBERNATION */ /* Routines for PM-transition notifications */ static BLOCKING_NOTIFIER_HEAD(pm_chain_head); int register_pm_notifier(struct notifier_block *nb){	return blocking_notifier_chain_register(&pm_chain_head, nb);}EXPORT_SYMBOL_GPL(register_pm_notifier); int unregister_pm_notifier(struct notifier_block *nb){	return blocking_notifier_chain_unregister(&pm_chain_head, nb);}EXPORT_SYMBOL_GPL(unregister_pm_notifier); int pm_notifier_call_chain_robust(unsigned long val_up, unsigned long val_down){	int ret; 	ret = blocking_notifier_call_chain_robust(&pm_chain_head, val_up, val_down, NULL); 	return notifier_to_errno(ret);} int pm_notifier_call_chain(unsigned long val){	return blocking_notifier_call_chain(&pm_chain_head, val, NULL);} /* If set, devices may be suspended and resumed asynchronously. */int pm_async_enabled = 1; static int __init pm_async_setup(char *str){	if (!strcmp(str, "off"))		pm_async_enabled = 0;	return 1;}__setup("pm_async=", pm_async_setup); static ssize_t pm_async_show(struct kobject *kobj, struct kobj_attribute *attr,			     char *buf){	return sysfs_emit(buf, "%d\n", pm_async_enabled);} static ssize_t pm_async_store(struct kobject *kobj, struct kobj_attribute *attr,			      const char *buf, size_t n){	unsigned long val; 	if (kstrtoul(buf, 10, &val))		return -EINVAL; 	if (val > 1)		return -EINVAL; 	pm_async_enabled = val;	return n;} power_attr(pm_async); #ifdef CONFIG_SUSPENDstatic ssize_t mem_sleep_show(struct kobject *kobj, struct kobj_attribute *attr,			      char *buf){	ssize_t count = 0;	suspend_state_t i; 	for (i = PM_SUSPEND_MIN; i < PM_SUSPEND_MAX; i++) {		if (i >= PM_SUSPEND_MEM && cxl_mem_active())			continue;		if (mem_sleep_states[i]) {			const char *label = mem_sleep_states[i]; 			if (mem_sleep_current == i)				count += sysfs_emit_at(buf, count, "[%s] ", label);			else				count += sysfs_emit_at(buf, count, "%s ", label);		}	} 	/* Convert the last space to a newline if needed. */	if (count > 0)		buf[count - 1] = '\n'; 	return count;} static suspend_state_t decode_suspend_state(const char *buf, size_t n){	suspend_state_t state;	char *p;	int len; 	p = memchr(buf, '\n', n);	len = p ? p - buf : n; 	for (state = PM_SUSPEND_MIN; state < PM_SUSPEND_MAX; state++) {		const char *label = mem_sleep_states[state]; 		if (label && len == strlen(label) && !strncmp(buf, label, len))			return state;	} 	return PM_SUSPEND_ON;} static ssize_t mem_sleep_store(struct kobject *kobj, struct kobj_attribute *attr,			       const char *buf, size_t n){	suspend_state_t state;	int error; 	error = pm_autosleep_lock();	if (error)		return error; 	if (pm_autosleep_state() > PM_SUSPEND_ON) {		error = -EBUSY;		goto out;	} 	state = decode_suspend_state(buf, n);	if (state < PM_SUSPEND_MAX && state > PM_SUSPEND_ON)		mem_sleep_current = state;	else		error = -EINVAL;  out:	pm_autosleep_unlock();	return error ? error : n;} power_attr(mem_sleep); /* * sync_on_suspend: Sync file systems before suspend. * * show() returns whether file systems sync before suspend is enabled. * store() accepts 0 or 1.  0 disables file systems sync and 1 enables it. */bool sync_on_suspend_enabled = !IS_ENABLED(CONFIG_SUSPEND_SKIP_SYNC); static ssize_t sync_on_suspend_show(struct kobject *kobj,				   struct kobj_attribute *attr, char *buf){	return sysfs_emit(buf, "%d\n", sync_on_suspend_enabled);} static ssize_t sync_on_suspend_store(struct kobject *kobj,				    struct kobj_attribute *attr,				    const char *buf, size_t n){	unsigned long val; 	if (kstrtoul(buf, 10, &val))		return -EINVAL; 	if (val > 1)		return -EINVAL; 	sync_on_suspend_enabled = !!val;	return n;} power_attr(sync_on_suspend);#endif /* CONFIG_SUSPEND */ #ifdef CONFIG_PM_SLEEP_DEBUGint pm_test_level = TEST_NONE; static const char * const pm_tests[__TEST_AFTER_LAST] = {	[TEST_NONE] = "none",	[TEST_CORE] = "core",	[TEST_CPUS] = "processors",	[TEST_PLATFORM] = "platform",	[TEST_DEVICES] = "devices",	[TEST_FREEZER] = "freezer",}; static ssize_t pm_test_show(struct kobject *kobj, struct kobj_attribute *attr,				char *buf){	ssize_t count = 0;	int level; 	for (level = TEST_FIRST; level <= TEST_MAX; level++)		if (pm_tests[level]) {			if (level == pm_test_level)				count += sysfs_emit_at(buf, count, "[%s] ", pm_tests[level]);			else				count += sysfs_emit_at(buf, count, "%s ", pm_tests[level]);		} 	/* Convert the last space to a newline if needed. */	if (count > 0)		buf[count - 1] = '\n'; 	return count;} static ssize_t pm_test_store(struct kobject *kobj, struct kobj_attribute *attr,				const char *buf, size_t n){	unsigned int sleep_flags;	const char * const *s;	int error = -EINVAL;	int level;	char *p;	int len; 	p = memchr(buf, '\n', n);	len = p ? p - buf : n; 	sleep_flags = lock_system_sleep(); 	level = TEST_FIRST;	for (s = &pm_tests[level]; level <= TEST_MAX; s++, level++)		if (*s && len == strlen(*s) && !strncmp(buf, *s, len)) {			pm_test_level = level;			error = 0;			break;		} 	unlock_system_sleep(sleep_flags); 	return error ? error : n;} power_attr(pm_test);#endif /* CONFIG_PM_SLEEP_DEBUG */ #define SUSPEND_NR_STEPS	SUSPEND_RESUME#define REC_FAILED_NUM		2 struct suspend_stats {	unsigned int step_failures[SUSPEND_NR_STEPS];	unsigned int success;	unsigned int fail;	int last_failed_dev;	char failed_devs[REC_FAILED_NUM][40];	int last_failed_errno;	int errno[REC_FAILED_NUM];	int last_failed_step;	u64 last_hw_sleep;	u64 total_hw_sleep;	u64 max_hw_sleep;	enum suspend_stat_step failed_steps[REC_FAILED_NUM];}; static struct suspend_stats suspend_stats;static DEFINE_MUTEX(suspend_stats_lock); void dpm_save_failed_dev(const char *name){	mutex_lock(&suspend_stats_lock); 	strscpy(suspend_stats.failed_devs[suspend_stats.last_failed_dev],		name, sizeof(suspend_stats.failed_devs[0]));	suspend_stats.last_failed_dev++;	suspend_stats.last_failed_dev %= REC_FAILED_NUM; 	mutex_unlock(&suspend_stats_lock);} void dpm_save_failed_step(enum suspend_stat_step step){	suspend_stats.step_failures[step-1]++;	suspend_stats.failed_steps[suspend_stats.last_failed_step] = step;	suspend_stats.last_failed_step++;	suspend_stats.last_failed_step %= REC_FAILED_NUM;} void dpm_save_errno(int err){	if (!err) {		suspend_stats.success++;		return;	} 	suspend_stats.fail++; 	suspend_stats.errno[suspend_stats.last_failed_errno] = err;	suspend_stats.last_failed_errno++;	suspend_stats.last_failed_errno %= REC_FAILED_NUM;} void pm_report_hw_sleep_time(u64 t){	suspend_stats.last_hw_sleep = t;	suspend_stats.total_hw_sleep += t;}EXPORT_SYMBOL_GPL(pm_report_hw_sleep_time); void pm_report_max_hw_sleep(u64 t){	suspend_stats.max_hw_sleep = t;}EXPORT_SYMBOL_GPL(pm_report_max_hw_sleep); static const char * const suspend_step_names[] = {	[SUSPEND_WORKING] = "",	[SUSPEND_FREEZE] = "freeze",	[SUSPEND_PREPARE] = "prepare",	[SUSPEND_SUSPEND] = "suspend",	[SUSPEND_SUSPEND_LATE] = "suspend_late",	[SUSPEND_SUSPEND_NOIRQ] = "suspend_noirq",	[SUSPEND_RESUME_NOIRQ] = "resume_noirq",	[SUSPEND_RESUME_EARLY] = "resume_early",	[SUSPEND_RESUME] = "resume",}; #define suspend_attr(_name, format_str)				\static ssize_t _name##_show(struct kobject *kobj,		\		struct kobj_attribute *attr, char *buf)		\{								\	return sysfs_emit(buf, format_str, suspend_stats._name);\}								\static struct kobj_attribute _name = __ATTR_RO(_name) suspend_attr(success, "%u\n");suspend_attr(fail, "%u\n");suspend_attr(last_hw_sleep, "%llu\n");suspend_attr(total_hw_sleep, "%llu\n");suspend_attr(max_hw_sleep, "%llu\n"); #define suspend_step_attr(_name, step)		\static ssize_t _name##_show(struct kobject *kobj,		\		struct kobj_attribute *attr, char *buf)		\{								\	return sysfs_emit(buf, "%u\n",				\		       suspend_stats.step_failures[step-1]);	\}								\static struct kobj_attribute _name = __ATTR_RO(_name) suspend_step_attr(failed_freeze, SUSPEND_FREEZE);suspend_step_attr(failed_prepare, SUSPEND_PREPARE);suspend_step_attr(failed_suspend, SUSPEND_SUSPEND);suspend_step_attr(failed_suspend_late, SUSPEND_SUSPEND_LATE);suspend_step_attr(failed_suspend_noirq, SUSPEND_SUSPEND_NOIRQ);suspend_step_attr(failed_resume, SUSPEND_RESUME);suspend_step_attr(failed_resume_early, SUSPEND_RESUME_EARLY);suspend_step_attr(failed_resume_noirq, SUSPEND_RESUME_NOIRQ); static ssize_t last_failed_dev_show(struct kobject *kobj,		struct kobj_attribute *attr, char *buf)

Lines 1–500 of 1161.