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 protected static final String[] REASON_STRINGS = { 62 "Last Resort Watchdog", // REASON_LAST_RESORT_WATCHDOG 63 "WifiNative Failure", // REASON_WIFINATIVE_FAILURE 64 "Sta Interface Down", // REASON_STA_IFACE_DOWN 65 "API call (e.g. user)", // REASON_API_CALL 66 "Subsystem Restart" // REASON_SUBSYSTEM_RESTART 67 }; 68 69 private final Context mContext; 70 private final ActiveModeWarden mActiveModeWarden; 71 private final Clock mClock; 72 // Time since boot (in millis) that restart occurred 73 private final LinkedList<Long> mPastRestartTimes; 74 private final WifiNative mWifiNative; 75 private int mSelfRecoveryReason; 76 private boolean mDidWeTriggerSelfRecovery; 77 private SubsystemRestartListenerInternal mSubsystemRestartListener; 78 79 private class SubsystemRestartListenerInternal 80 implements HalDeviceManager.SubsystemRestartListener{ 81 @Override onSubsystemRestart()82 public void onSubsystemRestart() { 83 String reasonString = ""; 84 if (!mDidWeTriggerSelfRecovery) { 85 // We did not trigger recovery, but looks like the firmware crashed? 86 mSelfRecoveryReason = REASON_SUBSYSTEM_RESTART; 87 } 88 89 if (mSelfRecoveryReason < REASON_STRINGS.length && mSelfRecoveryReason >= 0) { 90 reasonString = REASON_STRINGS[mSelfRecoveryReason]; 91 } 92 93 Log.e(TAG, "Restarting wifi for reason: " + reasonString); 94 mActiveModeWarden.recoveryRestartWifi(mSelfRecoveryReason, reasonString, 95 mSelfRecoveryReason != REASON_LAST_RESORT_WATCHDOG 96 && mSelfRecoveryReason != REASON_API_CALL); 97 98 mDidWeTriggerSelfRecovery = false; 99 } 100 } 101 SelfRecovery(Context context, ActiveModeWarden activeModeWarden, Clock clock, WifiNative wifiNative)102 public SelfRecovery(Context context, ActiveModeWarden activeModeWarden, 103 Clock clock, WifiNative wifiNative) { 104 mContext = context; 105 mActiveModeWarden = activeModeWarden; 106 mClock = clock; 107 mPastRestartTimes = new LinkedList<>(); 108 mWifiNative = wifiNative; 109 mSubsystemRestartListener = new SubsystemRestartListenerInternal(); 110 mWifiNative.registerSubsystemRestartListener(mSubsystemRestartListener); 111 mDidWeTriggerSelfRecovery = false; 112 } 113 114 /** 115 * Trigger recovery. 116 * 117 * This method does the following: 118 * 1. Checks reason code used to trigger recovery 119 * 2. Checks for sta iface down triggers and disables wifi by sending {@link 120 * ActiveModeWarden#recoveryDisableWifi()} to {@link ActiveModeWarden} to disable wifi. 121 * 3. Throttles restart calls for underlying native failures 122 * 4. Sends {@link ActiveModeWarden#recoveryRestartWifi(int)} to {@link ActiveModeWarden} to 123 * initiate the stack restart. 124 * @param reason One of the above |REASON_*| codes. 125 */ trigger(@ecoveryReason int reason)126 public void trigger(@RecoveryReason int reason) { 127 if (!(reason == REASON_LAST_RESORT_WATCHDOG || reason == REASON_WIFINATIVE_FAILURE 128 || reason == REASON_STA_IFACE_DOWN || reason == REASON_API_CALL)) { 129 Log.e(TAG, "Invalid trigger reason. Ignoring..."); 130 return; 131 } 132 if (reason == REASON_STA_IFACE_DOWN) { 133 Log.e(TAG, "STA interface down, disable wifi"); 134 mActiveModeWarden.recoveryDisableWifi(); 135 return; 136 } 137 138 Log.e(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]); 139 if (reason == REASON_WIFINATIVE_FAILURE) { 140 int maxRecoveriesPerHour = mContext.getResources().getInteger( 141 R.integer.config_wifiMaxNativeFailureSelfRecoveryPerHour); 142 if (maxRecoveriesPerHour == 0) { 143 Log.e(TAG, "Recovery disabled. Disabling wifi"); 144 mActiveModeWarden.recoveryDisableWifi(); 145 return; 146 } 147 trimPastRestartTimes(); 148 if (mPastRestartTimes.size() >= maxRecoveriesPerHour) { 149 Log.e(TAG, "Already restarted wifi " + maxRecoveriesPerHour + " times in" 150 + " last 1 hour. Disabling wifi"); 151 mActiveModeWarden.recoveryDisableWifi(); 152 return; 153 } 154 mPastRestartTimes.add(mClock.getElapsedSinceBootMillis()); 155 } 156 157 mSelfRecoveryReason = reason; 158 mDidWeTriggerSelfRecovery = true; 159 if (!mWifiNative.startSubsystemRestart()) { 160 // HAL call failed, fallback to internal flow. 161 mSubsystemRestartListener.onSubsystemRestart(); 162 } 163 } 164 165 /** 166 * Process the mPastRestartTimes list, removing elements outside the max restarts time window 167 */ trimPastRestartTimes()168 private void trimPastRestartTimes() { 169 Iterator<Long> iter = mPastRestartTimes.iterator(); 170 long now = mClock.getElapsedSinceBootMillis(); 171 while (iter.hasNext()) { 172 Long restartTimeMillis = iter.next(); 173 if (now - restartTimeMillis > TimeUnit.HOURS.toMillis(1)) { 174 iter.remove(); 175 } else { 176 break; 177 } 178 } 179 } 180 } 181