From 6112f541728bdbc9c8a03bdc1f0fe1447a49ed70 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Tue, 19 May 2015 14:33:44 +0300 Subject: [PATCH] Implement iphb based timers for use from within mce There are some mce state transitions that should be taken some time after display is powered off and device has potentially entered late suspend. These timers are re-evaluated when the device resume from suspend, but that might be too late and applications can end up making decisions based on mce state data that was frozen in time. There are nemo-keepalive interfaces that use libiphb for waking up from suspend, but those can't be used from mce because they depend on mce for keeping the device away from suspend via mce cpu-keepalive dbus-service. Add mce code module mce-hbtimer, which makes available: Timers that use regular glib timeouts, but are backed up by iphb wakeups in case the device gets suspended. About the backdated copyright blurb: The code dealing with iphb wakeups is derived from keepalive-heartbeat.c from nemo-keepalive package that was written by me for Jolla and uses the same license as mce - LGPL v2.1. [mce] Implement iphb based timers for use from within mce. Contributes to JB#28706 --- .depend | 18 + Makefile | 4 + mce-hbtimer.c | 1095 +++++++++++++++++++++++++++++++++++++++++++++++++ mce-hbtimer.h | 58 +++ mce.c | 6 + rpm/mce.spec | 1 + 6 files changed, 1182 insertions(+) create mode 100644 mce-hbtimer.c create mode 100644 mce-hbtimer.h diff --git a/.depend b/.depend index 8bc9cccf..67527f1d 100644 --- a/.depend +++ b/.depend @@ -210,6 +210,22 @@ mce-hal.pic.o:\ mce-lib.h\ mce-log.h\ +mce-hbtimer.o:\ + mce-hbtimer.c\ + datapipe.h\ + libwakelock.h\ + mce-hbtimer.h\ + mce-log.h\ + mce.h\ + +mce-hbtimer.pic.o:\ + mce-hbtimer.c\ + datapipe.h\ + libwakelock.h\ + mce-hbtimer.h\ + mce-log.h\ + mce.h\ + mce-hybris.o:\ mce-hybris.c\ datapipe.h\ @@ -315,6 +331,7 @@ mce.o:\ mce-dsme.h\ mce-fbdev.h\ mce-gconf.h\ + mce-hbtimer.h\ mce-log.h\ mce-modules.h\ mce-sensorfw.h\ @@ -336,6 +353,7 @@ mce.pic.o:\ mce-dsme.h\ mce-fbdev.h\ mce-gconf.h\ + mce-hbtimer.h\ mce-log.h\ mce-modules.h\ mce-sensorfw.h\ diff --git a/Makefile b/Makefile index 085f8b09..b51d50f0 100644 --- a/Makefile +++ b/Makefile @@ -260,6 +260,7 @@ MCE_PKG_NAMES += gmodule-2.0 MCE_PKG_NAMES += dbus-1 MCE_PKG_NAMES += dbus-glib-1 MCE_PKG_NAMES += dsme +MCE_PKG_NAMES += libiphb MCE_PKG_NAMES += libsystemd-daemon MCE_PKG_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(MCE_PKG_NAMES)) @@ -278,6 +279,7 @@ MCE_CORE += mce-fbdev.c MCE_CORE += mce-dbus.c MCE_CORE += mce-dsme.c MCE_CORE += mce-gconf.c +MCE_CORE += mce-hbtimer.c MCE_CORE += event-input.c MCE_CORE += event-switches.c MCE_CORE += mce-hal.c @@ -531,6 +533,8 @@ NORMALIZE_USES_SPC =\ mce-fbdev.h\ mce-command-line.c\ mce-command-line.h\ + mce-hbtimer.c\ + mce-hbtimer.h\ mce-hybris.c\ mce-hybris.h\ mce-modules.h\ diff --git a/mce-hbtimer.c b/mce-hbtimer.c new file mode 100644 index 00000000..444553a0 --- /dev/null +++ b/mce-hbtimer.c @@ -0,0 +1,1095 @@ +/** + * @file mce-hbtimer.c + * + * Mode Control Entity - Suspend proof timer functionality + * + *

+ * + * Copyright (C) 2014-2015 Jolla Ltd. + * + *

+ * + * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#include "mce-hbtimer.h" + +#include "mce.h" +#include "mce-log.h" + +#ifdef ENABLE_WAKELOCKS +# include "libwakelock.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ========================================================================= * + * Types and functions + * ========================================================================= */ + +/* ------------------------------------------------------------------------- * + * TIMER_METHODS + * ------------------------------------------------------------------------- */ + +/** State data for mce heartbeat timers */ +struct mce_hbtimer_t +{ + /** Timer name, used for debug logging purposes */ + char *hbt_name; + + /** Trigger time, milliseconds in CLOCK_BOOTTIME base */ + int64_t hbt_trigger; + + /** Timer callback function */ + GSourceFunc hbt_notify; + + /** Timer delay in milliseconds */ + int hbt_period; + + /** Flag for: control within hbt_notify() */ + bool hbt_in_notify; + + /** User data to pass to hbt_notify() */ + void *hbt_user_data; +}; + +mce_hbtimer_t * mce_hbtimer_create (const char *name, int period, GSourceFunc notify, void *user_data); +void mce_hbtimer_delete (mce_hbtimer_t *self); + +bool mce_hbtimer_is_active (const mce_hbtimer_t *self); +const char *mce_hbtimer_get_name (const mce_hbtimer_t *self); +void mce_hbtimer_set_period (mce_hbtimer_t *self, int period); +void mce_hbtimer_start (mce_hbtimer_t *self); +void mce_hbtimer_stop (mce_hbtimer_t *self); + +static void mce_hbtimer_notify (mce_hbtimer_t *self); +static void mce_hbtimer_set_trigger (mce_hbtimer_t *self, int64_t trigger); + +/* ------------------------------------------------------------------------- * + * GENERIC_UTILITIES + * ------------------------------------------------------------------------- */ + +/** Monotonic tick value used to signify "not-set" */ +#define NO_TICK INT64_MAX + +static int64_t mht_get_monotick (void); +static guint mht_add_iowatch (int fd, bool close_on_unref, GIOCondition cnd, GIOFunc io_cb, gpointer aptr); + +/* ------------------------------------------------------------------------- * + * QUEUE_MANAGEMENT + * ------------------------------------------------------------------------- */ + +/** List of registered timers */ +static GSList *mht_queue_timer_list = 0; + +void mht_queue_dispatch_timers (void); +static void mht_queue_schedule_wakeups (void); +static void mht_queue_garbage_collect (void); +static void mht_queue_add_timer (mce_hbtimer_t *self); +static void mht_queue_remove_timer (mce_hbtimer_t *self); +static bool mht_queue_has_timer (const mce_hbtimer_t *self); + +/* ------------------------------------------------------------------------- * + * GLIB_WAKEUPS + * ------------------------------------------------------------------------- */ + +/** Source ID for currently active glib timer wakeup */ +static guint mce_hbtimer_glib_wait_id = 0; + +static gboolean mht_glib_wakeup_cb (gpointer aptr); +static void mht_glib_set_wakeup (int64_t trigger, int64_t now); + +/* ------------------------------------------------------------------------- * + * IPHB_WAKEUPS + * ------------------------------------------------------------------------- */ + +/** How much wakeups from suspend can be delayed [s] + * + * To increase changes of aligning with other iphb wakeups this + * should be >= heartbeat period (=12 seconds) */ +#define MHT_IPHB_WAKEUP_MAX_DELAY_S 12 + +/** Cached timestamp of last requested iphb wakeup */ +static int64_t mht_iphb_wakeup_tick = NO_TICK; + +/** Source id for iphb wakeup input watch */ +static guint mht_iphb_wakeup_watch_id = 0; + +static gboolean mht_iphb_wakeup_cb (GIOChannel *chn, GIOCondition cnd, gpointer data); +static void mht_iphb_set_wakeup (int64_t trigger, int64_t now); + +/* ------------------------------------------------------------------------- * + * IPHB_CONNECTION + * ------------------------------------------------------------------------- */ + +/** Number of times iphb connect is attempted after dsme startup */ +#define MHT_CONNECTION_MAX_RETRIES 5 + +/** Delay between iphb connect attempts [ms] */ +#define MHT_CONNECTION_RETRY_DELAY_MS 5000 + +/** Timer ID for: iphb connection attempts */ +static guint mht_connection_timer_id = 0; + +/** Number of connection attempts so far */ +static gint mht_connection_retry_no = 0; + +/** Handle for iphb connection */ +static iphb_t mht_connection_handle = 0; + +static bool mht_connection_try_to_open (void); + +static gboolean mht_connection_timer_cb (gpointer aptr); +static bool mht_connection_is_pending (void); +static void mht_connection_start_timer (void); +static void mht_connection_stop_timer (void); + +static void mht_connection_open (void); +static void mht_connection_close (void); + +/* ------------------------------------------------------------------------- * + * DATAPIPE_HANDLERS + * ------------------------------------------------------------------------- */ + +/** Availability of dsme; from dsme_available_pipe */ +static service_state_t dsme_available = SERVICE_STATE_UNDEF; + +/** Device is shutting down; assume false */ +static bool shutting_down = false; + +static void mht_datapipe_dsme_available_cb (gconstpointer data); +static void mht_datapipe_device_resumed_cb (gconstpointer data); +static void mht_datapipe_shutting_down_cb (gconstpointer data); + +static void mht_datapipe_init(void); +static void mht_datapipe_quit(void); + +/* ------------------------------------------------------------------------- * + * MODULE_INIT + * ------------------------------------------------------------------------- */ + +/** Flag for: mce_hbtimer_init() has been called */ +static bool mce_hbtimer_initialized = false; + +void mce_hbtimer_init (void); +void mce_hbtimer_quit (void); + +/* ========================================================================= * + * GENERIC_UTILITIES + * ========================================================================= */ + +/** Get CLOCK_BOOTTIME time stamp in milliseconds + * + * @return current boottime in ms resolution + */ +static int64_t +mht_get_monotick(void) +{ + int64_t res = 0; + + struct timespec ts; + + if( clock_gettime(CLOCK_BOOTTIME, &ts) == 0 ) { + res = ts.tv_sec; + res *= 1000; + res += ts.tv_nsec / 1000000; + } + + return res; +} + +/** Helper for creating I/O watch for file descriptor + */ +static guint +mht_add_iowatch(int fd, bool close_on_unref, + GIOCondition cnd, GIOFunc io_cb, gpointer aptr) +{ + guint wid = 0; + GIOChannel *chn = 0; + + if( !(chn = g_io_channel_unix_new(fd)) ) + goto cleanup; + + g_io_channel_set_close_on_unref(chn, close_on_unref); + + cnd |= G_IO_ERR | G_IO_HUP | G_IO_NVAL; + + if( !(wid = g_io_add_watch(chn, cnd, io_cb, aptr)) ) + goto cleanup; + +cleanup: + if( chn != 0 ) g_io_channel_unref(chn); + + return wid; + +} + +/* ========================================================================= * + * TIMER_METHODS + * ========================================================================= */ + +/** Create heatbeat timer + * + * @param name timer name + * @param period timer delay [ms] + * @param notify function to be called when triggered + * @param user_data pointer to be passed to notify function + * + * @return heartbeat timer object + */ +mce_hbtimer_t * +mce_hbtimer_create(const char *name, + int period, + GSourceFunc notify, + void *user_data) +{ + mce_hbtimer_t *self = calloc(1, sizeof *self); + + self->hbt_name = name ? strdup(name) : 0; + self->hbt_notify = notify; + self->hbt_period = period; + self->hbt_user_data = user_data; + self->hbt_trigger = NO_TICK; + self->hbt_in_notify = false; + + mht_queue_add_timer(self); + + return self; +} + +/** Delete heatbeat timer + * + * @param self heartbeat timer object, or NULL + */ +void +mce_hbtimer_delete(mce_hbtimer_t *self) +{ + if( !self ) + goto EXIT; + + mht_queue_remove_timer(self); + mht_queue_schedule_wakeups(); + + free(self->hbt_name), + self->hbt_name = 0; + + free(self); + +EXIT: + return; +} + +/** Predicate for: heatbeat timer has been started + * + * @param self heartbeat timer object, or NULL + * + * @return true if heartbeat timer has been started, false otherwise + */ +bool +mce_hbtimer_is_active(const mce_hbtimer_t *self) +{ + bool active = false; + if( self ) + active = (self->hbt_trigger < NO_TICK); + return active; +} + +/** Get heatbeat timer name + * + * @param self heartbeat timer object, or NULL + * + * @return "invalid" if object is not valid, + * "unknown" if object has no name, or + * object name + */ + +const char * +mce_hbtimer_get_name(const mce_hbtimer_t *self) +{ + const char *name = "invalid"; + if( self ) + name = self->hbt_name; + return name ?: "unknown"; +} + +/** Set heatbeat timer period + * + * @param self heartbeat timer object, or NULL + * @param period timer delay [ms] + */ +void +mce_hbtimer_set_period(mce_hbtimer_t *self, int period) +{ + if( self ) + self->hbt_period = period; +} + +/** Call heatbeat timer notification functiom + * + * @param self heartbeat timer object, or NULL + */ +static void +mce_hbtimer_notify(mce_hbtimer_t *self) +{ + if( !self ) + goto EXIT; + + if( self->hbt_in_notify ) + goto EXIT; + + if( !self->hbt_notify ) + goto EXIT; + + self->hbt_in_notify = true; + self->hbt_trigger = NO_TICK; + + bool again = self->hbt_notify(self->hbt_user_data); + + /* Check that notify callback did not delete the timer */ + if( !mht_queue_has_timer(self) ) + goto EXIT; + + self->hbt_in_notify = false; + + if( again ) + mce_hbtimer_start(self); + +EXIT: + return; +} + +/** Set heatbeat timer trigger time stamp + * + * @param self heartbeat timer object, or NULL + * @param trigger trigger time + */ + +static void +mce_hbtimer_set_trigger(mce_hbtimer_t *self, int64_t trigger) +{ + if( !self ) + goto EXIT; + + if( self->hbt_trigger == trigger ) + goto EXIT; + + self->hbt_trigger = trigger; + mht_queue_schedule_wakeups(); + +EXIT: + return; +} + +/** Start heatbeat timer + * + * @param self heartbeat timer object, or NULL + */ +void +mce_hbtimer_start(mce_hbtimer_t *self) +{ + if( !self ) + goto EXIT; + + mce_log(LL_DEBUG, "start %s %d", mce_hbtimer_get_name(self), + self->hbt_period); + int64_t now = mht_get_monotick(); + int64_t trigger = now + self->hbt_period; + mce_hbtimer_set_trigger(self, trigger); + +EXIT: + return; +} + +/** Stop heatbeat timer + * + * @param self heartbeat timer object, or NULL + */ +void +mce_hbtimer_stop(mce_hbtimer_t *self) +{ + if( !self ) + goto EXIT; + + mce_log(LL_DEBUG, "stop %s", mce_hbtimer_get_name(self)); + mce_hbtimer_set_trigger(self, NO_TICK); + mht_queue_schedule_wakeups(); + +EXIT: + return; +} + +/* ========================================================================= * + * QUEUE_MANAGEMENT + * ========================================================================= */ + +/** Clean up unused timer list slots + * + * When timers are deleted, the associated node in mht_queue_timer_list + * is left in place with data pointer set to zero. + * + * This function can be used to remove such stale entries in suitable + * point outside the dispatch iteration loop. + */ +static void +mht_queue_garbage_collect(void) +{ + GSList **tail = &mht_queue_timer_list; + + GSList *item; + + while( (item = *tail) ) { + if( item->data ) { + tail = &item->next; + } + else { + *tail = item->next; + item->next = 0; + g_slist_free(item); + } + } +} + +/** Predicate for: heartbeat timer is registered + * + * @param self heartbeat timer object, or NULL + * + * return true if timer is registered, false otherwise + */ +static bool +mht_queue_has_timer(const mce_hbtimer_t *self) +{ + bool has_timer = false; + + if( !self ) + goto EXIT; + + has_timer = g_slist_find(mht_queue_timer_list, self) != 0; + +EXIT: + return has_timer; +} + +/** Register heartbeat timer + * + * @param self heartbeat timer object, or NULL + */ +static void +mht_queue_add_timer(mce_hbtimer_t *self) +{ + if( !self ) + goto EXIT; + + /* Try to find recyclable vacated timer slot */ + GSList *item = g_slist_find(mht_queue_timer_list, 0); + + if( item ) + item->data = self; + else + mht_queue_timer_list = g_slist_prepend(mht_queue_timer_list, self); + +EXIT: + return; +} + +/** Unregister heartbeat timer + * + * @param self heartbeat timer object, or NULL + */ +static void +mht_queue_remove_timer(mce_hbtimer_t *self) +{ + if( !self ) + goto EXIT; + + GSList *item = g_slist_find(mht_queue_timer_list, self); + + /* Vacate the slot by clearing the data link */ + if( item ) + item->data = 0; + +EXIT: + return; +} + +/** Scan registered heartbeat timers and schdule nearest wakeup + */ +static void +mht_queue_schedule_wakeups(void) +{ + if( !mce_hbtimer_initialized ) + goto EXIT; + + int64_t trigger = NO_TICK; + bool compact = false; + + for( GSList *item = mht_queue_timer_list; item; item = item->next ) { + mce_hbtimer_t *timer = item->data; + + if( !timer ) + compact = true; + else if( trigger > timer->hbt_trigger ) + trigger = timer->hbt_trigger; + } + + if( compact ) + mht_queue_garbage_collect(); + + int64_t now = mht_get_monotick(); + + if( trigger < now ) + trigger = now; + + mht_glib_set_wakeup(trigger, now); + mht_iphb_set_wakeup(trigger, now); + +EXIT: + return; +} + +/** Scan registered heartbeat timers and notify triggered ones + */ +void +mht_queue_dispatch_timers(void) +{ + static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + + if( !mce_hbtimer_initialized ) + goto EXIT; + + /* We need to be sure that actions resulting from + * timer notifications do not cause recursive dispatching + * to take place. */ + + if( pthread_mutex_trylock(&mutex) != 0 ) + goto EXIT; + + /* Block suspend during dispatching */ +#ifdef ENABLE_WAKELOCKS + wakelock_lock("mce_hbtimer_dispatch", -1); +#endif + + int64_t now = mht_get_monotick(); + + for( GSList *item = mht_queue_timer_list; item; item = item->next ) { + mce_hbtimer_t *timer = item->data; + + if( !timer ) + continue; + + if( timer->hbt_trigger == NO_TICK ) + continue; + + mce_log(LL_DEBUG, "%s T%+"PRId64" ms", + mce_hbtimer_get_name(timer), + now - timer->hbt_trigger); + + if( now < timer->hbt_trigger ) + continue; + + mce_hbtimer_notify(timer); + } + + /* Check the next timer to trigger */ + mht_queue_schedule_wakeups(); + +#ifdef ENABLE_WAKELOCKS + wakelock_unlock("mce_hbtimer_dispatch"); +#endif + + pthread_mutex_unlock(&mutex); + +EXIT: + return; +} + +/* ========================================================================= * + * GLIB_WAKEUPS + * ========================================================================= */ + +/** Glib timeout callback for dispatching heartbeat timers + * + * @param aptr (not used) + * + * @return FALSE to stop timeout from repeating + */ +static gboolean +mht_glib_wakeup_cb(gpointer aptr) +{ + (void) aptr; + + if( !mce_hbtimer_glib_wait_id ) + goto EXIT; + + mce_hbtimer_glib_wait_id = 0; + + mce_log(LL_DEBUG, "glib wakeup; dispatch hbtimers"); + mht_queue_dispatch_timers(); + +EXIT: + return FALSE; +} + +/** Reprogram glib timeout for dispatching heartbeat timers + * + * @param trigger when to trigger + * @param now current time + */ +static void +mht_glib_set_wakeup(int64_t trigger, int64_t now) +{ + static int64_t prev = NO_TICK; + + int delay = -1; + + /* The glib timers use CLOCK_MONOTONIC while we need to evaluate + * triggering in CLOCK_BOOTTIME. + * + * Assume glib reprogramming is cheap enough so that we do not need + * to try to avoid it. + */ + + if( mce_hbtimer_glib_wait_id ) { + g_source_remove(mce_hbtimer_glib_wait_id), + mce_hbtimer_glib_wait_id = 0; + } + if( trigger != NO_TICK ) { + delay = (int)(trigger - now); + mce_hbtimer_glib_wait_id = + g_timeout_add(delay, mht_glib_wakeup_cb, 0); + } + + if( prev != trigger ) { + prev = trigger; + mce_log(LL_DEBUG, "glib wakeup in %d ms", delay); + } +} + +/* ========================================================================= * + * IPHB_WAKEUPS + * ========================================================================= */ + +/** iphb wakeup callback for dispatching heartbeat timers + * + * @param chn io channel + * @param cnd io condition + * @param data (unused) + * + * @return TRUE to keep io watch alive, or FALSE to disable it + */ +static gboolean +mht_iphb_wakeup_cb(GIOChannel *chn, GIOCondition cnd, gpointer data) +{ + (void)data; + + gboolean keep_going = FALSE; + + int fd = g_io_channel_unix_get_fd(chn); + + if( fd < 0 ) + goto cleanup_nak; + + if( cnd & ~G_IO_IN ) + goto cleanup_nak; + + char buf[256]; + + int rc = read(fd, buf, sizeof buf); + + if( rc == 0 ) { + if( !shutting_down ) + mce_log(LL_ERR, "unexpected eof"); + goto cleanup_nak; + } + + if( rc == -1 ) { + if( errno == EINTR || errno == EAGAIN ) + goto cleanup_ack; + + mce_log(LL_ERR, "read error: %m"); + goto cleanup_nak; + } + + if( mht_iphb_wakeup_tick == NO_TICK ) + goto cleanup_ack; + + /* clear programmed state */ + mht_iphb_wakeup_tick = NO_TICK; + + /* notify */ + mce_log(LL_DEBUG, "iphb wakeup; dispatch hbtimers"); + mht_queue_dispatch_timers(); + +cleanup_ack: + keep_going = TRUE; + +cleanup_nak: + + if( !keep_going ) { + mht_iphb_wakeup_watch_id = 0; + mht_connection_close(); + } + + return keep_going; +} + +/** Reprogram iphb timeout for dispatching heartbeat timers + * + * @param trigger when to trigger + * @param now current time + */ +static void +mht_iphb_set_wakeup(int64_t trigger, int64_t now) +{ + /* Assume: iphb timer should be stopped */ + int lo = 0; + int hi = 0; + int64_t tick = NO_TICK; + + if( mht_connection_handle && trigger != NO_TICK) { + /* Calculate the iphb wakeup range to be used. */ + int64_t delay = (trigger - now + 999) / 1000; + + lo = (int)delay; + hi = lo + MHT_IPHB_WAKEUP_MAX_DELAY_S; + + /* Calculate the next full BOOTTIME second after low bound + * of iphb wakeup. This is used for avoiding constant iphb + * ipc when wakeups get re-evaluated. + * + * There might be up to one second jitter in actual wakeup + * vs cached time stamp, but it does not matter since: + * 1) the wide iphb range means the wake ups from suspend + * are going to be off by several seconds on average + * anyway + * 2) if the device does not get suspended, the higher + * resolution glib timeout gets triggered on time + */ + + tick = now + delay * 1000; + tick += 999; + tick -= tick % 1000; + } + + if( mht_iphb_wakeup_tick != tick ) { + mht_iphb_wakeup_tick = tick; + + if( mht_connection_handle ) + iphb_wait2(mht_connection_handle, lo, hi, 0, 1); + + mce_log(LL_DEBUG, "iphb wakeup in [%d, %d] s", lo, hi); + } +} + +/* ========================================================================= * + * IPHB_CONNECTION + * ========================================================================= */ + +/** Try to establish iphb socket connection + * + * Note: This is a helper function meant to be called only from + * mht_connection_open() and mht_connection_timer_cb() + * functions. + * + * @return true if connection is made, false otherwise + */ +static bool +mht_connection_try_to_open(void) +{ + iphb_t handle = 0; + + if( mht_connection_handle ) + goto cleanup; + + if( !(handle = iphb_open(0)) ) { + mce_log(LL_WARN, "iphb_open: %m"); + goto cleanup; + } + + int fd = iphb_get_fd(handle); + if( fd == -1 ) { + mce_log(LL_WARN, "iphb_get_fd: %m"); + goto cleanup; + } + + /* set up io watch */ + mht_iphb_wakeup_watch_id = + mht_add_iowatch(fd, false, G_IO_IN, mht_iphb_wakeup_cb, 0); + + if( !mht_iphb_wakeup_watch_id ) + goto cleanup; + + /* cache the handle */ + mht_connection_handle = handle, handle = 0; + + mce_log(LL_DEBUG, "iphb connected; dispatch hbtimers"); + mht_queue_dispatch_timers(); + +cleanup: + + if( handle ) iphb_close(handle); + + return mht_connection_handle != 0; +} + +/** Callback for connect reattempt timer + * + * @param aptr (unused) + * + * @return TRUE to keep timer repeating, or FALSE to stop it + */ +static gboolean +mht_connection_timer_cb(gpointer aptr) +{ + (void)aptr; + + if( !mht_connection_timer_id ) + goto cleanup; + + ++mht_connection_retry_no; + + if( !mht_connection_try_to_open() ) { + if( mht_connection_retry_no < MHT_CONNECTION_MAX_RETRIES ) + goto cleanup; + + mce_log(LL_WARN, "connect failed %d times; giving up", + mht_connection_retry_no); + } + else { + mce_log(LL_DEBUG, "connected after %d retries", + mht_connection_retry_no); + } + + mht_connection_timer_id = 0; + +cleanup: + + return mht_connection_timer_id != 0; +} + +/** Start connect reattempt timer + */ +static void +mht_connection_start_timer(void) +{ + if( !mht_connection_timer_id && mce_hbtimer_initialized ) { + mht_connection_retry_no = 0; + mht_connection_timer_id = + g_timeout_add(MHT_CONNECTION_RETRY_DELAY_MS, + mht_connection_timer_cb, 0); + } +} + +/** Cancel connect reattempt timer + */ +static void +mht_connection_stop_timer(void) +{ + if( mht_connection_timer_id ) { + g_source_remove(mht_connection_timer_id), + mht_connection_timer_id = 0; + } +} + +/** Predicate for: Connect reattempt timer is active + */ +static bool +mht_connection_is_pending(void) +{ + return mht_connection_timer_id != 0; +} + +/** Start connecting to iphb socket + * + * If the connection cant be made immediately it + * will be reattempted few times via timer callback. + */ +static void +mht_connection_open(void) +{ + if( mht_connection_is_pending() ) { + // Retry timer already set up + } + else if( !mht_connection_try_to_open() ) { + // Could not connect now - start retry timer + mht_connection_start_timer(); + } +} + +/** Close connection to iphb socket + */ +static void +mht_connection_close(void) +{ + /* stop pending connect attempts */ + mht_connection_stop_timer(); + + /* remove io watch */ + if( mht_iphb_wakeup_watch_id ) { + g_source_remove(mht_iphb_wakeup_watch_id), + mht_iphb_wakeup_watch_id = 0; + } + + /* close handle */ + if( mht_connection_handle ) { + iphb_close(mht_connection_handle), + mht_connection_handle = 0; + + mce_log(LL_DEBUG, "iphb disconnected"); + + /* reset last programmed wakeup */ + mht_iphb_set_wakeup(NO_TICK, NO_TICK); + } +} + +/* ========================================================================= * + * DATAPIPE_HANDLERS + * ========================================================================= */ + +/** Resumed from suspend notification */ +static void mht_datapipe_device_resumed_cb(gconstpointer data) +{ + (void) data; + + /* Check triggers & update glib wakeup after resume */ + mce_log(LL_DEBUG, "resumed; dispatch hbtimers"); + mht_queue_dispatch_timers(); +} + +/** Datapipe trigger for dsme availability + */ +static void mht_datapipe_dsme_available_cb(gconstpointer const data) +{ + service_state_t prev = dsme_available; + dsme_available = GPOINTER_TO_INT(data); + + if( dsme_available == prev ) + goto EXIT; + + mce_log(LL_DEBUG, "DSME dbus service: %s -> %s", + service_state_repr(prev), + service_state_repr(dsme_available)); + + if( dsme_available == SERVICE_STATE_RUNNING ) + mht_connection_open(); + else + mht_connection_close(); + +EXIT: + return; +} + +/** Change notifications for shutting_down + */ +static void mht_datapipe_shutting_down_cb(gconstpointer data) +{ + bool prev = shutting_down; + shutting_down = GPOINTER_TO_INT(data); + + if( shutting_down == prev ) + goto EXIT; + + mce_log(LL_DEBUG, "shutting_down = %d -> %d", prev, shutting_down); + + /* Loss of iphb connection is expected during shutdown */ + +EXIT: + return; +} + +/** Array of datapipe handlers */ +static datapipe_handler_t mht_datapipe_handlers[] = +{ + // output triggers + { + .datapipe = &device_resumed_pipe, + .output_cb = mht_datapipe_device_resumed_cb, + }, + { + .datapipe = &dsme_available_pipe, + .output_cb = mht_datapipe_dsme_available_cb, + }, + { + .datapipe = &shutting_down_pipe, + .output_cb = mht_datapipe_shutting_down_cb, + }, + + // sentinel + { + .datapipe = 0, + } +}; + +static datapipe_bindings_t mht_datapipe_bindings = +{ + .module = "mce_hbtimer", + .handlers = mht_datapipe_handlers, +}; + +/** Append triggers/filters to datapipes + */ +static void mht_datapipe_init(void) +{ + datapipe_bindings_init(&mht_datapipe_bindings); +} + +/** Remove triggers/filters from datapipes + */ +static void mht_datapipe_quit(void) +{ + datapipe_bindings_quit(&mht_datapipe_bindings); +} + +/* ========================================================================= * + * MODULE_INIT + * ========================================================================= */ + +void +mce_hbtimer_init(void) +{ + /* Connect to datapipes */ + mht_datapipe_init(); + + /* Mark as initialized */ + mce_hbtimer_initialized = true; + + /* Schedule timers that have been created + * before initialization took place */ + mht_queue_schedule_wakeups(); +} + +void +mce_hbtimer_quit(void) +{ + /* Mark as de-initialized */ + mce_hbtimer_initialized = false; + + /* Disconnect from datapipes */ + mht_datapipe_quit(); + + /* Remove wakeups */ + mht_glib_set_wakeup(NO_TICK, NO_TICK); + mht_iphb_set_wakeup(NO_TICK, NO_TICK); + + /* close iphb connection */ + mht_connection_close(); +} diff --git a/mce-hbtimer.h b/mce-hbtimer.h new file mode 100644 index 00000000..62399f49 --- /dev/null +++ b/mce-hbtimer.h @@ -0,0 +1,58 @@ +/** + * @file mce-hbtimer.h + * + * Mode Control Entity - Suspend proof timer functionality + * + *

+ * + * Copyright (C) 2014-2015 Jolla Ltd. + * + *

+ * + * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#ifndef MCE_HBTIMER_H_ +# define MCE_HBTIMER_H_ + +# include +# include + +# ifdef __cplusplus +extern "C" { +# endif + +typedef struct mce_hbtimer_t mce_hbtimer_t; + +mce_hbtimer_t * mce_hbtimer_create (const char *name, int period, GSourceFunc notify, void *user_data); +void mce_hbtimer_delete (mce_hbtimer_t *self); + +bool mce_hbtimer_is_active (const mce_hbtimer_t *self); +const char *mce_hbtimer_get_name (const mce_hbtimer_t *self); +void mce_hbtimer_set_period (mce_hbtimer_t *self, int period); + +void mce_hbtimer_start (mce_hbtimer_t *self); +void mce_hbtimer_stop (mce_hbtimer_t *self); + +void mce_hbtimer_dispatch (void); + +void mce_hbtimer_init (void); +void mce_hbtimer_quit (void); + +# ifdef __cplusplus +}; +# endif + +#endif /* MCE_HBTIMER_H_ */ diff --git a/mce.c b/mce.c index f2794163..0bce9e9f 100644 --- a/mce.c +++ b/mce.c @@ -23,6 +23,7 @@ #include "mce-log.h" #include "mce-conf.h" #include "mce-fbdev.h" +#include "mce-hbtimer.h" #include "mce-gconf.h" #include "mce-dbus.h" #include "mce-dsme.h" @@ -114,6 +115,7 @@ static void mce_cleanup_wakelocks(void) wakelock_unlock("mce_led_breathing"); wakelock_unlock("mce_lpm_off"); wakelock_unlock("mce_tklock_notify"); + wakelock_unlock("mce_hbtimer_dispatch"); } #endif // ENABLE_WAKELOCKS @@ -965,6 +967,9 @@ int main(int argc, char **argv) /* Setup all datapipes */ mce_datapipe_init(); + /* Allow registering of suspend proof timers */ + mce_hbtimer_init(); + /* Initialise mode management * pre-requisite: mce_gconf_init() * pre-requisite: mce_dbus_init() @@ -1049,6 +1054,7 @@ int main(int argc, char **argv) mce_powerkey_exit(); mce_dsme_exit(); mce_mode_exit(); + mce_hbtimer_quit(); /* Free all datapipes */ mce_datapipe_quit(); diff --git a/rpm/mce.spec b/rpm/mce.spec index 2a2513ac..ae739e48 100644 --- a/rpm/mce.spec +++ b/rpm/mce.spec @@ -17,6 +17,7 @@ Conflicts: lipstick-qt5 < 0.24.7 BuildRequires: pkgconfig(dbus-1) >= 1.0.2 BuildRequires: pkgconfig(dbus-glib-1) BuildRequires: pkgconfig(dsme) >= 0.58 +BuildRequires: pkgconfig(libiphb) BuildRequires: pkgconfig(glib-2.0) >= 2.36.0 BuildRequires: pkgconfig(mce) >= 1.15.0 BuildRequires: pkgconfig(libsystemd-daemon)