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