API to query which cores are exclusively assigned.

On devices with cpuset support, the foreground app
may have a core exclusively assigned to it. Add an
API that allows apps to query which core that is,
so it can use that to set scheduling affinity.

Bug: 27381794
Change-Id: Iea148776da6d7abb745ccf9e6ad5ad46491e340a
diff --git a/api/current.txt b/api/current.txt
index 07505b3..e03dcd2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29046,6 +29046,7 @@
   public class Process {
     ctor public Process();
     method public static final long getElapsedCpuTime();
+    method public static final int[] getExclusiveCores();
     method public static final int getGidForName(java.lang.String);
     method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
     method public static final int getUidForName(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 089ccd3..d4c33ca 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -31338,6 +31338,7 @@
   public class Process {
     ctor public Process();
     method public static final long getElapsedCpuTime();
+    method public static final int[] getExclusiveCores();
     method public static final int getGidForName(java.lang.String);
     method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
     method public static final int getUidForName(java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 21a9db0..b0640f2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -29057,6 +29057,7 @@
   public class Process {
     ctor public Process();
     method public static final long getElapsedCpuTime();
+    method public static final int[] getExclusiveCores();
     method public static final int getGidForName(java.lang.String);
     method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
     method public static final int getUidForName(java.lang.String);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 9984755..38d5b34 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -975,6 +975,31 @@
             throws IllegalArgumentException, SecurityException;
 
     /**
+     * On some devices, the foreground process may have one or more CPU
+     * cores exclusively reserved for it. This method can be used to
+     * retrieve which cores that are (if any), so the calling process
+     * can then use sched_setaffinity() to lock a thread to these cores.
+     * Note that the calling process must currently be running in the
+     * foreground for this method to return any cores.
+     *
+     * The CPU core(s) exclusively reserved for the foreground process will
+     * stay reserved for as long as the process stays in the foreground.
+     *
+     * As soon as a process leaves the foreground, those CPU cores will
+     * no longer be reserved for it, and will most likely be reserved for
+     * the new foreground process. It's not necessary to change the affinity
+     * of your process when it leaves the foreground (if you had previously
+     * set it to use a reserved core); the OS will automatically take care
+     * of resetting the affinity at that point.
+     *
+     * @return an array of integers, indicating the CPU cores exclusively
+     * reserved for this process. The array will have length zero if no
+     * CPU cores are exclusively reserved for this process at this point
+     * in time.
+     */
+    public static final native int[] getExclusiveCores();
+
+    /**
      * Set the priority of the calling thread, based on Linux priorities.  See
      * {@link #setThreadPriority(int, int)} for more information.
      * 
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index ee8fb19..f7a5e8a 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -17,6 +17,8 @@
 
 #define LOG_TAG "Process"
 
+// To make sure cpu_set_t is included from sched.h
+#define _GNU_SOURCE 1
 #include <utils/Log.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
@@ -288,6 +290,139 @@
     return (int) sp;
 }
 
+#ifdef ENABLE_CPUSETS
+/** Sample CPUset list format:
+ *  0-3,4,6-8
+ */
+static void parse_cpuset_cpus(char *cpus, cpu_set_t *cpu_set) {
+    unsigned int start, end, matched, i;
+    char *cpu_range = strtok(cpus, ",");
+    while (cpu_range != NULL) {
+        start = end = 0;
+        matched = sscanf(cpu_range, "%u-%u", &start, &end);
+        cpu_range = strtok(NULL, ",");
+        if (start >= CPU_SETSIZE) {
+            ALOGE("parse_cpuset_cpus: ignoring CPU number larger than %d.", CPU_SETSIZE);
+            continue;
+        } else if (end >= CPU_SETSIZE) {
+            ALOGE("parse_cpuset_cpus: ignoring CPU numbers larger than %d.", CPU_SETSIZE);
+            end = CPU_SETSIZE - 1;
+        }
+        if (matched == 1) {
+            CPU_SET(start, cpu_set);
+        } else if (matched == 2) {
+            for (i = start; i <= end; i++) {
+                CPU_SET(i, cpu_set);
+            }
+        } else {
+            ALOGE("Failed to match cpus");
+        }
+    }
+    return;
+}
+
+/**
+ * Stores the CPUs assigned to the cpuset corresponding to the
+ * SchedPolicy in the passed in cpu_set.
+ */
+static void get_cpuset_cores_for_policy(SchedPolicy policy, cpu_set_t *cpu_set)
+{
+    FILE *file;
+    const char *filename;
+
+    CPU_ZERO(cpu_set);
+
+    switch (policy) {
+        case SP_BACKGROUND:
+            filename = "/dev/cpuset/background/cpus";
+            break;
+        case SP_FOREGROUND:
+        case SP_AUDIO_APP:
+        case SP_AUDIO_SYS:
+            filename = "/dev/cpuset/foreground/cpus";
+            break;
+        case SP_TOP_APP:
+            filename = "/dev/cpuset/top-app/cpus";
+            break;
+        default:
+            filename = NULL;
+    }
+
+    if (!filename) return;
+
+    file = fopen(filename, "re");
+    if (file != NULL) {
+        // Parse cpus string
+        char *line = NULL;
+        size_t len = 0;
+        ssize_t num_read = getline(&line, &len, file);
+        fclose (file);
+        if (num_read > 0) {
+            parse_cpuset_cpus(line, cpu_set);
+        } else {
+            ALOGE("Failed to read %s", filename);
+        }
+        free(line);
+    }
+    return;
+}
+#endif
+
+
+/**
+ * Determine CPU cores exclusively assigned to the
+ * cpuset corresponding to the SchedPolicy and store
+ * them in the passed in cpu_set_t
+ */
+void get_exclusive_cpuset_cores(SchedPolicy policy, cpu_set_t *cpu_set) {
+#ifdef ENABLE_CPUSETS
+    int i;
+    cpu_set_t tmp_set;
+    get_cpuset_cores_for_policy(policy, cpu_set);
+    for (i = 0; i < SP_CNT; i++) {
+        if ((SchedPolicy) i == policy) continue;
+        get_cpuset_cores_for_policy((SchedPolicy)i, &tmp_set);
+        // First get cores exclusive to one set or the other
+        CPU_XOR(&tmp_set, cpu_set, &tmp_set);
+        // Then get the ones only in cpu_set
+        CPU_AND(cpu_set, cpu_set, &tmp_set);
+    }
+#else
+    (void) policy;
+    CPU_ZERO(cpu_set);
+#endif
+    return;
+}
+
+jintArray android_os_Process_getExclusiveCores(JNIEnv* env, jobject clazz) {
+    SchedPolicy sp;
+    cpu_set_t cpu_set;
+    jintArray cpus;
+    int pid = getpid();
+    if (get_sched_policy(pid, &sp) != 0) {
+        signalExceptionForGroupError(env, errno);
+        return NULL;
+    }
+    get_exclusive_cpuset_cores(sp, &cpu_set);
+    int num_cpus = CPU_COUNT(&cpu_set);
+    cpus = env->NewIntArray(num_cpus);
+    if (cpus == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+
+    jint* cpu_elements = env->GetIntArrayElements(cpus, 0);
+    int count = 0;
+    for (int i = 0; i < CPU_SETSIZE && count < num_cpus; i++) {
+        if (CPU_ISSET(i, &cpu_set)) {
+            cpu_elements[count++] = i;
+        }
+    }
+
+    env->ReleaseIntArrayElements(cpus, cpu_elements, 0);
+    return cpus;
+}
+
 static void android_os_Process_setCanSelfBackground(JNIEnv* env, jobject clazz, jboolean bgOk) {
     // Establishes the calling thread as illegal to put into the background.
     // Typically used only for the system process's main looper.
@@ -1053,6 +1188,7 @@
     {"setThreadGroup",      "(II)V", (void*)android_os_Process_setThreadGroup},
     {"setProcessGroup",     "(II)V", (void*)android_os_Process_setProcessGroup},
     {"getProcessGroup",     "(I)I", (void*)android_os_Process_getProcessGroup},
+    {"getExclusiveCores",   "()[I", (void*)android_os_Process_getExclusiveCores},
     {"setSwappiness",   "(IZ)Z", (void*)android_os_Process_setSwappiness},
     {"setArgV0",    "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0},
     {"setUid", "(I)I", (void*)android_os_Process_setUid},