• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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