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