Commit 1b531e55 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge suspend-to-idle rework material for v5.4.

* pm-s2idle-rework: (21 commits)
  ACPI: PM: s2idle: Always set up EC GPE for system wakeup
  ACPI: PM: s2idle: Avoid rearming SCI for wakeup unnecessarily
  PM: suspend: Fix platform_suspend_prepare_noirq()
  intel-hid: Disable button array during suspend-to-idle
  intel-hid: intel-vbtn: Avoid leaking wakeup_mode set
  ACPI: PM: s2idle: Execute LPS0 _DSM functions with suspended devices
  ACPI: EC: PM: Make acpi_ec_dispatch_gpe() print debug message
  ACPI: EC: PM: Consolidate some code depending on PM_SLEEP
  ACPI: PM: s2idle: Eliminate acpi_sleep_no_ec_events()
  ACPI: PM: s2idle: Switch EC over to polling during "noirq" suspend
  ACPI: PM: s2idle: Add acpi.sleep_no_lps0 module parameter
  ACPI: PM: s2idle: Rearrange lps0_device_attach()
  ACPI: PM: Set up EC GPE for system wakeup from drivers that need it
  PM: sleep: Drop dpm_noirq_begin() and dpm_noirq_end()
  PM: sleep: Integrate suspend-to-idle with generig suspend flow
  PM: sleep: Simplify suspend-to-idle control flow
  ACPI: PM: Set s2idle_wakeup earlier and clear it later
  PM: sleep: Fix possible overflow in pm_system_cancel_wakeup()
  ACPI: EC: Return bool from acpi_ec_dispatch_gpe()
  ACPICA: Return u32 from acpi_dispatch_gpe()
  ...
parents 78c0f050 b90ff355
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -644,17 +644,17 @@ ACPI_EXPORT_SYMBOL(acpi_get_gpe_status)
 * PARAMETERS:  gpe_device          - Parent GPE Device. NULL for GPE0/GPE1
 *              gpe_number          - GPE level within the GPE block
 *
 * RETURN:      None
 * RETURN:      INTERRUPT_HANDLED or INTERRUPT_NOT_HANDLED
 *
 * DESCRIPTION: Detect and dispatch a General Purpose Event to either a function
 *              (e.g. EC) or method (e.g. _Lxx/_Exx) handler.
 *
 ******************************************************************************/
void acpi_dispatch_gpe(acpi_handle gpe_device, u32 gpe_number)
u32 acpi_dispatch_gpe(acpi_handle gpe_device, u32 gpe_number)
{
	ACPI_FUNCTION_TRACE(acpi_dispatch_gpe);

	acpi_ev_detect_gpe(gpe_device, NULL, gpe_number);
	return acpi_ev_detect_gpe(gpe_device, NULL, gpe_number);
}

ACPI_EXPORT_SYMBOL(acpi_dispatch_gpe)
+33 −24
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <asm/io.h>
@@ -1048,24 +1049,6 @@ void acpi_ec_unblock_transactions(void)
		acpi_ec_start(first_ec, true);
}

void acpi_ec_mark_gpe_for_wake(void)
{
	if (first_ec && !ec_no_wakeup)
		acpi_mark_gpe_for_wake(NULL, first_ec->gpe);
}

void acpi_ec_set_gpe_wake_mask(u8 action)
{
	if (first_ec && !ec_no_wakeup)
		acpi_set_gpe_wake_mask(NULL, first_ec->gpe, action);
}

void acpi_ec_dispatch_gpe(void)
{
	if (first_ec)
		acpi_dispatch_gpe(NULL, first_ec->gpe);
}

/* --------------------------------------------------------------------------
                                Event Management
   -------------------------------------------------------------------------- */
@@ -1931,7 +1914,7 @@ static int acpi_ec_suspend(struct device *dev)
	struct acpi_ec *ec =
		acpi_driver_data(to_acpi_device(dev));

	if (acpi_sleep_no_ec_events() && ec_freeze_events)
	if (!pm_suspend_no_platform() && ec_freeze_events)
		acpi_ec_disable_event(ec);
	return 0;
}
@@ -1948,7 +1931,6 @@ static int acpi_ec_suspend_noirq(struct device *dev)
	    ec->reference_count >= 1)
		acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_DISABLE);

	if (acpi_sleep_no_ec_events())
	acpi_ec_enter_noirq(ec);

	return 0;
@@ -1958,7 +1940,6 @@ static int acpi_ec_resume_noirq(struct device *dev)
{
	struct acpi_ec *ec = acpi_driver_data(to_acpi_device(dev));

	if (acpi_sleep_no_ec_events())
	acpi_ec_leave_noirq(ec);

	if (ec_no_wakeup && test_bit(EC_FLAGS_STARTED, &ec->flags) &&
@@ -1976,7 +1957,35 @@ static int acpi_ec_resume(struct device *dev)
	acpi_ec_enable_event(ec);
	return 0;
}
#endif

void acpi_ec_mark_gpe_for_wake(void)
{
	if (first_ec && !ec_no_wakeup)
		acpi_mark_gpe_for_wake(NULL, first_ec->gpe);
}
EXPORT_SYMBOL_GPL(acpi_ec_mark_gpe_for_wake);

void acpi_ec_set_gpe_wake_mask(u8 action)
{
	if (pm_suspend_no_platform() && first_ec && !ec_no_wakeup)
		acpi_set_gpe_wake_mask(NULL, first_ec->gpe, action);
}

bool acpi_ec_dispatch_gpe(void)
{
	u32 ret;

	if (!first_ec)
		return false;

	ret = acpi_dispatch_gpe(NULL, first_ec->gpe);
	if (ret == ACPI_INTERRUPT_HANDLED) {
		pm_pr_dbg("EC GPE dispatched\n");
		return true;
	}
	return false;
}
#endif /* CONFIG_PM_SLEEP */

static const struct dev_pm_ops acpi_ec_pm = {
	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(acpi_ec_suspend_noirq, acpi_ec_resume_noirq)
+1 −5
Original line number Diff line number Diff line
@@ -194,9 +194,6 @@ void acpi_ec_ecdt_probe(void);
void acpi_ec_dsdt_probe(void);
void acpi_ec_block_transactions(void);
void acpi_ec_unblock_transactions(void);
void acpi_ec_mark_gpe_for_wake(void);
void acpi_ec_set_gpe_wake_mask(u8 action);
void acpi_ec_dispatch_gpe(void);
int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit,
			      acpi_handle handle, acpi_ec_query_func func,
			      void *data);
@@ -204,6 +201,7 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit);

#ifdef CONFIG_PM_SLEEP
void acpi_ec_flush_work(void);
bool acpi_ec_dispatch_gpe(void);
#endif


@@ -212,11 +210,9 @@ void acpi_ec_flush_work(void);
  -------------------------------------------------------------------------- */
#ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT
extern bool acpi_s2idle_wakeup(void);
extern bool acpi_sleep_no_ec_events(void);
extern int acpi_sleep_init(void);
#else
static inline bool acpi_s2idle_wakeup(void) { return false; }
static inline bool acpi_sleep_no_ec_events(void) { return true; }
static inline int acpi_sleep_init(void) { return -ENXIO; }
#endif

+88 −77
Original line number Diff line number Diff line
@@ -89,6 +89,10 @@ bool acpi_sleep_state_supported(u8 sleep_state)
}

#ifdef CONFIG_ACPI_SLEEP
static bool sleep_no_lps0 __read_mostly;
module_param(sleep_no_lps0, bool, 0644);
MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface");

static u32 acpi_target_sleep_state = ACPI_STATE_S0;

u32 acpi_target_system_state(void)
@@ -158,11 +162,11 @@ static int __init init_nvs_nosave(const struct dmi_system_id *d)
	return 0;
}

static bool acpi_sleep_no_lps0;
static bool acpi_sleep_default_s3;

static int __init init_no_lps0(const struct dmi_system_id *d)
static int __init init_default_s3(const struct dmi_system_id *d)
{
	acpi_sleep_no_lps0 = true;
	acpi_sleep_default_s3 = true;
	return 0;
}

@@ -363,7 +367,7 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = {
	 * S0 Idle firmware interface.
	 */
	{
	.callback = init_no_lps0,
	.callback = init_default_s3,
	.ident = "Dell XPS13 9360",
	.matches = {
		DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
@@ -376,7 +380,7 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = {
	 * https://bugzilla.kernel.org/show_bug.cgi?id=199057).
	 */
	{
	.callback = init_no_lps0,
	.callback = init_default_s3,
	.ident = "ThinkPad X1 Tablet(2016)",
	.matches = {
		DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@@ -524,8 +528,9 @@ static void acpi_pm_end(void)
	acpi_sleep_tts_switch(acpi_target_sleep_state);
}
#else /* !CONFIG_ACPI_SLEEP */
#define sleep_no_lps0	(1)
#define acpi_target_sleep_state	ACPI_STATE_S0
#define acpi_sleep_no_lps0	(false)
#define acpi_sleep_default_s3	(1)
static inline void acpi_sleep_dmi_check(void) {}
#endif /* CONFIG_ACPI_SLEEP */

@@ -691,7 +696,6 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = {
	.recover = acpi_pm_finish,
};

static bool s2idle_in_progress;
static bool s2idle_wakeup;

/*
@@ -904,41 +908,42 @@ static int lps0_device_attach(struct acpi_device *adev,
	if (lps0_device_handle)
		return 0;

	if (acpi_sleep_no_lps0) {
		acpi_handle_info(adev->handle,
				 "Low Power S0 Idle interface disabled\n");
		return 0;
	}

	if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0))
		return 0;

	guid_parse(ACPI_LPS0_DSM_UUID, &lps0_dsm_guid);
	/* Check if the _DSM is present and as expected. */
	out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 1, 0, NULL);
	if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) {
		char bitmask = *(char *)out_obj->buffer.pointer;
	if (!out_obj || out_obj->type != ACPI_TYPE_BUFFER) {
		acpi_handle_debug(adev->handle,
				  "_DSM function 0 evaluation failed\n");
		return 0;
	}

	lps0_dsm_func_mask = *(char *)out_obj->buffer.pointer;

	ACPI_FREE(out_obj);

	acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n",
			  lps0_dsm_func_mask);

		lps0_dsm_func_mask = bitmask;
	lps0_device_handle = adev->handle;

	lpi_device_get_constraints();

	/*
		 * Use suspend-to-idle by default if the default
		 * suspend mode was not set from the command line.
	 * Use suspend-to-idle by default if the default suspend mode was not
	 * set from the command line.
	 */
		if (mem_sleep_default > PM_SUSPEND_MEM)
	if (mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3)
		mem_sleep_current = PM_SUSPEND_TO_IDLE;

		acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n",
				  bitmask);

	/*
	 * Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the
	 * EC GPE to be enabled while suspended for certain wakeup devices to
	 * work, so mark it as wakeup-capable.
	 */
	acpi_ec_mark_gpe_for_wake();
	} else {
		acpi_handle_debug(adev->handle,
				  "_DSM function 0 evaluation failed\n");
	}
	ACPI_FREE(out_obj);

	lpi_device_get_constraints();

	return 0;
}
@@ -951,98 +956,110 @@ static struct acpi_scan_handler lps0_handler = {
static int acpi_s2idle_begin(void)
{
	acpi_scan_lock_acquire();
	s2idle_in_progress = true;
	return 0;
}

static int acpi_s2idle_prepare(void)
{
	if (lps0_device_handle) {
		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF);
		acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY);

	if (acpi_sci_irq_valid()) {
		enable_irq_wake(acpi_sci_irq);
		acpi_ec_set_gpe_wake_mask(ACPI_GPE_ENABLE);
	}

	if (acpi_sci_irq_valid())
		enable_irq_wake(acpi_sci_irq);

	acpi_enable_wakeup_devices(ACPI_STATE_S0);

	/* Change the configuration of GPEs to avoid spurious wakeup. */
	acpi_enable_all_wakeup_gpes();
	acpi_os_wait_events_complete();

	s2idle_wakeup = true;
	return 0;
}

static void acpi_s2idle_wake(void)
static int acpi_s2idle_prepare_late(void)
{
	if (!lps0_device_handle)
		return;
	if (!lps0_device_handle || sleep_no_lps0)
		return 0;

	if (pm_debug_messages_on)
		lpi_check_constraints();

	acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF);
	acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY);

	return 0;
}

static void acpi_s2idle_wake(void)
{
	/*
	 * If IRQD_WAKEUP_ARMED is not set for the SCI at this point, it means
	 * that the SCI has triggered while suspended, so cancel the wakeup in
	 * case it has not been a wakeup event (the GPEs will be checked later).
	 * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the SCI has
	 * not triggered while suspended, so bail out.
	 */
	if (acpi_sci_irq_valid() &&
	    !irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) {
		pm_system_cancel_wakeup();
		s2idle_wakeup = true;
	if (!acpi_sci_irq_valid() ||
	    irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq)))
		return;

	/*
		 * On some platforms with the LPS0 _DSM device noirq resume
		 * takes too much time for EC wakeup events to survive, so look
		 * for them now.
	 * If there are EC events to process, the wakeup may be a spurious one
	 * coming from the EC.
	 */
		acpi_ec_dispatch_gpe();
	}
}

static void acpi_s2idle_sync(void)
{
	if (acpi_ec_dispatch_gpe()) {
		/*
	 * Process all pending events in case there are any wakeup ones.
		 * Cancel the wakeup and process all pending events in case
		 * there are any wakeup ones in there.
		 *
	 * The EC driver uses the system workqueue and an additional special
	 * one, so those need to be flushed too.
		 * Note that if any non-EC GPEs are active at this point, the
		 * SCI will retrigger after the rearming below, so no events
		 * should be missed by canceling the wakeup here.
		 */
	acpi_os_wait_events_complete();	/* synchronize SCI IRQ handling */
		pm_system_cancel_wakeup();
		/*
		 * The EC driver uses the system workqueue and an additional
		 * special one, so those need to be flushed too.
		 */
		acpi_os_wait_events_complete(); /* synchronize EC GPE processing */
		acpi_ec_flush_work();
		acpi_os_wait_events_complete(); /* synchronize Notify handling */
	s2idle_wakeup = false;

		rearm_wake_irq(acpi_sci_irq);
	}
}

static void acpi_s2idle_restore_early(void)
{
	if (!lps0_device_handle || sleep_no_lps0)
		return;

	acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT);
	acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON);
}

static void acpi_s2idle_restore(void)
{
	s2idle_wakeup = false;

	acpi_enable_all_runtime_gpes();

	acpi_disable_wakeup_devices(ACPI_STATE_S0);

	if (acpi_sci_irq_valid())
		disable_irq_wake(acpi_sci_irq);

	if (lps0_device_handle) {
	if (acpi_sci_irq_valid()) {
		acpi_ec_set_gpe_wake_mask(ACPI_GPE_DISABLE);

		acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT);
		acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON);
		disable_irq_wake(acpi_sci_irq);
	}
}

static void acpi_s2idle_end(void)
{
	s2idle_in_progress = false;
	acpi_scan_lock_release();
}

static const struct platform_s2idle_ops acpi_s2idle_ops = {
	.begin = acpi_s2idle_begin,
	.prepare = acpi_s2idle_prepare,
	.prepare_late = acpi_s2idle_prepare_late,
	.wake = acpi_s2idle_wake,
	.sync = acpi_s2idle_sync,
	.restore_early = acpi_s2idle_restore_early,
	.restore = acpi_s2idle_restore,
	.end = acpi_s2idle_end,
};
@@ -1063,7 +1080,6 @@ static void acpi_sleep_suspend_setup(void)
}

#else /* !CONFIG_SUSPEND */
#define s2idle_in_progress	(false)
#define s2idle_wakeup		(false)
#define lps0_device_handle	(NULL)
static inline void acpi_sleep_suspend_setup(void) {}
@@ -1074,11 +1090,6 @@ bool acpi_s2idle_wakeup(void)
	return s2idle_wakeup;
}

bool acpi_sleep_no_ec_events(void)
{
	return !s2idle_in_progress || !lps0_device_handle;
}

#ifdef CONFIG_PM_SLEEP
static u32 saved_bm_rld;

+12 −23
Original line number Diff line number Diff line
@@ -716,7 +716,7 @@ static void async_resume_noirq(void *data, async_cookie_t cookie)
	put_device(dev);
}

void dpm_noirq_resume_devices(pm_message_t state)
static void dpm_noirq_resume_devices(pm_message_t state)
{
	struct device *dev;
	ktime_t starttime = ktime_get();
@@ -760,13 +760,6 @@ void dpm_noirq_resume_devices(pm_message_t state)
	trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
}

void dpm_noirq_end(void)
{
	resume_device_irqs();
	device_wakeup_disarm_wake_irqs();
	cpuidle_resume();
}

/**
 * dpm_resume_noirq - Execute "noirq resume" callbacks for all devices.
 * @state: PM transition of the system being carried out.
@@ -777,7 +770,11 @@ void dpm_noirq_end(void)
void dpm_resume_noirq(pm_message_t state)
{
	dpm_noirq_resume_devices(state);
	dpm_noirq_end();

	resume_device_irqs();
	device_wakeup_disarm_wake_irqs();

	cpuidle_resume();
}

static pm_callback_t dpm_subsys_resume_early_cb(struct device *dev,
@@ -1291,11 +1288,6 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
	if (async_error)
		goto Complete;

	if (pm_wakeup_pending()) {
		async_error = -EBUSY;
		goto Complete;
	}

	if (dev->power.syscore || dev->power.direct_complete)
		goto Complete;

@@ -1362,14 +1354,7 @@ static int device_suspend_noirq(struct device *dev)
	return __device_suspend_noirq(dev, pm_transition, false);
}

void dpm_noirq_begin(void)
{
	cpuidle_pause();
	device_wakeup_arm_wake_irqs();
	suspend_device_irqs();
}

int dpm_noirq_suspend_devices(pm_message_t state)
static int dpm_noirq_suspend_devices(pm_message_t state)
{
	ktime_t starttime = ktime_get();
	int error = 0;
@@ -1426,7 +1411,11 @@ int dpm_suspend_noirq(pm_message_t state)
{
	int ret;

	dpm_noirq_begin();
	cpuidle_pause();

	device_wakeup_arm_wake_irqs();
	suspend_device_irqs();

	ret = dpm_noirq_suspend_devices(state);
	if (ret)
		dpm_resume_noirq(resume_event(state));
Loading