naccyde / rpms / systemd

Forked from rpms/systemd a year ago
Clone
2aacef
From db8a187c67e4829e39fe28e25003816b64db80db Mon Sep 17 00:00:00 2001
2aacef
From: Yu Watanabe <watanabe.yu+github@gmail.com>
2aacef
Date: Mon, 14 Nov 2022 02:08:05 +0900
2aacef
Subject: [PATCH] sleep: introduce SuspendEstimationSec=
2aacef
2aacef
Before v252, HibernateDelaySec= specifies the maximum timespan that the
2aacef
system in suspend state, and the system hibernate after the timespan.
2aacef
2aacef
However, after 96d662fa4c8cab24da57523c5e49e6ef3967fc13, the setting is
2aacef
repurposed as the default interval to measure battery charge level and
2aacef
estimate the battery discharging late. And if the system has enough
2aacef
battery capacity, then the system will stay in suspend state and not
2aacef
hibernate even if the time passed. See issue #25269.
2aacef
2aacef
To keep the backward compatibility, let's introduce another setting
2aacef
SuspendEstimationSec= for controlling the interval to measure
2aacef
battery charge level, and make HibernateDelaySec= work as of v251.
2aacef
2aacef
This also drops implementation details from the man page.
2aacef
2aacef
Fixes #25269.
2aacef
2aacef
(cherry picked from commit 4f58b656d92b09a953b7cffcfd1ee6d5136a57ed)
2aacef
2aacef
Resolves: #2151612
2aacef
---
2aacef
 man/systemd-sleep.conf.xml | 58 ++++++++++++++++++++------------------
2aacef
 src/shared/sleep-config.c  | 11 ++++++--
2aacef
 src/shared/sleep-config.h  |  3 ++
2aacef
 src/sleep/sleep.c          | 48 ++++++++++++++++++++++---------
2aacef
 src/sleep/sleep.conf       |  3 +-
2aacef
 5 files changed, 77 insertions(+), 46 deletions(-)
2aacef
2aacef
diff --git a/man/systemd-sleep.conf.xml b/man/systemd-sleep.conf.xml
2aacef
index be04f2cdf1..79ebef1fef 100644
2aacef
--- a/man/systemd-sleep.conf.xml
2aacef
+++ b/man/systemd-sleep.conf.xml
2aacef
@@ -77,29 +77,16 @@
2aacef
       <varlistentry>
2aacef
         <term>suspend-then-hibernate</term>
2aacef
 
2aacef
-        <listitem><para>A low power state where initially user.slice unit is freezed.
2aacef
-        If the hardware supports low-battery alarms (ACPI _BTP), then the system is
2aacef
-        first suspended (the state is stored in RAM) and then hibernates if the system
2aacef
-        is woken up by the hardware via ACPI low-battery signal. Unit user.slice is
2aacef
-        thawed when system returns from hibernation. If the hardware does not support
2aacef
-        low-battery alarms (ACPI _BTP), then the system is suspended based on battery's
2aacef
-        current percentage capacity. If the current battery capacity is higher than 5%, the
2aacef
-        system suspends for interval calculated using battery discharge rate per hour or
2aacef
-        <command>HibernateDelaySec=</command>
2aacef
-        if former is not available.
2aacef
-        Battery discharge rate per hour is stored in a file which is created after
2aacef
-        initial suspend-resume cycle. The value is calculated using battery decreasing
2aacef
-        charge level over a timespan for which system was suspended. For each battery
2aacef
-        connected to the system, there is a unique entry. After RTC alarm wakeup from
2aacef
-        suspend, battery discharge rate per hour is again estimated. If the current battery
2aacef
-        charge level is equal to or less than 5%, the system will be hibernated (the state
2aacef
-        is then stored on disk) else the system goes back to suspend for the interval
2aacef
-        calculated using battery discharge rate per hour.
2aacef
-        In case of manual wakeup, if the battery was discharged while the system was
2aacef
-        suspended, the battery discharge rate is estimated and stored on the filesystem.
2aacef
-        In case the system is woken up by the hardware via the ACPI low-battery signal,
2aacef
-        then it hibernates.
2aacef
-        </para></listitem>
2aacef
+        <listitem>
2aacef
+          <para>A low power state where the system is initially suspended (the state is stored in
2aacef
+          RAM). If the system supports low-battery alarms (ACPI _BTP), then the system will be woken up by
2aacef
+          the ACPI low-battery signal and hibernated (the state is then stored on disk). Also, if not
2aacef
+          interrupted within the timespan specified by <varname>HibernateDelaySec=</varname> or the estimated
2aacef
+          timespan until the system battery charge level goes down to 5%, then the system will be woken up by the
2aacef
+          RTC alarm and hibernated. The estimated timespan is calculated from the change of the battery
2aacef
+          capacity level after the time specified by <varname>SuspendEstimationSec=</varname> or when
2aacef
+          the system is woken up from the suspend.</para>
2aacef
+        </listitem>
2aacef
       </varlistentry>
2aacef
 
2aacef
     </variablelist>
2aacef
@@ -189,13 +176,28 @@
2aacef
         uses the value of <varname>SuspendState=</varname> when suspending and the value of <varname>HibernateState=</varname> when hibernating.
2aacef
         </para></listitem>
2aacef
       </varlistentry>
2aacef
+
2aacef
       <varlistentry>
2aacef
         <term><varname>HibernateDelaySec=</varname></term>
2aacef
-        <listitem><para>The amount of time the system spends in suspend mode
2aacef
-        before the RTC alarm wakes the system, before the battery discharge rate
2aacef
-        can be estimated and used instead to calculate the suspension interval.
2aacef
-        <citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. Defaults
2aacef
-        to 2h.</para></listitem>
2aacef
+
2aacef
+        <listitem>
2aacef
+          <para>The amount of time the system spends in suspend mode before the system is
2aacef
+          automatically put into hibernate mode. Only used by
2aacef
+          <citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
2aacef
+          If the system has a battery, then defaults to the estimated timespan until the system battery charge level goes down to 5%.
2aacef
+          If the system has no battery, then defaults to 2h.</para>
2aacef
+        </listitem>
2aacef
+      </varlistentry>
2aacef
+
2aacef
+      <varlistentry>
2aacef
+        <term><varname>SuspendEstimationSec=</varname></term>
2aacef
+
2aacef
+        <listitem>
2aacef
+          <para>The RTC alarm will wake the system after the specified timespan to measure the system battery
2aacef
+          capacity level and estimate battery discharging rate, which is used for estimating timespan until the system battery charge
2aacef
+          level goes down to 5%. Only used by
2aacef
+          <citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
2aacef
+          Defaults to 2h.</para></listitem>
2aacef
       </varlistentry>
2aacef
     </variablelist>
2aacef
   </refsect1>
2aacef
diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c
2aacef
index 359d293fd0..74653effa2 100644
2aacef
--- a/src/shared/sleep-config.c
2aacef
+++ b/src/shared/sleep-config.c
2aacef
@@ -65,10 +65,14 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) {
2aacef
         int allow_suspend = -1, allow_hibernate = -1,
2aacef
             allow_s2h = -1, allow_hybrid_sleep = -1;
2aacef
 
2aacef
-        sc = new0(SleepConfig, 1);
2aacef
+        sc = new(SleepConfig, 1);
2aacef
         if (!sc)
2aacef
                 return log_oom();
2aacef
 
2aacef
+        *sc = (SleepConfig) {
2aacef
+                .hibernate_delay_usec = USEC_INFINITY,
2aacef
+        };
2aacef
+
2aacef
         const ConfigTableItem items[] = {
2aacef
                 { "Sleep", "AllowSuspend",              config_parse_tristate, 0, &allow_suspend                  },
2aacef
                 { "Sleep", "AllowHibernation",          config_parse_tristate, 0, &allow_hibernate                },
2aacef
@@ -83,6 +87,7 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) {
2aacef
                 { "Sleep", "HybridSleepState",          config_parse_strv,     0, sc->states + SLEEP_HYBRID_SLEEP },
2aacef
 
2aacef
                 { "Sleep", "HibernateDelaySec",         config_parse_sec,      0, &sc->hibernate_delay_usec       },
2aacef
+                { "Sleep", "SuspendEstimationSec",      config_parse_sec,      0, &sc->suspend_estimation_usec    },
2aacef
                 {}
2aacef
         };
2aacef
 
2aacef
@@ -113,8 +118,8 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) {
2aacef
                 sc->modes[SLEEP_HYBRID_SLEEP] = strv_new("suspend", "platform", "shutdown");
2aacef
         if (!sc->states[SLEEP_HYBRID_SLEEP])
2aacef
                 sc->states[SLEEP_HYBRID_SLEEP] = strv_new("disk");
2aacef
-        if (sc->hibernate_delay_usec == 0)
2aacef
-                sc->hibernate_delay_usec = 2 * USEC_PER_HOUR;
2aacef
+        if (sc->suspend_estimation_usec == 0)
2aacef
+                sc->suspend_estimation_usec = DEFAULT_SUSPEND_ESTIMATION_USEC;
2aacef
 
2aacef
         /* ensure values set for all required fields */
2aacef
         if (!sc->states[SLEEP_SUSPEND] || !sc->modes[SLEEP_HIBERNATE]
2aacef
diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h
2aacef
index 226fab4b9f..480e90c95b 100644
2aacef
--- a/src/shared/sleep-config.h
2aacef
+++ b/src/shared/sleep-config.h
2aacef
@@ -6,6 +6,8 @@
2aacef
 #include "hashmap.h"
2aacef
 #include "time-util.h"
2aacef
 
2aacef
+#define DEFAULT_SUSPEND_ESTIMATION_USEC (1 * USEC_PER_HOUR)
2aacef
+
2aacef
 typedef enum SleepOperation {
2aacef
         SLEEP_SUSPEND,
2aacef
         SLEEP_HIBERNATE,
2aacef
@@ -20,6 +22,7 @@ typedef struct SleepConfig {
2aacef
         char **modes[_SLEEP_OPERATION_MAX];
2aacef
         char **states[_SLEEP_OPERATION_MAX];
2aacef
         usec_t hibernate_delay_usec;
2aacef
+        usec_t suspend_estimation_usec;
2aacef
 } SleepConfig;
2aacef
 
2aacef
 SleepConfig* free_sleep_config(SleepConfig *sc);
2aacef
diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c
2aacef
index 039b123dcc..0bbea9e856 100644
2aacef
--- a/src/sleep/sleep.c
2aacef
+++ b/src/sleep/sleep.c
2aacef
@@ -268,10 +268,13 @@ static int execute(
2aacef
 
2aacef
 static int custom_timer_suspend(const SleepConfig *sleep_config) {
2aacef
         _cleanup_hashmap_free_ Hashmap *last_capacity = NULL, *current_capacity = NULL;
2aacef
+        usec_t hibernate_timestamp;
2aacef
         int r;
2aacef
 
2aacef
         assert(sleep_config);
2aacef
 
2aacef
+        hibernate_timestamp = usec_add(now(CLOCK_BOOTTIME), sleep_config->hibernate_delay_usec);
2aacef
+
2aacef
         while (battery_is_low() == 0) {
2aacef
                 _cleanup_close_ int tfd = -1;
2aacef
                 struct itimerspec ts = {};
2aacef
@@ -287,14 +290,25 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) {
2aacef
                 if (r < 0)
2aacef
                         return log_error_errno(r, "Error fetching battery capacity percentage: %m");
2aacef
 
2aacef
-                r = get_total_suspend_interval(last_capacity, &suspend_interval);
2aacef
-                if (r < 0) {
2aacef
-                        log_debug_errno(r, "Failed to estimate suspend interval using previous discharge rate, ignoring: %m");
2aacef
-                        /* In case of no battery or any errors, system suspend interval will be set to HibernateDelaySec=. */
2aacef
-                        suspend_interval = sleep_config->hibernate_delay_usec;
2aacef
+                if (hashmap_isempty(last_capacity))
2aacef
+                        /* In case of no battery, system suspend interval will be set to HibernateDelaySec= or 2 hours. */
2aacef
+                        suspend_interval = timestamp_is_set(hibernate_timestamp) ? sleep_config->hibernate_delay_usec : DEFAULT_SUSPEND_ESTIMATION_USEC;
2aacef
+                else {
2aacef
+                        r = get_total_suspend_interval(last_capacity, &suspend_interval);
2aacef
+                        if (r < 0) {
2aacef
+                                log_debug_errno(r, "Failed to estimate suspend interval using previous discharge rate, ignoring: %m");
2aacef
+                                /* In case of any errors, especially when we do not know the battery
2aacef
+                                 * discharging rate, system suspend interval will be set to
2aacef
+                                 * SuspendEstimationSec=. */
2aacef
+                                suspend_interval = sleep_config->suspend_estimation_usec;
2aacef
+                        }
2aacef
                 }
2aacef
 
2aacef
+                /* Do not suspend more than HibernateDelaySec= */
2aacef
                 usec_t before_timestamp = now(CLOCK_BOOTTIME);
2aacef
+                suspend_interval = MIN(suspend_interval, usec_sub_unsigned(hibernate_timestamp, before_timestamp));
2aacef
+                if (suspend_interval <= 0)
2aacef
+                        break; /* system should hibernate */
2aacef
 
2aacef
                 log_debug("Set timerfd wake alarm for %s", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC));
2aacef
                 /* Wake alarm for system with or without battery to hibernate or estimate discharge rate whichever is applicable */
2aacef
@@ -377,7 +391,7 @@ static int freeze_thaw_user_slice(const char **method) {
2aacef
 
2aacef
 static int execute_s2h(const SleepConfig *sleep_config) {
2aacef
         _unused_ _cleanup_(freeze_thaw_user_slice) const char *auto_method_thaw = NULL;
2aacef
-        int r, k;
2aacef
+        int r;
2aacef
 
2aacef
         assert(sleep_config);
2aacef
 
2aacef
@@ -387,15 +401,21 @@ static int execute_s2h(const SleepConfig *sleep_config) {
2aacef
         else
2aacef
                 auto_method_thaw = "ThawUnit"; /* from now on we want automatic thawing */;
2aacef
 
2aacef
-        r = check_wakeup_type();
2aacef
-        if (r < 0)
2aacef
-                log_debug_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
2aacef
-
2aacef
-        k = battery_trip_point_alarm_exists();
2aacef
-        if (k < 0)
2aacef
-                log_debug_errno(k, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
2aacef
+        /* Only check if we have automated battery alarms if HibernateDelaySec= is not set, as in that case
2aacef
+         * we'll busy poll for the configured interval instead */
2aacef
+        if (!timestamp_is_set(sleep_config->hibernate_delay_usec)) {
2aacef
+                r = check_wakeup_type();
2aacef
+                if (r < 0)
2aacef
+                        log_debug_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
2aacef
+                else {
2aacef
+                        r = battery_trip_point_alarm_exists();
2aacef
+                        if (r < 0)
2aacef
+                                log_debug_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
2aacef
+                }
2aacef
+        } else
2aacef
+                r = 0;  /* Force fallback path */
2aacef
 
2aacef
-        if (r >= 0 && k > 0) {
2aacef
+        if (r > 0) { /* If we have both wakeup alarms and battery trip point support, use them */
2aacef
                 log_debug("Attempting to suspend...");
2aacef
                 r = execute(sleep_config, SLEEP_SUSPEND, NULL);
2aacef
                 if (r < 0)
2aacef
diff --git a/src/sleep/sleep.conf b/src/sleep/sleep.conf
2aacef
index a3d31140d8..4c8e8b9680 100644
2aacef
--- a/src/sleep/sleep.conf
2aacef
+++ b/src/sleep/sleep.conf
2aacef
@@ -23,4 +23,5 @@
2aacef
 #HibernateState=disk
2aacef
 #HybridSleepMode=suspend platform shutdown
2aacef
 #HybridSleepState=disk
2aacef
-#HibernateDelaySec=120min
2aacef
+#HibernateDelaySec=
2aacef
+#SuspendEstimationSec=60min