Skip to content

Commit

Permalink
Try to lay present nvidia cards to sleep when suspending/hibernating
Browse files Browse the repository at this point in the history
The system won't resume after suspend when nvidia drivers are in use.
This is, because nvidia cards have to be told to go into suspension
or hibernation before putting the computer to sleep.
After waking up, the cards have to be reactivated, or the display
would not come back online.

Modern nvidia driver installments provide a script for this at
`/usr/bin/nvidia-sleep.sh`, but the script is not guaranteed to
exist.

With this patch elogind will do the following, if the path
  `/proc/driver/nvidia/suspend`
exists:

* Save the active VT if the active one is not a 'tty' display
* Switch to a neutral VT '0' (kernel log)
* If the switch succeeded, write either either "suspend" or
  "hibernate" to `/proc/driver/nvidia/suspend`

And when waking up:

* If a VT was saved, switch back to it
* Write "resume" to `/proc/driver/nvidia/suspend`

This basically recreates the functionality of
`/usr/bin/nvidia-sleep.sh` within elogind.

Closes: #140

Signed-off-by: Sven Eden <sven.eden@prydeworx.com>

(cherry picked from commit ebde530)
(cherry picked from commit 5bbe13a)
(cherry picked from commit 465a6f4)
(cherry picked from commit 274aae1)
  • Loading branch information
Yamakuzure committed Nov 22, 2020
1 parent 497690b commit fc0661e
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 38 deletions.
39 changes: 11 additions & 28 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions src/login/logind-dbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,10 @@ static int method_do_shutdown_or_sleep(

int interactive, r;

#if 1 /// elogind needs these to get the caller uid
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
#endif // 1

assert(m);
assert(message);
#if 0 /// elogind does not need this to be checked
Expand Down Expand Up @@ -2014,6 +2018,13 @@ static int method_do_shutdown_or_sleep(
if (r != 0)
return r;

#if 1 /// elogind will use the senders uid to send nvidia cards to sleep on suspend/hibernate
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_UID, &creds);
if (r >= 0) {
(void) sd_bus_creds_get_uid(creds, &m->scheduled_sleep_uid);
}
#endif // 1

r = bus_manager_shutdown_or_sleep_now_or_later(m, unit_name, w, error);
if (r < 0)
return r;
Expand Down
4 changes: 4 additions & 0 deletions src/login/logind.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ struct Manager {
contains the action we are supposed to perform after the
delay is over */
HandleAction pending_action;

/* To allow elogind to put nvidia cards to sleep on suspend/hibernate,
we store the users uid to get the right VT information */
uid_t scheduled_sleep_uid;
#endif // 0
sd_event_source *inhibit_timeout_source;

Expand Down
109 changes: 99 additions & 10 deletions src/sleep/sleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,90 @@

/// Additional includes needed by elogind
#include "exec-elogind.h"
#include "sd-login.h"
#include "sleep.h"
#include "terminal-util.h"
#include "utmp-wtmp.h"

static char* arg_verb = NULL;

STATIC_DESTRUCTOR_REGISTER(arg_verb, freep);

static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
#if 1 /// If an nvidia card is present, elogind informs its driver about suspend/resume actions
static int nvidia_sleep(Manager* m, char const* verb, unsigned* vtnr) {
static char const* drv_suspend = "/proc/driver/nvidia/suspend";
struct stat std;
int r, vt = 0, x11 = 0;
char** session;
char** sessions;
char* type;

assert(verb);
assert(vtnr);

// See whether an nvidia suspension is possible
r = stat(drv_suspend, &std);
if (r)
return 0;

if (STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate")) {
*vtnr = 0;

// Find the (active) sessions of the sleep sender
r = sd_uid_get_sessions(m->scheduled_sleep_uid, 1, &sessions);
if (r < 0)
return 0;

// Find one with a VT (Really, sessions should hold only one active session anyway!)
STRV_FOREACH(session, sessions) {
int k;
k = sd_session_get_vt(*session, &vt);
if (k >= 0)
break;
}

// See whether the type of any active session is not "tty"
STRV_FOREACH(session, sessions) {
int k;
k = sd_session_get_type(*session, &type);
if ((k >= 0) && strcmp("tty", strnull(type)))
++x11;
}

strv_free(sessions);

// Get to a safe non-gui VT if we are on any GUI
if ( x11 > 0 ) {
*vtnr = vt;
(void) chvt(0);
}

// Okay, go to sleep.
if (STR_IN_SET(verb, "suspend", "suspend-then-hibernate"))
r = write_string_file(drv_suspend, "suspend", WRITE_STRING_FILE_DISABLE_BUFFER);
else
r = write_string_file(drv_suspend, "hibernate", WRITE_STRING_FILE_DISABLE_BUFFER);

if (r)
return 0;
} else if (streq(verb, "resume")) {
// Wakeup the device
(void) write_string_file(drv_suspend, verb, WRITE_STRING_FILE_DISABLE_BUFFER);
// Then try to change back
if (*vtnr > 0)
(void) chvt(*vtnr);
}

return 1;
}
#endif // 1

static int write_hibernate_location_info(const HibernateLocation* hibernate_location) {
char offset_str[DECIMAL_STR_MAX(uint64_t)];
char resume_str[DECIMAL_STR_MAX(unsigned) * 2 + STRLEN(":")];
int r;
#if 1 /// To support LVM setups, elogind uses device numbers
char device_num_str [DECIMAL_STR_MAX(uint32_t) * 2 + 2];
char device_num_str[DECIMAL_STR_MAX(uint32_t) * 2 + 2];
struct stat stb;
#endif // 1

Expand Down Expand Up @@ -202,9 +273,10 @@ static int lock_all_homes(void) {
#if 0 /// elogind uses the values stored in its manager instance
static int execute(char **modes, char **states) {
#else // 0

static int execute(Manager* m, char **modes, char **states) {
#endif // 0
char *arguments[] = {
char* arguments[] = {
NULL,
(char*) "pre",
arg_verb,
Expand All @@ -231,6 +303,11 @@ static int execute(Manager* m, char **modes, char **states) {
[STDOUT_COLLECT] = m,
[STDOUT_CONSUME] = m,
};
int have_nvidia;
unsigned vtnr = 0;
int e;
_cleanup_free_ char *l = NULL;
char const* mode_location = strcmp( verb, "suspend") ? "/sys/power/disk" : "/sys/power/mem_sleep";

assert(m);
#endif // 1
Expand Down Expand Up @@ -273,13 +350,13 @@ static int execute(Manager* m, char **modes, char **states) {
m->callback_must_succeed = m->allow_suspend_interrupts;

log_debug_elogind("Executing suspend hook scripts... (Must succeed: %s)",
m->callback_must_succeed ? "YES" : "no" );
m->callback_must_succeed ? "YES" : "no");

r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_output, gather_args, arguments, NULL, EXEC_DIR_NONE);

log_debug_elogind("Result is %d (callback_failed: %s)", r, m->callback_failed ? "true" : "false");

if ( m->callback_must_succeed && (r || m->callback_failed) ) {
if (m->callback_must_succeed && (r || m->callback_failed)) {
e = asprintf(&l, "A sleep script in %s or %s failed! [%d]\n"
"The system %s has been cancelled!",
SYSTEM_SLEEP_PATH, PKGSYSCONFDIR "/system-sleep",
Expand Down Expand Up @@ -308,6 +385,9 @@ static int execute(Manager* m, char **modes, char **states) {
LOG_MESSAGE("Suspending system..."),
"SLEEP=%s", arg_verb);

/* See whether we have an nvidia card to put to sleep */
have_nvidia = nvidia_sleep(m, verb, &vtnr);

r = write_state(&f, states);
if (r < 0)
log_struct_errno(LOG_ERR, r,
Expand All @@ -320,6 +400,10 @@ static int execute(Manager* m, char **modes, char **states) {
LOG_MESSAGE("System resumed."),
"SLEEP=%s", arg_verb);

/* Wakeup a possibly put to sleep nvidia card */
if (have_nvidia)
nvidia_sleep(m, "resume", &vtnr);

arguments[1] = (char*) "post";
#if 0 /// On wakeup the order might matter, so do not have elogind executing the post scripts in parallel!
(void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
Expand All @@ -330,10 +414,10 @@ static int execute(Manager* m, char **modes, char **states) {
return r;
}


#if 0 /// elogind uses the values stored in its manager instance
static int execute_s2h(const SleepConfig *sleep_config) {
#else // 0

static int execute_s2h(Manager *sleep_config) {
#endif // 0
_cleanup_close_ int tfd = -1;
Expand Down Expand Up @@ -503,15 +587,17 @@ static int run(int argc, char *argv[]) {

DEFINE_MAIN_FUNCTION(run);
#else // 0

int do_sleep(Manager *m, const char *verb) {
bool allow;
char **modes = NULL, **states = NULL;
char** modes = NULL;
char** states = NULL;
int r;

assert(verb);
assert(m);

arg_verb = (char*)verb;
arg_verb = (char*) verb;

log_debug_elogind("%s called for %s", __FUNCTION__, strnull(verb));

Expand All @@ -534,8 +620,11 @@ int do_sleep(Manager *m, const char *verb) {

/* Now execute either s2h or the regular sleep */
if (streq(arg_verb, "suspend-then-hibernate"))
return execute_s2h(m);
r = execute_s2h(m);
else
r = execute(m, modes, states);

return execute(m, modes, states);
return r;
}

#endif // 0

0 comments on commit fc0661e

Please sign in to comment.