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 <[email protected]> --- 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 _______________________________________________ Devel mailing list [email protected] http://lists.laptop.org/listinfo/devel
