Merge pull request #31986 from YHNdnzj/suspend-mem-mode
sleep: add SleepMemMode= setting for configuring /sys/power/mem_sleep
This commit is contained in:
+29
-11
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -23,5 +23,6 @@
|
||||
#AllowHybridSleep=yes
|
||||
#SuspendState=mem standby freeze
|
||||
#HibernateMode=platform shutdown
|
||||
#SleepMemMode=
|
||||
#HibernateDelaySec=
|
||||
#SuspendEstimationSec=60min
|
||||
|
||||
@@ -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 =/");
|
||||
|
||||
Reference in New Issue
Block a user