xref: /openbmc/phosphor-power/phosphor-regulators/src/manager.cpp (revision f54021972b91be5058b50e9046bb0dd5a3b22a80)
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