1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.os;
18 
19 import android.content.pm.PackageManager;
20 import android.os.Binder;
21 import android.os.IBinder;
22 import android.os.ISchedulingPolicyService;
23 import android.os.Process;
24 import android.os.RemoteException;
25 import android.util.Log;
26 
27 import com.android.server.SystemServerInitThreadPool;
28 
29 /**
30  * The implementation of the scheduling policy service interface.
31  *
32  * @hide
33  */
34 public class SchedulingPolicyService extends ISchedulingPolicyService.Stub {
35 
36     private static final String TAG = "SchedulingPolicyService";
37 
38     // Minimum and maximum values allowed for requestPriority parameter prio
39     private static final int PRIORITY_MIN = 1;
40     private static final int PRIORITY_MAX = 3;
41 
42     private static final String[] MEDIA_PROCESS_NAMES = new String[] {
43             "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service
44     };
45     private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
46         @Override
47         public void binderDied() {
48             requestCpusetBoost(false /*enable*/, null /*client*/);
49         }
50     };
51     // Current process that received a cpuset boost
52     private int mBoostedPid = -1;
53     // Current client registered to the death recipient
54     private IBinder mClient;
55 
SchedulingPolicyService()56     public SchedulingPolicyService() {
57         // system_server (our host) could have crashed before. The app may not survive
58         // it, but mediaserver/media.codec could have, and mediaserver probably tried
59         // to disable the boost while we were dead.
60         // We do a restore of media.codec to default cpuset upon service restart to
61         // catch this case. We can't leave media.codec in boosted state, because we've
62         // lost the death recipient of mClient from mediaserver after the restart,
63         // if mediaserver dies in the future we won't have a notification to reset.
64         // (Note that if mediaserver thinks we're in boosted state before the crash,
65         // the state could go out of sync temporarily until mediaserver enables/disable
66         // boost next time, but this won't be a big issue.)
67         SystemServerInitThreadPool.get().submit(() -> {
68             synchronized (mDeathRecipient) {
69                 // only do this if we haven't already got a request to boost.
70                 if (mBoostedPid == -1) {
71                     int[] nativePids = Process.getPidsForCommands(MEDIA_PROCESS_NAMES);
72                     if (nativePids != null && nativePids.length == 1) {
73                         mBoostedPid = nativePids[0];
74                         disableCpusetBoost(nativePids[0]);
75                     }
76                 }
77             }
78         }, TAG + ".<init>");
79     }
80 
81     // TODO(b/35196900) We should pass the period in time units, rather
82     // than a fixed priority number.
requestPriority(int pid, int tid, int prio, boolean isForApp)83     public int requestPriority(int pid, int tid, int prio, boolean isForApp) {
84         //Log.i(TAG, "requestPriority(pid=" + pid + ", tid=" + tid + ", prio=" + prio + ")");
85 
86         // Verify that the caller uid is permitted, priority is in range,
87         // and that the callback thread specified by app belongs to the app that
88         // called mediaserver or audioserver.
89         // Once we've verified that the caller uid is permitted, we can trust the pid but
90         // we can't trust the tid.  No need to explicitly check for pid == 0 || tid == 0,
91         // since if not the case then the getThreadGroupLeader() test will also fail.
92         if (!isPermitted() || prio < PRIORITY_MIN ||
93                 prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) {
94            return PackageManager.PERMISSION_DENIED;
95         }
96         if (Binder.getCallingUid() != Process.BLUETOOTH_UID) {
97             try {
98                 // make good use of our CAP_SYS_NICE capability
99                 Process.setThreadGroup(tid, !isForApp ?
100                   Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_RT_APP);
101             } catch (RuntimeException e) {
102                 Log.e(TAG, "Failed setThreadGroup: " + e);
103                 return PackageManager.PERMISSION_DENIED;
104            }
105         }
106         try {
107             // must be in this order or it fails the schedulability constraint
108             Process.setThreadScheduler(tid, Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK,
109                                        prio);
110         } catch (RuntimeException e) {
111             Log.e(TAG, "Failed setThreadScheduler: " + e);
112             return PackageManager.PERMISSION_DENIED;
113         }
114         return PackageManager.PERMISSION_GRANTED;
115     }
116 
117     // Request to move media.codec process between SP_FOREGROUND and SP_TOP_APP.
requestCpusetBoost(boolean enable, IBinder client)118     public int requestCpusetBoost(boolean enable, IBinder client) {
119         // Can only allow mediaserver to call this.
120         if (Binder.getCallingPid() != Process.myPid() &&
121                 Binder.getCallingUid() != Process.MEDIA_UID) {
122             return PackageManager.PERMISSION_DENIED;
123         }
124 
125         int[] nativePids = Process.getPidsForCommands(MEDIA_PROCESS_NAMES);
126         if (nativePids == null || nativePids.length != 1) {
127             Log.e(TAG, "requestCpusetBoost: can't find media.codec process");
128             return PackageManager.PERMISSION_DENIED;
129         }
130 
131         synchronized (mDeathRecipient) {
132             if (enable) {
133                 return enableCpusetBoost(nativePids[0], client);
134             } else {
135                 return disableCpusetBoost(nativePids[0]);
136             }
137         }
138     }
139 
enableCpusetBoost(int pid, IBinder client)140     private int enableCpusetBoost(int pid, IBinder client) {
141         if (mBoostedPid == pid) {
142             return PackageManager.PERMISSION_GRANTED;
143         }
144 
145         // The mediacodec process has changed, clean up the old pid and
146         // client before we boost the new process, so that the state
147         // is left clean if things go wrong.
148         mBoostedPid = -1;
149         if (mClient != null) {
150             try {
151                 mClient.unlinkToDeath(mDeathRecipient, 0);
152             } catch (Exception e) {
153             } finally {
154                 mClient = null;
155             }
156         }
157 
158         try {
159             client.linkToDeath(mDeathRecipient, 0);
160 
161             Log.i(TAG, "Moving " + pid + " to group " + Process.THREAD_GROUP_TOP_APP);
162             Process.setProcessGroup(pid, Process.THREAD_GROUP_TOP_APP);
163 
164             mBoostedPid = pid;
165             mClient = client;
166 
167             return PackageManager.PERMISSION_GRANTED;
168         } catch (Exception e) {
169             Log.e(TAG, "Failed enableCpusetBoost: " + e);
170             try {
171                 // unlink if things go wrong and don't crash.
172                 client.unlinkToDeath(mDeathRecipient, 0);
173             } catch (Exception e1) {}
174         }
175 
176         return PackageManager.PERMISSION_DENIED;
177     }
178 
disableCpusetBoost(int pid)179     private int disableCpusetBoost(int pid) {
180         int boostedPid = mBoostedPid;
181 
182         // Clean up states first.
183         mBoostedPid = -1;
184         if (mClient != null) {
185             try {
186                 mClient.unlinkToDeath(mDeathRecipient, 0);
187             } catch (Exception e) {
188             } finally {
189                 mClient = null;
190             }
191         }
192 
193         // Try restore the old thread group, no need to fail as the
194         // mediacodec process could be dead just now.
195         if (boostedPid == pid) {
196             try {
197                 Log.i(TAG, "Moving " + pid + " back to group default");
198                 Process.setProcessGroup(pid, Process.THREAD_GROUP_DEFAULT);
199             } catch (Exception e) {
200                 Log.w(TAG, "Couldn't move pid " + pid + " back to group default");
201             }
202         }
203 
204         return PackageManager.PERMISSION_GRANTED;
205     }
206 
isPermitted()207     private boolean isPermitted() {
208         // schedulerservice hidl
209         if (Binder.getCallingPid() == Process.myPid()) {
210             return true;
211         }
212 
213         switch (Binder.getCallingUid()) {
214         case Process.AUDIOSERVER_UID:  // fastcapture, fastmixer
215         case Process.CAMERASERVER_UID: // camera high frame rate recording
216         case Process.BLUETOOTH_UID:    // Bluetooth audio playback
217             return true;
218         default:
219             return false;
220         }
221     }
222 }
223