This post briefs about the source code flow of how early suspend mechanism in power managerment works in Android. This code references are with respect to Android open source code.
$ vim frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void setHalAutoSuspendModeLocked(boolean enable) {
if (enable != mHalAutoSuspendModeEnabled) {
if (DEBUG) {
Slog.d(TAG, "Setting HAL auto-suspend mode to " + enable);
}
mHalAutoSuspendModeEnabled = enable;
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setHalAutoSuspend(" + enable + ")");
try {
mNativeWrapper.nativeSetAutoSuspend(enable);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
}
$ vim frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
if (enable) {
android::base::Timer t;
enableAutoSuspend();
if (t.duration() > 100ms) {
ALOGD("Excessive delay in autosuspend_enable() while turning screen off");
}
} else {
android::base::Timer t;
disableAutoSuspend();
if (t.duration() > 100ms) {
ALOGD("Excessive delay in autosuspend_disable() while turning screen on");
}
}
}
static JNINativeMethod gPowerManagerServiceMethods[] = {
{ "nativeSetAutoSuspend", "(Z)V",
(void*) nativeSetAutoSuspend },
}
int register_android_server_PowerManagerService(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "com/android/server/power/PowerManagerService",
gPowerManagerServiceMethods, NELEM(gPowerManagerServiceMethods));
(void) res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
// Callbacks
jclass clazz;
FIND_CLASS(clazz, "com/android/server/power/PowerManagerService");
GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivityFromNative, clazz,
"userActivityFromNative", "(JII)V");
// Initialize
for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) {
gLastEventTime[i] = LLONG_MIN;
}
gPowerManagerServiceObj = NULL;
return 0;
}
$ vim system/core/libsuspend/autosuspend.c
static int autosuspend_init(void) {
if (autosuspend_ops != NULL) {
return 0;
}
autosuspend_ops = autosuspend_wakeup_count_init();
if (autosuspend_ops == NULL) {
ALOGE("failed to initialize autosuspend");
return -1;
}
ALOGV("autosuspend initialized");
return 0;
}
int autosuspend_enable(void) {
int ret;
ret = autosuspend_init();
if (ret) {
return ret;
}
ALOGV("autosuspend_enable");
if (autosuspend_enabled) {
return 0;
}
ret = autosuspend_ops->enable();
if (ret) {
return ret;
}
autosuspend_enabled = true;
return 0;
}
$ vim system/core/libsuspend/autosuspend_wakeup_count.cpp
#define BASE_SLEEP_TIME 100000
#define MAX_SLEEP_TIME 60000000
static int state_fd = -1;
static int wakeup_count_fd;
using android::base::ReadFdToString;
using android::base::Trim;
using android::base::WriteStringToFd;
static pthread_t suspend_thread;
static sem_t suspend_lockout;
static constexpr char sleep_state[] = "mem";
static void (*wakeup_func)(bool success) = NULL;
static int sleep_time = BASE_SLEEP_TIME;
static constexpr char sys_power_state[] = "/sys/power/state";
static constexpr char sys_power_wakeup_count[] = "/sys/power/wakeup_count";
static bool autosuspend_is_init = false;
static void update_sleep_time(bool success) {
if (success) {
sleep_time = BASE_SLEEP_TIME;
return;
}
// double sleep time after each failure up to one minute
sleep_time = MIN(sleep_time * 2, MAX_SLEEP_TIME);
}
static void* suspend_thread_func(void* arg __attribute__((unused))) {
bool success = true;
while (true) {
update_sleep_time(success);
usleep(sleep_time);
success = false;
LOG(VERBOSE) << "read wakeup_count";
lseek(wakeup_count_fd, 0, SEEK_SET);
std::string wakeup_count;
if (!ReadFdToString(wakeup_count_fd, &wakeup_count)) {
PLOG(ERROR) << "error reading from " << sys_power_wakeup_count;
continue;
}
wakeup_count = Trim(wakeup_count);
if (wakeup_count.empty()) {
LOG(ERROR) << "empty wakeup count";
continue;
}
LOG(VERBOSE) << "wait";
int ret = sem_wait(&suspend_lockout);
if (ret < 0) {
PLOG(ERROR) << "error waiting on semaphore";
continue;
}
LOG(VERBOSE) << "write " << wakeup_count << " to wakeup_count";
if (WriteStringToFd(wakeup_count, wakeup_count_fd)) {
LOG(VERBOSE) << "write " << sleep_state << " to " << sys_power_state;
success = WriteStringToFd(sleep_state, state_fd);
void (*func)(bool success) = wakeup_func;
if (func != NULL) {
(*func)(success);
}
} else {
PLOG(ERROR) << "error writing to " << sys_power_wakeup_count;
}
LOG(VERBOSE) << "release sem";
ret = sem_post(&suspend_lockout);
if (ret < 0) {
PLOG(ERROR) << "error releasing semaphore";
}
}
return NULL;
}
static int init_state_fd(void) {
if (state_fd >= 0) {
return 0;
}
int fd = TEMP_FAILURE_RETRY(open(sys_power_state, O_CLOEXEC | O_RDWR));
if (fd < 0) {
PLOG(ERROR) << "error opening " << sys_power_state;
return -1;
}
state_fd = fd;
LOG(INFO) << "init_state_fd success";
return 0;
}
static int autosuspend_init(void) {
if (autosuspend_is_init) {
return 0;
}
int ret = init_state_fd();
if (ret < 0) {
return -1;
}
wakeup_count_fd = TEMP_FAILURE_RETRY(open(sys_power_wakeup_count, O_CLOEXEC | O_RDWR));
if (wakeup_count_fd < 0) {
PLOG(ERROR) << "error opening " << sys_power_wakeup_count;
goto err_open_wakeup_count;
}
ret = sem_init(&suspend_lockout, 0, 0);
if (ret < 0) {
PLOG(ERROR) << "error creating suspend_lockout semaphore";
goto err_sem_init;
}
ret = pthread_create(&suspend_thread, NULL, suspend_thread_func, NULL);
if (ret) {
LOG(ERROR) << "error creating thread: " << strerror(ret);
goto err_pthread_create;
}
LOG(VERBOSE) << "autosuspend_init success";
autosuspend_is_init = true;
return 0;
err_pthread_create:
sem_destroy(&suspend_lockout);
err_sem_init:
close(wakeup_count_fd);
err_open_wakeup_count:
return -1;
}
static int autosuspend_wakeup_count_enable(void) {
LOG(VERBOSE) << "autosuspend_wakeup_count_enable";
int ret = autosuspend_init();
if (ret < 0) {
LOG(ERROR) << "autosuspend_init failed";
return ret;
}
ret = sem_post(&suspend_lockout);
if (ret < 0) {
PLOG(ERROR) << "error changing semaphore";
}
LOG(VERBOSE) << "autosuspend_wakeup_count_enable done";
return ret;
}
static int autosuspend_wakeup_count_disable(void) {
LOG(VERBOSE) << "autosuspend_wakeup_count_disable";
if (!autosuspend_is_init) {
return 0; // always successful if no thread is running yet
}
int ret = sem_wait(&suspend_lockout);
if (ret < 0) {
PLOG(ERROR) << "error changing semaphore";
}
LOG(VERBOSE) << "autosuspend_wakeup_count_disable done";
return ret;
}
static int force_suspend(int timeout_ms) {
LOG(VERBOSE) << "force_suspend called with timeout: " << timeout_ms;
int ret = init_state_fd();
if (ret < 0) {
return ret;
}
return WriteStringToFd(sleep_state, state_fd) ? 0 : -1;
}
static void autosuspend_set_wakeup_callback(void (*func)(bool success)) {
if (wakeup_func != NULL) {
LOG(ERROR) << "duplicate wakeup callback applied, keeping original";
return;
}
wakeup_func = func;
}
struct autosuspend_ops autosuspend_wakeup_count_ops = {
.enable = autosuspend_wakeup_count_enable,
.disable = autosuspend_wakeup_count_disable,
.force_suspend = force_suspend,
.set_wakeup_callback = autosuspend_set_wakeup_callback,
};
struct autosuspend_ops* autosuspend_wakeup_count_init(void) {
return &autosuspend_wakeup_count_ops;
}
LOG(VERBOSE) << "write " << sleep_state << " to " << sys_power_state;
success = WriteStringToFd(sleep_state, state_fd);
In above code, WriteStringToFd writes “echo mem > /sys/power/state” which goes on calling, state_store in common/kernel/power/main.c
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
suspend_state_t state;
int error;
error = pm_autosleep_lock();
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) {
error = -EBUSY;
goto out;
}
state = decode_state(buf, n);
if (state < PM_SUSPEND_MAX) {
#ifdef CONFIG_EARLYSUSPEND
if (state == PM_SUSPEND_ON || state == PM_SUSPEND_MEM) {
error = 0;
request_suspend_state(state);
}
#else
error = pm_suspend(state);
#endif
}
else if (state == PM_SUSPEND_MAX)
error = hibernate();
else
error = -EINVAL;
out:
pm_autosleep_unlock();
return error ? error : n;
}
power_attr(state);
Now if we have early suspend enabled, the function request_suspend_state will be called in common/kernel/power/earlysuspend.c
void request_suspend_state(suspend_state_t new_state)
{
unsigned long irqflags;
int old_sleep;
spin_lock_irqsave(&state_lock, irqflags);
old_sleep = state & SUSPEND_REQUESTED;
if (debug_mask & DEBUG_USER_STATE) {
struct timespec ts;
struct rtc_time tm;
getnstimeofday(&ts);
rtc_time_to_tm(ts.tv_sec, &tm);
pr_info("request_suspend_state: %s (%d->%d) at %lld "
"(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n",
new_state != PM_SUSPEND_ON ? "sleep" : "wakeup",
requested_suspend_state, new_state,
ktime_to_ns(ktime_get()),
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}
pr_info("%s,%d,old_sleep=%d,new_state=%d\n", __func__, __LINE__,
old_sleep, new_state);
if (!old_sleep && new_state != PM_SUSPEND_ON) {
state |= SUSPEND_REQUESTED;
queue_work(suspend_work_queue, &early_suspend_work);
} else if (old_sleep && new_state == PM_SUSPEND_ON) {
state &= ~SUSPEND_REQUESTED;
wake_lock(&main_wake_lock);
queue_work(suspend_work_queue, &late_resume_work);
}
requested_suspend_state = new_state;
spin_unlock_irqrestore(&state_lock, irqflags);
}
The above call for queue_work(suspend_work_queue, &early_suspend_work); gets called as,
static void early_suspend(struct work_struct *work)
{
struct early_suspend *pos;
unsigned long irqflags;
int abort = 0;
mutex_lock(&early_suspend_lock);
spin_lock_irqsave(&state_lock, irqflags);
if (state == SUSPEND_REQUESTED)
state |= SUSPENDED;
else
abort = 1;
spin_unlock_irqrestore(&state_lock, irqflags);
if (abort) {
if (debug_mask & DEBUG_SUSPEND)
pr_info("early_suspend: abort, state %d\n", state);
mutex_unlock(&early_suspend_lock);
goto abort;
}
if (debug_mask & DEBUG_SUSPEND)
pr_info("early_suspend: call handlers\n");
list_for_each_entry(pos, &early_suspend_handlers, link) {
if (pos->suspend != NULL)
pos->suspend(pos);
}
mutex_unlock(&early_suspend_lock);
if (debug_mask & DEBUG_SUSPEND)
pr_info("early_suspend: sync\n");
sys_sync();
abort:
spin_lock_irqsave(&state_lock, irqflags);
if (state == SUSPEND_REQUESTED_AND_SUSPENDED)
wake_unlock(&main_wake_lock);
spin_unlock_irqrestore(&state_lock, irqflags);
}