Commit 86b84bdd authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge branch 'pm-sleep'

Merge changes related to system-wide power management for 6.9-rc1:

 - Fix and clean up system suspend statistics collection (Rafael
   Wysocki).

 - Simplify device suspend and resume handling in the power management
   core code (Rafael Wysocki).

 - Add support for LZ4 compression algorithm to the hibernation image
   creation and loading code (Nikhil V).

 - Fix PCI hibernation support description (Yiwei Lin).

 - Make hibernation take set_memory_ro() return values into account as
   appropriate (Christophe Leroy).

 - Set mem_sleep_current during kernel command line setup to avoid an
   ordering issue with handling it (Maulik Shah).

 - Fix wake IRQs handling when pm_runtime_force_suspend() is used as a
   driver's system suspend callback (Qingliang Li).

* pm-sleep: (21 commits)
  PM: sleep: wakeirq: fix wake irq warning in system suspend
  PM: suspend: Set mem_sleep_current during kernel command line setup
  PM: hibernate: Don't ignore return from set_memory_ro()
  PM: hibernate: Support to select compression algorithm
  Documentation: PM: Fix PCI hibernation support description
  PM: hibernate: Add support for LZ4 compression for hibernation
  PM: hibernate: Move to crypto APIs for LZO compression
  PM: hibernate: Rename lzo* to make it generic
  PM: sleep: Call dpm_async_fn() directly in each suspend phase
  PM: sleep: Move devices to new lists earlier in each suspend phase
  PM: sleep: Move some assignments from under a lock
  PM: sleep: stats: Log errors right after running suspend callbacks
  PM: sleep: stats: Use locking in dpm_save_failed_dev()
  PM: sleep: stats: Call dpm_save_failed_step() at most once per phase
  PM: sleep: stats: Define suspend_stats next to the code using it
  PM: sleep: stats: Use unsigned int for success and failure counters
  PM: sleep: stats: Use an array of step failure counters
  PM: sleep: stats: Use array of suspend step names
  PM: sleep: Relocate two device PM core functions
  PM: sleep: Simplify dpm_suspended_list walk in dpm_resume()
  ...
parents f0a0fc10 e7a7681c
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -1748,6 +1748,17 @@
				(that will set all pages holding image data
				during restoration read-only).

	hibernate.compressor= 	[HIBERNATION] Compression algorithm to be
				used with hibernation.
				Format: { lzo | lz4 }
				Default: lzo

				lzo: Select LZO compression algorithm to
				compress/decompress hibernation image.

				lz4: Select LZ4 compression algorithm to
				compress/decompress hibernation image.

	highmem=nn[KMG]	[KNL,BOOT] forces the highmem zone to have an exact
			size of <nn>. This works even on boxes that have no
			highmem otherwise. This also works to reduce highmem
+1 −1
Original line number Diff line number Diff line
@@ -625,7 +625,7 @@ The PCI subsystem-level callbacks they correspond to::
	pci_pm_poweroff()
	pci_pm_poweroff_noirq()

work in analogy with pci_pm_suspend() and pci_pm_poweroff_noirq(), respectively,
work in analogy with pci_pm_suspend() and pci_pm_suspend_noirq(), respectively,
although they don't attempt to save the device's standard configuration
registers.

+116 −151
Original line number Diff line number Diff line
@@ -60,7 +60,6 @@ static LIST_HEAD(dpm_suspended_list);
static LIST_HEAD(dpm_late_early_list);
static LIST_HEAD(dpm_noirq_list);

struct suspend_stats suspend_stats;
static DEFINE_MUTEX(dpm_list_mtx);
static pm_message_t pm_transition;

@@ -578,6 +577,35 @@ bool dev_pm_skip_resume(struct device *dev)
	return !dev->power.must_resume;
}

static bool is_async(struct device *dev)
{
	return dev->power.async_suspend && pm_async_enabled
		&& !pm_trace_is_enabled();
}

static bool dpm_async_fn(struct device *dev, async_func_t func)
{
	reinit_completion(&dev->power.completion);

	if (is_async(dev)) {
		dev->power.async_in_progress = true;

		get_device(dev);

		if (async_schedule_dev_nocall(func, dev))
			return true;

		put_device(dev);
	}
	/*
	 * Because async_schedule_dev_nocall() above has returned false or it
	 * has not been called at all, func() is not running and it is safe to
	 * update the async_in_progress flag without extra synchronization.
	 */
	dev->power.async_in_progress = false;
	return false;
}

/**
 * device_resume_noirq - Execute a "noirq resume" callback for given device.
 * @dev: Device to handle.
@@ -657,42 +685,12 @@ static void device_resume_noirq(struct device *dev, pm_message_t state, bool asy
	TRACE_RESUME(error);

	if (error) {
		suspend_stats.failed_resume_noirq++;
		dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
		async_error = error;
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
	}
}

static bool is_async(struct device *dev)
{
	return dev->power.async_suspend && pm_async_enabled
		&& !pm_trace_is_enabled();
}

static bool dpm_async_fn(struct device *dev, async_func_t func)
{
	reinit_completion(&dev->power.completion);

	if (is_async(dev)) {
		dev->power.async_in_progress = true;

		get_device(dev);

		if (async_schedule_dev_nocall(func, dev))
			return true;

		put_device(dev);
	}
	/*
	 * Because async_schedule_dev_nocall() above has returned false or it
	 * has not been called at all, func() is not running and it is safe to
	 * update the async_in_progress flag without extra synchronization.
	 */
	dev->power.async_in_progress = false;
	return false;
}

static void async_resume_noirq(void *data, async_cookie_t cookie)
{
	struct device *dev = data;
@@ -707,9 +705,12 @@ static void dpm_noirq_resume_devices(pm_message_t state)
	ktime_t starttime = ktime_get();

	trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, true);
	mutex_lock(&dpm_list_mtx);

	async_error = 0;
	pm_transition = state;

	mutex_lock(&dpm_list_mtx);

	/*
	 * Trigger the resume of "async" devices upfront so they don't have to
	 * wait for the "non-async" ones they don't depend on.
@@ -736,6 +737,9 @@ static void dpm_noirq_resume_devices(pm_message_t state)
	mutex_unlock(&dpm_list_mtx);
	async_synchronize_full();
	dpm_show_time(starttime, state, 0, "noirq");
	if (async_error)
		dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);

	trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
}

@@ -817,8 +821,7 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy
	complete_all(&dev->power.completion);

	if (error) {
		suspend_stats.failed_resume_early++;
		dpm_save_failed_step(SUSPEND_RESUME_EARLY);
		async_error = error;
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, state, async ? " async early" : " early", error);
	}
@@ -842,9 +845,12 @@ void dpm_resume_early(pm_message_t state)
	ktime_t starttime = ktime_get();

	trace_suspend_resume(TPS("dpm_resume_early"), state.event, true);
	mutex_lock(&dpm_list_mtx);

	async_error = 0;
	pm_transition = state;

	mutex_lock(&dpm_list_mtx);

	/*
	 * Trigger the resume of "async" devices upfront so they don't have to
	 * wait for the "non-async" ones they don't depend on.
@@ -871,6 +877,9 @@ void dpm_resume_early(pm_message_t state)
	mutex_unlock(&dpm_list_mtx);
	async_synchronize_full();
	dpm_show_time(starttime, state, 0, "early");
	if (async_error)
		dpm_save_failed_step(SUSPEND_RESUME_EARLY);

	trace_suspend_resume(TPS("dpm_resume_early"), state.event, false);
}

@@ -974,8 +983,7 @@ static void device_resume(struct device *dev, pm_message_t state, bool async)
	TRACE_RESUME(error);

	if (error) {
		suspend_stats.failed_resume++;
		dpm_save_failed_step(SUSPEND_RESUME);
		async_error = error;
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, state, async ? " async" : "", error);
	}
@@ -1004,10 +1012,11 @@ void dpm_resume(pm_message_t state)
	trace_suspend_resume(TPS("dpm_resume"), state.event, true);
	might_sleep();

	mutex_lock(&dpm_list_mtx);
	pm_transition = state;
	async_error = 0;

	mutex_lock(&dpm_list_mtx);

	/*
	 * Trigger the resume of "async" devices upfront so they don't have to
	 * wait for the "non-async" ones they don't depend on.
@@ -1017,29 +1026,25 @@ void dpm_resume(pm_message_t state)

	while (!list_empty(&dpm_suspended_list)) {
		dev = to_device(dpm_suspended_list.next);
		list_move_tail(&dev->power.entry, &dpm_prepared_list);

		if (!dev->power.async_in_progress) {
			get_device(dev);

		if (!dev->power.async_in_progress) {
			mutex_unlock(&dpm_list_mtx);

			device_resume(dev, state, false);

			mutex_lock(&dpm_list_mtx);
		}

		if (!list_empty(&dev->power.entry))
			list_move_tail(&dev->power.entry, &dpm_prepared_list);

		mutex_unlock(&dpm_list_mtx);

			put_device(dev);

			mutex_lock(&dpm_list_mtx);
		}
	}
	mutex_unlock(&dpm_list_mtx);
	async_synchronize_full();
	dpm_show_time(starttime, state, 0, NULL);
	if (async_error)
		dpm_save_failed_step(SUSPEND_RESUME);

	cpufreq_resume();
	devfreq_resume();
@@ -1187,7 +1192,7 @@ static void dpm_superior_set_must_resume(struct device *dev)
}

/**
 * __device_suspend_noirq - Execute a "noirq suspend" callback for given device.
 * device_suspend_noirq - Execute a "noirq suspend" callback for given device.
 * @dev: Device to handle.
 * @state: PM transition of the system being carried out.
 * @async: If true, the device is being suspended asynchronously.
@@ -1195,7 +1200,7 @@ static void dpm_superior_set_must_resume(struct device *dev)
 * The driver of @dev will not receive interrupts while this function is being
 * executed.
 */
static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool async)
static int device_suspend_noirq(struct device *dev, pm_message_t state, bool async)
{
	pm_callback_t callback = NULL;
	const char *info = NULL;
@@ -1240,6 +1245,8 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
	error = dpm_run_callback(callback, dev, state, info);
	if (error) {
		async_error = error;
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
		goto Complete;
	}

@@ -1269,54 +1276,37 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
static void async_suspend_noirq(void *data, async_cookie_t cookie)
{
	struct device *dev = data;
	int error;

	error = __device_suspend_noirq(dev, pm_transition, true);
	if (error) {
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, pm_transition, " async", error);
	}

	device_suspend_noirq(dev, pm_transition, true);
	put_device(dev);
}

static int device_suspend_noirq(struct device *dev)
{
	if (dpm_async_fn(dev, async_suspend_noirq))
		return 0;

	return __device_suspend_noirq(dev, pm_transition, false);
}

static int dpm_noirq_suspend_devices(pm_message_t state)
{
	ktime_t starttime = ktime_get();
	int error = 0;

	trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
	mutex_lock(&dpm_list_mtx);

	pm_transition = state;
	async_error = 0;

	mutex_lock(&dpm_list_mtx);

	while (!list_empty(&dpm_late_early_list)) {
		struct device *dev = to_device(dpm_late_early_list.prev);

		get_device(dev);
		mutex_unlock(&dpm_list_mtx);

		error = device_suspend_noirq(dev);
		list_move(&dev->power.entry, &dpm_noirq_list);

		mutex_lock(&dpm_list_mtx);
		if (dpm_async_fn(dev, async_suspend_noirq))
			continue;

		if (error) {
			pm_dev_err(dev, state, " noirq", error);
			dpm_save_failed_dev(dev_name(dev));
		} else if (!list_empty(&dev->power.entry)) {
			list_move(&dev->power.entry, &dpm_noirq_list);
		}
		get_device(dev);

		mutex_unlock(&dpm_list_mtx);

		error = device_suspend_noirq(dev, state, false);

		put_device(dev);

		mutex_lock(&dpm_list_mtx);
@@ -1324,15 +1314,16 @@ static int dpm_noirq_suspend_devices(pm_message_t state)
		if (error || async_error)
			break;
	}

	mutex_unlock(&dpm_list_mtx);

	async_synchronize_full();
	if (!error)
		error = async_error;

	if (error) {
		suspend_stats.failed_suspend_noirq++;
	if (error)
		dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
	}

	dpm_show_time(starttime, state, error, "noirq");
	trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, false);
	return error;
@@ -1375,14 +1366,14 @@ static void dpm_propagate_wakeup_to_parent(struct device *dev)
}

/**
 * __device_suspend_late - Execute a "late suspend" callback for given device.
 * device_suspend_late - Execute a "late suspend" callback for given device.
 * @dev: Device to handle.
 * @state: PM transition of the system being carried out.
 * @async: If true, the device is being suspended asynchronously.
 *
 * Runtime PM is disabled for @dev while this function is being executed.
 */
static int __device_suspend_late(struct device *dev, pm_message_t state, bool async)
static int device_suspend_late(struct device *dev, pm_message_t state, bool async)
{
	pm_callback_t callback = NULL;
	const char *info = NULL;
@@ -1434,6 +1425,8 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
	error = dpm_run_callback(callback, dev, state, info);
	if (error) {
		async_error = error;
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, state, async ? " async late" : " late", error);
		goto Complete;
	}
	dpm_propagate_wakeup_to_parent(dev);
@@ -1450,24 +1443,11 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
static void async_suspend_late(void *data, async_cookie_t cookie)
{
	struct device *dev = data;
	int error;

	error = __device_suspend_late(dev, pm_transition, true);
	if (error) {
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, pm_transition, " async", error);
	}
	device_suspend_late(dev, pm_transition, true);
	put_device(dev);
}

static int device_suspend_late(struct device *dev)
{
	if (dpm_async_fn(dev, async_suspend_late))
		return 0;

	return __device_suspend_late(dev, pm_transition, false);
}

/**
 * dpm_suspend_late - Execute "late suspend" callbacks for all devices.
 * @state: PM transition of the system being carried out.
@@ -1478,32 +1458,28 @@ int dpm_suspend_late(pm_message_t state)
	int error = 0;

	trace_suspend_resume(TPS("dpm_suspend_late"), state.event, true);
	wake_up_all_idle_cpus();
	mutex_lock(&dpm_list_mtx);

	pm_transition = state;
	async_error = 0;

	while (!list_empty(&dpm_suspended_list)) {
		struct device *dev = to_device(dpm_suspended_list.prev);

		get_device(dev);

		mutex_unlock(&dpm_list_mtx);

		error = device_suspend_late(dev);
	wake_up_all_idle_cpus();

	mutex_lock(&dpm_list_mtx);

		if (!list_empty(&dev->power.entry))
	while (!list_empty(&dpm_suspended_list)) {
		struct device *dev = to_device(dpm_suspended_list.prev);

		list_move(&dev->power.entry, &dpm_late_early_list);

		if (error) {
			pm_dev_err(dev, state, " late", error);
			dpm_save_failed_dev(dev_name(dev));
		}
		if (dpm_async_fn(dev, async_suspend_late))
			continue;

		get_device(dev);

		mutex_unlock(&dpm_list_mtx);

		error = device_suspend_late(dev, state, false);

		put_device(dev);

		mutex_lock(&dpm_list_mtx);
@@ -1511,12 +1487,14 @@ int dpm_suspend_late(pm_message_t state)
		if (error || async_error)
			break;
	}

	mutex_unlock(&dpm_list_mtx);

	async_synchronize_full();
	if (!error)
		error = async_error;

	if (error) {
		suspend_stats.failed_suspend_late++;
		dpm_save_failed_step(SUSPEND_SUSPEND_LATE);
		dpm_resume_early(resume_event(state));
	}
@@ -1597,12 +1575,12 @@ static void dpm_clear_superiors_direct_complete(struct device *dev)
}

/**
 * __device_suspend - Execute "suspend" callbacks for given device.
 * device_suspend - Execute "suspend" callbacks for given device.
 * @dev: Device to handle.
 * @state: PM transition of the system being carried out.
 * @async: If true, the device is being suspended asynchronously.
 */
static int __device_suspend(struct device *dev, pm_message_t state, bool async)
static int device_suspend(struct device *dev, pm_message_t state, bool async)
{
	pm_callback_t callback = NULL;
	const char *info = NULL;
@@ -1716,8 +1694,11 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
	dpm_watchdog_clear(&wd);

 Complete:
	if (error)
	if (error) {
		async_error = error;
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, state, async ? " async" : "", error);
	}

	complete_all(&dev->power.completion);
	TRACE_SUSPEND(error);
@@ -1727,25 +1708,11 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
static void async_suspend(void *data, async_cookie_t cookie)
{
	struct device *dev = data;
	int error;

	error = __device_suspend(dev, pm_transition, true);
	if (error) {
		dpm_save_failed_dev(dev_name(dev));
		pm_dev_err(dev, pm_transition, " async", error);
	}

	device_suspend(dev, pm_transition, true);
	put_device(dev);
}

static int device_suspend(struct device *dev)
{
	if (dpm_async_fn(dev, async_suspend))
		return 0;

	return __device_suspend(dev, pm_transition, false);
}

/**
 * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices.
 * @state: PM transition of the system being carried out.
@@ -1761,29 +1728,25 @@ int dpm_suspend(pm_message_t state)
	devfreq_suspend();
	cpufreq_suspend();

	mutex_lock(&dpm_list_mtx);
	pm_transition = state;
	async_error = 0;
	while (!list_empty(&dpm_prepared_list)) {
		struct device *dev = to_device(dpm_prepared_list.prev);

		get_device(dev);
	mutex_lock(&dpm_list_mtx);

		mutex_unlock(&dpm_list_mtx);
	while (!list_empty(&dpm_prepared_list)) {
		struct device *dev = to_device(dpm_prepared_list.prev);

		error = device_suspend(dev);
		list_move(&dev->power.entry, &dpm_suspended_list);

		mutex_lock(&dpm_list_mtx);
		if (dpm_async_fn(dev, async_suspend))
			continue;

		if (error) {
			pm_dev_err(dev, state, "", error);
			dpm_save_failed_dev(dev_name(dev));
		} else if (!list_empty(&dev->power.entry)) {
			list_move(&dev->power.entry, &dpm_suspended_list);
		}
		get_device(dev);

		mutex_unlock(&dpm_list_mtx);

		error = device_suspend(dev, state, false);

		put_device(dev);

		mutex_lock(&dpm_list_mtx);
@@ -1791,14 +1754,16 @@ int dpm_suspend(pm_message_t state)
		if (error || async_error)
			break;
	}

	mutex_unlock(&dpm_list_mtx);

	async_synchronize_full();
	if (!error)
		error = async_error;
	if (error) {
		suspend_stats.failed_suspend++;

	if (error)
		dpm_save_failed_step(SUSPEND_SUSPEND);
	}

	dpm_show_time(starttime, state, error, NULL);
	trace_suspend_resume(TPS("dpm_suspend"), state.event, false);
	return error;
@@ -1949,11 +1914,11 @@ int dpm_suspend_start(pm_message_t state)
	int error;

	error = dpm_prepare(state);
	if (error) {
		suspend_stats.failed_prepare++;
	if (error)
		dpm_save_failed_step(SUSPEND_PREPARE);
	} else
	else
		error = dpm_suspend(state);

	dpm_show_time(starttime, state, error, "start");
	return error;
}
+3 −1
Original line number Diff line number Diff line
@@ -313,8 +313,10 @@ void dev_pm_enable_wake_irq_complete(struct device *dev)
		return;

	if (wirq->status & WAKE_IRQ_DEDICATED_MANAGED &&
	    wirq->status & WAKE_IRQ_DEDICATED_REVERSE)
	    wirq->status & WAKE_IRQ_DEDICATED_REVERSE) {
		enable_irq(wirq->irq);
		wirq->status |= WAKE_IRQ_DEDICATED_ENABLED;
	}
}

/**
+15 −15
Original line number Diff line number Diff line
@@ -662,8 +662,8 @@ struct pm_subsys_data {

struct dev_pm_info {
	pm_message_t		power_state;
	unsigned int		can_wakeup:1;
	unsigned int		async_suspend:1;
	bool			can_wakeup:1;
	bool			async_suspend:1;
	bool			in_dpm_list:1;	/* Owned by the PM core */
	bool			is_prepared:1;	/* Owned by the PM core */
	bool			is_suspended:1;	/* Ditto */
@@ -682,10 +682,10 @@ struct dev_pm_info {
	bool			syscore:1;
	bool			no_pm_callbacks:1;	/* Owned by the PM core */
	bool			async_in_progress:1;	/* Owned by the PM core */
	unsigned int		must_resume:1;	/* Owned by the PM core */
	unsigned int		may_skip_resume:1;	/* Set by subsystems */
	bool			must_resume:1;		/* Owned by the PM core */
	bool			may_skip_resume:1;	/* Set by subsystems */
#else
	unsigned int		should_wakeup:1;
	bool			should_wakeup:1;
#endif
#ifdef CONFIG_PM
	struct hrtimer		suspend_timer;
@@ -696,17 +696,17 @@ struct dev_pm_info {
	atomic_t		usage_count;
	atomic_t		child_count;
	unsigned int		disable_depth:3;
	unsigned int		idle_notification:1;
	unsigned int		request_pending:1;
	unsigned int		deferred_resume:1;
	unsigned int		needs_force_resume:1;
	unsigned int		runtime_auto:1;
	bool			idle_notification:1;
	bool			request_pending:1;
	bool			deferred_resume:1;
	bool			needs_force_resume:1;
	bool			runtime_auto:1;
	bool			ignore_children:1;
	unsigned int		no_callbacks:1;
	unsigned int		irq_safe:1;
	unsigned int		use_autosuspend:1;
	unsigned int		timer_autosuspends:1;
	unsigned int		memalloc_noio:1;
	bool			no_callbacks:1;
	bool			irq_safe:1;
	bool			use_autosuspend:1;
	bool			timer_autosuspends:1;
	bool			memalloc_noio:1;
	unsigned int		links_count;
	enum rpm_request	request;
	enum rpm_status		runtime_status;
Loading