Message ID | 1406888440-5692-3-git-send-email-alberto.milone@canonical.com |
---|---|
State | Accepted |
Headers | show |
On 08/01/2014 06:20 PM, Alberto Milone wrote: > This allows sharing Logind functions between s3.c and s4.c > > Signed-off-by: Alberto Milone <alberto.milone@canonical.com> > --- > src/lib/include/fwts_framework.h | 2 +- > src/lib/include/fwts_pm_method.h | 74 ++++++- > src/lib/include/fwts_types.h | 7 + > src/lib/src/Makefile.am | 6 +- > src/lib/src/fwts_framework.c | 10 +- > src/lib/src/fwts_pm_method.c | 447 +++++++++++++++++++++++++++++++++++++++ > 6 files changed, 532 insertions(+), 14 deletions(-) > create mode 100644 src/lib/src/fwts_pm_method.c > > diff --git a/src/lib/include/fwts_framework.h b/src/lib/include/fwts_framework.h > index ca6d41e..02fc931 100644 > --- a/src/lib/include/fwts_framework.h > +++ b/src/lib/include/fwts_framework.h > @@ -27,7 +27,7 @@ > #include "fwts_log.h" > #include "fwts_list.h" > #include "fwts_acpica_mode.h" > -#include "fwts_pm_method.h" > +#include "fwts_types.h" > > #define FWTS_FRAMEWORK_MAGIC 0x2af61aec > > diff --git a/src/lib/include/fwts_pm_method.h b/src/lib/include/fwts_pm_method.h > index 8a69719..f2dade5 100644 > --- a/src/lib/include/fwts_pm_method.h > +++ b/src/lib/include/fwts_pm_method.h > @@ -20,11 +20,73 @@ > #ifndef __FWTS_PM_METHOD_MODE_H__ > #define __FWTS_PM_METHOD_MODE_H__ > > -typedef enum { > - logind, > - pm_utils, > - sysfs, > - undefined > -} fwts_pm_method; > +#include <glib.h> > +#include <gio/gio.h> > +#include <time.h> > +#include <stdbool.h> > + > +#include "fwts_types.h" > + > +typedef struct > +{ > + fwts_framework *fw; > + time_t t_start; > + time_t t_end; > + GDBusProxy *logind_proxy; > + GDBusConnection *logind_connection; > + GMainLoop *gmainloop; > + char *action; > + int min_delay; > +} fwts_pm_method_vars; > + > +static inline void free_pm_method_vars(void *); > + > +#define _cleanup_free_pm_vars_ __attribute__((cleanup(free_pm_method_vars))) > + > +#define PM_SUSPEND_LOGIND "Suspend" > +#define PM_SUSPEND_HYBRID_LOGIND "HybridSleep" > +#define PM_HIBERNATE_LOGIND "Hibernate" > + > +#define FWTS_SUSPEND "FWTS_SUSPEND" > +#define FWTS_RESUME "FWTS_RESUME" > +#define FWTS_HIBERNATE "FWTS_HIBERNATE" > +#define FWTS_RESUME "FWTS_RESUME" > + > +static inline void free_pm_method_vars(void *vars) > +{ > + fwts_pm_method_vars *var = *(void**)vars; > + > + if (var) { > + if (var->logind_proxy) { > + g_object_unref(var->logind_proxy); > + var->logind_proxy = NULL; > + } > + if (var->logind_connection) { > + g_object_unref(var->logind_connection); > + var->logind_connection = NULL; > + } > + if (var->gmainloop) { > + g_main_loop_unref(var->gmainloop); > + var->gmainloop = NULL; > + } > + if (var->action) { > + free(var->action); > + var->action = NULL; > + } > + } > + free(var); > + var = NULL; > +} > + > +int fwts_logind_init_proxy(fwts_pm_method_vars *fwts_settings); > +int fwts_logind_wait_for_resume_from_action(fwts_pm_method_vars *fwts_settings, const char *action, int minimum_delay); > +bool fwts_logind_can_suspend(fwts_pm_method_vars *fwts_settings); > +bool fwts_logind_can_hybrid_suspend(fwts_pm_method_vars *fwts_settings); > +bool fwts_logind_can_hibernate(fwts_pm_method_vars *fwts_settings); > +bool fwts_sysfs_can_suspend(const fwts_pm_method_vars *fwts_settings); > +bool fwts_sysfs_can_hybrid_suspend(const fwts_pm_method_vars *fwts_settings); > +bool fwts_sysfs_can_hibernate(const fwts_pm_method_vars *fwts_settings); > +int fwts_sysfs_do_suspend(const fwts_pm_method_vars *fwts_settings, bool s3_hybrid); > +int fwts_sysfs_do_hibernate(const fwts_pm_method_vars *fwts_settings); > > #endif > diff --git a/src/lib/include/fwts_types.h b/src/lib/include/fwts_types.h > index f4b67d0..6dc1cdb 100644 > --- a/src/lib/include/fwts_types.h > +++ b/src/lib/include/fwts_types.h > @@ -45,4 +45,11 @@ typedef enum { > FWTS_BOOL_ERROR = -1 > } fwts_bool; > > +typedef enum { > + FWTS_PM_LOGIND, > + FWTS_PM_PMUTILS, > + FWTS_PM_SYSFS, > + FWTS_PM_UNDEFINED > +} fwts_pm_method; > + > #endif > diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am > index 4391841..1385c68 100644 > --- a/src/lib/src/Makefile.am > +++ b/src/lib/src/Makefile.am > @@ -2,6 +2,7 @@ AM_CPPFLAGS = \ > -I$(top_srcdir)/src/lib/include \ > -I$(top_srcdir)/src/acpica/source/include \ > -I$(top_srcdir)/src/acpica/source/compiler \ > + `pkg-config --cflags glib-2.0 gio-2.0` \ > -DDATAROOTDIR=\"$(datarootdir)\" \ > -Wall -Werror -Wextra > > @@ -11,7 +12,7 @@ libfwts_la_LDFLAGS = \ > -lm -lpcre -lpthread \ > -version-info 1:0:0 \ > -L$(top_builddir)/src/acpica/source/compiler \ > - -lfwtsiasl > + -lfwtsiasl `pkg-config --libs glib-2.0 gio-2.0` > > libfwts_la_CPPFLAGS = $(AM_CPPFLAGS) -DACPI_DEBUG_OUTPUT > > @@ -71,4 +72,5 @@ libfwts_la_SOURCES = \ > fwts_text_list.c \ > fwts_tty.c \ > fwts_uefi.c \ > - fwts_wakealarm.c > + fwts_wakealarm.c \ > + fwts_pm_method.c > diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c > index 475e9a4..7229af5 100644 > --- a/src/lib/src/fwts_framework.c > +++ b/src/lib/src/fwts_framework.c > @@ -1006,11 +1006,11 @@ static int fwts_framework_acpica_parse(fwts_framework *fw, const char *arg) > static int fwts_framework_pm_method_parse(fwts_framework *fw, const char *arg) > { > if (strcmp(arg, "logind") == 0) > - fw->pm_method = logind; > + fw->pm_method = FWTS_PM_LOGIND; > else if (strcmp(arg, "pm-utils") == 0) > - fw->pm_method = pm_utils; > + fw->pm_method = FWTS_PM_PMUTILS; > else if (strcmp(arg, "sysfs") == 0) > - fw->pm_method = sysfs; > + fw->pm_method = FWTS_PM_SYSFS; > else { > fprintf(stderr, "--pm-method only supports logind, pm-utils, and sysfs methods\n"); > return FWTS_ERROR; > @@ -1256,8 +1256,8 @@ int fwts_framework_args(const int argc, char **argv) > if ((fw = (fwts_framework *)calloc(1, sizeof(fwts_framework))) == NULL) > return FWTS_ERROR; > > - /* Set the power method to undefined before we parse arguments */ > - fw->pm_method = undefined; > + /* Set the power method to FWTS_PM_UNDEFINED before we parse arguments */ > + fw->pm_method = FWTS_PM_UNDEFINED; > > ret = fwts_args_add_options(fwts_framework_options, > fwts_framework_options_handler, NULL); > diff --git a/src/lib/src/fwts_pm_method.c b/src/lib/src/fwts_pm_method.c > new file mode 100644 > index 0000000..846aeff > --- /dev/null > +++ b/src/lib/src/fwts_pm_method.c > @@ -0,0 +1,447 @@ > +/* > + * Copyright (C) 2014 Canonical > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * as published by the Free Software Foundation; either version 2 > + * of the License, or (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. > + * > + */ > + > +#include <glib.h> > +#include <gio/gio.h> > + > +#include "fwts.h" > +#include "fwts_pm_method.h" > + > +/* > + * logind_do() > + * call Logind to perform an action > + */ > +static gboolean logind_do(gpointer data) > +{ > + GError *error = NULL; > + GVariant *reply; > + fwts_pm_method_vars *fwts_settings = (fwts_pm_method_vars *)data; > + > + /* If the loop is not running, return TRUE so as to repeat the operation */ > + if (g_main_loop_is_running (fwts_settings->gmainloop)) { > + fwts_log_info(fwts_settings->fw, "Requesting %s action\n", fwts_settings->action); > + reply = g_dbus_proxy_call_sync(fwts_settings->logind_proxy, > + fwts_settings->action, > + g_variant_new ("(b)", > + FALSE), > + G_DBUS_CALL_FLAGS_NONE, > + -1, > + NULL, > + &error); > + > + if (reply != NULL) { > + g_variant_unref(reply); > + } > + else { > + fwts_log_error(fwts_settings->fw, > + "Error from Logind: %s\n", > + error->message); > + g_error_free(error); > + } > + > + return FALSE; > + > + } > + fwts_log_info(fwts_settings->fw, "Glib loop not ready\n"); > + return TRUE; > +} > + > +/* > + * logind_signal_subscribe() > + * subscribe to a signal coming from Logind > + */ > +static guint logind_signal_subscribe( > + GDBusConnection *connection, > + const gchar *logind_signal, > + GDBusSignalCallback callback, > + gpointer user_data) > +{ > + return g_dbus_connection_signal_subscribe (connection, > + "org.freedesktop.login1", /* sender */ > + "org.freedesktop.login1.Manager", > + logind_signal, > + "/org/freedesktop/login1", > + NULL, /* arg0 */ > + G_DBUS_SIGNAL_FLAGS_NONE, > + callback, > + user_data, > + NULL); > +} > + > +/* > + * logind_signal_unsubscribe() > + * unsubscribe from a signal coming from Logind > + */ > +static void logind_signal_unsubscribe(GDBusConnection *connection, guint subscription_id) > +{ > + g_dbus_connection_signal_unsubscribe(connection, subscription_id); > +} > + > +/* > + * logind_on_signal() > + * callback to handle suspend and resume events > + */ > +static void logind_on_signal( > + GDBusConnection *connection, > + const gchar *sender_name, > + const gchar *object_path, > + const gchar *interface_name, > + const gchar *signal_name, > + GVariant *parameters, > + gpointer user_data) > +{ > + gboolean status, is_s3; > + char buffer[50]; > + fwts_pm_method_vars *fwts_settings = (fwts_pm_method_vars *)user_data; > + > + /* Prevent -Werror=unused-parameter from complaining */ > + FWTS_UNUSED(connection); > + FWTS_UNUSED(sender_name); > + FWTS_UNUSED(object_path); > + FWTS_UNUSED(interface_name); > + FWTS_UNUSED(signal_name); > + > + is_s3 = (strcmp(fwts_settings->action, PM_SUSPEND_LOGIND) == 0 || > + strcmp(fwts_settings->action, PM_SUSPEND_HYBRID_LOGIND) == 0); > + > + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE ("(b)"))) { > + fwts_log_error(fwts_settings->fw, "Suspend type %s\n", > + g_variant_get_type_string(parameters)); > + return; > + } > + else { > + g_variant_get(parameters, "(b)", &status); > + fwts_log_info(fwts_settings->fw, > + "Suspend status: %s\n", > + status ? "true" : "false"); > + > + if (status) { > + time(&(fwts_settings->t_start)); > + snprintf(buffer, sizeof(buffer), "Starting fwts %s\n", is_s3 ? "suspend" : "hibernate"); > + (void)fwts_klog_write(fwts_settings->fw, buffer); > + snprintf(buffer, sizeof(buffer), "%s\n", fwts_settings->action); > + (void)fwts_klog_write(fwts_settings->fw, buffer); > + } > + else { > + time(&(fwts_settings->t_end)); > + (void)fwts_klog_write(fwts_settings->fw, FWTS_RESUME "\n"); > + (void)fwts_klog_write(fwts_settings->fw, "Finished fwts resume\n"); > + /* > + * Let's give the system some time to get back from S3 > + * or Logind will refuse to suspend and shoot both events > + * without doing anything > + */ > + if (fwts_settings->min_delay < 3) { > + fwts_log_info(fwts_settings->fw, > + "Skipping the minimum delay (%d) and using a 3 seconds delay instead\n", > + fwts_settings->min_delay); > + sleep(3); > + } > + g_main_loop_quit(fwts_settings->gmainloop); > + } > + } > +} > + > +/* > + * logind_can_do_action() > + * test supported Logind actions that reply with a string > + */ > +static bool logind_can_do_action(fwts_pm_method_vars *fwts_settings, const char* action) > +{ > + GVariant *reply; > + GError *error = NULL; > + bool status = false; > + gchar *response; > + > + if (fwts_logind_init_proxy(fwts_settings) != 0) > + return false; > + > + reply = g_dbus_proxy_call_sync(fwts_settings->logind_proxy, > + action, > + NULL, > + G_DBUS_CALL_FLAGS_NONE, > + -1, > + NULL, > + &error); > + > + if (reply != NULL) { > + if (!g_variant_is_of_type(reply, G_VARIANT_TYPE ("(s)"))) { > + fwts_log_error(fwts_settings->fw, > + "Unexpected response to %s action: %s\n", > + action, > + g_variant_get_type_string (reply)); > + > + g_variant_unref(reply); > + return status; > + } > + > + g_variant_get(reply, "(&s)", &response); > + fwts_log_info(fwts_settings->fw, "Response to %s is %s\n", > + action, response); > + > + if (strcmp(response, "challenge") == 0) { > + fwts_log_error(fwts_settings->fw, > + "%s action available only after authorisation\n", > + action); > + } else if (strcmp(response, "yes") == 0) { > + fwts_log_info(fwts_settings->fw, > + "User allowed to execute the %s action\n", > + action); > + status = true; > + } else if (strcmp(response, "no") == 0) { > + fwts_log_error(fwts_settings->fw, > + "User not allowed to execute the %s action\n", > + action); > + } else if (strcmp(response, "na") == 0) { > + fwts_log_error(fwts_settings->fw, > + "Hardware doesn't support %s action\n", > + action); > + } > + > + g_variant_unref(reply); > + } > + else { > + fwts_log_error(fwts_settings->fw, > + "Invalid response from Logind on %s action\n", > + action); > + g_error_free(error); > + } > + > + return status; > +} > + > +/* > + * fwts_logind_init_proxy() > + * initialise the Dbus proxy for Logind Manager > + */ > +int fwts_logind_init_proxy(fwts_pm_method_vars *fwts_settings) > +{ > + int status = 0; > + > + if (fwts_settings->logind_connection == NULL) > + fwts_settings->logind_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); > + > + if (fwts_settings->logind_connection == NULL) { > + status = 1; > + fwts_log_error(fwts_settings->fw, "Cannot establish a connection to Dbus\n"); > + goto out; > + } > + > + if (fwts_settings->logind_proxy == NULL) { > + fwts_settings->logind_proxy = g_dbus_proxy_new_sync(fwts_settings->logind_connection, > + G_DBUS_PROXY_FLAGS_NONE, > + NULL, "org.freedesktop.login1", > + "/org/freedesktop/login1", > + "org.freedesktop.login1.Manager", > + NULL, NULL); > + } > + > + if (fwts_settings->logind_proxy == NULL) { > + status = 1; > + fwts_log_error(fwts_settings->fw, > + "Cannot establish a connection to login1.Manager\n"); > + goto out; > + } > + > +out: > + return status; > +} > + > +/* > + * fwts_logind_wait_for_resume_from_action() > + * start Glib mainloop and listen to suspend/resume events coming from Logind > + * exit the loop and return the duration after an event. > + * > + * "action" is one of the following actions supported by Logind: > + * "Suspend", "Hibernate", "HybridSleep" > + * "minimum_delay" is the minimum delay to use before the function returns > + * the function after resume > + */ > +int fwts_logind_wait_for_resume_from_action( > + fwts_pm_method_vars *fwts_settings, > + const char *action, > + int minimum_delay) > +{ > + guint subscription_id = 0; > + int duration = 0; > + > + /* Check that the action is supported */ > + if (!(strcmp(action, PM_SUSPEND_LOGIND) == 0 || > + strcmp(action, PM_SUSPEND_HYBRID_LOGIND) == 0 || > + strcmp(action, PM_HIBERNATE_LOGIND) == 0)) { > + fwts_log_error(fwts_settings->fw, "Unknown logind action: %s\n", action); > + return 0; > + } > + > + /* Initialise the proxy */ > + if (fwts_logind_init_proxy(fwts_settings) != 0) { > + fwts_log_error(fwts_settings->fw, "Failed to initialise logind proxy\n"); > + return 0; > + } > + > + /* Set the action to perform */ > + fwts_settings->action = strdup(action); > + if (!fwts_settings->action) { > + fwts_log_error(fwts_settings->fw, "Failed to initialise logind action\n"); > + return 0; > + } > + > + /* Set the minimum delay (this is needed for both S3 and S4) */ > + fwts_settings->min_delay = minimum_delay; > + > + /* Subscribe to the signal that Logind sends on resume */ > + subscription_id = logind_signal_subscribe(fwts_settings->logind_connection, > + "PrepareForSleep", > + logind_on_signal, > + fwts_settings); > + > + /* Start the main loop */ > + fwts_settings->gmainloop = g_main_loop_new(NULL, FALSE); > + if (fwts_settings->gmainloop) { > + g_timeout_add(0.1, logind_do, fwts_settings); > + > + g_main_loop_run(fwts_settings->gmainloop); > + duration = (int)(fwts_settings->t_end - fwts_settings->t_start); > + > + /* Optional, as it will be freed together with the struct */ > + g_main_loop_unref(fwts_settings->gmainloop); > + fwts_settings->gmainloop = NULL; > + } > + else { > + fwts_log_error(fwts_settings->fw, "Failed to start glib mainloop\n"); > + } > + > + /* Unsubscribe from the signal */ > + logind_signal_unsubscribe(fwts_settings->logind_connection, > + subscription_id); > + > + return duration; > +} > + > +/* > + * fwts_logind_can_suspend() > + * return a boolean that states whether suspend is a supported action or not > + */ > +bool fwts_logind_can_suspend(fwts_pm_method_vars *fwts_settings) > +{ > + return logind_can_do_action(fwts_settings, "CanSuspend"); > +} > + > +/* > + * fwts_logind_can_hybrid_suspend() > + * return a boolean that states whether hybrid suspend is a supported action or not > + */ > +bool fwts_logind_can_hybrid_suspend(fwts_pm_method_vars *fwts_settings) > +{ > + return logind_can_do_action(fwts_settings, "CanHybridSleep"); > +} > + > +/* > + * fwts_logind_can_hibernate() > + * return a boolean that states whether hibernate is a supported action or not > + */ > +bool fwts_logind_can_hibernate(fwts_pm_method_vars *fwts_settings) > +{ > + return logind_can_do_action(fwts_settings, "CanHibernate"); > +} > + > +/* > + * fwts_sysfs_can_suspend() > + * return a boolean that states whether suspend is a supported action or not > + */ > +bool fwts_sysfs_can_suspend(const fwts_pm_method_vars *fwts_settings) > +{ > + return fwts_file_first_line_contains_string(fwts_settings->fw, > + "/sys/power/state", > + "mem"); > +} > + > +/* > + * fwts_sysfs_can_hybrid_suspend() > + * return a boolean that states whether hybrid suspend is a supported action or not > + */ > +bool fwts_sysfs_can_hybrid_suspend(const fwts_pm_method_vars *fwts_settings) > +{ > + bool status; > + > + status = fwts_file_first_line_contains_string(fwts_settings->fw, > + "/sys/power/state", > + "disk"); > + > + if (!status) > + return FALSE; > + > + return fwts_file_first_line_contains_string(fwts_settings->fw, > + "/sys/power/disk", > + "suspend"); > +} > + > +/* > + * fwts_sysfs_can_hibernate() > + * return a boolean that states whether hibernate is a supported action or not > + */ > +bool fwts_sysfs_can_hibernate(const fwts_pm_method_vars *fwts_settings) > +{ > + return fwts_file_first_line_contains_string(fwts_settings->fw, > + "/sys/power/state", > + "disk"); > +} > + > +/* > + * fwts_sysfs_do_suspend() > + * enter either S3 or hybrid S3 > + * return the exit status > + */ > +int fwts_sysfs_do_suspend(const fwts_pm_method_vars *fwts_settings, bool s3_hybrid) > +{ > + int status; > + > + if (s3_hybrid) { > + status = fwts_write_string_file(fwts_settings->fw, > + "/sys/power/disk", > + "suspend"); > + > + if (status != FWTS_OK) > + return status; > + > + status = fwts_write_string_file(fwts_settings->fw, > + "/sys/power/state", > + "disk"); > + } > + else { > + status = fwts_write_string_file(fwts_settings->fw, > + "/sys/power/state", > + "mem"); > + } > + > + return status; > +} > + > +/* > + * fwts_sysfs_do_hibernate() > + * enter S4 > + * return the exit status > + */ > +int fwts_sysfs_do_hibernate(const fwts_pm_method_vars *fwts_settings) > +{ > + return fwts_write_string_file(fwts_settings->fw, > + "/sys/power/state", > + "disk"); > +} > + > Acked-by: Alex Hung <alex.hung@canonical.com>
On Mon, Aug 4, 2014 at 6:01 PM, Alex Hung <alex.hung@canonical.com> wrote: > On 08/01/2014 06:20 PM, Alberto Milone wrote: >> >> This allows sharing Logind functions between s3.c and s4.c >> >> Signed-off-by: Alberto Milone <alberto.milone@canonical.com> >> --- >> src/lib/include/fwts_framework.h | 2 +- >> src/lib/include/fwts_pm_method.h | 74 ++++++- >> src/lib/include/fwts_types.h | 7 + >> src/lib/src/Makefile.am | 6 +- >> src/lib/src/fwts_framework.c | 10 +- >> src/lib/src/fwts_pm_method.c | 447 >> +++++++++++++++++++++++++++++++++++++++ >> 6 files changed, 532 insertions(+), 14 deletions(-) >> create mode 100644 src/lib/src/fwts_pm_method.c >> >> diff --git a/src/lib/include/fwts_framework.h >> b/src/lib/include/fwts_framework.h >> index ca6d41e..02fc931 100644 >> --- a/src/lib/include/fwts_framework.h >> +++ b/src/lib/include/fwts_framework.h >> @@ -27,7 +27,7 @@ >> #include "fwts_log.h" >> #include "fwts_list.h" >> #include "fwts_acpica_mode.h" >> -#include "fwts_pm_method.h" >> +#include "fwts_types.h" >> >> #define FWTS_FRAMEWORK_MAGIC 0x2af61aec >> >> diff --git a/src/lib/include/fwts_pm_method.h >> b/src/lib/include/fwts_pm_method.h >> index 8a69719..f2dade5 100644 >> --- a/src/lib/include/fwts_pm_method.h >> +++ b/src/lib/include/fwts_pm_method.h >> @@ -20,11 +20,73 @@ >> #ifndef __FWTS_PM_METHOD_MODE_H__ >> #define __FWTS_PM_METHOD_MODE_H__ >> >> -typedef enum { >> - logind, >> - pm_utils, >> - sysfs, >> - undefined >> -} fwts_pm_method; >> +#include <glib.h> >> +#include <gio/gio.h> >> +#include <time.h> >> +#include <stdbool.h> >> + >> +#include "fwts_types.h" >> + >> +typedef struct >> +{ >> + fwts_framework *fw; >> + time_t t_start; >> + time_t t_end; >> + GDBusProxy *logind_proxy; >> + GDBusConnection *logind_connection; >> + GMainLoop *gmainloop; >> + char *action; >> + int min_delay; >> +} fwts_pm_method_vars; >> + >> +static inline void free_pm_method_vars(void *); >> + >> +#define _cleanup_free_pm_vars_ >> __attribute__((cleanup(free_pm_method_vars))) >> + >> +#define PM_SUSPEND_LOGIND "Suspend" >> +#define PM_SUSPEND_HYBRID_LOGIND "HybridSleep" >> +#define PM_HIBERNATE_LOGIND "Hibernate" >> + >> +#define FWTS_SUSPEND "FWTS_SUSPEND" >> +#define FWTS_RESUME "FWTS_RESUME" >> +#define FWTS_HIBERNATE "FWTS_HIBERNATE" >> +#define FWTS_RESUME "FWTS_RESUME" >> + >> +static inline void free_pm_method_vars(void *vars) >> +{ >> + fwts_pm_method_vars *var = *(void**)vars; >> + >> + if (var) { >> + if (var->logind_proxy) { >> + g_object_unref(var->logind_proxy); >> + var->logind_proxy = NULL; >> + } >> + if (var->logind_connection) { >> + g_object_unref(var->logind_connection); >> + var->logind_connection = NULL; >> + } >> + if (var->gmainloop) { >> + g_main_loop_unref(var->gmainloop); >> + var->gmainloop = NULL; >> + } >> + if (var->action) { >> + free(var->action); >> + var->action = NULL; >> + } >> + } >> + free(var); >> + var = NULL; >> +} >> + >> +int fwts_logind_init_proxy(fwts_pm_method_vars *fwts_settings); >> +int fwts_logind_wait_for_resume_from_action(fwts_pm_method_vars >> *fwts_settings, const char *action, int minimum_delay); >> +bool fwts_logind_can_suspend(fwts_pm_method_vars *fwts_settings); >> +bool fwts_logind_can_hybrid_suspend(fwts_pm_method_vars *fwts_settings); >> +bool fwts_logind_can_hibernate(fwts_pm_method_vars *fwts_settings); >> +bool fwts_sysfs_can_suspend(const fwts_pm_method_vars *fwts_settings); >> +bool fwts_sysfs_can_hybrid_suspend(const fwts_pm_method_vars >> *fwts_settings); >> +bool fwts_sysfs_can_hibernate(const fwts_pm_method_vars *fwts_settings); >> +int fwts_sysfs_do_suspend(const fwts_pm_method_vars *fwts_settings, bool >> s3_hybrid); >> +int fwts_sysfs_do_hibernate(const fwts_pm_method_vars *fwts_settings); >> >> #endif >> diff --git a/src/lib/include/fwts_types.h b/src/lib/include/fwts_types.h >> index f4b67d0..6dc1cdb 100644 >> --- a/src/lib/include/fwts_types.h >> +++ b/src/lib/include/fwts_types.h >> @@ -45,4 +45,11 @@ typedef enum { >> FWTS_BOOL_ERROR = -1 >> } fwts_bool; >> >> +typedef enum { >> + FWTS_PM_LOGIND, >> + FWTS_PM_PMUTILS, >> + FWTS_PM_SYSFS, >> + FWTS_PM_UNDEFINED >> +} fwts_pm_method; >> + >> #endif >> diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am >> index 4391841..1385c68 100644 >> --- a/src/lib/src/Makefile.am >> +++ b/src/lib/src/Makefile.am >> @@ -2,6 +2,7 @@ AM_CPPFLAGS = \ >> -I$(top_srcdir)/src/lib/include \ >> -I$(top_srcdir)/src/acpica/source/include \ >> -I$(top_srcdir)/src/acpica/source/compiler \ >> + `pkg-config --cflags glib-2.0 gio-2.0` \ >> -DDATAROOTDIR=\"$(datarootdir)\" \ >> -Wall -Werror -Wextra >> >> @@ -11,7 +12,7 @@ libfwts_la_LDFLAGS = \ >> -lm -lpcre -lpthread \ >> -version-info 1:0:0 \ >> -L$(top_builddir)/src/acpica/source/compiler \ >> - -lfwtsiasl >> + -lfwtsiasl `pkg-config --libs glib-2.0 gio-2.0` >> >> libfwts_la_CPPFLAGS = $(AM_CPPFLAGS) -DACPI_DEBUG_OUTPUT >> >> @@ -71,4 +72,5 @@ libfwts_la_SOURCES = \ >> fwts_text_list.c \ >> fwts_tty.c \ >> fwts_uefi.c \ >> - fwts_wakealarm.c >> + fwts_wakealarm.c \ >> + fwts_pm_method.c >> diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c >> index 475e9a4..7229af5 100644 >> --- a/src/lib/src/fwts_framework.c >> +++ b/src/lib/src/fwts_framework.c >> @@ -1006,11 +1006,11 @@ static int >> fwts_framework_acpica_parse(fwts_framework *fw, const char *arg) >> static int fwts_framework_pm_method_parse(fwts_framework *fw, const char >> *arg) >> { >> if (strcmp(arg, "logind") == 0) >> - fw->pm_method = logind; >> + fw->pm_method = FWTS_PM_LOGIND; >> else if (strcmp(arg, "pm-utils") == 0) >> - fw->pm_method = pm_utils; >> + fw->pm_method = FWTS_PM_PMUTILS; >> else if (strcmp(arg, "sysfs") == 0) >> - fw->pm_method = sysfs; >> + fw->pm_method = FWTS_PM_SYSFS; >> else { >> fprintf(stderr, "--pm-method only supports logind, >> pm-utils, and sysfs methods\n"); >> return FWTS_ERROR; >> @@ -1256,8 +1256,8 @@ int fwts_framework_args(const int argc, char **argv) >> if ((fw = (fwts_framework *)calloc(1, sizeof(fwts_framework))) == >> NULL) >> return FWTS_ERROR; >> >> - /* Set the power method to undefined before we parse arguments */ >> - fw->pm_method = undefined; >> + /* Set the power method to FWTS_PM_UNDEFINED before we parse >> arguments */ >> + fw->pm_method = FWTS_PM_UNDEFINED; >> >> ret = fwts_args_add_options(fwts_framework_options, >> fwts_framework_options_handler, NULL); >> diff --git a/src/lib/src/fwts_pm_method.c b/src/lib/src/fwts_pm_method.c >> new file mode 100644 >> index 0000000..846aeff >> --- /dev/null >> +++ b/src/lib/src/fwts_pm_method.c >> @@ -0,0 +1,447 @@ >> +/* >> + * Copyright (C) 2014 Canonical >> + * >> + * This program is free software; you can redistribute it and/or >> + * modify it under the terms of the GNU General Public License >> + * as published by the Free Software Foundation; either version 2 >> + * of the License, or (at your option) any later version. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + * GNU General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public License >> + * along with this program; if not, write to the Free Software >> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA >> 02110-1301, USA. >> + * >> + */ >> + >> +#include <glib.h> >> +#include <gio/gio.h> >> + >> +#include "fwts.h" >> +#include "fwts_pm_method.h" >> + >> +/* >> + * logind_do() >> + * call Logind to perform an action >> + */ >> +static gboolean logind_do(gpointer data) >> +{ >> + GError *error = NULL; >> + GVariant *reply; >> + fwts_pm_method_vars *fwts_settings = (fwts_pm_method_vars *)data; >> + >> + /* If the loop is not running, return TRUE so as to repeat the >> operation */ >> + if (g_main_loop_is_running (fwts_settings->gmainloop)) { >> + fwts_log_info(fwts_settings->fw, "Requesting %s action\n", >> fwts_settings->action); >> + reply = >> g_dbus_proxy_call_sync(fwts_settings->logind_proxy, >> + fwts_settings->action, >> + g_variant_new ("(b)", >> + FALSE), >> + G_DBUS_CALL_FLAGS_NONE, >> + -1, >> + NULL, >> + &error); >> + >> + if (reply != NULL) { >> + g_variant_unref(reply); >> + } >> + else { >> + fwts_log_error(fwts_settings->fw, >> + "Error from Logind: %s\n", >> + error->message); >> + g_error_free(error); >> + } >> + >> + return FALSE; >> + >> + } >> + fwts_log_info(fwts_settings->fw, "Glib loop not ready\n"); >> + return TRUE; >> +} >> + >> +/* >> + * logind_signal_subscribe() >> + * subscribe to a signal coming from Logind >> + */ >> +static guint logind_signal_subscribe( >> + GDBusConnection *connection, >> + const gchar *logind_signal, >> + GDBusSignalCallback callback, >> + gpointer user_data) >> +{ >> + return g_dbus_connection_signal_subscribe (connection, >> + "org.freedesktop.login1", /* sender */ >> + "org.freedesktop.login1.Manager", >> + logind_signal, >> + "/org/freedesktop/login1", >> + NULL, /* arg0 */ >> + G_DBUS_SIGNAL_FLAGS_NONE, >> + callback, >> + user_data, >> + NULL); >> +} >> + >> +/* >> + * logind_signal_unsubscribe() >> + * unsubscribe from a signal coming from Logind >> + */ >> +static void logind_signal_unsubscribe(GDBusConnection *connection, guint >> subscription_id) >> +{ >> + g_dbus_connection_signal_unsubscribe(connection, subscription_id); >> +} >> + >> +/* >> + * logind_on_signal() >> + * callback to handle suspend and resume events >> + */ >> +static void logind_on_signal( >> + GDBusConnection *connection, >> + const gchar *sender_name, >> + const gchar *object_path, >> + const gchar *interface_name, >> + const gchar *signal_name, >> + GVariant *parameters, >> + gpointer user_data) >> +{ >> + gboolean status, is_s3; >> + char buffer[50]; >> + fwts_pm_method_vars *fwts_settings = (fwts_pm_method_vars >> *)user_data; >> + >> + /* Prevent -Werror=unused-parameter from complaining */ >> + FWTS_UNUSED(connection); >> + FWTS_UNUSED(sender_name); >> + FWTS_UNUSED(object_path); >> + FWTS_UNUSED(interface_name); >> + FWTS_UNUSED(signal_name); >> + >> + is_s3 = (strcmp(fwts_settings->action, PM_SUSPEND_LOGIND) == 0 || >> + strcmp(fwts_settings->action, PM_SUSPEND_HYBRID_LOGIND) == >> 0); >> + >> + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE ("(b)"))) { >> + fwts_log_error(fwts_settings->fw, "Suspend type %s\n", >> + g_variant_get_type_string(parameters)); >> + return; >> + } >> + else { >> + g_variant_get(parameters, "(b)", &status); >> + fwts_log_info(fwts_settings->fw, >> + "Suspend status: %s\n", >> + status ? "true" : "false"); >> + >> + if (status) { >> + time(&(fwts_settings->t_start)); >> + snprintf(buffer, sizeof(buffer), "Starting fwts >> %s\n", is_s3 ? "suspend" : "hibernate"); >> + (void)fwts_klog_write(fwts_settings->fw, buffer); >> + snprintf(buffer, sizeof(buffer), "%s\n", >> fwts_settings->action); >> + (void)fwts_klog_write(fwts_settings->fw, buffer); >> + } >> + else { >> + time(&(fwts_settings->t_end)); >> + (void)fwts_klog_write(fwts_settings->fw, >> FWTS_RESUME "\n"); >> + (void)fwts_klog_write(fwts_settings->fw, "Finished >> fwts resume\n"); >> + /* >> + * Let's give the system some time to get back >> from S3 >> + * or Logind will refuse to suspend and shoot both >> events >> + * without doing anything >> + */ >> + if (fwts_settings->min_delay < 3) { >> + fwts_log_info(fwts_settings->fw, >> + "Skipping the minimum delay (%d) >> and using a 3 seconds delay instead\n", >> + fwts_settings->min_delay); >> + sleep(3); >> + } >> + g_main_loop_quit(fwts_settings->gmainloop); >> + } >> + } >> +} >> + >> +/* >> + * logind_can_do_action() >> + * test supported Logind actions that reply with a string >> + */ >> +static bool logind_can_do_action(fwts_pm_method_vars *fwts_settings, >> const char* action) >> +{ >> + GVariant *reply; >> + GError *error = NULL; >> + bool status = false; >> + gchar *response; >> + >> + if (fwts_logind_init_proxy(fwts_settings) != 0) >> + return false; >> + >> + reply = g_dbus_proxy_call_sync(fwts_settings->logind_proxy, >> + action, >> + NULL, >> + G_DBUS_CALL_FLAGS_NONE, >> + -1, >> + NULL, >> + &error); >> + >> + if (reply != NULL) { >> + if (!g_variant_is_of_type(reply, G_VARIANT_TYPE ("(s)"))) >> { >> + fwts_log_error(fwts_settings->fw, >> + "Unexpected response to %s action: %s\n", >> + action, >> + g_variant_get_type_string (reply)); >> + >> + g_variant_unref(reply); >> + return status; >> + } >> + >> + g_variant_get(reply, "(&s)", &response); >> + fwts_log_info(fwts_settings->fw, "Response to %s is %s\n", >> + action, response); >> + >> + if (strcmp(response, "challenge") == 0) { >> + fwts_log_error(fwts_settings->fw, >> + "%s action available only after >> authorisation\n", >> + action); >> + } else if (strcmp(response, "yes") == 0) { >> + fwts_log_info(fwts_settings->fw, >> + "User allowed to execute the %s action\n", >> + action); >> + status = true; >> + } else if (strcmp(response, "no") == 0) { >> + fwts_log_error(fwts_settings->fw, >> + "User not allowed to execute the %s >> action\n", >> + action); >> + } else if (strcmp(response, "na") == 0) { >> + fwts_log_error(fwts_settings->fw, >> + "Hardware doesn't support %s action\n", >> + action); >> + } >> + >> + g_variant_unref(reply); >> + } >> + else { >> + fwts_log_error(fwts_settings->fw, >> + "Invalid response from Logind on %s action\n", >> + action); >> + g_error_free(error); >> + } >> + >> + return status; >> +} >> + >> +/* >> + * fwts_logind_init_proxy() >> + * initialise the Dbus proxy for Logind Manager >> + */ >> +int fwts_logind_init_proxy(fwts_pm_method_vars *fwts_settings) >> +{ >> + int status = 0; >> + >> + if (fwts_settings->logind_connection == NULL) >> + fwts_settings->logind_connection = >> g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); >> + >> + if (fwts_settings->logind_connection == NULL) { >> + status = 1; >> + fwts_log_error(fwts_settings->fw, "Cannot establish a >> connection to Dbus\n"); >> + goto out; >> + } >> + >> + if (fwts_settings->logind_proxy == NULL) { >> + fwts_settings->logind_proxy = >> g_dbus_proxy_new_sync(fwts_settings->logind_connection, >> + G_DBUS_PROXY_FLAGS_NONE, >> + NULL, "org.freedesktop.login1", >> + "/org/freedesktop/login1", >> + "org.freedesktop.login1.Manager", >> + NULL, NULL); >> + } >> + >> + if (fwts_settings->logind_proxy == NULL) { >> + status = 1; >> + fwts_log_error(fwts_settings->fw, >> + "Cannot establish a connection to >> login1.Manager\n"); >> + goto out; >> + } >> + >> +out: >> + return status; >> +} >> + >> +/* >> + * fwts_logind_wait_for_resume_from_action() >> + * start Glib mainloop and listen to suspend/resume events coming from >> Logind >> + * exit the loop and return the duration after an event. >> + * >> + * "action" is one of the following actions supported by Logind: >> + * "Suspend", "Hibernate", "HybridSleep" >> + * "minimum_delay" is the minimum delay to use before the function >> returns >> + * the function after resume >> + */ >> +int fwts_logind_wait_for_resume_from_action( >> + fwts_pm_method_vars *fwts_settings, >> + const char *action, >> + int minimum_delay) >> +{ >> + guint subscription_id = 0; >> + int duration = 0; >> + >> + /* Check that the action is supported */ >> + if (!(strcmp(action, PM_SUSPEND_LOGIND) == 0 || >> + strcmp(action, PM_SUSPEND_HYBRID_LOGIND) == 0 || >> + strcmp(action, PM_HIBERNATE_LOGIND) == 0)) { >> + fwts_log_error(fwts_settings->fw, "Unknown logind action: >> %s\n", action); >> + return 0; >> + } >> + >> + /* Initialise the proxy */ >> + if (fwts_logind_init_proxy(fwts_settings) != 0) { >> + fwts_log_error(fwts_settings->fw, "Failed to initialise >> logind proxy\n"); >> + return 0; >> + } >> + >> + /* Set the action to perform */ >> + fwts_settings->action = strdup(action); >> + if (!fwts_settings->action) { >> + fwts_log_error(fwts_settings->fw, "Failed to initialise >> logind action\n"); >> + return 0; >> + } >> + >> + /* Set the minimum delay (this is needed for both S3 and S4) */ >> + fwts_settings->min_delay = minimum_delay; >> + >> + /* Subscribe to the signal that Logind sends on resume */ >> + subscription_id = >> logind_signal_subscribe(fwts_settings->logind_connection, >> + "PrepareForSleep", >> + logind_on_signal, >> + fwts_settings); >> + >> + /* Start the main loop */ >> + fwts_settings->gmainloop = g_main_loop_new(NULL, FALSE); >> + if (fwts_settings->gmainloop) { >> + g_timeout_add(0.1, logind_do, fwts_settings); >> + >> + g_main_loop_run(fwts_settings->gmainloop); >> + duration = (int)(fwts_settings->t_end - >> fwts_settings->t_start); >> + >> + /* Optional, as it will be freed together with the struct >> */ >> + g_main_loop_unref(fwts_settings->gmainloop); >> + fwts_settings->gmainloop = NULL; >> + } >> + else { >> + fwts_log_error(fwts_settings->fw, "Failed to start glib >> mainloop\n"); >> + } >> + >> + /* Unsubscribe from the signal */ >> + logind_signal_unsubscribe(fwts_settings->logind_connection, >> + subscription_id); >> + >> + return duration; >> +} >> + >> +/* >> + * fwts_logind_can_suspend() >> + * return a boolean that states whether suspend is a supported action or >> not >> + */ >> +bool fwts_logind_can_suspend(fwts_pm_method_vars *fwts_settings) >> +{ >> + return logind_can_do_action(fwts_settings, "CanSuspend"); >> +} >> + >> +/* >> + * fwts_logind_can_hybrid_suspend() >> + * return a boolean that states whether hybrid suspend is a supported >> action or not >> + */ >> +bool fwts_logind_can_hybrid_suspend(fwts_pm_method_vars *fwts_settings) >> +{ >> + return logind_can_do_action(fwts_settings, "CanHybridSleep"); >> +} >> + >> +/* >> + * fwts_logind_can_hibernate() >> + * return a boolean that states whether hibernate is a supported action >> or not >> + */ >> +bool fwts_logind_can_hibernate(fwts_pm_method_vars *fwts_settings) >> +{ >> + return logind_can_do_action(fwts_settings, "CanHibernate"); >> +} >> + >> +/* >> + * fwts_sysfs_can_suspend() >> + * return a boolean that states whether suspend is a supported action or >> not >> + */ >> +bool fwts_sysfs_can_suspend(const fwts_pm_method_vars *fwts_settings) >> +{ >> + return fwts_file_first_line_contains_string(fwts_settings->fw, >> + "/sys/power/state", >> + "mem"); >> +} >> + >> +/* >> + * fwts_sysfs_can_hybrid_suspend() >> + * return a boolean that states whether hybrid suspend is a supported >> action or not >> + */ >> +bool fwts_sysfs_can_hybrid_suspend(const fwts_pm_method_vars >> *fwts_settings) >> +{ >> + bool status; >> + >> + status = fwts_file_first_line_contains_string(fwts_settings->fw, >> + "/sys/power/state", >> + "disk"); >> + >> + if (!status) >> + return FALSE; >> + >> + return fwts_file_first_line_contains_string(fwts_settings->fw, >> + "/sys/power/disk", >> + "suspend"); >> +} >> + >> +/* >> + * fwts_sysfs_can_hibernate() >> + * return a boolean that states whether hibernate is a supported action >> or not >> + */ >> +bool fwts_sysfs_can_hibernate(const fwts_pm_method_vars *fwts_settings) >> +{ >> + return fwts_file_first_line_contains_string(fwts_settings->fw, >> + "/sys/power/state", >> + "disk"); >> +} >> + >> +/* >> + * fwts_sysfs_do_suspend() >> + * enter either S3 or hybrid S3 >> + * return the exit status >> + */ >> +int fwts_sysfs_do_suspend(const fwts_pm_method_vars *fwts_settings, bool >> s3_hybrid) >> +{ >> + int status; >> + >> + if (s3_hybrid) { >> + status = fwts_write_string_file(fwts_settings->fw, >> + "/sys/power/disk", >> + "suspend"); >> + >> + if (status != FWTS_OK) >> + return status; >> + >> + status = fwts_write_string_file(fwts_settings->fw, >> + "/sys/power/state", >> + "disk"); >> + } >> + else { >> + status = fwts_write_string_file(fwts_settings->fw, >> + "/sys/power/state", >> + "mem"); >> + } >> + >> + return status; >> +} >> + >> +/* >> + * fwts_sysfs_do_hibernate() >> + * enter S4 >> + * return the exit status >> + */ >> +int fwts_sysfs_do_hibernate(const fwts_pm_method_vars *fwts_settings) >> +{ >> + return fwts_write_string_file(fwts_settings->fw, >> + "/sys/power/state", >> + "disk"); >> +} >> + >> > > Acked-by: Alex Hung <alex.hung@canonical.com> > Acked-by: Keng-Yu Lin <kengyu@canonical.com>
diff --git a/src/lib/include/fwts_framework.h b/src/lib/include/fwts_framework.h index ca6d41e..02fc931 100644 --- a/src/lib/include/fwts_framework.h +++ b/src/lib/include/fwts_framework.h @@ -27,7 +27,7 @@ #include "fwts_log.h" #include "fwts_list.h" #include "fwts_acpica_mode.h" -#include "fwts_pm_method.h" +#include "fwts_types.h" #define FWTS_FRAMEWORK_MAGIC 0x2af61aec diff --git a/src/lib/include/fwts_pm_method.h b/src/lib/include/fwts_pm_method.h index 8a69719..f2dade5 100644 --- a/src/lib/include/fwts_pm_method.h +++ b/src/lib/include/fwts_pm_method.h @@ -20,11 +20,73 @@ #ifndef __FWTS_PM_METHOD_MODE_H__ #define __FWTS_PM_METHOD_MODE_H__ -typedef enum { - logind, - pm_utils, - sysfs, - undefined -} fwts_pm_method; +#include <glib.h> +#include <gio/gio.h> +#include <time.h> +#include <stdbool.h> + +#include "fwts_types.h" + +typedef struct +{ + fwts_framework *fw; + time_t t_start; + time_t t_end; + GDBusProxy *logind_proxy; + GDBusConnection *logind_connection; + GMainLoop *gmainloop; + char *action; + int min_delay; +} fwts_pm_method_vars; + +static inline void free_pm_method_vars(void *); + +#define _cleanup_free_pm_vars_ __attribute__((cleanup(free_pm_method_vars))) + +#define PM_SUSPEND_LOGIND "Suspend" +#define PM_SUSPEND_HYBRID_LOGIND "HybridSleep" +#define PM_HIBERNATE_LOGIND "Hibernate" + +#define FWTS_SUSPEND "FWTS_SUSPEND" +#define FWTS_RESUME "FWTS_RESUME" +#define FWTS_HIBERNATE "FWTS_HIBERNATE" +#define FWTS_RESUME "FWTS_RESUME" + +static inline void free_pm_method_vars(void *vars) +{ + fwts_pm_method_vars *var = *(void**)vars; + + if (var) { + if (var->logind_proxy) { + g_object_unref(var->logind_proxy); + var->logind_proxy = NULL; + } + if (var->logind_connection) { + g_object_unref(var->logind_connection); + var->logind_connection = NULL; + } + if (var->gmainloop) { + g_main_loop_unref(var->gmainloop); + var->gmainloop = NULL; + } + if (var->action) { + free(var->action); + var->action = NULL; + } + } + free(var); + var = NULL; +} + +int fwts_logind_init_proxy(fwts_pm_method_vars *fwts_settings); +int fwts_logind_wait_for_resume_from_action(fwts_pm_method_vars *fwts_settings, const char *action, int minimum_delay); +bool fwts_logind_can_suspend(fwts_pm_method_vars *fwts_settings); +bool fwts_logind_can_hybrid_suspend(fwts_pm_method_vars *fwts_settings); +bool fwts_logind_can_hibernate(fwts_pm_method_vars *fwts_settings); +bool fwts_sysfs_can_suspend(const fwts_pm_method_vars *fwts_settings); +bool fwts_sysfs_can_hybrid_suspend(const fwts_pm_method_vars *fwts_settings); +bool fwts_sysfs_can_hibernate(const fwts_pm_method_vars *fwts_settings); +int fwts_sysfs_do_suspend(const fwts_pm_method_vars *fwts_settings, bool s3_hybrid); +int fwts_sysfs_do_hibernate(const fwts_pm_method_vars *fwts_settings); #endif diff --git a/src/lib/include/fwts_types.h b/src/lib/include/fwts_types.h index f4b67d0..6dc1cdb 100644 --- a/src/lib/include/fwts_types.h +++ b/src/lib/include/fwts_types.h @@ -45,4 +45,11 @@ typedef enum { FWTS_BOOL_ERROR = -1 } fwts_bool; +typedef enum { + FWTS_PM_LOGIND, + FWTS_PM_PMUTILS, + FWTS_PM_SYSFS, + FWTS_PM_UNDEFINED +} fwts_pm_method; + #endif diff --git a/src/lib/src/Makefile.am b/src/lib/src/Makefile.am index 4391841..1385c68 100644 --- a/src/lib/src/Makefile.am +++ b/src/lib/src/Makefile.am @@ -2,6 +2,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib/include \ -I$(top_srcdir)/src/acpica/source/include \ -I$(top_srcdir)/src/acpica/source/compiler \ + `pkg-config --cflags glib-2.0 gio-2.0` \ -DDATAROOTDIR=\"$(datarootdir)\" \ -Wall -Werror -Wextra @@ -11,7 +12,7 @@ libfwts_la_LDFLAGS = \ -lm -lpcre -lpthread \ -version-info 1:0:0 \ -L$(top_builddir)/src/acpica/source/compiler \ - -lfwtsiasl + -lfwtsiasl `pkg-config --libs glib-2.0 gio-2.0` libfwts_la_CPPFLAGS = $(AM_CPPFLAGS) -DACPI_DEBUG_OUTPUT @@ -71,4 +72,5 @@ libfwts_la_SOURCES = \ fwts_text_list.c \ fwts_tty.c \ fwts_uefi.c \ - fwts_wakealarm.c + fwts_wakealarm.c \ + fwts_pm_method.c diff --git a/src/lib/src/fwts_framework.c b/src/lib/src/fwts_framework.c index 475e9a4..7229af5 100644 --- a/src/lib/src/fwts_framework.c +++ b/src/lib/src/fwts_framework.c @@ -1006,11 +1006,11 @@ static int fwts_framework_acpica_parse(fwts_framework *fw, const char *arg) static int fwts_framework_pm_method_parse(fwts_framework *fw, const char *arg) { if (strcmp(arg, "logind") == 0) - fw->pm_method = logind; + fw->pm_method = FWTS_PM_LOGIND; else if (strcmp(arg, "pm-utils") == 0) - fw->pm_method = pm_utils; + fw->pm_method = FWTS_PM_PMUTILS; else if (strcmp(arg, "sysfs") == 0) - fw->pm_method = sysfs; + fw->pm_method = FWTS_PM_SYSFS; else { fprintf(stderr, "--pm-method only supports logind, pm-utils, and sysfs methods\n"); return FWTS_ERROR; @@ -1256,8 +1256,8 @@ int fwts_framework_args(const int argc, char **argv) if ((fw = (fwts_framework *)calloc(1, sizeof(fwts_framework))) == NULL) return FWTS_ERROR; - /* Set the power method to undefined before we parse arguments */ - fw->pm_method = undefined; + /* Set the power method to FWTS_PM_UNDEFINED before we parse arguments */ + fw->pm_method = FWTS_PM_UNDEFINED; ret = fwts_args_add_options(fwts_framework_options, fwts_framework_options_handler, NULL); diff --git a/src/lib/src/fwts_pm_method.c b/src/lib/src/fwts_pm_method.c new file mode 100644 index 0000000..846aeff --- /dev/null +++ b/src/lib/src/fwts_pm_method.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2014 Canonical + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include <glib.h> +#include <gio/gio.h> + +#include "fwts.h" +#include "fwts_pm_method.h" + +/* + * logind_do() + * call Logind to perform an action + */ +static gboolean logind_do(gpointer data) +{ + GError *error = NULL; + GVariant *reply; + fwts_pm_method_vars *fwts_settings = (fwts_pm_method_vars *)data; + + /* If the loop is not running, return TRUE so as to repeat the operation */ + if (g_main_loop_is_running (fwts_settings->gmainloop)) { + fwts_log_info(fwts_settings->fw, "Requesting %s action\n", fwts_settings->action); + reply = g_dbus_proxy_call_sync(fwts_settings->logind_proxy, + fwts_settings->action, + g_variant_new ("(b)", + FALSE), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (reply != NULL) { + g_variant_unref(reply); + } + else { + fwts_log_error(fwts_settings->fw, + "Error from Logind: %s\n", + error->message); + g_error_free(error); + } + + return FALSE; + + } + fwts_log_info(fwts_settings->fw, "Glib loop not ready\n"); + return TRUE; +} + +/* + * logind_signal_subscribe() + * subscribe to a signal coming from Logind + */ +static guint logind_signal_subscribe( + GDBusConnection *connection, + const gchar *logind_signal, + GDBusSignalCallback callback, + gpointer user_data) +{ + return g_dbus_connection_signal_subscribe (connection, + "org.freedesktop.login1", /* sender */ + "org.freedesktop.login1.Manager", + logind_signal, + "/org/freedesktop/login1", + NULL, /* arg0 */ + G_DBUS_SIGNAL_FLAGS_NONE, + callback, + user_data, + NULL); +} + +/* + * logind_signal_unsubscribe() + * unsubscribe from a signal coming from Logind + */ +static void logind_signal_unsubscribe(GDBusConnection *connection, guint subscription_id) +{ + g_dbus_connection_signal_unsubscribe(connection, subscription_id); +} + +/* + * logind_on_signal() + * callback to handle suspend and resume events + */ +static void logind_on_signal( + GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + gboolean status, is_s3; + char buffer[50]; + fwts_pm_method_vars *fwts_settings = (fwts_pm_method_vars *)user_data; + + /* Prevent -Werror=unused-parameter from complaining */ + FWTS_UNUSED(connection); + FWTS_UNUSED(sender_name); + FWTS_UNUSED(object_path); + FWTS_UNUSED(interface_name); + FWTS_UNUSED(signal_name); + + is_s3 = (strcmp(fwts_settings->action, PM_SUSPEND_LOGIND) == 0 || + strcmp(fwts_settings->action, PM_SUSPEND_HYBRID_LOGIND) == 0); + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE ("(b)"))) { + fwts_log_error(fwts_settings->fw, "Suspend type %s\n", + g_variant_get_type_string(parameters)); + return; + } + else { + g_variant_get(parameters, "(b)", &status); + fwts_log_info(fwts_settings->fw, + "Suspend status: %s\n", + status ? "true" : "false"); + + if (status) { + time(&(fwts_settings->t_start)); + snprintf(buffer, sizeof(buffer), "Starting fwts %s\n", is_s3 ? "suspend" : "hibernate"); + (void)fwts_klog_write(fwts_settings->fw, buffer); + snprintf(buffer, sizeof(buffer), "%s\n", fwts_settings->action); + (void)fwts_klog_write(fwts_settings->fw, buffer); + } + else { + time(&(fwts_settings->t_end)); + (void)fwts_klog_write(fwts_settings->fw, FWTS_RESUME "\n"); + (void)fwts_klog_write(fwts_settings->fw, "Finished fwts resume\n"); + /* + * Let's give the system some time to get back from S3 + * or Logind will refuse to suspend and shoot both events + * without doing anything + */ + if (fwts_settings->min_delay < 3) { + fwts_log_info(fwts_settings->fw, + "Skipping the minimum delay (%d) and using a 3 seconds delay instead\n", + fwts_settings->min_delay); + sleep(3); + } + g_main_loop_quit(fwts_settings->gmainloop); + } + } +} + +/* + * logind_can_do_action() + * test supported Logind actions that reply with a string + */ +static bool logind_can_do_action(fwts_pm_method_vars *fwts_settings, const char* action) +{ + GVariant *reply; + GError *error = NULL; + bool status = false; + gchar *response; + + if (fwts_logind_init_proxy(fwts_settings) != 0) + return false; + + reply = g_dbus_proxy_call_sync(fwts_settings->logind_proxy, + action, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (reply != NULL) { + if (!g_variant_is_of_type(reply, G_VARIANT_TYPE ("(s)"))) { + fwts_log_error(fwts_settings->fw, + "Unexpected response to %s action: %s\n", + action, + g_variant_get_type_string (reply)); + + g_variant_unref(reply); + return status; + } + + g_variant_get(reply, "(&s)", &response); + fwts_log_info(fwts_settings->fw, "Response to %s is %s\n", + action, response); + + if (strcmp(response, "challenge") == 0) { + fwts_log_error(fwts_settings->fw, + "%s action available only after authorisation\n", + action); + } else if (strcmp(response, "yes") == 0) { + fwts_log_info(fwts_settings->fw, + "User allowed to execute the %s action\n", + action); + status = true; + } else if (strcmp(response, "no") == 0) { + fwts_log_error(fwts_settings->fw, + "User not allowed to execute the %s action\n", + action); + } else if (strcmp(response, "na") == 0) { + fwts_log_error(fwts_settings->fw, + "Hardware doesn't support %s action\n", + action); + } + + g_variant_unref(reply); + } + else { + fwts_log_error(fwts_settings->fw, + "Invalid response from Logind on %s action\n", + action); + g_error_free(error); + } + + return status; +} + +/* + * fwts_logind_init_proxy() + * initialise the Dbus proxy for Logind Manager + */ +int fwts_logind_init_proxy(fwts_pm_method_vars *fwts_settings) +{ + int status = 0; + + if (fwts_settings->logind_connection == NULL) + fwts_settings->logind_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); + + if (fwts_settings->logind_connection == NULL) { + status = 1; + fwts_log_error(fwts_settings->fw, "Cannot establish a connection to Dbus\n"); + goto out; + } + + if (fwts_settings->logind_proxy == NULL) { + fwts_settings->logind_proxy = g_dbus_proxy_new_sync(fwts_settings->logind_connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + NULL, NULL); + } + + if (fwts_settings->logind_proxy == NULL) { + status = 1; + fwts_log_error(fwts_settings->fw, + "Cannot establish a connection to login1.Manager\n"); + goto out; + } + +out: + return status; +} + +/* + * fwts_logind_wait_for_resume_from_action() + * start Glib mainloop and listen to suspend/resume events coming from Logind + * exit the loop and return the duration after an event. + * + * "action" is one of the following actions supported by Logind: + * "Suspend", "Hibernate", "HybridSleep" + * "minimum_delay" is the minimum delay to use before the function returns + * the function after resume + */ +int fwts_logind_wait_for_resume_from_action( + fwts_pm_method_vars *fwts_settings, + const char *action, + int minimum_delay) +{ + guint subscription_id = 0; + int duration = 0; + + /* Check that the action is supported */ + if (!(strcmp(action, PM_SUSPEND_LOGIND) == 0 || + strcmp(action, PM_SUSPEND_HYBRID_LOGIND) == 0 || + strcmp(action, PM_HIBERNATE_LOGIND) == 0)) { + fwts_log_error(fwts_settings->fw, "Unknown logind action: %s\n", action); + return 0; + } + + /* Initialise the proxy */ + if (fwts_logind_init_proxy(fwts_settings) != 0) { + fwts_log_error(fwts_settings->fw, "Failed to initialise logind proxy\n"); + return 0; + } + + /* Set the action to perform */ + fwts_settings->action = strdup(action); + if (!fwts_settings->action) { + fwts_log_error(fwts_settings->fw, "Failed to initialise logind action\n"); + return 0; + } + + /* Set the minimum delay (this is needed for both S3 and S4) */ + fwts_settings->min_delay = minimum_delay; + + /* Subscribe to the signal that Logind sends on resume */ + subscription_id = logind_signal_subscribe(fwts_settings->logind_connection, + "PrepareForSleep", + logind_on_signal, + fwts_settings); + + /* Start the main loop */ + fwts_settings->gmainloop = g_main_loop_new(NULL, FALSE); + if (fwts_settings->gmainloop) { + g_timeout_add(0.1, logind_do, fwts_settings); + + g_main_loop_run(fwts_settings->gmainloop); + duration = (int)(fwts_settings->t_end - fwts_settings->t_start); + + /* Optional, as it will be freed together with the struct */ + g_main_loop_unref(fwts_settings->gmainloop); + fwts_settings->gmainloop = NULL; + } + else { + fwts_log_error(fwts_settings->fw, "Failed to start glib mainloop\n"); + } + + /* Unsubscribe from the signal */ + logind_signal_unsubscribe(fwts_settings->logind_connection, + subscription_id); + + return duration; +} + +/* + * fwts_logind_can_suspend() + * return a boolean that states whether suspend is a supported action or not + */ +bool fwts_logind_can_suspend(fwts_pm_method_vars *fwts_settings) +{ + return logind_can_do_action(fwts_settings, "CanSuspend"); +} + +/* + * fwts_logind_can_hybrid_suspend() + * return a boolean that states whether hybrid suspend is a supported action or not + */ +bool fwts_logind_can_hybrid_suspend(fwts_pm_method_vars *fwts_settings) +{ + return logind_can_do_action(fwts_settings, "CanHybridSleep"); +} + +/* + * fwts_logind_can_hibernate() + * return a boolean that states whether hibernate is a supported action or not + */ +bool fwts_logind_can_hibernate(fwts_pm_method_vars *fwts_settings) +{ + return logind_can_do_action(fwts_settings, "CanHibernate"); +} + +/* + * fwts_sysfs_can_suspend() + * return a boolean that states whether suspend is a supported action or not + */ +bool fwts_sysfs_can_suspend(const fwts_pm_method_vars *fwts_settings) +{ + return fwts_file_first_line_contains_string(fwts_settings->fw, + "/sys/power/state", + "mem"); +} + +/* + * fwts_sysfs_can_hybrid_suspend() + * return a boolean that states whether hybrid suspend is a supported action or not + */ +bool fwts_sysfs_can_hybrid_suspend(const fwts_pm_method_vars *fwts_settings) +{ + bool status; + + status = fwts_file_first_line_contains_string(fwts_settings->fw, + "/sys/power/state", + "disk"); + + if (!status) + return FALSE; + + return fwts_file_first_line_contains_string(fwts_settings->fw, + "/sys/power/disk", + "suspend"); +} + +/* + * fwts_sysfs_can_hibernate() + * return a boolean that states whether hibernate is a supported action or not + */ +bool fwts_sysfs_can_hibernate(const fwts_pm_method_vars *fwts_settings) +{ + return fwts_file_first_line_contains_string(fwts_settings->fw, + "/sys/power/state", + "disk"); +} + +/* + * fwts_sysfs_do_suspend() + * enter either S3 or hybrid S3 + * return the exit status + */ +int fwts_sysfs_do_suspend(const fwts_pm_method_vars *fwts_settings, bool s3_hybrid) +{ + int status; + + if (s3_hybrid) { + status = fwts_write_string_file(fwts_settings->fw, + "/sys/power/disk", + "suspend"); + + if (status != FWTS_OK) + return status; + + status = fwts_write_string_file(fwts_settings->fw, + "/sys/power/state", + "disk"); + } + else { + status = fwts_write_string_file(fwts_settings->fw, + "/sys/power/state", + "mem"); + } + + return status; +} + +/* + * fwts_sysfs_do_hibernate() + * enter S4 + * return the exit status + */ +int fwts_sysfs_do_hibernate(const fwts_pm_method_vars *fwts_settings) +{ + return fwts_write_string_file(fwts_settings->fw, + "/sys/power/state", + "disk"); +} +
This allows sharing Logind functions between s3.c and s4.c Signed-off-by: Alberto Milone <alberto.milone@canonical.com> --- src/lib/include/fwts_framework.h | 2 +- src/lib/include/fwts_pm_method.h | 74 ++++++- src/lib/include/fwts_types.h | 7 + src/lib/src/Makefile.am | 6 +- src/lib/src/fwts_framework.c | 10 +- src/lib/src/fwts_pm_method.c | 447 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 532 insertions(+), 14 deletions(-) create mode 100644 src/lib/src/fwts_pm_method.c