/**
* @file mce-wltimer.c
*
* Mode Control Entity - Timers that block suspend until triggered
*
*
*
* Copyright (c) 2015 - 2023 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-wltimer.h"
#include "mce-log.h"
#include "mce-wakelock.h"
#include
#include
/* ========================================================================= *
* Types and functions
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* TIMER_METHODS
* ------------------------------------------------------------------------- */
/** State data for mce wakelock timers */
struct mce_wltimer_t
{
/** Timer name, used as wakelock name too */
char *wlt_name;
/** Timer delay in milliseconds */
int wlt_period;
/** Underlying glib timeout id */
guint wlt_timer_id;
/** Timer callback function */
GSourceFunc wlt_notify;
/** User data to pass to wlt_notify() */
void *wlt_user_data;
/** Currently handling notify */
bool wlt_triggered;
/** Timer start while in notify */
bool wlt_started;
/** Timer stop while in notify */
bool wlt_stopped;
};
mce_wltimer_t * mce_wltimer_create (const char *name, int period, GSourceFunc notify, void *user_data);
void mce_wltimer_delete (mce_wltimer_t *self);
static void mce_wltimer_eval_wakelock (const mce_wltimer_t *self);
bool mce_wltimer_is_active (const mce_wltimer_t *self);
const char *mce_wltimer_get_name (const mce_wltimer_t *self);
void mce_wltimer_set_period (mce_wltimer_t *self, int period);
void mce_wltimer_start (mce_wltimer_t *self);
void mce_wltimer_stop (mce_wltimer_t *self);
static gboolean mce_wltimer_gate_cb (gpointer aptr);
/* ------------------------------------------------------------------------- *
* QUEUE_MANAGEMENT
* ------------------------------------------------------------------------- */
/** Idle callback id for delayed garbage collect */
static guint mwt_queue_compact_id = 0;
/** List of registered timers */
static GSList *mwt_queue_timer_list = 0;
static void mwt_queue_compact (void);
static gboolean mwt_queue_compact_cb (gpointer aptr);
static void mwt_queue_schedule_compact (void);
static void mwt_queue_cancel_compact (void);
static void mwt_queue_add_timer (mce_wltimer_t *self);
static void mwt_queue_remove_timer (mce_wltimer_t *self);
static bool mwt_queue_has_timer (const mce_wltimer_t *self);
/* ------------------------------------------------------------------------- *
* MODULE_INIT
* ------------------------------------------------------------------------- */
/** Flag for: timers can be started */
static bool mce_wltimer_ready = true;
void mce_wltimer_init (void);
void mce_wltimer_quit (void);
/* ========================================================================= *
* TIMER_METHODS
* ========================================================================= */
/** Create wakelock 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 wakelock timer object
*/
mce_wltimer_t *
mce_wltimer_create(const char *name,
int period,
GSourceFunc notify,
void *user_data)
{
mce_wltimer_t *self = calloc(1, sizeof *self);
self->wlt_name = name ? strdup(name) : 0;
self->wlt_period = period;
self->wlt_timer_id = 0;
self->wlt_notify = notify;
self->wlt_user_data = user_data;
self->wlt_triggered = false;
self->wlt_started = false;
self->wlt_stopped = false;
mwt_queue_add_timer(self);
return self;
}
/** Delete wakelock timer
*
* @param self wakelock timer object, or NULL
*/
void
mce_wltimer_delete(mce_wltimer_t *self)
{
if( !self )
goto EXIT;
if( self->wlt_triggered )
mce_log(LL_DEBUG, "%s: timer delete while in notify",
mce_wltimer_get_name(self));
/* Clear the behaviour modifying flags so that the timer id
* and wakelock does get released at mce_wltimer_stop().
*
* Note that mwt_queue_remove_timer() invalidates
* timer object so that mce_wltimer_gate_cb() knows
* not to touch it anymore when user callback returns.
*/
self->wlt_triggered = false;
self->wlt_started = false;
self->wlt_stopped = false;
mce_wltimer_stop(self);
mwt_queue_remove_timer(self);
free(self->wlt_name),
self->wlt_name = 0;
free(self);
EXIT:
return;
}
/** Evaluate need for wakelock
*
* @param self wakelock timer object, or NULL
*/
static void
mce_wltimer_eval_wakelock(const mce_wltimer_t *self)
{
if( !self )
goto EXIT;
if( !self->wlt_name)
goto EXIT;
if( self->wlt_timer_id )
mce_wakelock_obtain(self->wlt_name, -1);
else
mce_wakelock_release(self->wlt_name);
EXIT:
return;
}
/** Predicate for: wakelock timer has been started
*
* @param self wakelock timer object, or NULL
*
* @return true if wakelock timer has been started, false otherwise
*/
bool
mce_wltimer_is_active(const mce_wltimer_t *self)
{
bool active = false;
if( self && self->wlt_timer_id ) {
active = !(self->wlt_triggered && self->wlt_stopped);
}
return active;
}
/** Get wakelock timer name
*
* @param self wakelock timer object, or NULL
*
* @return "invalid" if object is not valid,
* "unknown" if object has no name, or
* object name
*/
const char *
mce_wltimer_get_name(const mce_wltimer_t *self)
{
const char *name = "invalid";
if( self )
name = self->wlt_name;
return name ?: "unknown";
}
/** Set wakelock timer period
*
* @param self wakelock timer object, or NULL
* @param period timer delay [ms]
*/
void
mce_wltimer_set_period(mce_wltimer_t *self, int period)
{
if( self )
self->wlt_period = period;
}
/** Call wakelock timer notification functiom
*
* @param aptr wakelock timer object (as void pointer)
*/
static gboolean
mce_wltimer_gate_cb(gpointer aptr)
{
gboolean repeat = FALSE;
mce_wltimer_t *self = aptr;
if( !self->wlt_timer_id )
goto EXIT;
mce_log(LL_DEBUG, "trigger %s %d", mce_wltimer_get_name(self),
self->wlt_period);
if( self->wlt_notify ) {
self->wlt_triggered = true;
bool res = self->wlt_notify(self->wlt_user_data);
if( !mwt_queue_has_timer(self) ) {
/* The notify callback managed to delete the timer
* object, invalidate the pointer */
self = 0;
}
else {
if( self->wlt_started ) {
mce_log(LL_DEBUG, "%s: timer was started while in notify",
mce_wltimer_get_name(self));
repeat = true;
}
else if( self->wlt_stopped ) {
mce_log(LL_DEBUG, "%s: timer was stopped while in notify",
mce_wltimer_get_name(self));
repeat = false;
}
else {
/* Repeat/stop according to the callback return value */
repeat = res;
}
self->wlt_started = false;
self->wlt_stopped = false;
self->wlt_triggered = false;
}
}
EXIT:
if( self ) {
if( !repeat && self->wlt_timer_id )
self->wlt_timer_id = 0;
mce_wltimer_eval_wakelock(self);
}
return repeat;
}
/** Start wakelock timer
*
* @param self wakelock timer object, or NULL
*/
void
mce_wltimer_start(mce_wltimer_t *self)
{
if( !self )
goto EXIT;
if( self->wlt_triggered ) {
/* In notify - Keep alive after callback returns */
mce_log(LL_DEBUG, "%s: timer start while in notify",
mce_wltimer_get_name(self));
self->wlt_started = true;
self->wlt_stopped = false;
goto EXIT;
}
if( !mce_wltimer_ready )
goto EXIT;
if( self->wlt_period < 0 )
goto EXIT;
if( self->wlt_timer_id )
goto EXIT;
mce_log(LL_DEBUG, "start %s %d", mce_wltimer_get_name(self),
self->wlt_period);
if( self->wlt_period > 0 ) {
self->wlt_timer_id = g_timeout_add(self->wlt_period,
mce_wltimer_gate_cb,
self);
}
else {
self->wlt_timer_id = g_idle_add(mce_wltimer_gate_cb,
self);
}
EXIT:
mce_wltimer_eval_wakelock(self);
return;
}
/** Stop wakelock timer
*
* @param self wakelock timer object, or NULL
*/
void
mce_wltimer_stop(mce_wltimer_t *self)
{
if( !self )
goto EXIT;
if( self->wlt_triggered ) {
/* In notify - Stop after callback returns */
mce_log(LL_DEBUG, "%s: timer stop while in notify",
mce_wltimer_get_name(self));
self->wlt_started = false;
self->wlt_stopped = true;
goto EXIT;
}
if( !self->wlt_timer_id )
goto EXIT;
mce_log(LL_DEBUG, "stop %s", mce_wltimer_get_name(self));
g_source_remove(self->wlt_timer_id),
self->wlt_timer_id = 0;
EXIT:
mce_wltimer_eval_wakelock(self);
return;
}
/* ========================================================================= *
* QUEUE_MANAGEMENT
* ========================================================================= */
/** Clean up unused timer list slots
*
* When timers are deleted, the associated node in mwt_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
mwt_queue_compact(void)
{
mwt_queue_cancel_compact();
GSList **tail = &mwt_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);
}
}
}
/** Idle callback function for delayed garbage collect
*
* @param aptr user data pointer (unused)
*
* @return FALSE, to stop repeats
*/
static gboolean
mwt_queue_compact_cb(gpointer aptr)
{
(void)aptr;
if( !mwt_queue_compact_id )
goto EXIT;
mwt_queue_compact_id = 0;
mwt_queue_compact();
EXIT:
return FALSE;
}
/** Schedule delayed garbage collection
*/
static void mwt_queue_schedule_compact(void)
{
if( mwt_queue_compact_id )
goto EXIT;
mwt_queue_compact_id = g_idle_add(mwt_queue_compact_cb, 0);
EXIT:
return;
}
/** Cancel delayed garbage collection
*/
static void mwt_queue_cancel_compact(void)
{
if( !mwt_queue_compact_id )
goto EXIT;
g_source_remove(mwt_queue_compact_id),
mwt_queue_compact_id = 0;
EXIT:
return;
}
/** Predicate for: wakelock timer is registered
*
* @param self wakelock timer object, or NULL
*
* return true if timer is registered, false otherwise
*/
static bool
mwt_queue_has_timer(const mce_wltimer_t *self)
{
bool has_timer = false;
if( !self )
goto EXIT;
has_timer = g_slist_find(mwt_queue_timer_list, self) != 0;
EXIT:
return has_timer;
}
/** Register wakelock timer
*
* @param self wakelock timer object, or NULL
*/
static void
mwt_queue_add_timer(mce_wltimer_t *self)
{
if( !self )
goto EXIT;
/* Try to find recyclable vacated timer slot */
GSList *item = g_slist_find(mwt_queue_timer_list, 0);
if( item )
item->data = self;
else
mwt_queue_timer_list = g_slist_prepend(mwt_queue_timer_list, self);
EXIT:
return;
}
/** Unregister wakelock timer
*
* @param self wakelock timer object, or NULL
*/
static void
mwt_queue_remove_timer(mce_wltimer_t *self)
{
if( !self )
goto EXIT;
GSList *item = g_slist_find(mwt_queue_timer_list, self);
/* Vacate the slot by clearing the data link */
if( item )
item->data = 0;
/* Schedule removal of the link it self */
mwt_queue_schedule_compact();
EXIT:
return;
}
/* ========================================================================= *
* MODULE_INIT
* ========================================================================= */
void
mce_wltimer_init(void)
{
/* nop */
}
void
mce_wltimer_quit(void)
{
mce_log(LL_DEBUG, "deny suspend block timers");
/* Deny starting of timers */
mce_wltimer_ready = false;
/* Disable left-behind timer objects */
for( GSList *item = mwt_queue_timer_list; item; item = item->next ) {
mce_wltimer_t *timer = item->data;
if( !timer )
continue;
/* Note: What we have here is effectively a resource leak
* somewhere else. But all that can be done is to make
* sure we do not leave behind active timeouts that
* might then trigger callbacks in unexpected manner.
*/
mce_log(LL_WARN, "timer '%s' exists at deinit",
mce_wltimer_get_name(timer));
mce_wltimer_stop(timer);
item->data = 0;
}
/* Flush timer list */
mwt_queue_compact();
}