Merge pull request #31986 from YHNdnzj/suspend-mem-mode

sleep: add SleepMemMode= setting for configuring /sys/power/mem_sleep
This commit is contained in:
Yu Watanabe
2024-03-29 09:30:54 +09:00
committed by GitHub
8 changed files with 107 additions and 53 deletions
+29 -11
View File
@@ -153,6 +153,27 @@
<xi:include href="version-info.xml" xpointer="v240"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>SuspendState=</varname></term>
<listitem><para>The string to be written to <filename>/sys/power/state</filename> by <citerefentry>
<refentrytitle>systemd-suspend.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
More than one value can be specified by separating multiple values with whitespace. They will be
tried in turn, until one is written without error. If none of the writes succeed, the operation will
be aborted.</para>
<para>The allowed set of values is determined by the kernel and is shown in the file itself (use
<command>cat /sys/power/state</command> to display). See <ulink
url="https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
Basic sysfs Interfaces for System Suspend and Hibernation</ulink> for more details.</para>
<para>
<citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
uses this value when suspending.</para>
<xi:include href="version-info.xml" xpointer="v203"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>HibernateMode=</varname></term>
@@ -175,24 +196,21 @@
</varlistentry>
<varlistentry>
<term><varname>SuspendState=</varname></term>
<term><varname>SleepMemMode=</varname></term>
<listitem><para>The string to be written to <filename>/sys/power/state</filename> by <citerefentry>
<refentrytitle>systemd-suspend.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
<listitem><para>The string to be written to <filename>/sys/power/mem_sleep</filename>
when <option>SuspendState=mem</option> or <command>hybrid-sleep</command> is used.
More than one value can be specified by separating multiple values with whitespace. They will be
tried in turn, until one is written without error. If none of the writes succeed, the operation will
be aborted.</para>
be aborted. Defaults to empty, i.e. the kernel default or kernel command line option
<varname>mem_sleep_default=</varname> is respected.</para>
<para>The allowed set of values is determined by the kernel and is shown in the file itself (use
<command>cat /sys/power/state</command> to display). See <ulink
url="https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
<command>cat /sys/power/mem_sleep</command> to display). See the kernel documentation page
<ulink url="https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation">
Basic sysfs Interfaces for System Suspend and Hibernation</ulink> for more details.</para>
<para>
<citerefentry><refentrytitle>systemd-suspend-then-hibernate.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
uses this value when suspending.</para>
<xi:include href="version-info.xml" xpointer="v203"/></listitem>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
+5 -7
View File
@@ -1268,18 +1268,16 @@ int string_extract_line(const char *s, size_t i, char **ret) {
}
}
int string_contains_word_strv(const char *string, const char *separators, char **words, const char **ret_word) {
/* In the default mode with no separators specified, we split on whitespace and
* don't coalesce separators. */
int string_contains_word_strv(const char *string, const char *separators, char * const *words, const char **ret_word) {
/* In the default mode with no separators specified, we split on whitespace and coalesce separators. */
const ExtractFlags flags = separators ? EXTRACT_DONT_COALESCE_SEPARATORS : 0;
const char *found = NULL;
int r;
for (const char *p = string;;) {
for (;;) {
_cleanup_free_ char *w = NULL;
int r;
r = extract_first_word(&p, &w, separators, flags);
r = extract_first_word(&string, &w, separators, flags);
if (r < 0)
return r;
if (r == 0)
+1 -1
View File
@@ -271,7 +271,7 @@ char* string_erase(char *x);
int string_truncate_lines(const char *s, size_t n_lines, char **ret);
int string_extract_line(const char *s, size_t i, char **ret);
int string_contains_word_strv(const char *string, const char *separators, char **words, const char **ret_word);
int string_contains_word_strv(const char *string, const char *separators, char * const *words, const char **ret_word);
static inline int string_contains_word(const char *string, const char *separators, const char *word) {
return string_contains_word_strv(string, separators, STRV_MAKE(word), NULL);
}
+31 -14
View File
@@ -54,6 +54,8 @@ SleepConfig* sleep_config_free(SleepConfig *sc) {
strv_free(sc->modes[i]);
}
strv_free(sc->mem_modes);
return mfree(sc);
}
@@ -69,8 +71,8 @@ static int config_parse_sleep_mode(
void *data,
void *userdata) {
_cleanup_strv_free_ char **modes = NULL;
char ***sv = ASSERT_PTR(data);
_cleanup_strv_free_ char **modes = NULL;
int r;
assert(filename);
@@ -87,7 +89,7 @@ static int config_parse_sleep_mode(
return log_oom();
}
return free_and_replace(*sv, modes);
return strv_free_and_replace(*sv, modes);
}
static void sleep_config_validate_state_and_mode(SleepConfig *sc) {
@@ -140,6 +142,8 @@ int parse_sleep_config(SleepConfig **ret) {
{ "Sleep", "HybridSleepState", config_parse_warn_compat, DISABLED_LEGACY, NULL },
{ "Sleep", "HybridSleepMode", config_parse_warn_compat, DISABLED_LEGACY, NULL },
{ "Sleep", "SleepMemMode", config_parse_sleep_mode, 0, &sc->mem_modes },
{ "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_usec },
{ "Sleep", "SuspendEstimationSec", config_parse_sec, 0, &sc->suspend_estimation_usec },
{}
@@ -183,7 +187,7 @@ int parse_sleep_config(SleepConfig **ret) {
return 0;
}
int sleep_state_supported(char **states) {
int sleep_state_supported(char * const *states) {
_cleanup_free_ char *supported_sysfs = NULL;
const char *found;
int r;
@@ -213,22 +217,24 @@ int sleep_state_supported(char **states) {
return false;
}
int sleep_mode_supported(char **modes) {
int sleep_mode_supported(const char *path, char * const *modes) {
_cleanup_free_ char *supported_sysfs = NULL;
int r;
assert(path);
/* Unlike state, kernel has its own default choice if not configured */
if (strv_isempty(modes)) {
log_debug("No sleep mode configured, using kernel default.");
log_debug("No sleep mode configured, using kernel default for %s.", path);
return true;
}
if (access("/sys/power/disk", W_OK) < 0)
return log_debug_errno(errno, "/sys/power/disk is not writable: %m");
if (access(path, W_OK) < 0)
return log_debug_errno(errno, "%s is not writable: %m", path);
r = read_one_line_file("/sys/power/disk", &supported_sysfs);
r = read_one_line_file(path, &supported_sysfs);
if (r < 0)
return log_debug_errno(r, "Failed to read /sys/power/disk: %m");
return log_debug_errno(r, "Failed to read %s: %m", path);
for (const char *p = supported_sysfs;;) {
_cleanup_free_ char *word = NULL;
@@ -237,7 +243,7 @@ int sleep_mode_supported(char **modes) {
r = extract_first_word(&p, &word, NULL, 0);
if (r < 0)
return log_debug_errno(r, "Failed to parse /sys/power/disk: %m");
return log_debug_errno(r, "Failed to parse %s: %m", path);
if (r == 0)
break;
@@ -250,14 +256,15 @@ int sleep_mode_supported(char **modes) {
}
if (strv_contains(modes, mode)) {
log_debug("Disk sleep mode '%s' is supported by kernel.", mode);
log_debug("Sleep mode '%s' is supported by kernel (%s).", mode, path);
return true;
}
}
if (DEBUG_LOGGING) {
_cleanup_free_ char *joined = strv_join(modes, " ");
log_debug("None of the configured hibernation power modes are supported by kernel: %s", strnull(joined));
log_debug("None of the configured modes are supported by kernel (%s): %s",
path, strnull(joined));
}
return false;
}
@@ -341,8 +348,18 @@ static int sleep_supported_internal(
return false;
}
if (sleep_operation_is_hibernation(operation)) {
r = sleep_mode_supported(sleep_config->modes[operation]);
if (SLEEP_NEEDS_MEM_SLEEP(sleep_config, operation)) {
r = sleep_mode_supported("/sys/power/mem_sleep", sleep_config->mem_modes);
if (r < 0)
return r;
if (r == 0) {
*ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED;
return false;
}
}
if (SLEEP_OPERATION_IS_HIBERNATION(operation)) {
r = sleep_mode_supported("/sys/power/disk", sleep_config->modes[operation]);
if (r < 0)
return r;
if (r == 0) {
+18 -4
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "strv.h"
#include "time-util.h"
typedef enum SleepOperation {
@@ -20,7 +21,7 @@ typedef enum SleepOperation {
const char* sleep_operation_to_string(SleepOperation s) _const_;
SleepOperation sleep_operation_from_string(const char *s) _pure_;
static inline bool sleep_operation_is_hibernation(SleepOperation operation) {
static inline bool SLEEP_OPERATION_IS_HIBERNATION(SleepOperation operation) {
return IN_SET(operation, SLEEP_HIBERNATE, SLEEP_HYBRID_SLEEP);
}
@@ -28,7 +29,8 @@ typedef struct SleepConfig {
bool allow[_SLEEP_OPERATION_MAX];
char **states[_SLEEP_OPERATION_CONFIG_MAX];
char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image */
char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image (/sys/power/disk) */
char **mem_modes; /* /sys/power/mem_sleep */
usec_t hibernate_delay_usec;
usec_t suspend_estimation_usec;
@@ -39,6 +41,18 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SleepConfig*, sleep_config_free);
int parse_sleep_config(SleepConfig **sleep_config);
static inline bool SLEEP_NEEDS_MEM_SLEEP(const SleepConfig *sc, SleepOperation operation) {
assert(sc);
assert(operation >= 0 && operation < _SLEEP_OPERATION_CONFIG_MAX);
/* As per https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation,
* /sys/power/mem_sleep is honored if /sys/power/state is set to "mem" (common for suspend)
* or /sys/power/disk is set to "suspend" (hybrid-sleep). */
return strv_contains(sc->states[operation], "mem") ||
strv_contains(sc->modes[operation], "suspend");
}
typedef enum SleepSupport {
SLEEP_SUPPORTED,
SLEEP_DISABLED, /* Disabled in SleepConfig.allow */
@@ -55,5 +69,5 @@ static inline int sleep_supported(SleepOperation operation) {
}
/* Only for test-sleep-config */
int sleep_state_supported(char **states);
int sleep_mode_supported(char **modes);
int sleep_state_supported(char * const *states);
int sleep_mode_supported(const char *path, char * const *modes);
+18 -12
View File
@@ -151,22 +151,22 @@ static int write_state(int fd, char * const *states) {
return r;
}
static int write_mode(char * const *modes) {
int r = 0;
static int write_mode(const char *path, char * const *modes) {
int r, ret = 0;
assert(path);
STRV_FOREACH(mode, modes) {
int k;
k = write_string_file("/sys/power/disk", *mode, WRITE_STRING_FILE_DISABLE_BUFFER);
if (k >= 0) {
log_debug("Using sleep disk mode '%s'.", *mode);
r = write_string_file(path, *mode, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r >= 0) {
log_debug("Using sleep mode '%s' for %s.", *mode, path);
return 0;
}
RET_GATHER(r, log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", *mode));
RET_GATHER(ret, log_debug_errno(r, "Failed to write '%s' to %s: %m", *mode, path));
}
return r;
return ret;
}
static int lock_all_homes(void) {
@@ -237,8 +237,14 @@ static int execute(
if (state_fd < 0)
return log_error_errno(errno, "Failed to open /sys/power/state: %m");
if (SLEEP_NEEDS_MEM_SLEEP(sleep_config, operation)) {
r = write_mode("/sys/power/mem_sleep", sleep_config->mem_modes);
if (r < 0)
return log_error_errno(r, "Failed to write mode to /sys/power/mem_sleep: %m");
}
/* Configure hibernation settings if we are supposed to hibernate */
if (sleep_operation_is_hibernation(operation)) {
if (SLEEP_OPERATION_IS_HIBERNATION(operation)) {
_cleanup_(hibernation_device_done) HibernationDevice hibernation_device = {};
bool resume_set;
@@ -259,7 +265,7 @@ static int execute(
goto fail;
}
r = write_mode(sleep_config->modes[operation]);
r = write_mode("/sys/power/disk", sleep_config->modes[operation]);
if (r < 0) {
log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");
goto fail;
@@ -301,7 +307,7 @@ static int execute(
return 0;
fail:
if (sleep_operation_is_hibernation(operation))
if (SLEEP_OPERATION_IS_HIBERNATION(operation))
clear_efi_hibernate_location_and_warn();
return r;
+1
View File
@@ -23,5 +23,6 @@
#AllowHybridSleep=yes
#SuspendState=mem standby freeze
#HibernateMode=platform shutdown
#SleepMemMode=
#HibernateDelaySec=
#SuspendEstimationSec=60min
+4 -4
View File
@@ -52,10 +52,10 @@ TEST(sleep_supported) {
log_info("Standby configured: %s", yes_no(sleep_state_supported(standby) > 0));
log_info("Suspend configured: %s", yes_no(sleep_state_supported(mem) > 0));
log_info("Hibernate configured: %s", yes_no(sleep_state_supported(disk) > 0));
log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(sleep_mode_supported(suspend) > 0));
log_info("Hibernate+Reboot configured: %s", yes_no(sleep_mode_supported(reboot) > 0));
log_info("Hibernate+Platform configured: %s", yes_no(sleep_mode_supported(platform) > 0));
log_info("Hibernate+Shutdown configured: %s", yes_no(sleep_mode_supported(shutdown) > 0));
log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", suspend) > 0));
log_info("Hibernate+Reboot configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", reboot) > 0));
log_info("Hibernate+Platform configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", platform) > 0));
log_info("Hibernate+Shutdown configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", shutdown) > 0));
log_info("Freeze configured: %s", yes_no(sleep_state_supported(freeze) > 0));
log_info("/= high-level sleep verbs =/");