/** * @file libwakelock.c * Mode Control Entity - wakelock management *

* Copyright (C) 2012-2019 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 . */ /* NOTE: Only async-signal-safe functions can be called from this * module since we might need to use the functionality while * handling non-recoverable signals! */ #include "libwakelock.h" #include #include #include #include #include #include /** Whether to write debug logging to stderr * * If not enabled, no diagnostics of any kind gets written. */ #ifndef LWL_ENABLE_LOGGING # define LWL_ENABLE_LOGGING 01 #endif /** Prefix used for log messages */ #define LWL_LOG_PFIX "LWL: " /** Number to string helper * * @param buf work space * @param len size of work space * @param num number to convert * @return pointer to ascii representation of num */ static char *lwl_number(char *buf, size_t len, long long num) { char *pos = buf + len; int sgn = (num < 0); unsigned long long val = sgn ? -num : num; auto void emit(int c) { if( pos > buf ) *--pos = c; } /* terminate at end of work space */ emit(0); /* work backwards from least signigicant digits */ do { emit('0' + val % 10); } while( val /= 10 ); /* apply minus sign if needed */ if( sgn ) emit('-'); /* return pointer to 1st digit (not start of buffer) */ return pos; } /** String concatenation helper * * @param buf work space * @param len size of work space * @param str first string to copy * @param ... NULL terminated list of more strings to copy * @return pointer to the start of the buffer */ static char *lwl_concat(char *buf, size_t len, const char *str, ...) { char *pos = buf; char *end = buf + len - 1; va_list va; auto void emit(const char *s) { while( pos < end && *s ) *pos++ = *s++; } va_start(va, str); while( str ) emit(str), str = va_arg(va, const char *); va_end(va); *pos = 0; return buf; } #if LWL_ENABLE_LOGGING /** Flag for enabling wakelock debug logging */ static int lwl_debug_enabled = 0; /** Logging functionality that can be configured out at compile time */ static void lwl_debug_(const char *m, ...) { char buf[256]; char *pos = buf; char *end = buf + sizeof buf - 1; va_list va; auto void emit(const char *s) { while( pos < end && *s ) *pos++ = *s++; } emit("LWL: "); va_start(va, m); while( m ) { emit(m), m = va_arg(va, const char *); } va_end(va); if( write(STDERR_FILENO, buf, pos - buf) < 0 ) { // do not care } } # define lwl_debug(MSG, MORE...) \ do {\ if( lwl_debug_enabled ) lwl_debug_(MSG, ##MORE); \ } while( 0 ) #else # define lwl_debug(MSG, MORE...) do { } while( 0 ) #endif /** Flag that gets set once the process is about to exit */ static int lwl_shutting_down = 0; /** Sysfs entry for acquiring wakelocks */ static const char lwl_lock_path[] = "/sys/power/wake_lock"; /** Sysfs entry for releasing wakelocks */ static const char lwl_unlock_path[] = "/sys/power/wake_unlock"; /** Sysfs entry for allow/block early suspend */ static const char lwl_state_path[] = "/sys/power/state"; /** Sysfs entry for allow/block autosleep */ static const char lwl_autosleep_path[] = "/sys/power/autosleep"; /** Helper for writing to sysfs files */ static void lwl_write_file(const char *path, const char *data) { int file; lwl_debug(path, " << ", data, NULL); if( (file = TEMP_FAILURE_RETRY(open(path, O_WRONLY))) == -1 ) { lwl_debug(path, ": open: ", strerror(errno), "\n", NULL); } else { int size = strlen(data); errno = 0; if( TEMP_FAILURE_RETRY(write(file, data, size)) != size ) { lwl_debug(path, ": write: ", strerror(errno), "\n", NULL); } TEMP_FAILURE_RETRY(close(file)); } } /** Structure for holding static text + size */ typedef struct { const char *text; size_t size; } suspend_data_t; /** Early suspend disable string */ static const suspend_data_t data_on = { .text = "on", .size = 2 }; /** Autosleep disable string */ static const suspend_data_t data_off = { .text = "off", .size = 3 }; /** Early suspend / autosleep enable string */ static const suspend_data_t data_mem = { .text = "mem", .size = 3 }; /** Write text to a sysfs file * * @param path file to write * @param data text to write * @param size lenght of text * * @return true if write was successful, false otherwise */ static bool lwl_write_text(const char *path, const char *data, int size) { bool res = false; int fd = -1; if( !path || !data || size <= 0 ) goto cleanup; lwl_debug(path, " << ", data, "\n", NULL); 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; } /** Write fixed string to a sysfs file * * @param path file to write * @param data text of known size to write * * @return true if write was successful, false otherwise */ static bool lwl_write_data(const char *path, const suspend_data_t *data) { return lwl_write_text(path, data->text, data->size); } /** Helper for checking if/what kind of suspend model is supported */ suspend_type_t lwl_probe(void) { static suspend_type_t suspend_type = SUSPEND_TYPE_UNKN; if( suspend_type != SUSPEND_TYPE_UNKN ) goto EXIT; if( access(lwl_lock_path, W_OK) || access(lwl_unlock_path, W_OK) ) { /* No suspend without wakelock controls */ suspend_type = SUSPEND_TYPE_NONE; } else if( lwl_write_data(lwl_state_path, &data_on) ) { /* No error from disabling early suspend */ suspend_type = SUSPEND_TYPE_EARLY; } else if( lwl_write_data(lwl_autosleep_path, &data_off) ) { /* No error from disabling autosleep */ suspend_type = SUSPEND_TYPE_AUTO; } else { suspend_type = SUSPEND_TYPE_NONE; } EXIT: return suspend_type; } /** Use sysfs interface to create and enable a wakelock. * * @param name The name of the wakelock to obtain * @param ns Time in nanoseconds before the wakelock gets released * automatically, or negative value for no timeout. */ void wakelock_lock(const char *name, long long ns) { if( lwl_shutting_down ) goto EXIT; if( lwl_probe() > SUSPEND_TYPE_NONE ) { char tmp[64]; char num[64]; if( ns < 0 ) { lwl_concat(tmp, sizeof tmp, name, "\n", NULL); } else { lwl_concat(tmp, sizeof tmp, name, " ", lwl_number(num, sizeof num, ns), "\n", NULL); } lwl_write_file(lwl_lock_path, tmp); } EXIT: return; } /** Use sysfs interface to disable a wakelock. * * @param name The name of the wakelock to release * * Note: This will not delete the wakelocks. */ void wakelock_unlock(const char *name) { if( lwl_probe() > SUSPEND_TYPE_NONE ) { char tmp[64]; lwl_concat(tmp, sizeof tmp, name, "\n", NULL); lwl_write_file(lwl_unlock_path, tmp); } } /** Use sysfs interface to allow automatic entry to suspend * * After this call the device will enter suspend mode once all * the wakelocks have been released. * * Android kernels will enter early suspend (i.e. display is * turned off etc) even if there still are active wakelocks. */ void wakelock_allow_suspend(void) { if( lwl_shutting_down ) goto EXIT; switch( lwl_probe() ) { case SUSPEND_TYPE_EARLY: lwl_write_data(lwl_state_path, &data_mem); break; case SUSPEND_TYPE_AUTO: lwl_write_data(lwl_autosleep_path, &data_mem); break; default: break; } EXIT: return; } /** Use sysfs interface to block automatic entry to suspend * * The device will not enter suspend mode with or without * active wakelocks. */ void wakelock_block_suspend(void) { switch( lwl_probe() ) { case SUSPEND_TYPE_EARLY: lwl_write_data(lwl_state_path, &data_on); break; case SUSPEND_TYPE_AUTO: lwl_write_data(lwl_autosleep_path, &data_off); break; default: break; } } /** Block automatic suspend without possibility to unblock it again * * For use on exit path. We want to do clean exit from mainloop and * that might that code that re-enables autosuspend gets triggered * while we're on exit path. * * By calling this function when initiating daemon shutdown we are * protected against this. */ void wakelock_block_suspend_until_exit(void) { lwl_shutting_down = 1; wakelock_block_suspend(); } /** Enable wakelock debug logging (if support compiled in) */ void lwl_enable_logging(void) { lwl_debug_enabled = 1; }