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.wifi.resources.R; 24 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 import java.util.Iterator; 28 import java.util.LinkedList; 29 import java.util.concurrent.TimeUnit; 30 31 /** 32 * This class is used to recover the wifi stack from a fatal failure. The recovery mechanism 33 * involves triggering a stack restart (essentially simulating an airplane mode toggle) using 34 * {@link ActiveModeWarden}. 35 * The current triggers for: 36 * 1. Last resort watchdog bite. 37 * 2. HAL/wificond crashes during normal operation. 38 * 3. TBD: supplicant crashes during normal operation. 39 */ 40 public class SelfRecovery { 41 private static final String TAG = "WifiSelfRecovery"; 42 43 /** 44 * Reason codes for the various recovery triggers. 45 */ 46 public static final int REASON_LAST_RESORT_WATCHDOG = 0; 47 public static final int REASON_WIFINATIVE_FAILURE = 1; 48 public static final int REASON_STA_IFACE_DOWN = 2; 49 public static final int REASON_API_CALL = 3; 50 public static final int REASON_SUBSYSTEM_RESTART = 4; 51 52 @Retention(RetentionPolicy.SOURCE) 53 @IntDef(prefix = {"REASON_"}, value = { 54 REASON_LAST_RESORT_WATCHDOG, 55 REASON_WIFINATIVE_FAILURE, 56 REASON_STA_IFACE_DOWN, 57 REASON_API_CALL, 58 REASON_SUBSYSTEM_RESTART}) 59 public @interface RecoveryReason {} 60 61 /** 62 * State for self recovery. 63 */ 64 private static final int STATE_NO_RECOVERY = 0; 65 private static final int STATE_DISABLE_WIFI = 1; 66 private static final int STATE_RESTART_WIFI = 2; 67 68 @Retention(RetentionPolicy.SOURCE) 69 @IntDef(prefix = {"STATE_"}, value = { 70 STATE_NO_RECOVERY, 71 STATE_DISABLE_WIFI, 72 STATE_RESTART_WIFI}) 73 private @interface RecoveryState {} 74 75 private final Context mContext; 76 private final ActiveModeWarden mActiveModeWarden; 77 private final Clock mClock; 78 // Time since boot (in millis) that restart occurred 79 private final LinkedList<Long> mPastRestartTimes; 80 private final WifiNative mWifiNative; 81 private int mSelfRecoveryReason; 82 // Self recovery state 83 private @RecoveryState int mRecoveryState; 84 private SubsystemRestartListenerInternal mSubsystemRestartListener; 85 86 /** 87 * Return the recovery reason code as string. 88 * @param reason the reason code 89 * @return the recovery reason as string 90 */ getRecoveryReasonAsString(@ecoveryReason int reason)91 public static String getRecoveryReasonAsString(@RecoveryReason int reason) { 92 switch (reason) { 93 case REASON_LAST_RESORT_WATCHDOG: 94 return "Last Resort Watchdog"; 95 case REASON_WIFINATIVE_FAILURE: 96 return "WifiNative Failure"; 97 case REASON_STA_IFACE_DOWN: 98 return "Sta Interface Down"; 99 case REASON_API_CALL: 100 return "API call (e.g. user)"; 101 case REASON_SUBSYSTEM_RESTART: 102 return "Subsystem Restart"; 103 default: 104 return "Unknown " + reason; 105 } 106 } 107 108 /** 109 * Invoked when self recovery completed. 110 */ onRecoveryCompleted()111 public void onRecoveryCompleted() { 112 mRecoveryState = STATE_NO_RECOVERY; 113 } 114 115 /** 116 * Invoked when Wifi is stopped with all client mode managers removed. 117 */ onWifiStopped()118 public void onWifiStopped() { 119 if (mRecoveryState == STATE_DISABLE_WIFI) { 120 onRecoveryCompleted(); 121 } 122 } 123 124 /** 125 * Returns true if recovery is currently in progress. 126 */ isRecoveryInProgress()127 public boolean isRecoveryInProgress() { 128 // return true if in recovery progress 129 return mRecoveryState != STATE_NO_RECOVERY; 130 } 131 132 private class SubsystemRestartListenerInternal 133 implements HalDeviceManager.SubsystemRestartListener{ onSubsystemRestart(@ecoveryReason int reason)134 public void onSubsystemRestart(@RecoveryReason int reason) { 135 Log.e(TAG, "Restarting wifi for reason: " + getRecoveryReasonAsString(reason)); 136 mActiveModeWarden.recoveryRestartWifi(reason, 137 reason != REASON_LAST_RESORT_WATCHDOG && reason != REASON_API_CALL); 138 } 139 140 @Override onSubsystemRestart()141 public void onSubsystemRestart() { 142 if (mRecoveryState == STATE_RESTART_WIFI) { 143 // If the wifi restart recovery is triggered then proceed 144 onSubsystemRestart(mSelfRecoveryReason); 145 } else { 146 // We did not trigger recovery, but looks like the firmware crashed? 147 mRecoveryState = STATE_RESTART_WIFI; 148 onSubsystemRestart(REASON_SUBSYSTEM_RESTART); 149 } 150 } 151 } 152 SelfRecovery(Context context, ActiveModeWarden activeModeWarden, Clock clock, WifiNative wifiNative)153 public SelfRecovery(Context context, ActiveModeWarden activeModeWarden, 154 Clock clock, WifiNative wifiNative) { 155 mContext = context; 156 mActiveModeWarden = activeModeWarden; 157 mClock = clock; 158 mPastRestartTimes = new LinkedList<>(); 159 mWifiNative = wifiNative; 160 mSubsystemRestartListener = new SubsystemRestartListenerInternal(); 161 mWifiNative.registerSubsystemRestartListener(mSubsystemRestartListener); 162 mRecoveryState = STATE_NO_RECOVERY; 163 } 164 165 /** 166 * Trigger recovery. 167 * 168 * This method does the following: 169 * 1. Checks reason code used to trigger recovery 170 * 2. Checks for sta iface down triggers and disables wifi by sending {@link 171 * ActiveModeWarden#recoveryDisableWifi()} to {@link ActiveModeWarden} to disable wifi. 172 * 3. Throttles restart calls for underlying native failures 173 * 4. Sends {@link ActiveModeWarden#recoveryRestartWifi(int)} to {@link ActiveModeWarden} to 174 * initiate the stack restart. 175 * @param reason One of the above |REASON_*| codes. 176 */ trigger(@ecoveryReason int reason)177 public void trigger(@RecoveryReason int reason) { 178 if (!(reason == REASON_LAST_RESORT_WATCHDOG || reason == REASON_WIFINATIVE_FAILURE 179 || reason == REASON_STA_IFACE_DOWN || reason == REASON_API_CALL)) { 180 Log.e(TAG, "Invalid trigger reason. Ignoring..."); 181 return; 182 } 183 if (reason == REASON_STA_IFACE_DOWN) { 184 Log.e(TAG, "STA interface down, disable wifi"); 185 mActiveModeWarden.recoveryDisableWifi(); 186 mRecoveryState = STATE_DISABLE_WIFI; 187 return; 188 } 189 190 Log.e(TAG, "Triggering recovery for reason: " + getRecoveryReasonAsString(reason)); 191 if (reason == REASON_WIFINATIVE_FAILURE) { 192 int maxRecoveriesPerHour = mContext.getResources().getInteger( 193 R.integer.config_wifiMaxNativeFailureSelfRecoveryPerHour); 194 if (maxRecoveriesPerHour == 0) { 195 Log.e(TAG, "Recovery disabled. Disabling wifi"); 196 mActiveModeWarden.recoveryDisableWifi(); 197 mRecoveryState = STATE_DISABLE_WIFI; 198 return; 199 } 200 trimPastRestartTimes(); 201 if (mPastRestartTimes.size() >= maxRecoveriesPerHour) { 202 Log.e(TAG, "Already restarted wifi " + maxRecoveriesPerHour + " times in" 203 + " last 1 hour. Disabling wifi"); 204 mActiveModeWarden.recoveryDisableWifi(); 205 mRecoveryState = STATE_DISABLE_WIFI; 206 return; 207 } 208 mPastRestartTimes.add(mClock.getElapsedSinceBootMillis()); 209 } 210 211 mSelfRecoveryReason = reason; 212 mRecoveryState = STATE_RESTART_WIFI; 213 if (!mWifiNative.startSubsystemRestart()) { 214 // HAL call failed, fallback to internal flow. 215 mSubsystemRestartListener.onSubsystemRestart(reason); 216 } 217 } 218 219 /** 220 * Process the mPastRestartTimes list, removing elements outside the max restarts time window 221 */ trimPastRestartTimes()222 private void trimPastRestartTimes() { 223 Iterator<Long> iter = mPastRestartTimes.iterator(); 224 long now = mClock.getElapsedSinceBootMillis(); 225 while (iter.hasNext()) { 226 Long restartTimeMillis = iter.next(); 227 if (now - restartTimeMillis > TimeUnit.HOURS.toMillis(1)) { 228 iter.remove(); 229 } else { 230 break; 231 } 232 } 233 } 234 } 235