libwakelock.c 8.79 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/**
 * @file libwakelock.c
 * Mode Control Entity - wakelock management
 * <p>
 * Copyright (C) 2012-2019 Jolla Ltd.
 * <p>
 * @author Simo Piiroinen <simo.piiroinen@jollamobile.com>
 *
 * 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 <http://www.gnu.org/licenses/>.
 */
21

spiiroin's avatar
spiiroin committed
22 23 24 25 26
/* 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!
 */

27 28
#include "libwakelock.h"

spiiroin's avatar
spiiroin committed
29
#include <stdarg.h>
30
#include <stdbool.h>
31 32
#include <unistd.h>
#include <string.h>
33
#include <errno.h>
34
#include <fcntl.h>
35 36 37 38 39 40 41 42 43 44 45 46 47

/** 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: "

spiiroin's avatar
spiiroin committed
48 49 50 51 52 53
/** 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
54
 */
spiiroin's avatar
spiiroin committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
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;
}

105
#if LWL_ENABLE_LOGGING
106 107
/** Flag for enabling wakelock debug logging */
static int lwl_debug_enabled = 0;
spiiroin's avatar
spiiroin committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138

/** 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 {\
139
		if( lwl_debug_enabled ) lwl_debug_(MSG, ##MORE); \
spiiroin's avatar
spiiroin committed
140 141
	} while( 0 )

142
#else
spiiroin's avatar
spiiroin committed
143
# define lwl_debug(MSG, MORE...) do { } while( 0 )
144 145
#endif

146 147 148 149
/** Flag that gets set once the process is about to exit */
static int        lwl_shutting_down = 0;

/** Sysfs entry for acquiring wakelocks */
150
static const char lwl_lock_path[]   = "/sys/power/wake_lock";
151 152

/** Sysfs entry for releasing wakelocks */
153 154
static const char lwl_unlock_path[] = "/sys/power/wake_unlock";

155
/** Sysfs entry for allow/block early suspend */
156 157
static const char lwl_state_path[] = "/sys/power/state";

158 159 160
/** Sysfs entry for allow/block autosleep */
static const char lwl_autosleep_path[] = "/sys/power/autosleep";

161 162 163 164 165 166
/** Helper for writing to sysfs files
 */
static void lwl_write_file(const char *path, const char *data)
{
	int file;

spiiroin's avatar
spiiroin committed
167
	lwl_debug(path, " << ", data, NULL);
168 169

	if( (file = TEMP_FAILURE_RETRY(open(path, O_WRONLY))) == -1 ) {
spiiroin's avatar
spiiroin committed
170
		lwl_debug(path, ": open: ", strerror(errno), "\n", NULL);
171 172 173 174
	} else {
		int size = strlen(data);
		errno = 0;
		if( TEMP_FAILURE_RETRY(write(file, data, size)) != size ) {
spiiroin's avatar
spiiroin committed
175 176
			lwl_debug(path, ": write: ", strerror(errno),
				  "\n", NULL);
177 178 179 180 181
		}
		TEMP_FAILURE_RETRY(close(file));
	}
}

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
/** 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
205
 */
206 207
static bool
lwl_write_text(const char *path, const char *data, int size)
208
{
209 210 211 212 213 214 215 216 217 218
	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;
219

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
	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
 */
246
suspend_type_t lwl_probe(void)
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
{
	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;
267 268
	}

269 270
EXIT:
	return suspend_type;
271 272 273 274 275 276 277 278 279 280
}

/** 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)
{
281 282 283 284
	if( lwl_shutting_down )
		goto EXIT;

	if( lwl_probe() > SUSPEND_TYPE_NONE ) {
285
		char tmp[64];
spiiroin's avatar
spiiroin committed
286
		char num[64];
287
		if( ns < 0 ) {
spiiroin's avatar
spiiroin committed
288
			lwl_concat(tmp, sizeof tmp, name, "\n", NULL);
289
		} else {
290
			lwl_concat(tmp, sizeof tmp, name, " ",
spiiroin's avatar
spiiroin committed
291 292
				   lwl_number(num, sizeof num, ns),
				   "\n", NULL);
293 294 295
		}
		lwl_write_file(lwl_lock_path, tmp);
	}
296 297 298

EXIT:
	return;
299 300 301 302 303 304 305 306 307 308
}

/** 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)
{
309
	if( lwl_probe() > SUSPEND_TYPE_NONE ) {
310
		char tmp[64];
spiiroin's avatar
spiiroin committed
311
		lwl_concat(tmp, sizeof tmp, name, "\n", NULL);
312 313 314
		lwl_write_file(lwl_unlock_path, tmp);
	}
}
315 316 317 318 319 320 321 322 323 324
/** 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)
{
325 326 327 328 329 330 331 332 333 334 335 336
	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;
337
	}
338 339 340

EXIT:
	return;
341 342 343 344 345 346 347 348 349
}

/** 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)
{
350 351 352 353 354 355 356 357 358
	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;
359 360
	}
}
361 362 363 364 365 366 367 368 369 370 371 372 373

/** 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)
{
spiiroin's avatar
spiiroin committed
374 375
	lwl_shutting_down = 1;
	wakelock_block_suspend();
376
}
377 378 379 380 381

/** Enable wakelock debug logging (if support compiled in)
 */
void lwl_enable_logging(void)
{
382
	lwl_debug_enabled = 1;
383
}