1 /**
2 * Copyright © 2020 IBM Corporation
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 #include "manager.hpp"
18
19 #include "chassis.hpp"
20 #include "config_file_parser.hpp"
21 #include "exception_utils.hpp"
22 #include "format_utils.hpp"
23 #include "rule.hpp"
24 #include "utility.hpp"
25
26 #include <xyz/openbmc_project/Common/error.hpp>
27 #include <xyz/openbmc_project/State/Chassis/server.hpp>
28
29 #include <chrono>
30 #include <exception>
31 #include <functional>
32 #include <span>
33 #include <thread>
34 #include <tuple>
35 #include <utility>
36
37 namespace phosphor::power::regulators
38 {
39
40 namespace fs = std::filesystem;
41
42 constexpr auto busName = "xyz.openbmc_project.Power.Regulators";
43 constexpr auto managerObjPath = "/xyz/openbmc_project/power/regulators/manager";
44 constexpr auto chassisStatePath = "/xyz/openbmc_project/state/chassis0";
45 constexpr auto chassisStateIntf = "xyz.openbmc_project.State.Chassis";
46 constexpr auto chassisStateProp = "CurrentPowerState";
47 constexpr std::chrono::minutes maxTimeToWaitForCompatTypes{5};
48
49 using PowerState =
50 sdbusplus::xyz::openbmc_project::State::server::Chassis::PowerState;
51
52 /**
53 * Default configuration file name. This is used when the system does not
54 * implement the D-Bus compatible interface.
55 */
56 constexpr auto defaultConfigFileName = "config.json";
57
58 /**
59 * Standard configuration file directory. This directory is part of the
60 * firmware install image. It contains the standard version of the config file.
61 */
62 const fs::path standardConfigFileDir{"/usr/share/phosphor-regulators"};
63
64 /**
65 * Test configuration file directory. This directory can contain a test version
66 * of the config file. The test version will override the standard version.
67 */
68 const fs::path testConfigFileDir{"/etc/phosphor-regulators"};
69
Manager(sdbusplus::bus_t & bus,const sdeventplus::Event & event)70 Manager::Manager(sdbusplus::bus_t& bus, const sdeventplus::Event& event) :
71 ManagerObject{bus, managerObjPath}, bus{bus}, eventLoop{event},
72 services{bus},
73 phaseFaultTimer{event, std::bind(&Manager::phaseFaultTimerExpired, this)},
74 sensorTimer{event, std::bind(&Manager::sensorTimerExpired, this)}
75 {
76 // Create object to find compatible system types for current system.
77 // Note that some systems do not provide this information.
78 compatSysTypesFinder = std::make_unique<util::CompatibleSystemTypesFinder>(
79 bus, std::bind_front(&Manager::compatibleSystemTypesFound, this));
80
81 // If no system types found so far, try to load default config file
82 if (compatibleSystemTypes.empty())
83 {
84 loadConfigFile();
85 }
86
87 // Obtain D-Bus service name
88 bus.request_name(busName);
89
90 // If system is already powered on, enable monitoring
91 if (isSystemPoweredOn())
92 {
93 monitor(true);
94 }
95 }
96
configure()97 void Manager::configure()
98 {
99 // Clear any cached data or error history related to hardware devices
100 clearHardwareData();
101
102 // Wait until the config file has been loaded or hit max wait time
103 waitUntilConfigFileLoaded();
104
105 // Verify config file has been loaded and System object is valid
106 if (isConfigFileLoaded())
107 {
108 // Configure the regulator devices in the system
109 system->configure(services);
110 }
111 else
112 {
113 // Write error message to journal
114 services.getJournal().logError("Unable to configure regulator devices: "
115 "Configuration file not loaded");
116
117 // Log critical error since regulators could not be configured. Could
118 // cause hardware damage if default regulator settings are very wrong.
119 services.getErrorLogging().logConfigFileError(Entry::Level::Critical,
120 services.getJournal());
121
122 // Throw InternalFailure to propogate error status to D-Bus client
123 throw sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure{};
124 }
125 }
126
monitor(bool enable)127 void Manager::monitor(bool enable)
128 {
129 // Check whether already in the requested monitoring state
130 if (enable == isMonitoringEnabled)
131 {
132 return;
133 }
134
135 isMonitoringEnabled = enable;
136 if (isMonitoringEnabled)
137 {
138 services.getJournal().logDebug("Monitoring enabled");
139
140 // Restart phase fault detection timer with repeating 15 second interval
141 phaseFaultTimer.restart(std::chrono::seconds(15));
142
143 // Restart sensor monitoring timer with repeating 1 second interval
144 sensorTimer.restart(std::chrono::seconds(1));
145
146 // Enable sensors service; put all sensors in an active state
147 services.getSensors().enable();
148 }
149 else
150 {
151 services.getJournal().logDebug("Monitoring disabled");
152
153 // Disable timers
154 phaseFaultTimer.setEnabled(false);
155 sensorTimer.setEnabled(false);
156
157 // Disable sensors service; put all sensors in an inactive state
158 services.getSensors().disable();
159
160 // Verify config file has been loaded and System object is valid
161 if (isConfigFileLoaded())
162 {
163 // Close the regulator devices in the system. Monitoring is
164 // normally disabled because the system is being powered off. The
165 // devices should be closed in case hardware is removed or replaced
166 // while the system is powered off.
167 system->closeDevices(services);
168 }
169 }
170 }
171
compatibleSystemTypesFound(const std::vector<std::string> & types)172 void Manager::compatibleSystemTypesFound(const std::vector<std::string>& types)
173 {
174 // If we don't already have compatible system types
175 if (compatibleSystemTypes.empty())
176 {
177 std::string typesStr = format_utils::toString(std::span{types});
178 services.getJournal().logInfo(
179 std::format("Compatible system types found: {}", typesStr));
180
181 // Store compatible system types
182 compatibleSystemTypes = types;
183
184 // Find and load JSON config file based on system types
185 loadConfigFile();
186 }
187 }
188
phaseFaultTimerExpired()189 void Manager::phaseFaultTimerExpired()
190 {
191 // Verify config file has been loaded and System object is valid
192 if (isConfigFileLoaded())
193 {
194 // Detect redundant phase faults in regulator devices in the system
195 system->detectPhaseFaults(services);
196 }
197 }
198
sensorTimerExpired()199 void Manager::sensorTimerExpired()
200 {
201 // Notify sensors service that a sensor monitoring cycle is starting
202 services.getSensors().startCycle();
203
204 // Verify config file has been loaded and System object is valid
205 if (isConfigFileLoaded())
206 {
207 // Monitor sensors for the voltage rails in the system
208 system->monitorSensors(services);
209 }
210
211 // Notify sensors service that current sensor monitoring cycle has ended
212 services.getSensors().endCycle();
213 }
214
sighupHandler(sdeventplus::source::Signal &,const struct signalfd_siginfo *)215 void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/,
216 const struct signalfd_siginfo* /*sigInfo*/)
217 {
218 // Reload the JSON configuration file
219 loadConfigFile();
220 }
221
clearHardwareData()222 void Manager::clearHardwareData()
223 {
224 // Clear any cached hardware presence data and VPD values
225 services.getPresenceService().clearCache();
226 services.getVPD().clearCache();
227
228 // Verify config file has been loaded and System object is valid
229 if (isConfigFileLoaded())
230 {
231 // Clear any cached hardware data in the System object
232 system->clearCache();
233
234 // Clear error history related to hardware devices in the System object
235 system->clearErrorHistory();
236 }
237 }
238
findConfigFile()239 fs::path Manager::findConfigFile()
240 {
241 // Build list of possible base file names
242 std::vector<std::string> fileNames{};
243
244 // Add possible file names based on compatible system types (if any)
245 for (const std::string& systemType : compatibleSystemTypes)
246 {
247 // Look for file name that is entire system type + ".json"
248 // Example: com.acme.Hardware.Chassis.Model.MegaServer.json
249 fileNames.emplace_back(systemType + ".json");
250
251 // Look for file name that is last node of system type + ".json"
252 // Example: MegaServer.json
253 std::string::size_type pos = systemType.rfind('.');
254 if ((pos != std::string::npos) && ((systemType.size() - pos) > 1))
255 {
256 fileNames.emplace_back(systemType.substr(pos + 1) + ".json");
257 }
258 }
259
260 // Add default file name for systems that don't use compatible interface
261 fileNames.emplace_back(defaultConfigFileName);
262
263 // Look for a config file with one of the possible base names
264 for (const std::string& fileName : fileNames)
265 {
266 // Check if file exists in test directory
267 fs::path pathName{testConfigFileDir / fileName};
268 if (fs::exists(pathName))
269 {
270 return pathName;
271 }
272
273 // Check if file exists in standard directory
274 pathName = standardConfigFileDir / fileName;
275 if (fs::exists(pathName))
276 {
277 return pathName;
278 }
279 }
280
281 // No config file found; return empty path
282 return fs::path{};
283 }
284
isSystemPoweredOn()285 bool Manager::isSystemPoweredOn()
286 {
287 bool isOn{false};
288
289 try
290 {
291 // Get D-Bus property that contains the current power state for
292 // chassis0, which represents the entire system (all chassis)
293 using namespace phosphor::power::util;
294 auto service = getService(chassisStatePath, chassisStateIntf, bus);
295 if (!service.empty())
296 {
297 PowerState currentPowerState;
298 getProperty(chassisStateIntf, chassisStateProp, chassisStatePath,
299 service, bus, currentPowerState);
300 if (currentPowerState == PowerState::On)
301 {
302 isOn = true;
303 }
304 }
305 }
306 catch (const std::exception& e)
307 {
308 // Current power state might not be available yet. The regulators
309 // application can start before the power state is published on D-Bus.
310 }
311
312 return isOn;
313 }
314
loadConfigFile()315 void Manager::loadConfigFile()
316 {
317 try
318 {
319 // Find the absolute path to the config file
320 fs::path pathName = findConfigFile();
321 if (!pathName.empty())
322 {
323 // Log info message in journal; config file path is important
324 services.getJournal().logInfo(
325 "Loading configuration file " + pathName.string());
326
327 // Parse the config file
328 std::vector<std::unique_ptr<Rule>> rules{};
329 std::vector<std::unique_ptr<Chassis>> chassis{};
330 std::tie(rules, chassis) = config_file_parser::parse(pathName);
331
332 // Store config file information in a new System object. The old
333 // System object, if any, is automatically deleted.
334 system =
335 std::make_unique<System>(std::move(rules), std::move(chassis));
336 }
337 }
338 catch (const std::exception& e)
339 {
340 // Log error messages in journal
341 services.getJournal().logError(exception_utils::getMessages(e));
342 services.getJournal().logError("Unable to load configuration file");
343
344 // Log error
345 services.getErrorLogging().logConfigFileError(Entry::Level::Error,
346 services.getJournal());
347 }
348 }
349
waitUntilConfigFileLoaded()350 void Manager::waitUntilConfigFileLoaded()
351 {
352 // If config file not loaded and list of compatible system types is empty
353 if (!isConfigFileLoaded() && compatibleSystemTypes.empty())
354 {
355 // Loop until compatible system types found or waited max amount of time
356 auto start = std::chrono::system_clock::now();
357 std::chrono::system_clock::duration timeWaited{0};
358 while (compatibleSystemTypes.empty() &&
359 (timeWaited <= maxTimeToWaitForCompatTypes))
360 {
361 // Try to find list of compatible system types. Force finder object
362 // to re-find system types on D-Bus because we are not receiving
363 // InterfacesAdded signals within this while loop.
364 compatSysTypesFinder->refind();
365 if (compatibleSystemTypes.empty())
366 {
367 // Not found; sleep 5 seconds
368 using namespace std::chrono_literals;
369 std::this_thread::sleep_for(5s);
370 }
371 timeWaited = std::chrono::system_clock::now() - start;
372 }
373 }
374 }
375
376 } // namespace phosphor::power::regulators
377