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 String reason = bootReason; 105 if (reason == null) { 106 reason = SystemProperties.get(SYSTEM_BOOT_REASON); 107 } 108 switch (reason) { 109 case FORCED_SILENT: 110 Slogf.i(TAG, "Starting in forced silent mode"); 111 mForcedMode = true; 112 mSilentModeByHwState = true; 113 break; 114 case FORCED_NON_SILENT: 115 Slogf.i(TAG, "Starting in forced non-silent mode"); 116 mForcedMode = true; 117 mSilentModeByHwState = false; 118 break; 119 default: 120 mForcedMode = false; 121 } 122 } 123 init()124 void init() { 125 boolean forcedMode; 126 boolean silentMode; 127 synchronized (mLock) { 128 forcedMode = mForcedMode; 129 silentMode = mSilentModeByHwState; 130 } 131 if (forcedMode) { 132 updateKernelSilentMode(silentMode); 133 mService.notifySilentModeChange(silentMode); 134 Slogf.i(TAG, "Now in forced mode: monitoring %s is disabled", 135 mHwStateMonitoringFileName); 136 } else { 137 startMonitoringSilentModeHwState(); 138 } 139 } 140 release()141 void release() { 142 synchronized (mLock) { 143 stopMonitoringSilentModeHwStateLocked(); 144 } 145 } 146 147 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)148 void dump(IndentingPrintWriter writer) { 149 synchronized (mLock) { 150 writer.printf("mHwStateMonitoringFileName: %s\n", mHwStateMonitoringFileName); 151 writer.printf("mKernelSilentModeFileName: %s\n", mKernelSilentModeFileName); 152 writer.printf("Monitoring HW state signal: %b\n", mFileObserver != null); 153 writer.printf("Silent mode by HW state signal: %b\n", mSilentModeByHwState); 154 writer.printf("Forced silent mode: %b\n", mForcedMode); 155 } 156 } 157 isSilentMode()158 boolean isSilentMode() { 159 synchronized (mLock) { 160 return mSilentModeByHwState; 161 } 162 } 163 querySilentModeHwState()164 void querySilentModeHwState() { 165 FileObserver fileObserver; 166 synchronized (mLock) { 167 fileObserver = mFileObserver; 168 } 169 if (fileObserver != null) { 170 fileObserver.onEvent(FileObserver.MODIFY, mHwStateMonitoringFileName); 171 } 172 } 173 updateKernelSilentMode(boolean silent)174 void updateKernelSilentMode(boolean silent) { 175 try (BufferedWriter writer = 176 new BufferedWriter(new FileWriter(mKernelSilentModeFileName))) { 177 String value = silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE; 178 writer.write(value); 179 writer.flush(); 180 Slogf.i(TAG, "%s is updated to %s", mKernelSilentModeFileName, value); 181 } catch (IOException e) { 182 Slogf.w(TAG, "Failed to update %s to %s", mKernelSilentModeFileName, 183 silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE); 184 } 185 } 186 setSilentMode(String silentMode)187 void setSilentMode(String silentMode) { 188 switch (silentMode) { 189 case SILENT_MODE_FORCED_SILENT: 190 switchToForcedMode(true); 191 break; 192 case SILENT_MODE_FORCED_NON_SILENT: 193 switchToForcedMode(false); 194 break; 195 case SILENT_MODE_NON_FORCED: 196 switchToNonForcedMode(); 197 break; 198 default: 199 Slogf.w(TAG, "Unsupported silent mode: %s", silentMode); 200 } 201 } 202 switchToForcedMode(boolean silentMode)203 private void switchToForcedMode(boolean silentMode) { 204 boolean updated = false; 205 synchronized (mLock) { 206 if (!mForcedMode) { 207 stopMonitoringSilentModeHwStateLocked(); 208 mForcedMode = true; 209 } 210 if (mSilentModeByHwState != silentMode) { 211 mSilentModeByHwState = silentMode; 212 updated = true; 213 } 214 } 215 if (updated) { 216 updateKernelSilentMode(silentMode); 217 mService.notifySilentModeChange(silentMode); 218 } 219 Slogf.i(TAG, "Now in forced %s mode: monitoring %s is disabled", 220 silentMode ? "silent" : "non-silent", mHwStateMonitoringFileName); 221 } 222 switchToNonForcedMode()223 private void switchToNonForcedMode() { 224 boolean updated = false; 225 synchronized (mLock) { 226 if (mForcedMode) { 227 Slogf.i(TAG, "Now in non forced mode: monitoring %s is started", 228 mHwStateMonitoringFileName); 229 mForcedMode = false; 230 updated = true; 231 } 232 } 233 if (updated) { 234 startMonitoringSilentModeHwState(); 235 } 236 } 237 startMonitoringSilentModeHwState()238 private void startMonitoringSilentModeHwState() { 239 File monitorFile = new File(mHwStateMonitoringFileName); 240 if (!monitorFile.exists()) { 241 Slogf.w(TAG, "Failed to start monitoring Silent Mode HW state: %s doesn't exist", 242 mHwStateMonitoringFileName); 243 return; 244 } 245 FileObserver fileObserver = new FileObserver(monitorFile, FileObserver.MODIFY) { 246 @Override 247 public void onEvent(int event, String filename) { 248 boolean newSilentMode; 249 boolean oldSilentMode; 250 synchronized (mLock) { 251 // FileObserver can report events even after stopWatching is called. 252 if (mForcedMode || mFileObserver == null) { 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