[PATCH RFC powerd] Add support for inhibiting suspend via UPower

Sascha Silbe silbe at activitycentral.com
Tue May 22 16:17:07 EDT 2012


UPower has API to let applications set "latency requirements" [1]. The
UPower back-end code uses the kernel PM QoS interface [2] to set the
requested CPU (DMA) latency (in µs, up to ~35 minutes) and network
throughput (in kbps). cpuidle hooks into the PM QoS framework to
provide CPU latency management.

By monitoring UPower latency requests we can ensure that we don't
suspend even though some application needs to stay active despite
there being no noticeable user input.

It also has the benefit of letting applications use the UPower API
instead of powerd-specific inhibit files. Once the kernel supports
seamless suspends, taking scheduling information (timers!) and
requested latencies into account, UPower will most likely be the API
of choice for Gnome applications to register their requests.

The latency limits for inhibiting suspend are somewhat arbitrary, as
there's no direct correlation between the latency types known to
UPower and the effects of the user space initiated suspend powerd
does. If powerd suspends, applications won't get to run at all, not
just after some latency. By choosing rather high limits we can let
applications register themselves as requiring to be running from time
to time, but not having particular needs as to how fast the CPU is
running.

Future improvements to this feature may cause powerd to wake up from
time to time to let registered applications get scheduled, according
to the lowest registered latency. But how well that would work is
something that needs to be tested extensively, so it's out of scope
for now. And maybe the work is better spent implementing kernel-driven
seamless suspend rather than tuning the user space solution with its
known, inherent drawbacks.

[1] http://upower.freedesktop.org/docs/QoS.html
[2] https://www.kernel.org/doc/Documentation/power/pm_qos_interface.txt

Signed-off-by: Sascha Silbe <silbe at activitycentral.com>
---
 For UPower support to actually work, you'll need some fixes to
 UPower. One of them has already landed in upstream git, the others
 are queued for review. [3]

 [3] http://lists.freedesktop.org/archives/devkit-devel/2012-May/thread.html#1277

 powerd-dbus/Makefile         |    2 +-
 powerd-dbus/powerd-dbus.c    |    1 +
 powerd-dbus/powerd-dbus.h    |    1 +
 powerd-dbus/upower_monitor.c |  164 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 167 insertions(+), 1 deletion(-)

diff --git a/powerd-dbus/Makefile b/powerd-dbus/Makefile
index aecd61b..4040b4c 100644
--- a/powerd-dbus/Makefile
+++ b/powerd-dbus/Makefile
@@ -10,7 +10,7 @@ CFLAGS += -DG_LOG_DOMAIN=\"powerd-dbus\"
 
 all: powerd-dbus
 
-powerd-dbus: ohm-keystore-glue.h ohm-keystore.c network.c nm_monitor.c wpas_monitor.c powerd-dbus.c powerd-dbus.h
+powerd-dbus: ohm-keystore-glue.h ohm-keystore.c network.c nm_monitor.c wpas_monitor.c upower_monitor.c powerd-dbus.c powerd-dbus.h
 	opt=-combine; ln -sf /dev/null null.c; \
 	gcc -flto -c null.c 2>/dev/null && opt=-flto ; \
 	gcc $(CFLAGS) -fwhole-program $$opt \
diff --git a/powerd-dbus/powerd-dbus.c b/powerd-dbus/powerd-dbus.c
index ccc1ea5..23ac074 100644
--- a/powerd-dbus/powerd-dbus.c
+++ b/powerd-dbus/powerd-dbus.c
@@ -62,6 +62,7 @@ int main(void)
 	ohm_keystore_new();
 	wpas_monitor_init();
 	nm_monitor_init();
+	upower_monitor_init();
 
 	g_message("entering main loop");
 	g_main_loop_run(mainloop);
diff --git a/powerd-dbus/powerd-dbus.h b/powerd-dbus/powerd-dbus.h
index db12ebf..1a81423 100644
--- a/powerd-dbus/powerd-dbus.h
+++ b/powerd-dbus/powerd-dbus.h
@@ -13,6 +13,7 @@ typedef struct OhmKeystore OhmKeystore;
 OhmKeystore *ohm_keystore_new(void);
 
 int nm_monitor_init(void);
+int upower_monitor_init(void);
 int wpas_monitor_init(void);
 void nm_suspend_ok(gboolean suspend_ok);
 void wpas_suspend_ok(gboolean suspend_ok);
diff --git a/powerd-dbus/upower_monitor.c b/powerd-dbus/upower_monitor.c
new file mode 100644
index 0000000..1cd8d85
--- /dev/null
+++ b/powerd-dbus/upower_monitor.c
@@ -0,0 +1,164 @@
+#include <errno.h>
+#include <string.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include "powerd-dbus.h"
+
+
+#define UPOWER_SERVICE "org.freedesktop.UPower"
+#define UPOWER_QOS_PATH "/org/freedesktop/UPower/Policy"
+#define UPOWER_QOS_IFACE "org.freedesktop.UPower.QoS"
+
+static GDBusProxy *upower_obj = NULL;
+static gint32 network_throughput = -1;
+static gint32 cpu_dma_latency = -1;
+
+static guint timeout_id = 0;
+static gboolean last_susp_ok = TRUE;
+
+
+
+static gboolean send_susp_ok(void)
+{
+	last_susp_ok = TRUE;
+	powerd_send_event("allow-suspend", "upower_suspend_ok");
+	timeout_id = 0;
+	return FALSE;
+}
+
+static void communicate_state(gboolean new_susp_ok)
+{
+	/*
+	 * If suspend-not-OK but we had a timer about to send suspend-OK,
+	 * abort the timer.
+	 */
+	if (!new_susp_ok && timeout_id) {
+		g_message("%d: abort suspend-OK timer, suspend not OK.", (int)time(0));
+		g_source_remove(timeout_id);
+		timeout_id = 0;
+	}
+
+	if (new_susp_ok == last_susp_ok)
+		return;
+
+	/* communicate suspend-not-OK events immediately */
+	if (!new_susp_ok) {
+		last_susp_ok = new_susp_ok;
+		powerd_send_event("inhibit-suspend", "network_suspend_not_ok");
+		return;
+	}
+
+	/* nothing to do if we already have scheduled the suspend-OK message */
+	if (timeout_id)
+		return;
+
+	/* defer suspend-OK events for 8 seconds, to ensure things have settled */
+	timeout_id = g_timeout_add_seconds(8, (GSourceFunc) send_susp_ok,
+		NULL);
+	g_message("%d: sending suspend-OK message after settle delay", (int)time(0));
+}
+
+static void evaluate_latencies()
+{
+	gboolean new_susp_ok;
+
+	/* Allow suspend if requested CPU/DMA latency is more than 30
+	   seconds and requested network throughput is less than
+	   1MBps. For CPU/DMA latency it's more or less arbitrary as
+	   we don't actually wake up when there's no external event
+	   but rather just an application timer firing, but we can't
+	   do better and most applications that don't have something
+	   like powerd in mind wouldn't set such high limits anyway,
+	   so we err on the safe side of inhibiting suspend for
+	   latency sensitive applications. For network throughput it's
+	   also arbitrary since we inhibit suspend while there's
+	   regular network activity, so the interesting measure would
+	   be network latency rather than throughput.
+	*/
+	new_susp_ok = ((cpu_dma_latency == -1)			\
+		       || (cpu_dma_latency > 30*1000*1000))	\
+	  && ((network_throughput == -1)			\
+	      || (network_throughput < 8000));
+
+	communicate_state(new_susp_ok);
+}
+
+static void signal_handler(GDBusProxy *proxy, gchar *sender, gchar *signal,
+	GVariant *parameters, gpointer data)
+{
+	GVariant *value_variant, *type_variant;
+	const gchar *latency_type;
+	gint32 latency_value;
+
+	if (strcmp(signal, "LatencyChanged") != 0)
+		return;
+
+	if (g_variant_n_children(parameters) != 2)
+		return;
+
+	value_variant = g_variant_get_child_value(parameters, 1);
+	latency_value = g_variant_get_int32(value_variant);
+	g_variant_unref(value_variant);
+
+	type_variant = g_variant_get_child_value(parameters, 0);
+	latency_type = g_variant_get_string(type_variant, NULL);
+
+	g_message("upower %s %s %d", signal, latency_type, latency_value);
+
+	if (strcmp(latency_type, "cpu_dma") == 0)
+		cpu_dma_latency = latency_value; /* in microseconds */
+	else if (strcmp(latency_type, "network") == 0)
+		network_throughput = latency_value; /* in Kbps */
+	else {
+		/* unrecognised, so nothing changes for us */
+		g_variant_unref(type_variant);
+		return;
+	}
+
+	g_variant_unref(type_variant);
+	evaluate_latencies();
+}
+
+gint32 get_requested_latency(const char *latency_type)
+{
+	GError *error = NULL;
+	GVariant *result;
+	GVariant *value_variant;
+	gint32 value;
+
+	result = g_dbus_proxy_call_sync(upower_obj, "GetLatency",
+					g_variant_new("(s)", latency_type),
+					G_DBUS_CALL_FLAGS_NONE, -1, NULL,
+					&error);
+	if (!result)
+		g_error("Failed to get requested %s latency: %s\n",
+			latency_type, error->message);
+	if (!g_variant_n_children(result))
+		g_error("Invalid response from GetLatency(): no children");
+	value_variant = g_variant_get_child_value(result, 0);
+	value = g_variant_get_int32(value_variant);
+	g_variant_unref(value_variant);
+	g_variant_unref(result);
+	g_message("upower GetLatency() %s %d", latency_type, value);
+	return value;
+}
+
+int upower_monitor_init(void)
+{
+	GError *error = NULL;
+
+	upower_obj = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM,
+		G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL,
+		UPOWER_SERVICE, UPOWER_QOS_PATH, UPOWER_QOS_IFACE,
+		NULL, &error);
+	if (!upower_obj)
+		g_error("Error creating upower proxy: %s\n", error->message);
+
+	g_signal_connect(upower_obj, "g-signal", (GCallback) signal_handler, NULL);
+
+	cpu_dma_latency = get_requested_latency("cpu_dma");
+	network_throughput = get_requested_latency("network");
+
+	evaluate_latencies();
+	return 0;
+}
-- 
1.7.10




More information about the Devel mailing list