power: Release interaction lock when idle state is detected

An idle state node has been added to some of our common kernels
(e.g. qcom_msm8998). Use this node to unlock interaction boost before
the timeout expires when screen updates are not expected soon.

Change-Id: I43d1e2f2cbd6713baa132be7eb475243a8d4a5c6
diff --git a/android.hardware.power-service-qti.rc b/android.hardware.power-service-qti.rc
index 05c4d33..01f9e70 100644
--- a/android.hardware.power-service-qti.rc
+++ b/android.hardware.power-service-qti.rc
@@ -2,3 +2,8 @@
     class hal
     user system
     group system
+
+on post-fs
+    chmod 0664 /sys/devices/virtual/graphics/fb0/idle_time
+    chown system graphics /sys/devices/virtual/graphics/fb0/idle_time
+    write /sys/devices/virtual/graphics/fb0/idle_time 100
diff --git a/power-common.c b/power-common.c
index 533df83..b6a88e4 100644
--- a/power-common.c
+++ b/power-common.c
@@ -27,16 +27,19 @@
  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#define LOG_NIDEBUG 0
+//#define LOG_NDEBUG 0
 
 #include <dlfcn.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <poll.h>
+#include <sys/eventfd.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <pthread.h>
 
 #define LOG_TAG "QTI PowerHAL"
 #include <hardware/hardware.h>
@@ -48,13 +51,157 @@
 #include "power-common.h"
 #include "utils.h"
 
+#define MAX_LENGTH 64
+
 static struct hint_handles handles[NUM_HINTS];
 static int handleER = 0;
 
+static const char* fb_idle_paths[] = {"/sys/class/drm/card0/device/idle_state",
+                                      "/sys/class/graphics/fb0/idle_state"};
+
+static pthread_t tid;
+static pthread_once_t once = PTHREAD_ONCE_INIT;
+static pthread_cond_t interaction_cond = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t interaction_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static enum INTERACTION_STATE mState = INTERACTION_STATE_UNINITIALIZED;
+
+static int mIdleFd = 0;
+static int mEventFd = 0;
+
+const int kWaitDuration = 100;            /* ms */
 const int kMaxLaunchDuration = 5000;      /* ms */
 const int kMaxInteractiveDuration = 5000; /* ms */
 const int kMinInteractiveDuration = 500;  /* ms */
 
+static struct timespec s_previous_boost_timespec;
+static int s_previous_duration = 0;
+static int prev_interaction_handle = -1;
+
+int process_boost(int hint_id, int duration, int type) {
+    ALOGV("%s: acquiring perf lock", __func__);
+    int boost_handle = perf_hint_enable_with_type(hint_id, duration, type);
+    if (!CHECK_HANDLE(boost_handle)) {
+        ALOGE("Failed process_boost for boost_handle");
+    }
+    return boost_handle;
+}
+
+bool release_boost(int boost_handle) {
+    ALOGV("%s: releasing perf lock %i", __func__, boost_handle);
+    if (CHECK_HANDLE(boost_handle)) {
+        release_request(boost_handle);
+        return true;
+    }
+    return false;
+}
+
+int fb_idle_open(void) {
+    int fd;
+    int n = sizeof(fb_idle_paths) / sizeof(fb_idle_paths[0]);
+    for (int i = 0; i < n; i++) {
+        const char* path = fb_idle_paths[i];
+        fd = open(path, O_RDONLY);
+        if (fd >= 0)
+            return fd;
+    }
+    ALOGE("Unable to open fb idle state path (%d)", errno);
+    return -1;
+}
+
+void release() {
+    pthread_mutex_lock(&interaction_lock);
+    if (mState == INTERACTION_STATE_WAITING) {
+        if (release_boost(prev_interaction_handle)) {
+            prev_interaction_handle = -1;
+        }
+        mState = INTERACTION_STATE_IDLE;
+    } else {
+        // clear any wait aborts pending in event fd
+        uint64_t val;
+        ssize_t ret = read(mEventFd, &val, sizeof(val));
+
+        ALOGW_IF(ret < 0, "%s: failed to clear eventfd (%zd, %d)",
+                 __func__, ret, errno);
+    }
+    pthread_mutex_unlock(&interaction_lock);
+}
+
+void abortWaitLocked() {
+    uint64_t val = 1;
+    ssize_t ret = write(mEventFd, &val, sizeof(val));
+    if (ret != sizeof(val))
+        ALOGW("Unable to write to event fd (%zd)", ret);
+}
+
+void waitForIdle(int32_t wait_ms, int32_t timeout_ms) {
+    char data[MAX_LENGTH];
+    ssize_t ret;
+    struct pollfd pfd[2];
+
+    ALOGV("%s: wait:%d timeout:%d", __func__, wait_ms, timeout_ms);
+
+    pfd[0].fd = mEventFd;
+    pfd[0].events = POLLIN;
+    pfd[1].fd = mIdleFd;
+    pfd[1].events = POLLPRI | POLLERR;
+
+    ret = poll(pfd, 1, wait_ms);
+    if (ret > 0) {
+        ALOGV("%s: wait aborted", __func__);
+        return;
+    } else if (ret < 0) {
+        ALOGE("%s: error in poll while waiting", __func__);
+        return;
+    }
+
+    ret = pread(mIdleFd, data, sizeof(data), 0);
+    if (!ret) {
+        ALOGE("%s: Unexpected EOF!", __func__);
+        return;
+    }
+
+    if (!strncmp(data, "idle", 4)) {
+        ALOGV("%s: already idle", __func__);
+        return;
+    }
+
+    ret = poll(pfd, 2, timeout_ms);
+    if (ret < 0)
+        ALOGE("%s: Error on waiting for idle (%zd)", __func__, ret);
+    else if (ret == 0)
+        ALOGV("%s: timed out waiting for idle", __func__);
+    else if (pfd[0].revents)
+        ALOGV("%s: wait for idle aborted", __func__);
+    else if (pfd[1].revents)
+        ALOGV("%s: idle detected", __func__);
+}
+
+void* interaction_routine(void *vargp) {
+    while (true) {
+        pthread_mutex_lock(&interaction_lock);
+
+        while (mState == INTERACTION_STATE_IDLE)
+            pthread_cond_wait(&interaction_cond, &interaction_lock);
+
+        if (mState == INTERACTION_STATE_UNINITIALIZED) {
+            pthread_mutex_unlock(&interaction_lock);
+            return NULL;
+        }
+
+        mState = INTERACTION_STATE_WAITING;
+        pthread_mutex_unlock(&interaction_lock);
+
+        waitForIdle(kWaitDuration, s_previous_duration);
+        release();
+    }
+    return NULL;
+}
+
+static void create_once(void) {
+    pthread_create(&tid, NULL, interaction_routine, NULL);
+}
+
 void power_init() {
     ALOGI("Initing");
 
@@ -62,17 +209,40 @@
         handles[i].handle = 0;
         handles[i].ref_count = 0;
     }
+
+    pthread_mutex_lock(&interaction_lock);
+    if (mState != INTERACTION_STATE_UNINITIALIZED) {
+        pthread_mutex_unlock(&interaction_lock);
+        return;
+    }
+
+    int fd = fb_idle_open();
+    if (fd < 0) {
+        pthread_mutex_unlock(&interaction_lock);
+        return;
+    }
+    mIdleFd = fd;
+
+    mEventFd = eventfd(0, EFD_NONBLOCK);
+    if (mEventFd < 0) {
+        ALOGE("Unable to create event fd (%d)", errno);
+        close(mIdleFd);
+        pthread_mutex_unlock(&interaction_lock);
+        return;
+    }
+
+    mState = INTERACTION_STATE_IDLE;
+    pthread_once(&once, create_once);
+    pthread_mutex_unlock(&interaction_lock);
 }
 
 void process_interaction_hint(void* data) {
-    static struct timespec s_previous_boost_timespec;
-    static int s_previous_duration = 0;
-    static int prev_interaction_handle = -1;
-
     struct timespec cur_boost_timespec;
     long long elapsed_time;
     int duration = kMinInteractiveDuration;
 
+    pthread_mutex_lock(&interaction_lock);
+
     if (data) {
         int input_duration = *((int*)data);
         if (input_duration > duration) {
@@ -84,26 +254,48 @@
     clock_gettime(CLOCK_MONOTONIC, &cur_boost_timespec);
 
     elapsed_time = calc_timespan_us(s_previous_boost_timespec, cur_boost_timespec);
-    // don't hint if it's been less than 250ms since last boost
-    // also detect if we're doing anything resembling a fling
-    // support additional boosting in case of flings
-    if (elapsed_time < 250000 && duration <= kMinInteractiveDuration) {
-        return;
+    if (mState == INTERACTION_STATE_UNINITIALIZED) {
+        // don't hint if it's been less than 250ms since last boost
+        // also detect if we're doing anything resembling a fling
+        // support additional boosting in case of flings
+        if (elapsed_time < 250000 && duration <= kMinInteractiveDuration) {
+            pthread_mutex_unlock(&interaction_lock);
+            return;
+        }
     }
-    // also don't hint if previous hint's duration covers this hint's duration
-    if ((s_previous_duration * 1000) > (elapsed_time + duration * 1000)) {
-        return;
+
+    if (mState != INTERACTION_STATE_IDLE && duration <= s_previous_duration) {
+        // don't hint if previous hint's duration covers this hint's duration
+        if (elapsed_time <= (s_previous_duration - duration) * 1000) {
+            ALOGV("%s: Previous duration (%d) cover this (%d) elapsed: %lld",
+                  __func__, s_previous_duration, duration, elapsed_time);
+            pthread_mutex_unlock(&interaction_lock);
+            return;
+        }
     }
+
     s_previous_boost_timespec = cur_boost_timespec;
     s_previous_duration = duration;
 
-    int interaction_handle =
-            perf_hint_enable_with_type(VENDOR_HINT_SCROLL_BOOST, duration, SCROLL_VERTICAL);
+    if (mState == INTERACTION_STATE_UNINITIALIZED) {
+        int interaction_handle =
+                process_boost(VENDOR_HINT_SCROLL_BOOST, duration, SCROLL_VERTICAL);
 
-    if (CHECK_HANDLE(prev_interaction_handle)) {
-        release_request(prev_interaction_handle);
+        release_boost(prev_interaction_handle);
+        prev_interaction_handle = interaction_handle;
+        pthread_mutex_unlock(&interaction_lock);
+        return;
     }
-    prev_interaction_handle = interaction_handle;
+
+    if (mState == INTERACTION_STATE_WAITING)
+        abortWaitLocked();
+    else if (mState == INTERACTION_STATE_IDLE)
+        prev_interaction_handle =
+                process_boost(VENDOR_HINT_SCROLL_BOOST, INT_MAX, SCROLL_VERTICAL);
+
+    mState = INTERACTION_STATE_INTERACTION;
+    pthread_cond_signal(&interaction_cond);
+    pthread_mutex_unlock(&interaction_lock);
 }
 
 void process_activity_launch_hint(void* data) {
@@ -112,8 +304,7 @@
 
     // release lock early if launch has finished
     if (!data) {
-        if (CHECK_HANDLE(launch_handle)) {
-            release_request(launch_handle);
+        if (release_boost(launch_handle)) {
             launch_handle = -1;
         }
         launch_mode = 0;
@@ -121,7 +312,7 @@
     }
 
     if (!launch_mode) {
-        launch_handle = perf_hint_enable_with_type(VENDOR_HINT_FIRST_LAUNCH_BOOST,
+        launch_handle = process_boost(VENDOR_HINT_FIRST_LAUNCH_BOOST,
                                                    kMaxLaunchDuration, LAUNCH_BOOST_V1);
         if (!CHECK_HANDLE(launch_handle)) {
             ALOGE("Failed to perform launch boost");
diff --git a/power-common.h b/power-common.h
index 02fd557..1566061 100644
--- a/power-common.h
+++ b/power-common.h
@@ -45,6 +45,13 @@
 
 enum CPU_GOV_CHECK { CPU0 = 0, CPU1 = 1, CPU2 = 2, CPU3 = 3 };
 
+enum INTERACTION_STATE {
+    INTERACTION_STATE_UNINITIALIZED,
+    INTERACTION_STATE_IDLE,
+    INTERACTION_STATE_INTERACTION,
+    INTERACTION_STATE_WAITING,
+};
+
 void power_init(void);
 void power_hint(power_hint_t hint, void* data);
 bool is_expensive_rendering_supported();