diff mbox series

[RFC/RFT] PM: sleep: Ignore device driver suspend() callback return values

Message ID 7e3092234617e8479d3020e5fed7ff47ac750014.1731522552.git.len.brown@intel.com
State New
Headers show
Series [RFC/RFT] PM: sleep: Ignore device driver suspend() callback return values | expand

Commit Message

Len Brown Nov. 13, 2024, 6:35 p.m. UTC
From: Len Brown <len.brown@intel.com>

Drivers commonly return non-zero values from their suspend()
callbacks due to transient errors, not realizing that doing so
aborts system-wide suspend.

Ignore those return values.

Both before and after this patch, the correct method for a
device driver to abort system-wide suspend is to invoke
pm_system_wakeup() during the suspend flow.

Legacy behaviour can be restored by adding this line to your .config:
CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT=y

Signed-off-by: Len Brown <len.brown@intel.com>
---
 drivers/base/power/main.c |  4 ++++
 kernel/power/Kconfig      | 14 ++++++++++++++
 2 files changed, 18 insertions(+)

Comments

Rafael J. Wysocki Nov. 13, 2024, 7:23 p.m. UTC | #1
On Wed, Nov 13, 2024 at 7:35 PM Len Brown <lenb@kernel.org> wrote:
>
> From: Len Brown <len.brown@intel.com>
>
> Drivers commonly return non-zero values from their suspend()
> callbacks due to transient errors, not realizing that doing so
> aborts system-wide suspend.
>
> Ignore those return values.
>
> Both before and after this patch, the correct method for a
> device driver to abort system-wide suspend is to invoke
> pm_system_wakeup() during the suspend flow.
>
> Legacy behaviour can be restored by adding this line to your .config:
> CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT=y
>
> Signed-off-by: Len Brown <len.brown@intel.com>
> ---
>  drivers/base/power/main.c |  4 ++++
>  kernel/power/Kconfig      | 14 ++++++++++++++
>  2 files changed, 18 insertions(+)
>
> diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> index 4a67e83300e1..56b7c9c752b4 100644
> --- a/drivers/base/power/main.c
> +++ b/drivers/base/power/main.c
> @@ -1678,7 +1678,11 @@ static int device_suspend(struct device *dev, pm_message_t state, bool async)
>                 callback = pm_op(dev->driver->pm, state);
>         }
>
> +#if CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT
>         error = dpm_run_callback(callback, dev, state, info);
> +#else
> +       dpm_run_callback(callback, dev, state, info);
> +#endif
>
>   End:
>         if (!error) {

I would prefer something like:

    error = dpm_run_callback(callback, dev, state, info);

 End:
    if (!IS_ENABLED(PM_SLEEP_LEGACY_CALLBACK_ABORT)
            error = 0;

    if (!error ) {
        dev->power.is_suspended = true;
        if (device_may_wakeup(dev))
            dev->power.wakeup_path = true;

        dpm_propagate_wakeup_to_parent(dev);
        dpm_clear_superiors_direct_complete(dev);
    }

and analogously in _noirq() and _late().

> diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
> index afce8130d8b9..51b5d6c9bf1a 100644
> --- a/kernel/power/Kconfig
> +++ b/kernel/power/Kconfig
> @@ -141,6 +141,20 @@ config PM_SLEEP
>         depends on SUSPEND || HIBERNATE_CALLBACKS
>         select PM
>
> +config PM_SLEEP_LEGACY_CALLBACK_ABORT
> +       def_bool n

This isn't needed.

> +       depends on PM_SLEEP
> +       help
> +       This option enables the legacy API for device .suspend() callbacks.
> +       That API empowered any driver to abort system-wide suspend
> +       by returning any non-zero value from its .suspend() callback.
> +       In practice, these aborts are almost always spurious and unwanted.
> +
> +       Disabling this option (default) ignores .suspend() callback return values.
> +
> +       In both cases, any driver can abort system wide suspend by invoking
> +       pm_system_wakeup() during the suspend flow.
> +
>  config PM_SLEEP_SMP
>         def_bool y
>         depends on SMP
> --

It would be good to update the PM documentation too to take this new
option into account.
Rafael J. Wysocki Nov. 13, 2024, 7:33 p.m. UTC | #2
On Wed, Nov 13, 2024 at 8:23 PM Rafael J. Wysocki <rafael@kernel.org> wrote:
>
> On Wed, Nov 13, 2024 at 7:35 PM Len Brown <lenb@kernel.org> wrote:
> >
> > From: Len Brown <len.brown@intel.com>
> >
> > Drivers commonly return non-zero values from their suspend()
> > callbacks due to transient errors, not realizing that doing so
> > aborts system-wide suspend.
> >
> > Ignore those return values.
> >
> > Both before and after this patch, the correct method for a
> > device driver to abort system-wide suspend is to invoke
> > pm_system_wakeup() during the suspend flow.
> >
> > Legacy behaviour can be restored by adding this line to your .config:
> > CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT=y
> >
> > Signed-off-by: Len Brown <len.brown@intel.com>
> > ---
> >  drivers/base/power/main.c |  4 ++++
> >  kernel/power/Kconfig      | 14 ++++++++++++++
> >  2 files changed, 18 insertions(+)
> >
> > diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> > index 4a67e83300e1..56b7c9c752b4 100644
> > --- a/drivers/base/power/main.c
> > +++ b/drivers/base/power/main.c
> > @@ -1678,7 +1678,11 @@ static int device_suspend(struct device *dev, pm_message_t state, bool async)
> >                 callback = pm_op(dev->driver->pm, state);
> >         }
> >
> > +#if CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT
> >         error = dpm_run_callback(callback, dev, state, info);
> > +#else
> > +       dpm_run_callback(callback, dev, state, info);
> > +#endif
> >
> >   End:
> >         if (!error) {
>
> I would prefer something like:
>
>     error = dpm_run_callback(callback, dev, state, info);
>
>  End:
>     if (!IS_ENABLED(PM_SLEEP_LEGACY_CALLBACK_ABORT)
>             error = 0;
>
>     if (!error ) {
>         dev->power.is_suspended = true;
>         if (device_may_wakeup(dev))
>             dev->power.wakeup_path = true;
>
>         dpm_propagate_wakeup_to_parent(dev);
>         dpm_clear_superiors_direct_complete(dev);
>     }
>
> and analogously in _noirq() and _late().

Or even

    error = dpm_run_callback(callback, dev, state, info);

 End:
    if (!error || !IS_ENABLED(PM_SLEEP_LEGACY_CALLBACK_ABORT)) {
        dev->power.is_suspended = true;
        if (device_may_wakeup(dev))
            dev->power.wakeup_path = true;

        dpm_propagate_wakeup_to_parent(dev);
        dpm_clear_superiors_direct_complete(dev);
    }

    device_unlock(dev);
    dpm_watchdog_clear(&wd);

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

    complete_all(&dev->power.completion);
    TRACE_SUSPEND(error);
    if (IS_ENABLED(PM_SLEEP_LEGACY_CALLBACK_ABORT) {
        async_error = error;
        return error;
    }

    return 0;

So I want the error messages to be printed and TRACE_SUSPEND() to
record the error, but to return 0 in the end if the new option is not
set.

Also please CC the next version to the LKML.
diff mbox series

Patch

diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 4a67e83300e1..56b7c9c752b4 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -1678,7 +1678,11 @@  static int device_suspend(struct device *dev, pm_message_t state, bool async)
 		callback = pm_op(dev->driver->pm, state);
 	}
 
+#if CONFIG_PM_SLEEP_LEGACY_CALLBACK_ABORT
 	error = dpm_run_callback(callback, dev, state, info);
+#else
+	dpm_run_callback(callback, dev, state, info);
+#endif
 
  End:
 	if (!error) {
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index afce8130d8b9..51b5d6c9bf1a 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -141,6 +141,20 @@  config PM_SLEEP
 	depends on SUSPEND || HIBERNATE_CALLBACKS
 	select PM
 
+config PM_SLEEP_LEGACY_CALLBACK_ABORT
+	def_bool n
+	depends on PM_SLEEP
+	help
+	This option enables the legacy API for device .suspend() callbacks.
+	That API empowered any driver to abort system-wide suspend
+	by returning any non-zero value from its .suspend() callback.
+	In practice, these aborts are almost always spurious and unwanted.
+
+	Disabling this option (default) ignores .suspend() callback return values.
+
+	In both cases, any driver can abort system wide suspend by invoking
+	pm_system_wakeup() during the suspend flow.
+
 config PM_SLEEP_SMP
 	def_bool y
 	depends on SMP