1 /* 2 * Copyright (C) 2021 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.car.power; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.car.builtin.util.Slogf; 24 import android.os.FileObserver; 25 import android.os.SystemProperties; 26 27 import com.android.car.CarLog; 28 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 29 import com.android.car.internal.util.IndentingPrintWriter; 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import libcore.io.IoUtils; 34 35 import java.io.BufferedWriter; 36 import java.io.File; 37 import java.io.FileWriter; 38 import java.io.IOException; 39 import java.nio.file.Files; 40 import java.nio.file.Paths; 41 import java.util.Objects; 42 43 /** 44 * Class to handle Silent Mode and Non-Silent Mode. 45 * 46 * <p>This monitors {@code /sys/kernel/silent_boot/pm_silentmode_hw_state} to figure out when to 47 * switch to Silent Mode and updates {@code /sys/kernel/silent_boot/pm_silentmode_kernel_state} to 48 * tell early-init services about Silent Mode change. Also, it handles forced Silent Mode for 49 * testing purpose, which is given through reboot reason. 50 */ 51 final class SilentModeHandler { 52 static final String SILENT_MODE_FORCED_SILENT = "forced-silent"; 53 static final String SILENT_MODE_FORCED_NON_SILENT = "forced-non-silent"; 54 static final String SILENT_MODE_NON_FORCED = "non-forced-silent-mode"; 55 56 private static final String TAG = CarLog.tagFor(SilentModeHandler.class); 57 58 /** 59 * The folders that are searched for sysfs files. 60 * 61 * <p>The sysfs files for Silent Mode are searched in the following order: 62 * <ol> 63 * <li>/sys/kernel/silent_boot 64 * <li>/sys/power 65 * </ol> 66 * 67 * <p>Placing the sysfs files in {@code /sys/power} is deprecated, but for backwad 68 * compatibility, we fallback to the folder when the files don't exist in 69 * {@code /sys/kernel/silent_boot}. 70 */ 71 private static final String[] SYSFS_DIRS_FOR_SILENT_MODE = 72 new String[]{"/sys/kernel/silent_boot", "/sys/power"}; 73 private static final String SYSFS_FILENAME_HW_STATE_MONITORING = "pm_silentmode_hw_state"; 74 private static final String SYSFS_FILENAME_KERNEL_SILENTMODE = "pm_silentmode_kernel_state"; 75 private static final String VALUE_SILENT_MODE = "1"; 76 private static final String VALUE_NON_SILENT_MODE = "0"; 77 private static final String SYSTEM_BOOT_REASON = "sys.boot.reason"; 78 private static final String FORCED_NON_SILENT = "reboot,forcednonsilent"; 79 private static final String FORCED_SILENT = "reboot,forcedsilent"; 80 81 private final Object mLock = new Object(); 82 private final CarPowerManagementService mService; 83 private final String mHwStateMonitoringFileName; 84 private final String mKernelSilentModeFileName; 85 86 @GuardedBy("mLock") 87 private FileObserver mFileObserver; 88 @GuardedBy("mLock") 89 private boolean mSilentModeByHwState; 90 @GuardedBy("mLock") 91 private boolean mForcedMode; 92 93 @VisibleForTesting SilentModeHandler(@onNull CarPowerManagementService service, @Nullable String hwStateMonitoringFileName, @Nullable String kernelSilentModeFileName, @Nullable String bootReason)94 SilentModeHandler(@NonNull CarPowerManagementService service, 95 @Nullable String hwStateMonitoringFileName, @Nullable String kernelSilentModeFileName, 96 @Nullable String bootReason) { 97 Objects.requireNonNull(service, "CarPowerManagementService must not be null"); 98 mService = service; 99 String sysfsDir = searchForSysfsDir(); 100 mHwStateMonitoringFileName = hwStateMonitoringFileName == null 101 ? sysfsDir + SYSFS_FILENAME_HW_STATE_MONITORING : hwStateMonitoringFileName; 102 mKernelSilentModeFileName = kernelSilentModeFileName == null 103 ? sysfsDir + SYSFS_FILENAME_KERNEL_SILENTMODE : kernelSilentModeFileName; 104 if (bootReason == null) { 105 bootReason = SystemProperties.get(SYSTEM_BOOT_REASON); 106 } 107 switch (bootReason) { 108 case FORCED_SILENT: 109 Slogf.i(TAG, "Starting in forced silent mode"); 110 mForcedMode = true; 111 mSilentModeByHwState = true; 112 break; 113 case FORCED_NON_SILENT: 114 Slogf.i(TAG, "Starting in forced non-silent mode"); 115 mForcedMode = true; 116 mSilentModeByHwState = false; 117 break; 118 default: 119 mForcedMode = false; 120 } 121 } 122 init()123 void init() { 124 boolean forcedMode; 125 boolean silentMode; 126 synchronized (mLock) { 127 forcedMode = mForcedMode; 128 silentMode = mSilentModeByHwState; 129 } 130 if (forcedMode) { 131 updateKernelSilentMode(silentMode); 132 mService.notifySilentModeChange(silentMode); 133 Slogf.i(TAG, "Now in forced mode: monitoring %s is disabled", 134 mHwStateMonitoringFileName); 135 } else { 136 startMonitoringSilentModeHwState(); 137 } 138 } 139 release()140 void release() { 141 synchronized (mLock) { 142 stopMonitoringSilentModeHwStateLocked(); 143 } 144 } 145 146 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)147 void dump(IndentingPrintWriter writer) { 148 synchronized (mLock) { 149 writer.printf("mHwStateMonitoringFileName: %s\n", mHwStateMonitoringFileName); 150 writer.printf("mKernelSilentModeFileName: %s\n", mKernelSilentModeFileName); 151 writer.printf("Monitoring HW state signal: %b\n", mFileObserver != null); 152 writer.printf("Silent mode by HW state signal: %b\n", mSilentModeByHwState); 153 writer.printf("Forced silent mode: %b\n", mForcedMode); 154 } 155 } 156 isSilentMode()157 boolean isSilentMode() { 158 synchronized (mLock) { 159 return mSilentModeByHwState; 160 } 161 } 162 querySilentModeHwState()163 void querySilentModeHwState() { 164 FileObserver fileObserver; 165 synchronized (mLock) { 166 fileObserver = mFileObserver; 167 } 168 if (fileObserver != null) { 169 fileObserver.onEvent(FileObserver.MODIFY, mHwStateMonitoringFileName); 170 } 171 } 172 updateKernelSilentMode(boolean silent)173 void updateKernelSilentMode(boolean silent) { 174 try (BufferedWriter writer = 175 new BufferedWriter(new FileWriter(mKernelSilentModeFileName))) { 176 String value = silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE; 177 writer.write(value); 178 writer.flush(); 179 Slogf.i(TAG, "%s is updated to %s", mKernelSilentModeFileName, value); 180 } catch (IOException e) { 181 Slogf.w(TAG, "Failed to update %s to %s", mKernelSilentModeFileName, 182 silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE); 183 } 184 } 185 setSilentMode(String silentMode)186 void setSilentMode(String silentMode) { 187 switch (silentMode) { 188 case SILENT_MODE_FORCED_SILENT: 189 switchToForcedMode(true); 190 break; 191 case SILENT_MODE_FORCED_NON_SILENT: 192 switchToForcedMode(false); 193 break; 194 case SILENT_MODE_NON_FORCED: 195 switchToNonForcedMode(); 196 break; 197 default: 198 Slogf.w(TAG, "Unsupported silent mode: %s", silentMode); 199 } 200 } 201 switchToForcedMode(boolean silentMode)202 private void switchToForcedMode(boolean silentMode) { 203 boolean updated = false; 204 synchronized (mLock) { 205 if (!mForcedMode) { 206 stopMonitoringSilentModeHwStateLocked(); 207 mForcedMode = true; 208 } 209 if (mSilentModeByHwState != silentMode) { 210 mSilentModeByHwState = silentMode; 211 updated = true; 212 } 213 } 214 if (updated) { 215 updateKernelSilentMode(silentMode); 216 mService.notifySilentModeChange(silentMode); 217 } 218 Slogf.i(TAG, "Now in forced %s mode: monitoring %s is disabled", 219 silentMode ? "silent" : "non-silent", mHwStateMonitoringFileName); 220 } 221 switchToNonForcedMode()222 private void switchToNonForcedMode() { 223 boolean updated = false; 224 synchronized (mLock) { 225 if (mForcedMode) { 226 Slogf.i(TAG, "Now in non forced mode: monitoring %s is started", 227 mHwStateMonitoringFileName); 228 mForcedMode = false; 229 updated = true; 230 } 231 } 232 if (updated) { 233 startMonitoringSilentModeHwState(); 234 } 235 } 236 startMonitoringSilentModeHwState()237 private void startMonitoringSilentModeHwState() { 238 File monitorFile = new File(mHwStateMonitoringFileName); 239 if (!monitorFile.exists()) { 240 Slogf.w(TAG, "Failed to start monitoring Silent Mode HW state: %s doesn't exist", 241 mHwStateMonitoringFileName); 242 return; 243 } 244 FileObserver fileObserver = new FileObserver(monitorFile, FileObserver.MODIFY) { 245 @Override 246 public void onEvent(int event, String filename) { 247 boolean newSilentMode; 248 boolean oldSilentMode; 249 synchronized (mLock) { 250 // FileObserver can report events even after stopWatching is called. To ignore 251 // such events, check the current internal state. 252 if (mForcedMode) { 253 return; 254 } 255 oldSilentMode = mSilentModeByHwState; 256 try { 257 String contents = IoUtils.readFileAsString(mHwStateMonitoringFileName) 258 .trim(); 259 mSilentModeByHwState = VALUE_SILENT_MODE.equals(contents); 260 Slogf.i(TAG, "%s indicates %s mode", mHwStateMonitoringFileName, 261 mSilentModeByHwState ? "silent" : "non-silent"); 262 } catch (Exception e) { 263 Slogf.w(TAG, e, "Failed to read %s", mHwStateMonitoringFileName); 264 return; 265 } 266 newSilentMode = mSilentModeByHwState; 267 } 268 if (newSilentMode != oldSilentMode) { 269 updateKernelSilentMode(newSilentMode); 270 mService.notifySilentModeChange(newSilentMode); 271 } 272 } 273 }; 274 synchronized (mLock) { 275 mFileObserver = fileObserver; 276 } 277 fileObserver.startWatching(); 278 // Trigger the observer to get the initial contents 279 querySilentModeHwState(); 280 } 281 282 @GuardedBy("mLock") stopMonitoringSilentModeHwStateLocked()283 private void stopMonitoringSilentModeHwStateLocked() { 284 if (mFileObserver != null) { 285 mFileObserver.stopWatching(); 286 mFileObserver = null; 287 } 288 } 289 searchForSysfsDir()290 private static String searchForSysfsDir() { 291 for (String dir : SYSFS_DIRS_FOR_SILENT_MODE) { 292 if (Files.isDirectory(Paths.get(dir))) { 293 return dir + "/"; 294 } 295 } 296 return SYSFS_DIRS_FOR_SILENT_MODE[0] + "/"; 297 } 298 } 299