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 50 @Retention(RetentionPolicy.SOURCE) 51 @IntDef(prefix = {"REASON_"}, value = { 52 REASON_LAST_RESORT_WATCHDOG, 53 REASON_WIFINATIVE_FAILURE, 54 REASON_STA_IFACE_DOWN}) 55 public @interface RecoveryReason {} 56 57 protected static final String[] REASON_STRINGS = { 58 "Last Resort Watchdog", // REASON_LAST_RESORT_WATCHDOG 59 "WifiNative Failure", // REASON_WIFINATIVE_FAILURE 60 "Sta Interface Down" // REASON_STA_IFACE_DOWN 61 }; 62 63 private final Context mContext; 64 private final ActiveModeWarden mActiveModeWarden; 65 private final Clock mClock; 66 // Time since boot (in millis) that restart occurred 67 private final LinkedList<Long> mPastRestartTimes; SelfRecovery(Context context, ActiveModeWarden activeModeWarden, Clock clock)68 public SelfRecovery(Context context, ActiveModeWarden activeModeWarden, Clock clock) { 69 mContext = context; 70 mActiveModeWarden = activeModeWarden; 71 mClock = clock; 72 mPastRestartTimes = new LinkedList<>(); 73 } 74 75 /** 76 * Trigger recovery. 77 * 78 * This method does the following: 79 * 1. Checks reason code used to trigger recovery 80 * 2. Checks for sta iface down triggers and disables wifi by sending {@link 81 * ActiveModeWarden#recoveryDisableWifi()} to {@link ActiveModeWarden} to disable wifi. 82 * 3. Throttles restart calls for underlying native failures 83 * 4. Sends {@link ActiveModeWarden#recoveryRestartWifi(int)} to {@link ActiveModeWarden} to 84 * initiate the stack restart. 85 * @param reason One of the above |REASON_*| codes. 86 */ trigger(@ecoveryReason int reason)87 public void trigger(@RecoveryReason int reason) { 88 if (!(reason == REASON_LAST_RESORT_WATCHDOG || reason == REASON_WIFINATIVE_FAILURE 89 || reason == REASON_STA_IFACE_DOWN)) { 90 Log.e(TAG, "Invalid trigger reason. Ignoring..."); 91 return; 92 } 93 if (reason == REASON_STA_IFACE_DOWN) { 94 Log.e(TAG, "STA interface down, disable wifi"); 95 mActiveModeWarden.recoveryDisableWifi(); 96 return; 97 } 98 99 Log.e(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]); 100 if (reason == REASON_WIFINATIVE_FAILURE) { 101 int maxRecoveriesPerHour = mContext.getResources().getInteger( 102 R.integer.config_wifiMaxNativeFailureSelfRecoveryPerHour); 103 if (maxRecoveriesPerHour == 0) { 104 Log.e(TAG, "Recovery disabled. Disabling wifi"); 105 mActiveModeWarden.recoveryDisableWifi(); 106 return; 107 } 108 trimPastRestartTimes(); 109 if (mPastRestartTimes.size() >= maxRecoveriesPerHour) { 110 Log.e(TAG, "Already restarted wifi " + maxRecoveriesPerHour + " times in" 111 + " last 1 hour. Disabling wifi"); 112 mActiveModeWarden.recoveryDisableWifi(); 113 return; 114 } 115 mPastRestartTimes.add(mClock.getElapsedSinceBootMillis()); 116 } 117 mActiveModeWarden.recoveryRestartWifi(reason); 118 } 119 120 /** 121 * Process the mPastRestartTimes list, removing elements outside the max restarts time window 122 */ trimPastRestartTimes()123 private void trimPastRestartTimes() { 124 Iterator<Long> iter = mPastRestartTimes.iterator(); 125 long now = mClock.getElapsedSinceBootMillis(); 126 while (iter.hasNext()) { 127 Long restartTimeMillis = iter.next(); 128 if (now - restartTimeMillis > TimeUnit.HOURS.toMillis(1)) { 129 iter.remove(); 130 } else { 131 break; 132 } 133 } 134 } 135 } 136