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