• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.wifi;
18 
19 import android.annotation.IntDef;
20 import android.content.Context;
21 import android.util.Log;
22 
23 import com.android.server.wifi.proto.WifiStatsLog;
24 import com.android.wifi.resources.R;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.Iterator;
29 import java.util.LinkedList;
30 import java.util.concurrent.TimeUnit;
31 
32 /**
33  * This class is used to recover the wifi stack from a fatal failure. The recovery mechanism
34  * involves triggering a stack restart (essentially simulating an airplane mode toggle) using
35  * {@link ActiveModeWarden}.
36  * The current triggers for:
37  * 1. Last resort watchdog bite.
38  * 2. HAL/wificond crashes during normal operation.
39  * 3. TBD: supplicant crashes during normal operation.
40  */
41 public class SelfRecovery {
42     private static final String TAG = "WifiSelfRecovery";
43 
44     /**
45      * Reason codes for the various recovery triggers.
46      */
47     public static final int REASON_LAST_RESORT_WATCHDOG = 0;
48     public static final int REASON_WIFINATIVE_FAILURE = 1;
49     public static final int REASON_STA_IFACE_DOWN = 2;
50     public static final int REASON_API_CALL = 3;
51     public static final int REASON_SUBSYSTEM_RESTART = 4;
52     public static final int REASON_IFACE_ADDED = 5;
53 
54     @Retention(RetentionPolicy.SOURCE)
55     @IntDef(prefix = {"REASON_"}, value = {
56             REASON_LAST_RESORT_WATCHDOG,
57             REASON_WIFINATIVE_FAILURE,
58             REASON_STA_IFACE_DOWN,
59             REASON_API_CALL,
60             REASON_SUBSYSTEM_RESTART,
61             REASON_IFACE_ADDED})
62     public @interface RecoveryReason {}
63 
64     /**
65      * State for self recovery.
66      */
67     private static final int STATE_NO_RECOVERY = 0;
68     private static final int STATE_DISABLE_WIFI = 1;
69     private static final int STATE_RESTART_WIFI = 2;
70 
71     @Retention(RetentionPolicy.SOURCE)
72     @IntDef(prefix = {"STATE_"}, value = {
73             STATE_NO_RECOVERY,
74             STATE_DISABLE_WIFI,
75             STATE_RESTART_WIFI})
76     private @interface RecoveryState {}
77 
78     private final Context mContext;
79     private final ActiveModeWarden mActiveModeWarden;
80     private final Clock mClock;
81     // Time since boot (in millis) that restart occurred
82     private final LinkedList<Long> mPastRestartTimes;
83     private final WifiNative mWifiNative;
84     private final WifiGlobals mWifiGlobals;
85     private int mSelfRecoveryReason;
86     private long mLastSelfRecoveryTimeStampMillis = -1L;
87     // Self recovery state
88     private @RecoveryState int mRecoveryState;
89     private SubsystemRestartListenerInternal mSubsystemRestartListener;
90 
91     /**
92      * Return the recovery reason code as string.
93      * @param reason the reason code
94      * @return the recovery reason as string
95      */
getRecoveryReasonAsString(@ecoveryReason int reason)96     public static String getRecoveryReasonAsString(@RecoveryReason int reason) {
97         switch (reason) {
98             case REASON_LAST_RESORT_WATCHDOG:
99                 return "Last Resort Watchdog";
100             case REASON_WIFINATIVE_FAILURE:
101                 return "WifiNative Failure";
102             case REASON_STA_IFACE_DOWN:
103                 return "Sta Interface Down";
104             case REASON_API_CALL:
105                 return "API call (e.g. user)";
106             case REASON_SUBSYSTEM_RESTART:
107                 return "Subsystem Restart";
108             case REASON_IFACE_ADDED:
109                 return "Interface Added";
110             default:
111                 return "Unknown " + reason;
112         }
113     }
114 
115     /**
116      * Invoked when self recovery completed.
117      */
onRecoveryCompleted()118     public void onRecoveryCompleted() {
119         mRecoveryState = STATE_NO_RECOVERY;
120     }
121 
122     /**
123      * Invoked when Wifi is stopped with all client mode managers removed.
124      */
onWifiStopped()125     public void onWifiStopped() {
126         if (mRecoveryState == STATE_DISABLE_WIFI) {
127             onRecoveryCompleted();
128         }
129     }
130 
131     /**
132      * Returns true if recovery is currently in progress.
133      */
isRecoveryInProgress()134     public boolean isRecoveryInProgress() {
135         // return true if in recovery progress
136         return mRecoveryState != STATE_NO_RECOVERY;
137     }
138 
139     private class SubsystemRestartListenerInternal
140             implements HalDeviceManager.SubsystemRestartListener{
onSubsystemRestart(@ecoveryReason int reason)141         public void onSubsystemRestart(@RecoveryReason int reason) {
142             Log.e(TAG, "Restarting wifi for reason: " + getRecoveryReasonAsString(reason));
143             mActiveModeWarden.recoveryRestartWifi(reason,
144                     reason != REASON_LAST_RESORT_WATCHDOG && reason != REASON_API_CALL);
145         }
146 
147         @Override
onSubsystemRestart()148         public void onSubsystemRestart() {
149             if (mRecoveryState == STATE_RESTART_WIFI) {
150                 // If the wifi restart recovery is triggered then proceed
151                 onSubsystemRestart(mSelfRecoveryReason);
152             } else {
153                 // We did not trigger recovery, but looks like the firmware crashed?
154                 mRecoveryState = STATE_RESTART_WIFI;
155                 onSubsystemRestart(REASON_SUBSYSTEM_RESTART);
156             }
157         }
158     }
159 
SelfRecovery(Context context, ActiveModeWarden activeModeWarden, Clock clock, WifiNative wifiNative, WifiGlobals wifiGlobals)160     public SelfRecovery(Context context, ActiveModeWarden activeModeWarden,
161             Clock clock, WifiNative wifiNative, WifiGlobals wifiGlobals) {
162         mContext = context;
163         mActiveModeWarden = activeModeWarden;
164         mClock = clock;
165         mPastRestartTimes = new LinkedList<>();
166         mWifiNative = wifiNative;
167         mSubsystemRestartListener = new SubsystemRestartListenerInternal();
168         mWifiNative.registerSubsystemRestartListener(mSubsystemRestartListener);
169         mWifiGlobals = wifiGlobals;
170         mRecoveryState = STATE_NO_RECOVERY;
171     }
172 
173     /**
174      * Trigger recovery.
175      *
176      * This method does the following:
177      * 1. Checks reason code used to trigger recovery
178      * 2. Checks for sta iface down triggers and disables wifi by sending {@link
179      * ActiveModeWarden#recoveryDisableWifi()} to {@link ActiveModeWarden} to disable wifi.
180      * 3. Throttles restart calls for underlying native failures
181      * 4. Sends {@link ActiveModeWarden#recoveryRestartWifi(int)} to {@link ActiveModeWarden} to
182      * initiate the stack restart.
183      * @param reason One of the above |REASON_*| codes.
184      */
trigger(@ecoveryReason int reason)185     public void trigger(@RecoveryReason int reason) {
186         long timeElapsedFromLastTrigger = getTimeElapsedFromLastTrigger();
187         mLastSelfRecoveryTimeStampMillis = mClock.getWallClockMillis();
188         if (!(reason == REASON_LAST_RESORT_WATCHDOG || reason == REASON_WIFINATIVE_FAILURE
189                   || reason == REASON_STA_IFACE_DOWN || reason == REASON_API_CALL
190                   || reason == REASON_IFACE_ADDED)) {
191             Log.e(TAG, "Invalid trigger reason. Ignoring...");
192             WifiStatsLog.write(WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED,
193                     convertSelfRecoveryReason(reason),
194                     WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__RESULT__RES_INVALID_REASON,
195                     timeElapsedFromLastTrigger);
196             return;
197         }
198         if (reason == REASON_STA_IFACE_DOWN) {
199             Log.e(TAG, "STA interface down, disable wifi");
200             mActiveModeWarden.recoveryDisableWifi();
201             mRecoveryState = STATE_DISABLE_WIFI;
202             WifiStatsLog.write(WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED,
203                     convertSelfRecoveryReason(reason),
204                     WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__RESULT__RES_IFACE_DOWN,
205                     timeElapsedFromLastTrigger);
206             return;
207         }
208 
209         Log.e(TAG, "Triggering recovery for reason: " + getRecoveryReasonAsString(reason));
210         if (reason == REASON_IFACE_ADDED
211                 && !mWifiGlobals.isWifiInterfaceAddedSelfRecoveryEnabled()) {
212             Log.w(TAG, "Recovery on added interface is disabled. Ignoring...");
213             WifiStatsLog.write(WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED,
214                     convertSelfRecoveryReason(reason),
215                     WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__RESULT__RES_IFACE_ADD_DISABLED,
216                     timeElapsedFromLastTrigger);
217             return;
218         }
219         if (reason == REASON_WIFINATIVE_FAILURE) {
220             int maxRecoveriesPerHour = mContext.getResources().getInteger(
221                     R.integer.config_wifiMaxNativeFailureSelfRecoveryPerHour);
222             if (maxRecoveriesPerHour == 0) {
223                 Log.e(TAG, "Recovery disabled. Disabling wifi");
224                 mActiveModeWarden.recoveryDisableWifi();
225                 mRecoveryState = STATE_DISABLE_WIFI;
226                 WifiStatsLog.write(WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED,
227                         convertSelfRecoveryReason(reason),
228                         WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__RESULT__RES_RETRY_DISABLED,
229                         timeElapsedFromLastTrigger);
230                 return;
231             }
232             trimPastRestartTimes();
233             if (mPastRestartTimes.size() >= maxRecoveriesPerHour) {
234                 Log.e(TAG, "Already restarted wifi " + maxRecoveriesPerHour + " times in"
235                         + " last 1 hour. Disabling wifi");
236                 mActiveModeWarden.recoveryDisableWifi();
237                 mRecoveryState = STATE_DISABLE_WIFI;
238                 WifiStatsLog.write(WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED,
239                         convertSelfRecoveryReason(reason),
240                         WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__RESULT__RES_ABOVE_MAX_RETRY,
241                         timeElapsedFromLastTrigger);
242                 return;
243             }
244             mPastRestartTimes.add(mClock.getElapsedSinceBootMillis());
245         }
246 
247         mSelfRecoveryReason = reason;
248         mRecoveryState = STATE_RESTART_WIFI;
249         if (!mWifiNative.startSubsystemRestart()) {
250             // HAL call failed, fallback to internal flow.
251             mSubsystemRestartListener.onSubsystemRestart(reason);
252             WifiStatsLog.write(WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED,
253                     convertSelfRecoveryReason(reason),
254                     WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__RESULT__RES_RESTART_FAILURE,
255                     timeElapsedFromLastTrigger);
256             return;
257         }
258         WifiStatsLog.write(WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED,
259                 convertSelfRecoveryReason(reason),
260                 WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__RESULT__RES_RESTART_SUCCESS,
261                 timeElapsedFromLastTrigger);
262     }
263 
264     /**
265      * Process the mPastRestartTimes list, removing elements outside the max restarts time window
266      */
trimPastRestartTimes()267     private void trimPastRestartTimes() {
268         Iterator<Long> iter = mPastRestartTimes.iterator();
269         long now = mClock.getElapsedSinceBootMillis();
270         while (iter.hasNext()) {
271             Long restartTimeMillis = iter.next();
272             if (now - restartTimeMillis > TimeUnit.HOURS.toMillis(1)) {
273                 iter.remove();
274             } else {
275                 break;
276             }
277         }
278     }
279 
convertSelfRecoveryReason(int reason)280     private int convertSelfRecoveryReason(int reason) {
281         switch (reason) {
282             case SelfRecovery.REASON_LAST_RESORT_WATCHDOG:
283                 return WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__REASON__REASON_LAST_RESORT_WDOG;
284             case SelfRecovery.REASON_WIFINATIVE_FAILURE:
285                 return WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__REASON__REASON_WIFINATIVE_FAILURE;
286             case SelfRecovery.REASON_STA_IFACE_DOWN:
287                 return WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__REASON__REASON_STA_IFACE_DOWN;
288             case SelfRecovery.REASON_API_CALL:
289                 return WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__REASON__REASON_API_CALL;
290             case SelfRecovery.REASON_SUBSYSTEM_RESTART:
291                 return WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__REASON__REASON_SUBSYSTEM_RESTART;
292             case SelfRecovery.REASON_IFACE_ADDED:
293                 return WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__REASON__REASON_IFACE_ADDED;
294             default:
295                 return WifiStatsLog.WIFI_SELF_RECOVERY_TRIGGERED__REASON__REASON_UNKNOWN;
296         }
297     }
298 
getTimeElapsedFromLastTrigger()299     private long getTimeElapsedFromLastTrigger() {
300         if (mLastSelfRecoveryTimeStampMillis < 0) {
301             return -1L;
302         } else {
303             return (mClock.getWallClockMillis() - mLastSelfRecoveryTimeStampMillis);
304         }
305     }
306 }
307