• 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         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