1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-02-01 05:47:04 +03:00

Merge pull request #25374 from yuwata/sleep-fixlets

sleep: several fixlets
This commit is contained in:
Luca Boccassi 2023-01-30 09:51:21 +00:00 committed by GitHub
commit 0a5bd40a70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 120 deletions

View File

@ -77,29 +77,16 @@
<varlistentry> <varlistentry>
<term>suspend-then-hibernate</term> <term>suspend-then-hibernate</term>
<listitem><para>A low power state where initially user.slice unit is freezed. <listitem>
If the hardware supports low-battery alarms (ACPI _BTP), then the system is <para>A low power state where the system is initially suspended (the state is stored in
first suspended (the state is stored in RAM) and then hibernates if the system RAM). If the system supports low-battery alarms (ACPI _BTP), then the system will be woken up by
is woken up by the hardware via ACPI low-battery signal. Unit user.slice is the ACPI low-battery signal and hibernated (the state is then stored on disk). Also, if not
thawed when system returns from hibernation. If the hardware does not support interrupted within the timespan specified by <varname>HibernateDelaySec=</varname> or the estimated
low-battery alarms (ACPI _BTP), then the system is suspended based on battery's timespan until the system battery charge level goes down to 5%, then the system will be woken up by the
current percentage capacity. If the current battery capacity is higher than 5%, the RTC alarm and hibernated. The estimated timespan is calculated from the change of the battery
system suspends for interval calculated using battery discharge rate per hour or capacity level after the time specified by <varname>SuspendEstimationSec=</varname> or when
<command>HibernateDelaySec=</command> the system is woken up from the suspend.</para>
if former is not available. </listitem>
Battery discharge rate per hour is stored in a file which is created after
initial suspend-resume cycle. The value is calculated using battery decreasing
charge level over a timespan for which system was suspended. For each battery
connected to the system, there is a unique entry. After RTC alarm wakeup from
suspend, battery discharge rate per hour is again estimated. If the current battery
charge level is equal to or less than 5%, the system will be hibernated (the state
is then stored on disk) else the system goes back to suspend for the interval
calculated using battery discharge rate per hour.
In case of manual wakeup, if the battery was discharged while the system was
suspended, the battery discharge rate is estimated and stored on the filesystem.
In case the system is woken up by the hardware via the ACPI low-battery signal,
then it hibernates.
</para></listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
@ -189,13 +176,28 @@
uses the value of <varname>SuspendState=</varname> when suspending and the value of <varname>HibernateState=</varname> when hibernating. uses the value of <varname>SuspendState=</varname> when suspending and the value of <varname>HibernateState=</varname> when hibernating.
</para></listitem> </para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><varname>HibernateDelaySec=</varname></term> <term><varname>HibernateDelaySec=</varname></term>
<listitem><para>The amount of time the system spends in suspend mode
before the RTC alarm wakes the system, before the battery discharge rate <listitem>
can be estimated and used instead to calculate the suspension interval. <para>The amount of time the system spends in suspend mode before the system is
<citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. Defaults automatically put into hibernate mode. Only used by
to 2h.</para></listitem> <citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
If the system has a battery, then defaults to the estimated timespan until the system battery charge level goes down to 5%.
If the system has no battery, then defaults to 2h.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>SuspendEstimationSec=</varname></term>
<listitem>
<para>The RTC alarm will wake the system after the specified timespan to measure the system battery
capacity level and estimate battery discharging rate, which is used for estimating timespan until the system battery charge
level goes down to 5%. Only used by
<citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
Defaults to 2h.</para></listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -22,6 +22,7 @@
#include "btrfs-util.h" #include "btrfs-util.h"
#include "conf-parser.h" #include "conf-parser.h"
#include "constants.h" #include "constants.h"
#include "device-private.h"
#include "device-util.h" #include "device-util.h"
#include "devnum-util.h" #include "devnum-util.h"
#include "env-util.h" #include "env-util.h"
@ -65,10 +66,14 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) {
int allow_suspend = -1, allow_hibernate = -1, int allow_suspend = -1, allow_hibernate = -1,
allow_s2h = -1, allow_hybrid_sleep = -1; allow_s2h = -1, allow_hybrid_sleep = -1;
sc = new0(SleepConfig, 1); sc = new(SleepConfig, 1);
if (!sc) if (!sc)
return log_oom(); return log_oom();
*sc = (SleepConfig) {
.hibernate_delay_usec = USEC_INFINITY,
};
const ConfigTableItem items[] = { const ConfigTableItem items[] = {
{ "Sleep", "AllowSuspend", config_parse_tristate, 0, &allow_suspend }, { "Sleep", "AllowSuspend", config_parse_tristate, 0, &allow_suspend },
{ "Sleep", "AllowHibernation", config_parse_tristate, 0, &allow_hibernate }, { "Sleep", "AllowHibernation", config_parse_tristate, 0, &allow_hibernate },
@ -82,7 +87,8 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) {
{ "Sleep", "HybridSleepMode", config_parse_strv, 0, sc->modes + SLEEP_HYBRID_SLEEP }, { "Sleep", "HybridSleepMode", config_parse_strv, 0, sc->modes + SLEEP_HYBRID_SLEEP },
{ "Sleep", "HybridSleepState", config_parse_strv, 0, sc->states + SLEEP_HYBRID_SLEEP }, { "Sleep", "HybridSleepState", config_parse_strv, 0, sc->states + SLEEP_HYBRID_SLEEP },
{ "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_sec }, { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_usec },
{ "Sleep", "SuspendEstimationSec", config_parse_sec, 0, &sc->suspend_estimation_usec },
{} {}
}; };
@ -113,8 +119,8 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) {
sc->modes[SLEEP_HYBRID_SLEEP] = strv_new("suspend", "platform", "shutdown"); sc->modes[SLEEP_HYBRID_SLEEP] = strv_new("suspend", "platform", "shutdown");
if (!sc->states[SLEEP_HYBRID_SLEEP]) if (!sc->states[SLEEP_HYBRID_SLEEP])
sc->states[SLEEP_HYBRID_SLEEP] = strv_new("disk"); sc->states[SLEEP_HYBRID_SLEEP] = strv_new("disk");
if (sc->hibernate_delay_sec == 0) if (sc->suspend_estimation_usec == 0)
sc->hibernate_delay_sec = 2 * USEC_PER_HOUR; sc->suspend_estimation_usec = DEFAULT_SUSPEND_ESTIMATION_USEC;
/* Ensure values set for all required fields */ /* Ensure values set for all required fields */
if (!sc->states[SLEEP_SUSPEND] || !sc->modes[SLEEP_HIBERNATE] if (!sc->states[SLEEP_SUSPEND] || !sc->modes[SLEEP_HIBERNATE]
@ -137,16 +143,27 @@ static int battery_enumerator_new(sd_device_enumerator **ret) {
if (r < 0) if (r < 0)
return r; return r;
r = sd_device_enumerator_add_match_subsystem(e, "power_supply", /* match= */ true); r = sd_device_enumerator_add_match_subsystem(e, "power_supply", /* match = */ true);
if (r < 0) if (r < 0)
return r; return r;
r = sd_device_enumerator_add_match_property(e, "POWER_SUPPLY_TYPE", "Battery"); r = sd_device_enumerator_allow_uninitialized(e);
if (r < 0)
return r;
r = sd_device_enumerator_add_match_sysattr(e, "type", "Battery", /* match = */ true);
if (r < 0)
return r;
r = sd_device_enumerator_add_match_sysattr(e, "present", "1", /* match = */ true);
if (r < 0)
return r;
r = sd_device_enumerator_add_match_sysattr(e, "scope", "Device", /* match = */ false);
if (r < 0) if (r < 0)
return r; return r;
*ret = TAKE_PTR(e); *ret = TAKE_PTR(e);
return 0; return 0;
} }
@ -165,18 +182,13 @@ int get_capacity_by_name(Hashmap *capacities_by_name, const char *name) {
/* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */ /* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */
static int read_battery_capacity_percentage(sd_device *dev) { static int read_battery_capacity_percentage(sd_device *dev) {
const char *power_supply_capacity;
int battery_capacity, r; int battery_capacity, r;
assert(dev); assert(dev);
r = sd_device_get_property_value(dev, "POWER_SUPPLY_CAPACITY", &power_supply_capacity); r = device_get_sysattr_int(dev, "capacity", &battery_capacity);
if (r < 0) if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get property POWER_SUPPLY_CAPACITY: %m"); return log_device_debug_errno(dev, r, "Failed to read/parse POWER_SUPPLY_CAPACITY: %m");
r = safe_atoi(power_supply_capacity, &battery_capacity);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to parse property POWER_SUPPLY_CAPACITY: %m");
if (battery_capacity < 0 || battery_capacity > 100) if (battery_capacity < 0 || battery_capacity > 100)
return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity"); return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity");
@ -198,15 +210,9 @@ int battery_is_low(void) {
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to initialize battery enumerator: %m"); return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
FOREACH_DEVICE(e, dev) { FOREACH_DEVICE(e, dev)
r = read_battery_capacity_percentage(dev); if (read_battery_capacity_percentage(dev) > BATTERY_LOW_CAPACITY_LEVEL)
if (r < 0) {
log_device_debug_errno(dev, r, "Failed to get battery capacity, ignoring: %m");
continue;
}
if (r > BATTERY_LOW_CAPACITY_LEVEL)
return false; return false;
}
return true; return true;
} }
@ -259,61 +265,55 @@ int fetch_batteries_capacity_by_name(Hashmap **ret) {
return 0; return 0;
} }
/* Read file path and return hash of value in that file */ static int siphash24_compress_device_sysattr(sd_device *dev, const char *attr, struct siphash *state) {
static int get_battery_identifier(sd_device *dev, const char *property, struct siphash *state) {
const char *x; const char *x;
int r; int r;
assert(dev); assert(dev);
assert(property); assert(attr);
assert(state); assert(state);
r = sd_device_get_property_value(dev, property, &x); r = sd_device_get_sysattr_value(dev, attr, &x);
if (r == -ENOENT) if (r < 0)
log_device_debug_errno(dev, r, "Battery device property %s is unavailable, ignoring: %m", property); return log_device_debug_errno(dev, r, "Failed to read '%s' attribute: %m", attr);
else if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get battery device property %s: %m", property);
else if (isempty(x))
log_device_debug(dev, "Battery device property '%s' is empty.", property);
else
siphash24_compress_string(x, state);
if (!isempty(x))
siphash24_compress_string(x, state);
return 0;
}
static int siphash24_compress_id128(int (*getter)(sd_id128_t*), const char *name, struct siphash *state) {
sd_id128_t id;
int r;
assert(getter);
assert(state);
r = getter(&id);
if (r < 0)
return log_debug_errno(r, "Failed to get %s ID: %m", name);
siphash24_compress(&id, sizeof(sd_id128_t), state);
return 0; return 0;
} }
/* Read system and battery identifier from specific location and generate hash of it */ /* Read system and battery identifier from specific location and generate hash of it */
static int get_system_battery_identifier_hash(sd_device *dev, uint64_t *ret) { static int get_system_battery_identifier_hash(sd_device *dev, uint64_t *ret) {
struct siphash state; struct siphash state;
sd_id128_t machine_id, product_id;
int r;
assert(ret); assert(ret);
assert(dev); assert(dev);
siphash24_init(&state, BATTERY_DISCHARGE_RATE_HASH_KEY.bytes); siphash24_init(&state, BATTERY_DISCHARGE_RATE_HASH_KEY.bytes);
get_battery_identifier(dev, "POWER_SUPPLY_MANUFACTURER", &state); (void) siphash24_compress_device_sysattr(dev, "manufacturer", &state);
get_battery_identifier(dev, "POWER_SUPPLY_MODEL_NAME", &state); (void) siphash24_compress_device_sysattr(dev, "model_name", &state);
get_battery_identifier(dev, "POWER_SUPPLY_SERIAL_NUMBER", &state); (void) siphash24_compress_device_sysattr(dev, "serial_number", &state);
(void) siphash24_compress_id128(sd_id128_get_machine, "machine", &state);
r = sd_id128_get_machine(&machine_id); (void) siphash24_compress_id128(id128_get_product, "product", &state);
if (r == -ENOENT)
log_debug_errno(r, "machine ID is unavailable: %m");
else if (r < 0)
return log_debug_errno(r, "Failed to get machine ID: %m");
else
siphash24_compress(&machine_id, sizeof(sd_id128_t), &state);
r = id128_get_product(&product_id);
if (r == -ENOENT)
log_debug_errno(r, "product_id does not exist: %m");
else if (r < 0)
return log_debug_errno(r, "Failed to get product ID: %m");
else
siphash24_compress(&product_id, sizeof(sd_id128_t), &state);
*ret = siphash24_finalize(&state); *ret = siphash24_finalize(&state);
return 0; return 0;
} }
@ -391,11 +391,11 @@ static int put_battery_discharge_rate(int estimated_battery_discharge_rate, uint
estimated_battery_discharge_rate); estimated_battery_discharge_rate);
r = write_string_filef( r = write_string_filef(
DISCHARGE_RATE_FILEPATH, DISCHARGE_RATE_FILEPATH,
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_MKDIR_0755 | (trunc ? WRITE_STRING_FILE_TRUNCATE : 0), WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_MKDIR_0755 | (trunc ? WRITE_STRING_FILE_TRUNCATE : 0),
"%"PRIu64" %d", "%"PRIu64" %d",
system_hash_id, system_hash_id,
estimated_battery_discharge_rate); estimated_battery_discharge_rate);
if (r < 0) if (r < 0)
return log_debug_errno(r, "Failed to update %s: %m", DISCHARGE_RATE_FILEPATH); return log_debug_errno(r, "Failed to update %s: %m", DISCHARGE_RATE_FILEPATH);

View File

@ -6,6 +6,8 @@
#include "hashmap.h" #include "hashmap.h"
#include "time-util.h" #include "time-util.h"
#define DEFAULT_SUSPEND_ESTIMATION_USEC (1 * USEC_PER_HOUR)
typedef enum SleepOperation { typedef enum SleepOperation {
SLEEP_SUSPEND, SLEEP_SUSPEND,
SLEEP_HIBERNATE, SLEEP_HIBERNATE,
@ -19,7 +21,8 @@ typedef struct SleepConfig {
bool allow[_SLEEP_OPERATION_MAX]; bool allow[_SLEEP_OPERATION_MAX];
char **modes[_SLEEP_OPERATION_MAX]; char **modes[_SLEEP_OPERATION_MAX];
char **states[_SLEEP_OPERATION_MAX]; char **states[_SLEEP_OPERATION_MAX];
usec_t hibernate_delay_sec; usec_t hibernate_delay_usec;
usec_t suspend_estimation_usec;
} SleepConfig; } SleepConfig;
SleepConfig* free_sleep_config(SleepConfig *sc); SleepConfig* free_sleep_config(SleepConfig *sc);

View File

@ -267,36 +267,48 @@ static int execute(
} }
static int custom_timer_suspend(const SleepConfig *sleep_config) { static int custom_timer_suspend(const SleepConfig *sleep_config) {
usec_t hibernate_timestamp;
int r; int r;
assert(sleep_config); assert(sleep_config);
hibernate_timestamp = usec_add(now(CLOCK_BOOTTIME), sleep_config->hibernate_delay_usec);
while (battery_is_low() == 0) { while (battery_is_low() == 0) {
_cleanup_hashmap_free_ Hashmap *last_capacity = NULL, *current_capacity = NULL; _cleanup_hashmap_free_ Hashmap *last_capacity = NULL, *current_capacity = NULL;
_cleanup_close_ int tfd = -EBADF; _cleanup_close_ int tfd = -EBADF;
struct itimerspec ts = {}; struct itimerspec ts = {};
usec_t suspend_interval = sleep_config->hibernate_delay_sec, before_timestamp = 0, after_timestamp = 0, total_suspend_interval; usec_t suspend_interval;
bool woken_by_timer; bool woken_by_timer;
tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC); tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC);
if (tfd < 0) if (tfd < 0)
return log_error_errno(errno, "Error creating timerfd: %m"); return log_error_errno(errno, "Error creating timerfd: %m");
/* Store current battery capacity and current time before suspension */ /* Store current battery capacity before suspension */
r = fetch_batteries_capacity_by_name(&last_capacity); r = fetch_batteries_capacity_by_name(&last_capacity);
if (r >= 0) if (r < 0)
before_timestamp = now(CLOCK_BOOTTIME);
else if (r == -ENOENT)
/* In case of no battery, system suspend interval will be set to HibernateDelaySec=. */
log_debug_errno(r, "Suspend Interval value set to %s: %m", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC));
else
return log_error_errno(r, "Error fetching battery capacity percentage: %m"); return log_error_errno(r, "Error fetching battery capacity percentage: %m");
r = get_total_suspend_interval(last_capacity, &total_suspend_interval); if (hashmap_isempty(last_capacity))
if (r < 0) /* In case of no battery, system suspend interval will be set to HibernateDelaySec= or 2 hours. */
log_debug_errno(r, "Failed to estimate suspend interval using previous discharge rate, ignoring: %m"); suspend_interval = timestamp_is_set(hibernate_timestamp) ? sleep_config->hibernate_delay_usec : DEFAULT_SUSPEND_ESTIMATION_USEC;
else else {
suspend_interval = total_suspend_interval; r = get_total_suspend_interval(last_capacity, &suspend_interval);
if (r < 0) {
log_debug_errno(r, "Failed to estimate suspend interval using previous discharge rate, ignoring: %m");
/* In case of any errors, especially when we do not know the battery
* discharging rate, system suspend interval will be set to
* SuspendEstimationSec=. */
suspend_interval = sleep_config->suspend_estimation_usec;
}
}
/* Do not suspend more than HibernateDelaySec= */
usec_t before_timestamp = now(CLOCK_BOOTTIME);
suspend_interval = MIN(suspend_interval, usec_sub_unsigned(hibernate_timestamp, before_timestamp));
if (suspend_interval <= 0)
break; /* system should hibernate */
log_debug("Set timerfd wake alarm for %s", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC)); log_debug("Set timerfd wake alarm for %s", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC));
/* Wake alarm for system with or without battery to hibernate or estimate discharge rate whichever is applicable */ /* Wake alarm for system with or without battery to hibernate or estimate discharge rate whichever is applicable */
@ -316,17 +328,20 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) {
woken_by_timer = FLAGS_SET(r, POLLIN); woken_by_timer = FLAGS_SET(r, POLLIN);
r = fetch_batteries_capacity_by_name(&current_capacity); r = fetch_batteries_capacity_by_name(&current_capacity);
if (r < 0) { if (r < 0 || hashmap_isempty(current_capacity)) {
/* In case of no battery or error while getting charge level, no need to measure /* In case of no battery or error while getting charge level, no need to measure
* discharge rate. Instead system should wakeup if it is manual wakeup or * discharge rate. Instead the system should wake up if it is manual wakeup or
* hibernate if this is a timer wakeup. */ * hibernate if this is a timer wakeup. */
log_debug_errno(r, "Battery capacity percentage unavailable, cannot estimate discharge rate: %m"); if (r < 0)
log_debug_errno(r, "Battery capacity percentage unavailable, cannot estimate discharge rate: %m");
else
log_debug("No battery found.");
if (!woken_by_timer) if (!woken_by_timer)
return 0; return 0;
break; break;
} }
after_timestamp = now(CLOCK_BOOTTIME); usec_t after_timestamp = now(CLOCK_BOOTTIME);
log_debug("Attempting to estimate battery discharge rate after wakeup from %s sleep", log_debug("Attempting to estimate battery discharge rate after wakeup from %s sleep",
FORMAT_TIMESPAN(after_timestamp - before_timestamp, USEC_PER_HOUR)); FORMAT_TIMESPAN(after_timestamp - before_timestamp, USEC_PER_HOUR));
@ -379,7 +394,7 @@ static int freeze_thaw_user_slice(const char **method) {
static int execute_s2h(const SleepConfig *sleep_config) { static int execute_s2h(const SleepConfig *sleep_config) {
_unused_ _cleanup_(freeze_thaw_user_slice) const char *auto_method_thaw = "ThawUnit"; _unused_ _cleanup_(freeze_thaw_user_slice) const char *auto_method_thaw = "ThawUnit";
int r, k; int r;
assert(sleep_config); assert(sleep_config);
@ -387,15 +402,21 @@ static int execute_s2h(const SleepConfig *sleep_config) {
if (r < 0) if (r < 0)
log_debug_errno(r, "Failed to freeze unit user.slice, ignoring: %m"); log_debug_errno(r, "Failed to freeze unit user.slice, ignoring: %m");
r = check_wakeup_type(); /* Only check if we have automated battery alarms if HibernateDelaySec= is not set, as in that case
if (r < 0) * we'll busy poll for the configured interval instead */
log_debug_errno(r, "Failed to check hardware wakeup type, ignoring: %m"); if (!timestamp_is_set(sleep_config->hibernate_delay_usec)) {
r = check_wakeup_type();
if (r < 0)
log_debug_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
else {
r = battery_trip_point_alarm_exists();
if (r < 0)
log_debug_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
}
} else
r = 0; /* Force fallback path */
k = battery_trip_point_alarm_exists(); if (r > 0) { /* If we have both wakeup alarms and battery trip point support, use them */
if (k < 0)
log_debug_errno(k, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
if (r >= 0 && k > 0) {
log_debug("Attempting to suspend..."); log_debug("Attempting to suspend...");
r = execute(sleep_config, SLEEP_SUSPEND, NULL); r = execute(sleep_config, SLEEP_SUSPEND, NULL);
if (r < 0) if (r < 0)
@ -410,9 +431,9 @@ static int execute_s2h(const SleepConfig *sleep_config) {
return 0; return 0;
} else { } else {
r = custom_timer_suspend(sleep_config); r = custom_timer_suspend(sleep_config);
if(r < 0) if (r < 0)
return log_debug_errno(r, "Suspend cycle with manual battery discharge rate estimation failed: %m"); return log_debug_errno(r, "Suspend cycle with manual battery discharge rate estimation failed: %m");
if(r == 0) if (r == 0)
/* manual wakeup */ /* manual wakeup */
return 0; return 0;
} }

View File

@ -23,4 +23,5 @@
#HibernateState=disk #HibernateState=disk
#HybridSleepMode=suspend platform shutdown #HybridSleepMode=suspend platform shutdown
#HybridSleepState=disk #HybridSleepState=disk
#HibernateDelaySec=120min #HibernateDelaySec=
#SuspendEstimationSec=60min