How early suspend works in Android ?

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);
}

Leave a Comment