/** * @file mce-wakelock.c * Wakelock multiplexing code for the Mode Control Entity *

* Copyright (C) 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-wakelock.h" #include "mce-log.h" #include #include #include #include #include /* ========================================================================= * * FUNCTIONS & DATA * ========================================================================= */ /* ------------------------------------------------------------------------- * * SYSFS_API * ------------------------------------------------------------------------- */ /** Path to kernel wakelock obtain sysfs file */ static const char mwl_sysfs_lock_path[] = "/sys/power/wake_lock"; /** Path to kernel wakelock release sysfs file */ static const char mwl_sysfs_unlock_path[] = "/sys/power/wake_unlock"; static bool mwl_sysfs_write(const char *path, const char *data, int size); /* ------------------------------------------------------------------------- * * RAWLOCK_API * ------------------------------------------------------------------------- */ /** Name of the multiplexed "real" wakelock */ static const char mwl_rawlock_name[] = "mce_mux"; /** Flag for: "real" wakelock is held */ static bool mce_rawlock_locked = false; static bool mwl_rawlock_supported (void); static bool mwl_rawlock_lock (void); static bool mwl_rawlock_unlock (void); static void mwl_rawlock_set (bool lock); /* ------------------------------------------------------------------------- * * mwl_wakelock_t * ------------------------------------------------------------------------- */ /** Virtual wakelock object */ typedef struct mwl_wakelock_t { /** Name of the virtual wakelock */ gchar *wl_name; /** Release timer id */ guint wl_timer_id; } mwl_wakelock_t; static gboolean mwl_wakelock_timer_cb (gpointer aptr); static void mwl_wakelock_start_timer (mwl_wakelock_t *self, int delay_ms); static void mwl_wakelock_stop_timer (mwl_wakelock_t *self); static mwl_wakelock_t *mwl_wakelock_create (const char *name); static void mwl_wakelock_delete (mwl_wakelock_t *self); static void mwl_wakelock_delete_cb (void *self); /* ------------------------------------------------------------------------- * * MODULE_API * ------------------------------------------------------------------------- */ /** Flag for: wakelock module is ready for use */ static bool mce_wakelock_ready = false; /** Lookup table for tracked wakelock objects */ static GHashTable *mce_wakelock_lut = 0; // [name] -> mwl_wakelock_t * static mwl_wakelock_t *mce_wakelock_add_entry (const char *name); static void mce_wakelock_rem_entry (const char *name); static bool mce_wakelock_have_entries (void); void mce_wakelock_obtain (const char *name, int duration_ms); void mce_wakelock_release (const char *name); void mce_wakelock_init (void); void mce_wakelock_quit (void); void mce_wakelock_abort (void); /* ========================================================================= * * SYSFS_API * ========================================================================= */ /** Helper for writing to sysfs files */ static bool mwl_sysfs_write(const char *path, const char *data, int size) { bool res = false; int fd = -1; if( !path || !data || size <= 0 ) goto cleanup; if( (fd = open(path, O_WRONLY)) == -1 ) goto cleanup; if( write(fd, data, size) == -1 ) goto cleanup; res = true; cleanup: if( fd != -1 ) close(fd); return res; } /* ========================================================================= * * RAWLOCK_API * ========================================================================= */ /** Predicate for: wakelock sysfs control files exist */ static bool mwl_rawlock_supported(void) { return (access(mwl_sysfs_lock_path, W_OK) == 0 && access(mwl_sysfs_unlock_path, W_OK) == 0); } /** Async signal safe wakelock obtain */ static bool mwl_rawlock_lock(void) { return mwl_sysfs_write(mwl_sysfs_lock_path, mwl_rawlock_name, sizeof mwl_rawlock_name - 1); } /** Async signal safe wakelock release */ static bool mwl_rawlock_unlock(void) { return mwl_sysfs_write(mwl_sysfs_unlock_path, mwl_rawlock_name, sizeof mwl_rawlock_name - 1); } /** Set wakelock state * * @param lock true to obtain real wakelock, false to release */ static void mwl_rawlock_set(bool lock) { if( mce_rawlock_locked == lock ) goto EXIT; mce_log(LL_DEBUG, "wakelock %s", lock ? "obtain" : "release"); errno = 0; if( (mce_rawlock_locked = lock) ) { if( !mwl_rawlock_lock() ) mce_log(LL_ERR, "failed to obtain wakelock: %m"); } else { if( !mwl_rawlock_unlock() ) mce_log(LL_ERR, "failed to release wakelock: %m"); } EXIT: return; } /* ========================================================================= * * mwl_wakelock_t * ========================================================================= */ /** Timer callback for releasing virtual wakelock * * @param aptr wakelock object pointer */ static gboolean mwl_wakelock_timer_cb(gpointer aptr) { mwl_wakelock_t *self = aptr; if( !self ) goto EXIT; if( !self->wl_timer_id ) goto EXIT; self->wl_timer_id = 0; mce_wakelock_release(self->wl_name); EXIT: return FALSE; } /** Start virtual wakelock object release timer * * @param self wakelock object pointer, or NULL * @param delay_ms delay before releasing wakelock */ static void mwl_wakelock_start_timer(mwl_wakelock_t *self, int delay_ms) { if( !self ) goto EXIT; if( self->wl_timer_id ) { g_source_remove(self->wl_timer_id), self->wl_timer_id = 0; } if( delay_ms < 0 ) goto EXIT; if( delay_ms > 0 ) { self->wl_timer_id = g_timeout_add(delay_ms, mwl_wakelock_timer_cb, self); } else { self->wl_timer_id = g_idle_add(mwl_wakelock_timer_cb, self); } EXIT: return; } /** Stop virtual wakelock object release timer * * @param self wakelock object pointer, or NULL */ static void mwl_wakelock_stop_timer(mwl_wakelock_t *self) { if( !self ) goto EXIT; if( self->wl_timer_id ) { g_source_remove(self->wl_timer_id), self->wl_timer_id = 0; } EXIT: return; } /** Create virtual wakelock object * * @param name Name of wakelock object to create * * @return wakelock object pointer */ static mwl_wakelock_t * mwl_wakelock_create(const char *name) { mwl_wakelock_t *self = g_malloc0(sizeof *self); self->wl_name = g_strdup(name); self->wl_timer_id = 0; mce_log(LL_DEBUG, "wakelock %s obtain (mux)", self->wl_name); return self; } /** Delete virtual wakelock object * * @param self wakelock object pointer, or NULL */ static void mwl_wakelock_delete(mwl_wakelock_t *self) { if( !self ) goto EXIT; mwl_wakelock_stop_timer(self); mce_log(LL_DEBUG, "wakelock %s release (mux)", self->wl_name); g_free(self->wl_name); g_free(self); EXIT: return; } /** GDestroyNotify compatible delete callback * * @param aptr wakelock object pointer (as void pointer), or NULL */ static void mwl_wakelock_delete_cb(void *aptr) { mwl_wakelock_delete(aptr); } /* ========================================================================= * * MODULE_API * ========================================================================= */ /** Lookup or create a wakelock object by name * * @param name Name of the virtual wakelock * * @return object pointer, or NULL on errors */ static mwl_wakelock_t * mce_wakelock_add_entry(const char *name) { mwl_wakelock_t *self = 0; if( !mce_wakelock_lut ) goto EXIT; if( !(self = g_hash_table_lookup(mce_wakelock_lut, name)) ) { self = mwl_wakelock_create(name); g_hash_table_replace(mce_wakelock_lut, g_strdup(name), self); } EXIT: return self; } /** Remove a wakelock object by name * * @param name Name of the virtual wakelock */ static void mce_wakelock_rem_entry(const char *name) { if( !mce_wakelock_lut ) goto EXIT; g_hash_table_remove(mce_wakelock_lut, name); EXIT: return; } /** Predicate for: have virtual wakelocks * * @return true if there are active virtual wakelocks, false otherwise */ static bool mce_wakelock_have_entries(void) { bool have_entries = false; if( !mce_wakelock_lut ) goto EXIT; if( g_hash_table_size(mce_wakelock_lut) > 0 ) have_entries = true; EXIT: return have_entries; } /** Obtain virtual wakelock * * @param name Name of the virtual wakelock */ void mce_wakelock_obtain(const char *name, int duration_ms) { if( !mce_wakelock_ready ) goto EXIT; /* Add entry & start release timer */ mwl_wakelock_start_timer(mce_wakelock_add_entry(name), duration_ms); /* Re-evaluate need for real wakelock */ mwl_rawlock_set(mce_wakelock_have_entries()); EXIT: return; } /** Release virtual wakelock * * @param name Name of the virtual wakelock */ void mce_wakelock_release(const char *name) { if( !mce_wakelock_ready ) goto EXIT; /* Remove entry */ mce_wakelock_rem_entry(name); /* Re-evaluate need for real wakelock */ mwl_rawlock_set(mce_wakelock_have_entries()); EXIT: return; } /** Initialize mce wakelock subsystem */ void mce_wakelock_init(void) { /* Leave disabled if sysfs control files do not exist */ if( !mwl_rawlock_supported() ) goto EXIT; /* Setup entry look up table */ if( !mce_wakelock_lut ) mce_wakelock_lut = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, mwl_wakelock_delete_cb); /* In case previous mce instance managed to exit without * clearing wakelocks: unlock without error checking */ mwl_rawlock_unlock(); /* allow locking */ mce_wakelock_ready = true; EXIT: mce_log(LL_DEBUG, "wakelock usage %s", mce_wakelock_ready ? "enabled" : "disabled"); return; } /** Cleanup mce wakelock subsystem */ void mce_wakelock_quit(void) { /* Deny further locking */ mce_wakelock_ready = false; /* Flush entry look up table */ if( mce_wakelock_lut ) g_hash_table_unref(mce_wakelock_lut), mce_wakelock_lut = 0; /* If there were active internal wakelocks, * remove the real kernel wakelock too */ mwl_rawlock_set(false); } /** Async signal safe wakelock cleanup */ void mce_wakelock_abort(void) { /* Deny further locking */ mce_wakelock_ready = false; /* Uncondition unlock, using syscalls only */ mwl_rawlock_unlock(); }