Commit d2817063 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

Merge branch 'pm-sleep'

* pm-sleep: (29 commits)
  ACPI: PM: s2idle: Always set up EC GPE for system wakeup
  ACPI: PM: s2idle: Avoid rearming SCI for wakeup unnecessarily
  PM / wakeup: Unexport wakeup_source_sysfs_{add,remove}()
  PM / wakeup: Register wakeup class kobj after device is added
  PM / wakeup: Fix sysfs registration error path
  PM / wakeup: Show wakeup sources stats in sysfs
  PM / wakeup: Use wakeup_source_register() in wakelock.c
  PM / wakeup: Drop wakeup_source_init(), wakeup_source_prepare()
  PM: sleep: Replace strncmp() with str_has_prefix()
  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()
  PM/sleep: Expose suspend stats in sysfs
  ...
parents 0760bb9a 1b531e55
What: /sys/class/wakeup/
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
The /sys/class/wakeup/ directory contains pointers to all
wakeup sources in the kernel at that moment in time.
What: /sys/class/wakeup/.../name
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the name of the wakeup source.
What: /sys/class/wakeup/.../active_count
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the number of times the wakeup source was
activated.
What: /sys/class/wakeup/.../event_count
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the number of signaled wakeup events
associated with the wakeup source.
What: /sys/class/wakeup/.../wakeup_count
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the number of times the wakeup source might
abort suspend.
What: /sys/class/wakeup/.../expire_count
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the number of times the wakeup source's
timeout has expired.
What: /sys/class/wakeup/.../active_time_ms
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the amount of time the wakeup source has
been continuously active, in milliseconds. If the wakeup
source is not active, this file contains '0'.
What: /sys/class/wakeup/.../total_time_ms
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the total amount of time this wakeup source
has been active, in milliseconds.
What: /sys/class/wakeup/.../max_time_ms
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the maximum amount of time this wakeup
source has been continuously active, in milliseconds.
What: /sys/class/wakeup/.../last_change_ms
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
This file contains the monotonic clock time when the wakeup
source was touched last time, in milliseconds.
What: /sys/class/wakeup/.../prevent_suspend_time_ms
Date: June 2019
Contact: Tri Vo <trong@android.com>
Description:
The file contains the total amount of time this wakeup source
has been preventing autosleep, in milliseconds.
...@@ -301,3 +301,109 @@ Description: ...@@ -301,3 +301,109 @@ Description:
Using this sysfs file will override any values that were Using this sysfs file will override any values that were
set using the kernel command line for disk offset. set using the kernel command line for disk offset.
What: /sys/power/suspend_stats
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats directory contains suspend related
statistics.
What: /sys/power/suspend_stats/success
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/success file contains the number
of times entering system sleep state succeeded.
What: /sys/power/suspend_stats/fail
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/fail file contains the number
of times entering system sleep state failed.
What: /sys/power/suspend_stats/failed_freeze
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_freeze file contains the
number of times freezing processes failed.
What: /sys/power/suspend_stats/failed_prepare
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_prepare file contains the
number of times preparing all non-sysdev devices for
a system PM transition failed.
What: /sys/power/suspend_stats/failed_resume
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_resume file contains the
number of times executing "resume" callbacks of
non-sysdev devices failed.
What: /sys/power/suspend_stats/failed_resume_early
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_resume_early file contains
the number of times executing "early resume" callbacks
of devices failed.
What: /sys/power/suspend_stats/failed_resume_noirq
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_resume_noirq file contains
the number of times executing "noirq resume" callbacks
of devices failed.
What: /sys/power/suspend_stats/failed_suspend
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_suspend file contains
the number of times executing "suspend" callbacks
of all non-sysdev devices failed.
What: /sys/power/suspend_stats/failed_suspend_late
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_suspend_late file contains
the number of times executing "late suspend" callbacks
of all devices failed.
What: /sys/power/suspend_stats/failed_suspend_noirq
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/failed_suspend_noirq file contains
the number of times executing "noirq suspend" callbacks
of all devices failed.
What: /sys/power/suspend_stats/last_failed_dev
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/last_failed_dev file contains
the last device for which a suspend/resume callback failed.
What: /sys/power/suspend_stats/last_failed_errno
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/last_failed_errno file contains
the errno of the last failed attempt at entering
system sleep state.
What: /sys/power/suspend_stats/last_failed_step
Date: July 2019
Contact: Kalesh Singh <kaleshsingh96@gmail.com>
Description:
The /sys/power/suspend_stats/last_failed_step file contains
the last failed step in the suspend/resume path.
...@@ -644,17 +644,17 @@ ACPI_EXPORT_SYMBOL(acpi_get_gpe_status) ...@@ -644,17 +644,17 @@ ACPI_EXPORT_SYMBOL(acpi_get_gpe_status)
* PARAMETERS: gpe_device - Parent GPE Device. NULL for GPE0/GPE1 * PARAMETERS: gpe_device - Parent GPE Device. NULL for GPE0/GPE1
* gpe_number - GPE level within the GPE block * 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 * DESCRIPTION: Detect and dispatch a General Purpose Event to either a function
* (e.g. EC) or method (e.g. _Lxx/_Exx) handler. * (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_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) ACPI_EXPORT_SYMBOL(acpi_dispatch_gpe)
......
...@@ -497,7 +497,8 @@ acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev, ...@@ -497,7 +497,8 @@ acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev,
goto out; goto out;
mutex_lock(&acpi_pm_notifier_lock); mutex_lock(&acpi_pm_notifier_lock);
adev->wakeup.ws = wakeup_source_register(dev_name(&adev->dev)); adev->wakeup.ws = wakeup_source_register(&adev->dev,
dev_name(&adev->dev));
adev->wakeup.context.dev = dev; adev->wakeup.context.dev = dev;
adev->wakeup.context.func = func; adev->wakeup.context.func = func;
adev->wakeup.flags.notifier_present = true; adev->wakeup.flags.notifier_present = true;
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <linux/list.h> #include <linux/list.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/acpi.h> #include <linux/acpi.h>
#include <linux/dmi.h> #include <linux/dmi.h>
#include <asm/io.h> #include <asm/io.h>
...@@ -1048,24 +1049,6 @@ void acpi_ec_unblock_transactions(void) ...@@ -1048,24 +1049,6 @@ void acpi_ec_unblock_transactions(void)
acpi_ec_start(first_ec, true); 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 Event Management
-------------------------------------------------------------------------- */ -------------------------------------------------------------------------- */
...@@ -1931,7 +1914,7 @@ static int acpi_ec_suspend(struct device *dev) ...@@ -1931,7 +1914,7 @@ static int acpi_ec_suspend(struct device *dev)
struct acpi_ec *ec = struct acpi_ec *ec =
acpi_driver_data(to_acpi_device(dev)); 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); acpi_ec_disable_event(ec);
return 0; return 0;
} }
...@@ -1948,7 +1931,6 @@ static int acpi_ec_suspend_noirq(struct device *dev) ...@@ -1948,7 +1931,6 @@ static int acpi_ec_suspend_noirq(struct device *dev)
ec->reference_count >= 1) ec->reference_count >= 1)
acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_DISABLE); acpi_set_gpe(NULL, ec->gpe, ACPI_GPE_DISABLE);
if (acpi_sleep_no_ec_events())
acpi_ec_enter_noirq(ec); acpi_ec_enter_noirq(ec);
return 0; return 0;
...@@ -1958,7 +1940,6 @@ static int acpi_ec_resume_noirq(struct device *dev) ...@@ -1958,7 +1940,6 @@ static int acpi_ec_resume_noirq(struct device *dev)
{ {
struct acpi_ec *ec = acpi_driver_data(to_acpi_device(dev)); struct acpi_ec *ec = acpi_driver_data(to_acpi_device(dev));
if (acpi_sleep_no_ec_events())
acpi_ec_leave_noirq(ec); acpi_ec_leave_noirq(ec);
if (ec_no_wakeup && test_bit(EC_FLAGS_STARTED, &ec->flags) && if (ec_no_wakeup && test_bit(EC_FLAGS_STARTED, &ec->flags) &&
...@@ -1976,7 +1957,35 @@ static int acpi_ec_resume(struct device *dev) ...@@ -1976,7 +1957,35 @@ static int acpi_ec_resume(struct device *dev)
acpi_ec_enable_event(ec); acpi_ec_enable_event(ec);
return 0; 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 = { static const struct dev_pm_ops acpi_ec_pm = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(acpi_ec_suspend_noirq, acpi_ec_resume_noirq) SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(acpi_ec_suspend_noirq, acpi_ec_resume_noirq)
......
...@@ -194,9 +194,6 @@ void acpi_ec_ecdt_probe(void); ...@@ -194,9 +194,6 @@ void acpi_ec_ecdt_probe(void);
void acpi_ec_dsdt_probe(void); void acpi_ec_dsdt_probe(void);
void acpi_ec_block_transactions(void); void acpi_ec_block_transactions(void);
void acpi_ec_unblock_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, int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit,
acpi_handle handle, acpi_ec_query_func func, acpi_handle handle, acpi_ec_query_func func,
void *data); void *data);
...@@ -204,6 +201,7 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit); ...@@ -204,6 +201,7 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit);
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
void acpi_ec_flush_work(void); void acpi_ec_flush_work(void);
bool acpi_ec_dispatch_gpe(void);
#endif #endif
...@@ -212,11 +210,9 @@ void acpi_ec_flush_work(void); ...@@ -212,11 +210,9 @@ void acpi_ec_flush_work(void);
-------------------------------------------------------------------------- */ -------------------------------------------------------------------------- */
#ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT #ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT
extern bool acpi_s2idle_wakeup(void); extern bool acpi_s2idle_wakeup(void);
extern bool acpi_sleep_no_ec_events(void);
extern int acpi_sleep_init(void); extern int acpi_sleep_init(void);
#else #else
static inline bool acpi_s2idle_wakeup(void) { return false; } 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; } static inline int acpi_sleep_init(void) { return -ENXIO; }
#endif #endif
......
...@@ -89,6 +89,10 @@ bool acpi_sleep_state_supported(u8 sleep_state) ...@@ -89,6 +89,10 @@ bool acpi_sleep_state_supported(u8 sleep_state)
} }
#ifdef CONFIG_ACPI_SLEEP #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; static u32 acpi_target_sleep_state = ACPI_STATE_S0;
u32 acpi_target_system_state(void) u32 acpi_target_system_state(void)
...@@ -158,11 +162,11 @@ static int __init init_nvs_nosave(const struct dmi_system_id *d) ...@@ -158,11 +162,11 @@ static int __init init_nvs_nosave(const struct dmi_system_id *d)
return 0; 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; return 0;
} }
...@@ -363,7 +367,7 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = { ...@@ -363,7 +367,7 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = {
* S0 Idle firmware interface. * S0 Idle firmware interface.
*/ */
{ {
.callback = init_no_lps0, .callback = init_default_s3,
.ident = "Dell XPS13 9360", .ident = "Dell XPS13 9360",
.matches = { .matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
...@@ -376,7 +380,7 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = { ...@@ -376,7 +380,7 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = {
* https://bugzilla.kernel.org/show_bug.cgi?id=199057). * https://bugzilla.kernel.org/show_bug.cgi?id=199057).
*/ */
{ {
.callback = init_no_lps0, .callback = init_default_s3,
.ident = "ThinkPad X1 Tablet(2016)", .ident = "ThinkPad X1 Tablet(2016)",
.matches = { .matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
...@@ -524,8 +528,9 @@ static void acpi_pm_end(void) ...@@ -524,8 +528,9 @@ static void acpi_pm_end(void)
acpi_sleep_tts_switch(acpi_target_sleep_state); acpi_sleep_tts_switch(acpi_target_sleep_state);
} }
#else /* !CONFIG_ACPI_SLEEP */ #else /* !CONFIG_ACPI_SLEEP */
#define sleep_no_lps0 (1)
#define acpi_target_sleep_state ACPI_STATE_S0 #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) {} static inline void acpi_sleep_dmi_check(void) {}
#endif /* CONFIG_ACPI_SLEEP */ #endif /* CONFIG_ACPI_SLEEP */
...@@ -691,7 +696,6 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = { ...@@ -691,7 +696,6 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = {
.recover = acpi_pm_finish, .recover = acpi_pm_finish,
}; };
static bool s2idle_in_progress;
static bool s2idle_wakeup; static bool s2idle_wakeup;
/* /*
...@@ -904,41 +908,42 @@ static int lps0_device_attach(struct acpi_device *adev, ...@@ -904,41 +908,42 @@ static int lps0_device_attach(struct acpi_device *adev,
if (lps0_device_handle) if (lps0_device_handle)
return 0; 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)) if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0))
return 0; return 0;
guid_parse(ACPI_LPS0_DSM_UUID, &lps0_dsm_guid); guid_parse(ACPI_LPS0_DSM_UUID, &lps0_dsm_guid);
/* Check if the _DSM is present and as expected. */ /* Check if the _DSM is present and as expected. */
out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 1, 0, NULL); out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 1, 0, NULL);
if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) { if (!out_obj || out_obj->type != ACPI_TYPE_BUFFER) {
char bitmask = *(char *)out_obj->buffer.pointer; 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; lps0_device_handle = adev->handle;
lpi_device_get_constraints();
/* /*
* Use suspend-to-idle by default if the default * Use suspend-to-idle by default if the default suspend mode was not
* suspend mode was not set from the command line. * 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; 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(); 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; return 0;
} }
...@@ -951,98 +956,110 @@ static struct acpi_scan_handler lps0_handler = { ...@@ -951,98 +956,110 @@ static struct acpi_scan_handler lps0_handler = {
static int acpi_s2idle_begin(void) static int acpi_s2idle_begin(void)
{ {
acpi_scan_lock_acquire(); acpi_scan_lock_acquire();
s2idle_in_progress = true;
return 0; return 0;
} }
static int acpi_s2idle_prepare(void) static int acpi_s2idle_prepare(void)
{ {
if (lps0_device_handle) { if (acpi_sci_irq_valid()) {
acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF); enable_irq_wake(acpi_sci_irq);
acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY);
acpi_ec_set_gpe_wake_mask(ACPI_GPE_ENABLE); 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); acpi_enable_wakeup_devices(ACPI_STATE_S0);
/* Change the configuration of GPEs to avoid spurious wakeup. */ /* Change the configuration of GPEs to avoid spurious wakeup. */
acpi_enable_all_wakeup_gpes(); acpi_enable_all_wakeup_gpes();
acpi_os_wait_events_complete(); acpi_os_wait_events_complete();
s2idle_wakeup = true;
return 0; return 0;
} }
static void acpi_s2idle_wake(void) static int acpi_s2idle_prepare_late(void)
{ {
if (!lps0_device_handle) if (!lps0_device_handle || sleep_no_lps0)
return; return 0;
if (pm_debug_messages_on) if (pm_debug_messages_on)
lpi_check_constraints(); 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 * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the SCI has
* that the SCI has triggered while suspended, so cancel the wakeup in * not triggered while suspended, so bail out.
* case it has not been a wakeup event (the GPEs will be checked later).
*/ */
if (acpi_sci_irq_valid() && if (!acpi_sci_irq_valid() ||
!irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) { irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq)))
pm_system_cancel_wakeup(); return;
s2idle_wakeup = true;
/* /*
* On some platforms with the LPS0 _DSM device noirq resume * If there are EC events to process, the wakeup may be a spurious one
* takes too much time for EC wakeup events to survive, so look * coming from the EC.
* for them now.
*/ */
acpi_ec_dispatch_gpe(); if (acpi_ec_dispatch_gpe()) {
}
}
static void acpi_s2idle_sync(void)
{
/* /*
* 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 * Note that if any non-EC GPEs are active at this point, the
* one, so those need to be flushed too. * 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_ec_flush_work();
acpi_os_wait_events_complete(); /* synchronize Notify handling */ 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) static void acpi_s2idle_restore(void)
{ {
s2idle_wakeup = false;
acpi_enable_all_runtime_gpes(); acpi_enable_all_runtime_gpes();
acpi_disable_wakeup_devices(ACPI_STATE_S0); acpi_disable_wakeup_devices(ACPI_STATE_S0);
if (acpi_sci_irq_valid()) if (acpi_sci_irq_valid()) {
disable_irq_wake(acpi_sci_irq);
if (lps0_device_handle) {
acpi_ec_set_gpe_wake_mask(ACPI_GPE_DISABLE); acpi_ec_set_gpe_wake_mask(ACPI_GPE_DISABLE);
disable_irq_wake(acpi_sci_irq);
acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT);
acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON);
} }
} }
static void acpi_s2idle_end(void) static void acpi_s2idle_end(void)
{ {
s2idle_in_progress = false;
acpi_scan_lock_release(); acpi_scan_lock_release();
} }
static const struct platform_s2idle_ops acpi_s2idle_ops = { static const struct platform_s2idle_ops acpi_s2idle_ops = {
.begin = acpi_s2idle_begin, .begin = acpi_s2idle_begin,
.prepare = acpi_s2idle_prepare, .prepare = acpi_s2idle_prepare,
.prepare_late = acpi_s2idle_prepare_late,
.wake = acpi_s2idle_wake, .wake = acpi_s2idle_wake,
.sync = acpi_s2idle_sync, .restore_early = acpi_s2idle_restore_early,
.restore = acpi_s2idle_restore, .restore = acpi_s2idle_restore,
.end = acpi_s2idle_end, .end = acpi_s2idle_end,
}; };
...@@ -1063,7 +1080,6 @@ static void acpi_sleep_suspend_setup(void) ...@@ -1063,7 +1080,6 @@ static void acpi_sleep_suspend_setup(void)
} }
#else /* !CONFIG_SUSPEND */ #else /* !CONFIG_SUSPEND */
#define s2idle_in_progress (false)
#define s2idle_wakeup (false) #define s2idle_wakeup (false)
#define lps0_device_handle (NULL) #define lps0_device_handle (NULL)
static inline void acpi_sleep_suspend_setup(void) {} static inline void acpi_sleep_suspend_setup(void) {}
...@@ -1074,11 +1090,6 @@ bool acpi_s2idle_wakeup(void) ...@@ -1074,11 +1090,6 @@ bool acpi_s2idle_wakeup(void)
return s2idle_wakeup; return s2idle_wakeup;
} }
bool acpi_sleep_no_ec_events(void)
{
return !s2idle_in_progress || !lps0_device_handle;
}
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
static u32 saved_bm_rld; static u32 saved_bm_rld;
......
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o
obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o
obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o
obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o
obj-$(CONFIG_HAVE_CLK) += clock_ops.o obj-$(CONFIG_HAVE_CLK) += clock_ops.o
......
...@@ -716,7 +716,7 @@ static void async_resume_noirq(void *data, async_cookie_t cookie) ...@@ -716,7 +716,7 @@ static void async_resume_noirq(void *data, async_cookie_t cookie)
put_device(dev); 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; struct device *dev;
ktime_t starttime = ktime_get(); ktime_t starttime = ktime_get();
...@@ -760,13 +760,6 @@ void dpm_noirq_resume_devices(pm_message_t state) ...@@ -760,13 +760,6 @@ void dpm_noirq_resume_devices(pm_message_t state)
trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false); 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. * dpm_resume_noirq - Execute "noirq resume" callbacks for all devices.
* @state: PM transition of the system being carried out. * @state: PM transition of the system being carried out.
...@@ -777,7 +770,11 @@ void dpm_noirq_end(void) ...@@ -777,7 +770,11 @@ void dpm_noirq_end(void)
void dpm_resume_noirq(pm_message_t state) void dpm_resume_noirq(pm_message_t state)
{ {
dpm_noirq_resume_devices(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, 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 ...@@ -1291,11 +1288,6 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
if (async_error) if (async_error)
goto Complete; goto Complete;
if (pm_wakeup_pending()) {
async_error = -EBUSY;
goto Complete;
}
if (dev->power.syscore || dev->power.direct_complete) if (dev->power.syscore || dev->power.direct_complete)
goto Complete; goto Complete;
...@@ -1362,14 +1354,7 @@ static int device_suspend_noirq(struct device *dev) ...@@ -1362,14 +1354,7 @@ static int device_suspend_noirq(struct device *dev)
return __device_suspend_noirq(dev, pm_transition, false); return __device_suspend_noirq(dev, pm_transition, false);
} }
void dpm_noirq_begin(void) static int dpm_noirq_suspend_devices(pm_message_t state)
{
cpuidle_pause();
device_wakeup_arm_wake_irqs();
suspend_device_irqs();
}
int dpm_noirq_suspend_devices(pm_message_t state)
{ {
ktime_t starttime = ktime_get(); ktime_t starttime = ktime_get();
int error = 0; int error = 0;
...@@ -1426,7 +1411,11 @@ int dpm_suspend_noirq(pm_message_t state) ...@@ -1426,7 +1411,11 @@ int dpm_suspend_noirq(pm_message_t state)
{ {
int ret; int ret;
dpm_noirq_begin(); cpuidle_pause();
device_wakeup_arm_wake_irqs();
suspend_device_irqs();
ret = dpm_noirq_suspend_devices(state); ret = dpm_noirq_suspend_devices(state);
if (ret) if (ret)
dpm_resume_noirq(resume_event(state)); dpm_resume_noirq(resume_event(state));
......
...@@ -149,3 +149,21 @@ static inline void device_pm_init(struct device *dev) ...@@ -149,3 +149,21 @@ static inline void device_pm_init(struct device *dev)
device_pm_sleep_init(dev); device_pm_sleep_init(dev);
pm_runtime_init(dev); pm_runtime_init(dev);
} }
#ifdef CONFIG_PM_SLEEP
/* drivers/base/power/wakeup_stats.c */
extern int wakeup_source_sysfs_add(struct device *parent,
struct wakeup_source *ws);
extern void wakeup_source_sysfs_remove(struct wakeup_source *ws);
extern int pm_wakeup_source_sysfs_add(struct device *parent);
#else /* !CONFIG_PM_SLEEP */
static inline int pm_wakeup_source_sysfs_add(struct device *parent)
{
return 0;
}
#endif /* CONFIG_PM_SLEEP */
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include <linux/export.h> #include <linux/export.h>
#include <linux/pm_qos.h> #include <linux/pm_qos.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/pm_wakeup.h>
#include <linux/atomic.h> #include <linux/atomic.h>
#include <linux/jiffies.h> #include <linux/jiffies.h>
#include "power.h" #include "power.h"
...@@ -667,8 +668,13 @@ int dpm_sysfs_add(struct device *dev) ...@@ -667,8 +668,13 @@ int dpm_sysfs_add(struct device *dev)
if (rc) if (rc)
goto err_wakeup; goto err_wakeup;
} }
rc = pm_wakeup_source_sysfs_add(dev);
if (rc)
goto err_latency;
return 0; return 0;
err_latency:
sysfs_unmerge_group(&dev->kobj, &pm_qos_latency_tolerance_attr_group);
err_wakeup: err_wakeup:
sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group);
err_runtime: err_runtime:
......
...@@ -72,22 +72,7 @@ static struct wakeup_source deleted_ws = { ...@@ -72,22 +72,7 @@ static struct wakeup_source deleted_ws = {
.lock = __SPIN_LOCK_UNLOCKED(deleted_ws.lock), .lock = __SPIN_LOCK_UNLOCKED(deleted_ws.lock),
}; };
/** static DEFINE_IDA(wakeup_ida);
* wakeup_source_prepare - Prepare a new wakeup source for initialization.
* @ws: Wakeup source to prepare.
* @name: Pointer to the name of the new wakeup source.
*
* Callers must ensure that the @name string won't be freed when @ws is still in
* use.
*/
void wakeup_source_prepare(struct wakeup_source *ws, const char *name)
{
if (ws) {
memset(ws, 0, sizeof(*ws));
ws->name = name;
}
}
EXPORT_SYMBOL_GPL(wakeup_source_prepare);
/** /**
* wakeup_source_create - Create a struct wakeup_source object. * wakeup_source_create - Create a struct wakeup_source object.
...@@ -96,13 +81,31 @@ EXPORT_SYMBOL_GPL(wakeup_source_prepare); ...@@ -96,13 +81,31 @@ EXPORT_SYMBOL_GPL(wakeup_source_prepare);
struct wakeup_source *wakeup_source_create(const char *name) struct wakeup_source *wakeup_source_create(const char *name)
{ {
struct wakeup_source *ws; struct wakeup_source *ws;
const char *ws_name;
int id;
ws = kmalloc(sizeof(*ws), GFP_KERNEL); ws = kzalloc(sizeof(*ws), GFP_KERNEL);
if (!ws) if (!ws)
return NULL; goto err_ws;
ws_name = kstrdup_const(name, GFP_KERNEL);
if (!ws_name)
goto err_name;
ws->name = ws_name;
id = ida_alloc(&wakeup_ida, GFP_KERNEL);
if (id < 0)
goto err_id;
ws->id = id;
wakeup_source_prepare(ws, name ? kstrdup_const(name, GFP_KERNEL) : NULL);
return ws; return ws;
err_id:
kfree_const(ws->name);
err_name:
kfree(ws);
err_ws:
return NULL;
} }
EXPORT_SYMBOL_GPL(wakeup_source_create); EXPORT_SYMBOL_GPL(wakeup_source_create);
...@@ -134,6 +137,13 @@ static void wakeup_source_record(struct wakeup_source *ws) ...@@ -134,6 +137,13 @@ static void wakeup_source_record(struct wakeup_source *ws)
spin_unlock_irqrestore(&deleted_ws.lock, flags); spin_unlock_irqrestore(&deleted_ws.lock, flags);
} }
static void wakeup_source_free(struct wakeup_source *ws)
{
ida_free(&wakeup_ida, ws->id);
kfree_const(ws->name);
kfree(ws);
}
/** /**
* wakeup_source_destroy - Destroy a struct wakeup_source object. * wakeup_source_destroy - Destroy a struct wakeup_source object.
* @ws: Wakeup source to destroy. * @ws: Wakeup source to destroy.
...@@ -147,8 +157,7 @@ void wakeup_source_destroy(struct wakeup_source *ws) ...@@ -147,8 +157,7 @@ void wakeup_source_destroy(struct wakeup_source *ws)
__pm_relax(ws); __pm_relax(ws);
wakeup_source_record(ws); wakeup_source_record(ws);
kfree_const(ws->name); wakeup_source_free(ws);
kfree(ws);
} }
EXPORT_SYMBOL_GPL(wakeup_source_destroy); EXPORT_SYMBOL_GPL(wakeup_source_destroy);
...@@ -200,16 +209,26 @@ EXPORT_SYMBOL_GPL(wakeup_source_remove); ...@@ -200,16 +209,26 @@ EXPORT_SYMBOL_GPL(wakeup_source_remove);
/** /**
* wakeup_source_register - Create wakeup source and add it to the list. * wakeup_source_register - Create wakeup source and add it to the list.
* @dev: Device this wakeup source is associated with (or NULL if virtual).
* @name: Name of the wakeup source to register. * @name: Name of the wakeup source to register.
*/ */
struct wakeup_source *wakeup_source_register(const char *name) struct wakeup_source *wakeup_source_register(struct device *dev,
const char *name)
{ {
struct wakeup_source *ws; struct wakeup_source *ws;
int ret;
ws = wakeup_source_create(name); ws = wakeup_source_create(name);
if (ws) if (ws) {
if (!dev || device_is_registered(dev)) {
ret = wakeup_source_sysfs_add(dev, ws);
if (ret) {
wakeup_source_free(ws);
return NULL;
}
}
wakeup_source_add(ws); wakeup_source_add(ws);
}
return ws; return ws;
} }
EXPORT_SYMBOL_GPL(wakeup_source_register); EXPORT_SYMBOL_GPL(wakeup_source_register);
...@@ -222,6 +241,7 @@ void wakeup_source_unregister(struct wakeup_source *ws) ...@@ -222,6 +241,7 @@ void wakeup_source_unregister(struct wakeup_source *ws)
{ {
if (ws) { if (ws) {
wakeup_source_remove(ws); wakeup_source_remove(ws);
wakeup_source_sysfs_remove(ws);
wakeup_source_destroy(ws); wakeup_source_destroy(ws);
} }
} }
...@@ -265,7 +285,7 @@ int device_wakeup_enable(struct device *dev) ...@@ -265,7 +285,7 @@ int device_wakeup_enable(struct device *dev)
if (pm_suspend_target_state != PM_SUSPEND_ON) if (pm_suspend_target_state != PM_SUSPEND_ON)
dev_dbg(dev, "Suspicious %s() during system transition!\n", __func__); dev_dbg(dev, "Suspicious %s() during system transition!\n", __func__);
ws = wakeup_source_register(dev_name(dev)); ws = wakeup_source_register(dev, dev_name(dev));
if (!ws) if (!ws)
return -ENOMEM; return -ENOMEM;
...@@ -859,7 +879,7 @@ EXPORT_SYMBOL_GPL(pm_system_wakeup); ...@@ -859,7 +879,7 @@ EXPORT_SYMBOL_GPL(pm_system_wakeup);
void pm_system_cancel_wakeup(void) void pm_system_cancel_wakeup(void)
{ {
atomic_dec(&pm_abort_suspend); atomic_dec_if_positive(&pm_abort_suspend);
} }
void pm_wakeup_clear(bool reset) void pm_wakeup_clear(bool reset)
......
// SPDX-License-Identifier: GPL-2.0
/*
* Wakeup statistics in sysfs
*
* Copyright (c) 2019 Linux Foundation
* Copyright (c) 2019 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
* Copyright (c) 2019 Google Inc.
*/
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/slab.h>
#include <linux/timekeeping.h>
#include "power.h"
static struct class *wakeup_class;
#define wakeup_attr(_name) \
static ssize_t _name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct wakeup_source *ws = dev_get_drvdata(dev); \
\
return sprintf(buf, "%lu\n", ws->_name); \
} \
static DEVICE_ATTR_RO(_name)
wakeup_attr(active_count);
wakeup_attr(event_count);
wakeup_attr(wakeup_count);
wakeup_attr(expire_count);
static ssize_t active_time_ms_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wakeup_source *ws = dev_get_drvdata(dev);
ktime_t active_time =
ws->active ? ktime_sub(ktime_get(), ws->last_time) : 0;
return sprintf(buf, "%lld\n", ktime_to_ms(active_time));
}
static DEVICE_ATTR_RO(active_time_ms);
static ssize_t total_time_ms_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wakeup_source *ws = dev_get_drvdata(dev);
ktime_t active_time;
ktime_t total_time = ws->total_time;
if (ws->active) {
active_time = ktime_sub(ktime_get(), ws->last_time);
total_time = ktime_add(total_time, active_time);
}
return sprintf(buf, "%lld\n", ktime_to_ms(total_time));
}
static DEVICE_ATTR_RO(total_time_ms);
static ssize_t max_time_ms_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wakeup_source *ws = dev_get_drvdata(dev);
ktime_t active_time;
ktime_t max_time = ws->max_time;
if (ws->active) {
active_time = ktime_sub(ktime_get(), ws->last_time);
if (active_time > max_time)
max_time = active_time;
}
return sprintf(buf, "%lld\n", ktime_to_ms(max_time));
}
static DEVICE_ATTR_RO(max_time_ms);
static ssize_t last_change_ms_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wakeup_source *ws = dev_get_drvdata(dev);
return sprintf(buf, "%lld\n", ktime_to_ms(ws->last_time));
}
static DEVICE_ATTR_RO(last_change_ms);
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct wakeup_source *ws = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", ws->name);
}
static DEVICE_ATTR_RO(name);
static ssize_t prevent_suspend_time_ms_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct wakeup_source *ws = dev_get_drvdata(dev);
ktime_t prevent_sleep_time = ws->prevent_sleep_time;
if (ws->active && ws->autosleep_enabled) {
prevent_sleep_time = ktime_add(prevent_sleep_time,
ktime_sub(ktime_get(), ws->start_prevent_time));
}
return sprintf(buf, "%lld\n", ktime_to_ms(prevent_sleep_time));
}
static DEVICE_ATTR_RO(prevent_suspend_time_ms);
static struct attribute *wakeup_source_attrs[] = {
&dev_attr_name.attr,
&dev_attr_active_count.attr,
&dev_attr_event_count.attr,
&dev_attr_wakeup_count.attr,
&dev_attr_expire_count.attr,
&dev_attr_active_time_ms.attr,
&dev_attr_total_time_ms.attr,
&dev_attr_max_time_ms.attr,
&dev_attr_last_change_ms.attr,
&dev_attr_prevent_suspend_time_ms.attr,
NULL,
};
ATTRIBUTE_GROUPS(wakeup_source);
static void device_create_release(struct device *dev)
{
kfree(dev);
}
static struct device *wakeup_source_device_create(struct device *parent,
struct wakeup_source *ws)
{
struct device *dev = NULL;
int retval = -ENODEV;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
device_initialize(dev);
dev->devt = MKDEV(0, 0);
dev->class = wakeup_class;
dev->parent = parent;
dev->groups = wakeup_source_groups;
dev->release = device_create_release;
dev_set_drvdata(dev, ws);
device_set_pm_not_required(dev);
retval = kobject_set_name(&dev->kobj, "wakeup%d", ws->id);
if (retval)
goto error;
retval = device_add(dev);
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
/**
* wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs.
* @parent: Device given wakeup source is associated with (or NULL if virtual).
* @ws: Wakeup source to be added in sysfs.
*/
int wakeup_source_sysfs_add(struct device *parent, struct wakeup_source *ws)
{
struct device *dev;
dev = wakeup_source_device_create(parent, ws);
if (IS_ERR(dev))
return PTR_ERR(dev);
ws->dev = dev;
return 0;
}
/**
* pm_wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs
* for a device if they're missing.
* @parent: Device given wakeup source is associated with
*/
int pm_wakeup_source_sysfs_add(struct device *parent)
{
if (!parent->power.wakeup || parent->power.wakeup->dev)
return 0;
return wakeup_source_sysfs_add(parent, parent->power.wakeup);
}
/**
* wakeup_source_sysfs_remove - Remove wakeup_source attributes from sysfs.
* @ws: Wakeup source to be removed from sysfs.
*/
void wakeup_source_sysfs_remove(struct wakeup_source *ws)
{
device_unregister(ws->dev);
}
static int __init wakeup_sources_sysfs_init(void)
{
wakeup_class = class_create(THIS_MODULE, "wakeup");
return PTR_ERR_OR_ZERO(wakeup_class);
}
postcore_initcall(wakeup_sources_sysfs_init);
...@@ -253,35 +253,45 @@ static void intel_button_array_enable(struct device *device, bool enable) ...@@ -253,35 +253,45 @@ static void intel_button_array_enable(struct device *device, bool enable)
static int intel_hid_pm_prepare(struct device *device) static int intel_hid_pm_prepare(struct device *device)
{ {
if (device_may_wakeup(device)) {
struct intel_hid_priv *priv = dev_get_drvdata(device); struct intel_hid_priv *priv = dev_get_drvdata(device);
priv->wakeup_mode = true; priv->wakeup_mode = true;
}
return 0; return 0;
} }
static void intel_hid_pm_complete(struct device *device)
{
struct intel_hid_priv *priv = dev_get_drvdata(device);
priv->wakeup_mode = false;
}
static int intel_hid_pl_suspend_handler(struct device *device) static int intel_hid_pl_suspend_handler(struct device *device)
{ {
if (pm_suspend_via_firmware()) {
intel_hid_set_enable(device, false);
intel_button_array_enable(device, false); intel_button_array_enable(device, false);
}
if (!pm_suspend_no_platform())
intel_hid_set_enable(device, false);
return 0; return 0;
} }
static int intel_hid_pl_resume_handler(struct device *device) static int intel_hid_pl_resume_handler(struct device *device)
{ {
struct intel_hid_priv *priv = dev_get_drvdata(device); intel_hid_pm_complete(device);
priv->wakeup_mode = false; if (!pm_suspend_no_platform())
if (pm_resume_via_firmware()) {
intel_hid_set_enable(device, true); intel_hid_set_enable(device, true);
intel_button_array_enable(device, true); intel_button_array_enable(device, true);
}
return 0; return 0;
} }
static const struct dev_pm_ops intel_hid_pl_pm_ops = { static const struct dev_pm_ops intel_hid_pl_pm_ops = {
.prepare = intel_hid_pm_prepare, .prepare = intel_hid_pm_prepare,
.complete = intel_hid_pm_complete,
.freeze = intel_hid_pl_suspend_handler, .freeze = intel_hid_pl_suspend_handler,
.thaw = intel_hid_pl_resume_handler, .thaw = intel_hid_pl_resume_handler,
.restore = intel_hid_pl_resume_handler, .restore = intel_hid_pl_resume_handler,
...@@ -491,6 +501,12 @@ static int intel_hid_probe(struct platform_device *device) ...@@ -491,6 +501,12 @@ static int intel_hid_probe(struct platform_device *device)
} }
device_init_wakeup(&device->dev, true); device_init_wakeup(&device->dev, true);
/*
* In order for system wakeup to work, the EC GPE has to be marked as
* a wakeup one, so do that here (this setting will persist, but it has
* no effect until the wakeup mask is set for the EC GPE).
*/
acpi_ec_mark_gpe_for_wake();
return 0; return 0;
err_remove_notify: err_remove_notify:
......
...@@ -176,6 +176,12 @@ static int intel_vbtn_probe(struct platform_device *device) ...@@ -176,6 +176,12 @@ static int intel_vbtn_probe(struct platform_device *device)
return -EBUSY; return -EBUSY;
device_init_wakeup(&device->dev, true); device_init_wakeup(&device->dev, true);
/*
* In order for system wakeup to work, the EC GPE has to be marked as
* a wakeup one, so do that here (this setting will persist, but it has
* no effect until the wakeup mask is set for the EC GPE).
*/
acpi_ec_mark_gpe_for_wake();
return 0; return 0;
} }
...@@ -195,22 +201,30 @@ static int intel_vbtn_remove(struct platform_device *device) ...@@ -195,22 +201,30 @@ static int intel_vbtn_remove(struct platform_device *device)
static int intel_vbtn_pm_prepare(struct device *dev) static int intel_vbtn_pm_prepare(struct device *dev)
{ {
if (device_may_wakeup(dev)) {
struct intel_vbtn_priv *priv = dev_get_drvdata(dev); struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
priv->wakeup_mode = true; priv->wakeup_mode = true;
}
return 0; return 0;
} }
static int intel_vbtn_pm_resume(struct device *dev) static void intel_vbtn_pm_complete(struct device *dev)
{ {
struct intel_vbtn_priv *priv = dev_get_drvdata(dev); struct intel_vbtn_priv *priv = dev_get_drvdata(dev);
priv->wakeup_mode = false; priv->wakeup_mode = false;
}
static int intel_vbtn_pm_resume(struct device *dev)
{
intel_vbtn_pm_complete(dev);
return 0; return 0;
} }
static const struct dev_pm_ops intel_vbtn_pm_ops = { static const struct dev_pm_ops intel_vbtn_pm_ops = {
.prepare = intel_vbtn_pm_prepare, .prepare = intel_vbtn_pm_prepare,
.complete = intel_vbtn_pm_complete,
.resume = intel_vbtn_pm_resume, .resume = intel_vbtn_pm_resume,
.restore = intel_vbtn_pm_resume, .restore = intel_vbtn_pm_resume,
.thaw = intel_vbtn_pm_resume, .thaw = intel_vbtn_pm_resume,
......
...@@ -1459,13 +1459,13 @@ static int ep_create_wakeup_source(struct epitem *epi) ...@@ -1459,13 +1459,13 @@ static int ep_create_wakeup_source(struct epitem *epi)
struct wakeup_source *ws; struct wakeup_source *ws;
if (!epi->ep->ws) { if (!epi->ep->ws) {
epi->ep->ws = wakeup_source_register("eventpoll"); epi->ep->ws = wakeup_source_register(NULL, "eventpoll");
if (!epi->ep->ws) if (!epi->ep->ws)
return -ENOMEM; return -ENOMEM;
} }
name = epi->ffd.file->f_path.dentry->d_name.name; name = epi->ffd.file->f_path.dentry->d_name.name;
ws = wakeup_source_register(name); ws = wakeup_source_register(NULL, name);
if (!ws) if (!ws)
return -ENOMEM; return -ENOMEM;
......
...@@ -297,6 +297,9 @@ ACPI_GLOBAL(u8, acpi_gbl_system_awake_and_running); ...@@ -297,6 +297,9 @@ ACPI_GLOBAL(u8, acpi_gbl_system_awake_and_running);
#define ACPI_HW_DEPENDENT_RETURN_OK(prototype) \ #define ACPI_HW_DEPENDENT_RETURN_OK(prototype) \
ACPI_EXTERNAL_RETURN_OK(prototype) ACPI_EXTERNAL_RETURN_OK(prototype)
#define ACPI_HW_DEPENDENT_RETURN_UINT32(prototype) \
ACPI_EXTERNAL_RETURN_UINT32(prototype)
#define ACPI_HW_DEPENDENT_RETURN_VOID(prototype) \ #define ACPI_HW_DEPENDENT_RETURN_VOID(prototype) \
ACPI_EXTERNAL_RETURN_VOID(prototype) ACPI_EXTERNAL_RETURN_VOID(prototype)
...@@ -307,6 +310,9 @@ ACPI_GLOBAL(u8, acpi_gbl_system_awake_and_running); ...@@ -307,6 +310,9 @@ ACPI_GLOBAL(u8, acpi_gbl_system_awake_and_running);
#define ACPI_HW_DEPENDENT_RETURN_OK(prototype) \ #define ACPI_HW_DEPENDENT_RETURN_OK(prototype) \
static ACPI_INLINE prototype {return(AE_OK);} static ACPI_INLINE prototype {return(AE_OK);}
#define ACPI_HW_DEPENDENT_RETURN_UINT32(prototype) \
static ACPI_INLINE prototype {return(0);}
#define ACPI_HW_DEPENDENT_RETURN_VOID(prototype) \ #define ACPI_HW_DEPENDENT_RETURN_VOID(prototype) \
static ACPI_INLINE prototype {return;} static ACPI_INLINE prototype {return;}
...@@ -738,7 +744,7 @@ ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status ...@@ -738,7 +744,7 @@ ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status
u32 gpe_number, u32 gpe_number,
acpi_event_status acpi_event_status
*event_status)) *event_status))
ACPI_HW_DEPENDENT_RETURN_VOID(void acpi_dispatch_gpe(acpi_handle gpe_device, u32 gpe_number)) ACPI_HW_DEPENDENT_RETURN_UINT32(u32 acpi_dispatch_gpe(acpi_handle gpe_device, u32 gpe_number))
ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status acpi_disable_all_gpes(void)) ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status acpi_disable_all_gpes(void))
ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status acpi_enable_all_runtime_gpes(void)) ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status acpi_enable_all_runtime_gpes(void))
ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status acpi_enable_all_wakeup_gpes(void)) ACPI_HW_DEPENDENT_RETURN_STATUS(acpi_status acpi_enable_all_wakeup_gpes(void))
......
...@@ -931,6 +931,8 @@ int acpi_subsys_suspend_noirq(struct device *dev); ...@@ -931,6 +931,8 @@ int acpi_subsys_suspend_noirq(struct device *dev);
int acpi_subsys_suspend(struct device *dev); int acpi_subsys_suspend(struct device *dev);
int acpi_subsys_freeze(struct device *dev); int acpi_subsys_freeze(struct device *dev);
int acpi_subsys_poweroff(struct device *dev); int acpi_subsys_poweroff(struct device *dev);
void acpi_ec_mark_gpe_for_wake(void);
void acpi_ec_set_gpe_wake_mask(u8 action);
#else #else
static inline int acpi_subsys_prepare(struct device *dev) { return 0; } static inline int acpi_subsys_prepare(struct device *dev) { return 0; }
static inline void acpi_subsys_complete(struct device *dev) {} static inline void acpi_subsys_complete(struct device *dev) {}
...@@ -939,6 +941,8 @@ static inline int acpi_subsys_suspend_noirq(struct device *dev) { return 0; } ...@@ -939,6 +941,8 @@ static inline int acpi_subsys_suspend_noirq(struct device *dev) { return 0; }
static inline int acpi_subsys_suspend(struct device *dev) { return 0; } static inline int acpi_subsys_suspend(struct device *dev) { return 0; }
static inline int acpi_subsys_freeze(struct device *dev) { return 0; } static inline int acpi_subsys_freeze(struct device *dev) { return 0; }
static inline int acpi_subsys_poweroff(struct device *dev) { return 0; } static inline int acpi_subsys_poweroff(struct device *dev) { return 0; }
static inline void acpi_ec_mark_gpe_for_wake(void) {}
static inline void acpi_ec_set_gpe_wake_mask(u8 action) {}
#endif #endif
#ifdef CONFIG_ACPI #ifdef CONFIG_ACPI
......
...@@ -238,6 +238,7 @@ extern void teardown_percpu_nmi(unsigned int irq); ...@@ -238,6 +238,7 @@ extern void teardown_percpu_nmi(unsigned int irq);
/* The following three functions are for the core kernel use only. */ /* The following three functions are for the core kernel use only. */
extern void suspend_device_irqs(void); extern void suspend_device_irqs(void);
extern void resume_device_irqs(void); extern void resume_device_irqs(void);
extern void rearm_wake_irq(unsigned int irq);
/** /**
* struct irq_affinity_notify - context for notification of IRQ affinity changes * struct irq_affinity_notify - context for notification of IRQ affinity changes
......
...@@ -712,8 +712,6 @@ struct dev_pm_domain { ...@@ -712,8 +712,6 @@ struct dev_pm_domain {
extern void device_pm_lock(void); extern void device_pm_lock(void);
extern void dpm_resume_start(pm_message_t state); extern void dpm_resume_start(pm_message_t state);
extern void dpm_resume_end(pm_message_t state); extern void dpm_resume_end(pm_message_t state);
extern void dpm_noirq_resume_devices(pm_message_t state);
extern void dpm_noirq_end(void);
extern void dpm_resume_noirq(pm_message_t state); extern void dpm_resume_noirq(pm_message_t state);
extern void dpm_resume_early(pm_message_t state); extern void dpm_resume_early(pm_message_t state);
extern void dpm_resume(pm_message_t state); extern void dpm_resume(pm_message_t state);
...@@ -722,8 +720,6 @@ extern void dpm_complete(pm_message_t state); ...@@ -722,8 +720,6 @@ extern void dpm_complete(pm_message_t state);
extern void device_pm_unlock(void); extern void device_pm_unlock(void);
extern int dpm_suspend_end(pm_message_t state); extern int dpm_suspend_end(pm_message_t state);
extern int dpm_suspend_start(pm_message_t state); extern int dpm_suspend_start(pm_message_t state);
extern void dpm_noirq_begin(void);
extern int dpm_noirq_suspend_devices(pm_message_t state);
extern int dpm_suspend_noirq(pm_message_t state); extern int dpm_suspend_noirq(pm_message_t state);
extern int dpm_suspend_late(pm_message_t state); extern int dpm_suspend_late(pm_message_t state);
extern int dpm_suspend(pm_message_t state); extern int dpm_suspend(pm_message_t state);
......
...@@ -21,6 +21,7 @@ struct wake_irq; ...@@ -21,6 +21,7 @@ struct wake_irq;
* struct wakeup_source - Representation of wakeup sources * struct wakeup_source - Representation of wakeup sources
* *
* @name: Name of the wakeup source * @name: Name of the wakeup source
* @id: Wakeup source id
* @entry: Wakeup source list entry * @entry: Wakeup source list entry
* @lock: Wakeup source lock * @lock: Wakeup source lock
* @wakeirq: Optional device specific wakeirq * @wakeirq: Optional device specific wakeirq
...@@ -35,11 +36,13 @@ struct wake_irq; ...@@ -35,11 +36,13 @@ struct wake_irq;
* @relax_count: Number of times the wakeup source was deactivated. * @relax_count: Number of times the wakeup source was deactivated.
* @expire_count: Number of times the wakeup source's timeout has expired. * @expire_count: Number of times the wakeup source's timeout has expired.
* @wakeup_count: Number of times the wakeup source might abort suspend. * @wakeup_count: Number of times the wakeup source might abort suspend.
* @dev: Struct device for sysfs statistics about the wakeup source.
* @active: Status of the wakeup source. * @active: Status of the wakeup source.
* @autosleep_enabled: Autosleep is active, so update @prevent_sleep_time. * @autosleep_enabled: Autosleep is active, so update @prevent_sleep_time.
*/ */
struct wakeup_source { struct wakeup_source {
const char *name; const char *name;
int id;
struct list_head entry; struct list_head entry;
spinlock_t lock; spinlock_t lock;
struct wake_irq *wakeirq; struct wake_irq *wakeirq;
...@@ -55,6 +58,7 @@ struct wakeup_source { ...@@ -55,6 +58,7 @@ struct wakeup_source {
unsigned long relax_count; unsigned long relax_count;
unsigned long expire_count; unsigned long expire_count;
unsigned long wakeup_count; unsigned long wakeup_count;
struct device *dev;
bool active:1; bool active:1;
bool autosleep_enabled:1; bool autosleep_enabled:1;
}; };
...@@ -81,12 +85,12 @@ static inline void device_set_wakeup_path(struct device *dev) ...@@ -81,12 +85,12 @@ static inline void device_set_wakeup_path(struct device *dev)
} }
/* drivers/base/power/wakeup.c */ /* drivers/base/power/wakeup.c */
extern void wakeup_source_prepare(struct wakeup_source *ws, const char *name);
extern struct wakeup_source *wakeup_source_create(const char *name); extern struct wakeup_source *wakeup_source_create(const char *name);
extern void wakeup_source_destroy(struct wakeup_source *ws); extern void wakeup_source_destroy(struct wakeup_source *ws);
extern void wakeup_source_add(struct wakeup_source *ws); extern void wakeup_source_add(struct wakeup_source *ws);
extern void wakeup_source_remove(struct wakeup_source *ws); extern void wakeup_source_remove(struct wakeup_source *ws);
extern struct wakeup_source *wakeup_source_register(const char *name); extern struct wakeup_source *wakeup_source_register(struct device *dev,
const char *name);
extern void wakeup_source_unregister(struct wakeup_source *ws); extern void wakeup_source_unregister(struct wakeup_source *ws);
extern int device_wakeup_enable(struct device *dev); extern int device_wakeup_enable(struct device *dev);
extern int device_wakeup_disable(struct device *dev); extern int device_wakeup_disable(struct device *dev);
...@@ -112,9 +116,6 @@ static inline bool device_can_wakeup(struct device *dev) ...@@ -112,9 +116,6 @@ static inline bool device_can_wakeup(struct device *dev)
return dev->power.can_wakeup; return dev->power.can_wakeup;
} }
static inline void wakeup_source_prepare(struct wakeup_source *ws,
const char *name) {}
static inline struct wakeup_source *wakeup_source_create(const char *name) static inline struct wakeup_source *wakeup_source_create(const char *name)
{ {
return NULL; return NULL;
...@@ -126,7 +127,8 @@ static inline void wakeup_source_add(struct wakeup_source *ws) {} ...@@ -126,7 +127,8 @@ static inline void wakeup_source_add(struct wakeup_source *ws) {}
static inline void wakeup_source_remove(struct wakeup_source *ws) {} static inline void wakeup_source_remove(struct wakeup_source *ws) {}
static inline struct wakeup_source *wakeup_source_register(const char *name) static inline struct wakeup_source *wakeup_source_register(struct device *dev,
const char *name)
{ {
return NULL; return NULL;
} }
...@@ -181,13 +183,6 @@ static inline void pm_wakeup_dev_event(struct device *dev, unsigned int msec, ...@@ -181,13 +183,6 @@ static inline void pm_wakeup_dev_event(struct device *dev, unsigned int msec,
#endif /* !CONFIG_PM_SLEEP */ #endif /* !CONFIG_PM_SLEEP */
static inline void wakeup_source_init(struct wakeup_source *ws,
const char *name)
{
wakeup_source_prepare(ws, name);
wakeup_source_add(ws);
}
static inline void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec) static inline void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec)
{ {
return pm_wakeup_ws_event(ws, msec, false); return pm_wakeup_ws_event(ws, msec, false);
......
...@@ -190,8 +190,9 @@ struct platform_suspend_ops { ...@@ -190,8 +190,9 @@ struct platform_suspend_ops {
struct platform_s2idle_ops { struct platform_s2idle_ops {
int (*begin)(void); int (*begin)(void);
int (*prepare)(void); int (*prepare)(void);
int (*prepare_late)(void);
void (*wake)(void); void (*wake)(void);
void (*sync)(void); void (*restore_early)(void);
void (*restore)(void); void (*restore)(void);
void (*end)(void); void (*end)(void);
}; };
...@@ -336,6 +337,7 @@ static inline void pm_set_suspend_via_firmware(void) {} ...@@ -336,6 +337,7 @@ static inline void pm_set_suspend_via_firmware(void) {}
static inline void pm_set_resume_via_firmware(void) {} static inline void pm_set_resume_via_firmware(void) {}
static inline bool pm_suspend_via_firmware(void) { return false; } static inline bool pm_suspend_via_firmware(void) { return false; }
static inline bool pm_resume_via_firmware(void) { return false; } static inline bool pm_resume_via_firmware(void) { return false; }
static inline bool pm_suspend_no_platform(void) { return false; }
static inline bool pm_suspend_default_s2idle(void) { return false; } static inline bool pm_suspend_default_s2idle(void) { return false; }
static inline void suspend_set_ops(const struct platform_suspend_ops *ops) {} static inline void suspend_set_ops(const struct platform_suspend_ops *ops) {}
......
...@@ -176,6 +176,26 @@ static void resume_irqs(bool want_early) ...@@ -176,6 +176,26 @@ static void resume_irqs(bool want_early)
} }
} }
/**
* rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup
* @irq: Interrupt to rearm
*/
void rearm_wake_irq(unsigned int irq)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
if (!desc || !(desc->istate & IRQS_SUSPENDED) ||
!irqd_is_wakeup_set(&desc->irq_data))
return;
desc->istate &= ~IRQS_SUSPENDED;
irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED);
__enable_irq(desc);
irq_put_desc_busunlock(desc, flags);
}
/** /**
* irq_pm_syscore_ops - enable interrupt lines early * irq_pm_syscore_ops - enable interrupt lines early
* *
......
...@@ -116,7 +116,7 @@ int pm_autosleep_set_state(suspend_state_t state) ...@@ -116,7 +116,7 @@ int pm_autosleep_set_state(suspend_state_t state)
int __init pm_autosleep_init(void) int __init pm_autosleep_init(void)
{ {
autosleep_ws = wakeup_source_register("autosleep"); autosleep_ws = wakeup_source_register(NULL, "autosleep");
if (!autosleep_ws) if (!autosleep_ws)
return -ENOMEM; return -ENOMEM;
......
...@@ -254,7 +254,6 @@ static ssize_t pm_test_store(struct kobject *kobj, struct kobj_attribute *attr, ...@@ -254,7 +254,6 @@ static ssize_t pm_test_store(struct kobject *kobj, struct kobj_attribute *attr,
power_attr(pm_test); power_attr(pm_test);
#endif /* CONFIG_PM_SLEEP_DEBUG */ #endif /* CONFIG_PM_SLEEP_DEBUG */
#ifdef CONFIG_DEBUG_FS
static char *suspend_step_name(enum suspend_stat_step step) static char *suspend_step_name(enum suspend_stat_step step)
{ {
switch (step) { switch (step) {
...@@ -275,6 +274,92 @@ static char *suspend_step_name(enum suspend_stat_step step) ...@@ -275,6 +274,92 @@ static char *suspend_step_name(enum suspend_stat_step step)
} }
} }
#define suspend_attr(_name) \
static ssize_t _name##_show(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%d\n", suspend_stats._name); \
} \
static struct kobj_attribute _name = __ATTR_RO(_name)
suspend_attr(success);
suspend_attr(fail);
suspend_attr(failed_freeze);
suspend_attr(failed_prepare);
suspend_attr(failed_suspend);
suspend_attr(failed_suspend_late);
suspend_attr(failed_suspend_noirq);
suspend_attr(failed_resume);
suspend_attr(failed_resume_early);
suspend_attr(failed_resume_noirq);
static ssize_t last_failed_dev_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int index;
char *last_failed_dev = NULL;
index = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
index %= REC_FAILED_NUM;
last_failed_dev = suspend_stats.failed_devs[index];
return sprintf(buf, "%s\n", last_failed_dev);
}
static struct kobj_attribute last_failed_dev = __ATTR_RO(last_failed_dev);
static ssize_t last_failed_errno_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int index;
int last_failed_errno;
index = suspend_stats.last_failed_errno + REC_FAILED_NUM - 1;
index %= REC_FAILED_NUM;
last_failed_errno = suspend_stats.errno[index];
return sprintf(buf, "%d\n", last_failed_errno);
}
static struct kobj_attribute last_failed_errno = __ATTR_RO(last_failed_errno);
static ssize_t last_failed_step_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int index;
enum suspend_stat_step step;
char *last_failed_step = NULL;
index = suspend_stats.last_failed_step + REC_FAILED_NUM - 1;
index %= REC_FAILED_NUM;
step = suspend_stats.failed_steps[index];
last_failed_step = suspend_step_name(step);
return sprintf(buf, "%s\n", last_failed_step);
}
static struct kobj_attribute last_failed_step = __ATTR_RO(last_failed_step);
static struct attribute *suspend_attrs[] = {
&success.attr,
&fail.attr,
&failed_freeze.attr,
&failed_prepare.attr,
&failed_suspend.attr,
&failed_suspend_late.attr,
&failed_suspend_noirq.attr,
&failed_resume.attr,
&failed_resume_early.attr,
&failed_resume_noirq.attr,
&last_failed_dev.attr,
&last_failed_errno.attr,
&last_failed_step.attr,
NULL,
};
static struct attribute_group suspend_attr_group = {
.name = "suspend_stats",
.attrs = suspend_attrs,
};
#ifdef CONFIG_DEBUG_FS
static int suspend_stats_show(struct seq_file *s, void *unused) static int suspend_stats_show(struct seq_file *s, void *unused)
{ {
int i, index, last_dev, last_errno, last_step; int i, index, last_dev, last_errno, last_step;
...@@ -495,7 +580,7 @@ static suspend_state_t decode_state(const char *buf, size_t n) ...@@ -495,7 +580,7 @@ static suspend_state_t decode_state(const char *buf, size_t n)
len = p ? p - buf : n; len = p ? p - buf : n;
/* Check hibernation first. */ /* Check hibernation first. */
if (len == 4 && !strncmp(buf, "disk", len)) if (len == 4 && str_has_prefix(buf, "disk"))
return PM_SUSPEND_MAX; return PM_SUSPEND_MAX;
#ifdef CONFIG_SUSPEND #ifdef CONFIG_SUSPEND
...@@ -794,6 +879,14 @@ static const struct attribute_group attr_group = { ...@@ -794,6 +879,14 @@ static const struct attribute_group attr_group = {
.attrs = g, .attrs = g,
}; };
static const struct attribute_group *attr_groups[] = {
&attr_group,
#ifdef CONFIG_PM_SLEEP
&suspend_attr_group,
#endif
NULL,
};
struct workqueue_struct *pm_wq; struct workqueue_struct *pm_wq;
EXPORT_SYMBOL_GPL(pm_wq); EXPORT_SYMBOL_GPL(pm_wq);
...@@ -815,7 +908,7 @@ static int __init pm_init(void) ...@@ -815,7 +908,7 @@ static int __init pm_init(void)
power_kobj = kobject_create_and_add("power", NULL); power_kobj = kobject_create_and_add("power", NULL);
if (!power_kobj) if (!power_kobj)
return -ENOMEM; return -ENOMEM;
error = sysfs_create_group(power_kobj, &attr_group); error = sysfs_create_groups(power_kobj, attr_groups);
if (error) if (error)
return error; return error;
pm_print_times_init(); pm_print_times_init();
......
...@@ -121,43 +121,25 @@ static void s2idle_loop(void) ...@@ -121,43 +121,25 @@ static void s2idle_loop(void)
{ {
pm_pr_dbg("suspend-to-idle\n"); pm_pr_dbg("suspend-to-idle\n");
for (;;) {
int error;
dpm_noirq_begin();
/* /*
* Suspend-to-idle equals * Suspend-to-idle equals:
* frozen processes + suspended devices + idle processors. * frozen processes + suspended devices + idle processors.
* Thus s2idle_enter() should be called right after * Thus s2idle_enter() should be called right after all devices have
* all devices have been suspended. * been suspended.
* *
* Wakeups during the noirq suspend of devices may be spurious, * Wakeups during the noirq suspend of devices may be spurious, so try
* so prevent them from terminating the loop right away. * to avoid them upfront.
*/ */
error = dpm_noirq_suspend_devices(PMSG_SUSPEND); for (;;) {
if (!error) if (s2idle_ops && s2idle_ops->wake)
s2idle_enter();
else if (error == -EBUSY && pm_wakeup_pending())
error = 0;
if (!error && s2idle_ops && s2idle_ops->wake)
s2idle_ops->wake(); s2idle_ops->wake();
dpm_noirq_resume_devices(PMSG_RESUME);
dpm_noirq_end();
if (error)
break;
if (s2idle_ops && s2idle_ops->sync)
s2idle_ops->sync();
if (pm_wakeup_pending()) if (pm_wakeup_pending())
break; break;
pm_wakeup_clear(false); pm_wakeup_clear(false);
s2idle_enter();
} }
pm_pr_dbg("resume from suspend-to-idle\n"); pm_pr_dbg("resume from suspend-to-idle\n");
...@@ -271,14 +253,21 @@ static int platform_suspend_prepare_late(suspend_state_t state) ...@@ -271,14 +253,21 @@ static int platform_suspend_prepare_late(suspend_state_t state)
static int platform_suspend_prepare_noirq(suspend_state_t state) static int platform_suspend_prepare_noirq(suspend_state_t state)
{ {
return state != PM_SUSPEND_TO_IDLE && suspend_ops->prepare_late ? if (state == PM_SUSPEND_TO_IDLE)
suspend_ops->prepare_late() : 0; return s2idle_ops && s2idle_ops->prepare_late ?
s2idle_ops->prepare_late() : 0;
return suspend_ops->prepare_late ? suspend_ops->prepare_late() : 0;
} }
static void platform_resume_noirq(suspend_state_t state) static void platform_resume_noirq(suspend_state_t state)
{ {
if (state != PM_SUSPEND_TO_IDLE && suspend_ops->wake) if (state == PM_SUSPEND_TO_IDLE) {
if (s2idle_ops && s2idle_ops->restore_early)
s2idle_ops->restore_early();
} else if (suspend_ops->wake) {
suspend_ops->wake(); suspend_ops->wake();
}
} }
static void platform_resume_early(suspend_state_t state) static void platform_resume_early(suspend_state_t state)
...@@ -415,11 +404,6 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) ...@@ -415,11 +404,6 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
if (error) if (error)
goto Devices_early_resume; goto Devices_early_resume;
if (state == PM_SUSPEND_TO_IDLE && pm_test_level != TEST_PLATFORM) {
s2idle_loop();
goto Platform_early_resume;
}
error = dpm_suspend_noirq(PMSG_SUSPEND); error = dpm_suspend_noirq(PMSG_SUSPEND);
if (error) { if (error) {
pr_err("noirq suspend of devices failed\n"); pr_err("noirq suspend of devices failed\n");
...@@ -432,6 +416,11 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) ...@@ -432,6 +416,11 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
if (suspend_test(TEST_PLATFORM)) if (suspend_test(TEST_PLATFORM))
goto Platform_wake; goto Platform_wake;
if (state == PM_SUSPEND_TO_IDLE) {
s2idle_loop();
goto Platform_wake;
}
error = suspend_disable_secondary_cpus(); error = suspend_disable_secondary_cpus();
if (error || suspend_test(TEST_CPUS)) if (error || suspend_test(TEST_CPUS))
goto Enable_cpus; goto Enable_cpus;
......
...@@ -27,7 +27,7 @@ static DEFINE_MUTEX(wakelocks_lock); ...@@ -27,7 +27,7 @@ static DEFINE_MUTEX(wakelocks_lock);
struct wakelock { struct wakelock {
char *name; char *name;
struct rb_node node; struct rb_node node;
struct wakeup_source ws; struct wakeup_source *ws;
#ifdef CONFIG_PM_WAKELOCKS_GC #ifdef CONFIG_PM_WAKELOCKS_GC
struct list_head lru; struct list_head lru;
#endif #endif
...@@ -46,7 +46,7 @@ ssize_t pm_show_wakelocks(char *buf, bool show_active) ...@@ -46,7 +46,7 @@ ssize_t pm_show_wakelocks(char *buf, bool show_active)
for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) { for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) {
wl = rb_entry(node, struct wakelock, node); wl = rb_entry(node, struct wakelock, node);
if (wl->ws.active == show_active) if (wl->ws->active == show_active)
str += scnprintf(str, end - str, "%s ", wl->name); str += scnprintf(str, end - str, "%s ", wl->name);
} }
if (str > buf) if (str > buf)
...@@ -112,16 +112,16 @@ static void __wakelocks_gc(struct work_struct *work) ...@@ -112,16 +112,16 @@ static void __wakelocks_gc(struct work_struct *work)
u64 idle_time_ns; u64 idle_time_ns;
bool active; bool active;
spin_lock_irq(&wl->ws.lock); spin_lock_irq(&wl->ws->lock);
idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time)); idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws->last_time));
active = wl->ws.active; active = wl->ws->active;
spin_unlock_irq(&wl->ws.lock); spin_unlock_irq(&wl->ws->lock);
if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC)) if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC))
break; break;
if (!active) { if (!active) {
wakeup_source_remove(&wl->ws); wakeup_source_unregister(wl->ws);
rb_erase(&wl->node, &wakelocks_tree); rb_erase(&wl->node, &wakelocks_tree);
list_del(&wl->lru); list_del(&wl->lru);
kfree(wl->name); kfree(wl->name);
...@@ -187,9 +187,15 @@ static struct wakelock *wakelock_lookup_add(const char *name, size_t len, ...@@ -187,9 +187,15 @@ static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
kfree(wl); kfree(wl);
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
wl->ws.name = wl->name;
wl->ws.last_time = ktime_get(); wl->ws = wakeup_source_register(NULL, wl->name);
wakeup_source_add(&wl->ws); if (!wl->ws) {
kfree(wl->name);
kfree(wl);
return ERR_PTR(-ENOMEM);
}
wl->ws->last_time = ktime_get();
rb_link_node(&wl->node, parent, node); rb_link_node(&wl->node, parent, node);
rb_insert_color(&wl->node, &wakelocks_tree); rb_insert_color(&wl->node, &wakelocks_tree);
wakelocks_lru_add(wl); wakelocks_lru_add(wl);
...@@ -233,9 +239,9 @@ int pm_wake_lock(const char *buf) ...@@ -233,9 +239,9 @@ int pm_wake_lock(const char *buf)
u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1; u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
do_div(timeout_ms, NSEC_PER_MSEC); do_div(timeout_ms, NSEC_PER_MSEC);
__pm_wakeup_event(&wl->ws, timeout_ms); __pm_wakeup_event(wl->ws, timeout_ms);
} else { } else {
__pm_stay_awake(&wl->ws); __pm_stay_awake(wl->ws);
} }
wakelocks_lru_most_recent(wl); wakelocks_lru_most_recent(wl);
...@@ -271,7 +277,7 @@ int pm_wake_unlock(const char *buf) ...@@ -271,7 +277,7 @@ int pm_wake_unlock(const char *buf)
ret = PTR_ERR(wl); ret = PTR_ERR(wl);
goto out; goto out;
} }
__pm_relax(&wl->ws); __pm_relax(wl->ws);
wakelocks_lru_most_recent(wl); wakelocks_lru_most_recent(wl);
wakelocks_gc(); wakelocks_gc();
......
...@@ -97,7 +97,7 @@ static int alarmtimer_rtc_add_device(struct device *dev, ...@@ -97,7 +97,7 @@ static int alarmtimer_rtc_add_device(struct device *dev,
if (!device_may_wakeup(rtc->dev.parent)) if (!device_may_wakeup(rtc->dev.parent))
return -1; return -1;
__ws = wakeup_source_register("alarmtimer"); __ws = wakeup_source_register(dev, "alarmtimer");
spin_lock_irqsave(&rtcdev_lock, flags); spin_lock_irqsave(&rtcdev_lock, flags);
if (!rtcdev) { if (!rtcdev) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment