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 "rule.hpp"
23 #include "utility.hpp"
24 
25 #include <xyz/openbmc_project/Common/error.hpp>
26 
27 #include <algorithm>
28 #include <chrono>
29 #include <exception>
30 #include <functional>
31 #include <map>
32 #include <tuple>
33 #include <utility>
34 #include <variant>
35 
36 namespace phosphor::power::regulators
37 {
38 
39 namespace fs = std::filesystem;
40 
41 constexpr auto busName = "xyz.openbmc_project.Power.Regulators";
42 constexpr auto managerObjPath = "/xyz/openbmc_project/power/regulators/manager";
43 constexpr auto compatibleIntf =
44     "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
45 constexpr auto compatibleNamesProp = "Names";
46 
47 /**
48  * Default configuration file name.  This is used when the system does not
49  * implement the D-Bus compatible interface.
50  */
51 constexpr auto defaultConfigFileName = "config.json";
52 
53 /**
54  * Standard configuration file directory.  This directory is part of the
55  * firmware install image.  It contains the standard version of the config file.
56  */
57 const fs::path standardConfigFileDir{"/usr/share/phosphor-regulators"};
58 
59 /**
60  * Test configuration file directory.  This directory can contain a test version
61  * of the config file.  The test version will override the standard version.
62  */
63 const fs::path testConfigFileDir{"/etc/phosphor-regulators"};
64 
65 Manager::Manager(sdbusplus::bus::bus& bus, const sdeventplus::Event& event) :
66     ManagerObject{bus, managerObjPath, true}, bus{bus}, eventLoop{event},
67     services{bus}
68 {
69     // Subscribe to D-Bus interfacesAdded signal from Entity Manager.  This
70     // notifies us if the compatible interface becomes available later.
71     std::string matchStr = sdbusplus::bus::match::rules::interfacesAdded() +
72                            sdbusplus::bus::match::rules::sender(
73                                "xyz.openbmc_project.EntityManager");
74     std::unique_ptr<sdbusplus::server::match::match> matchPtr =
75         std::make_unique<sdbusplus::server::match::match>(
76             bus, matchStr,
77             std::bind(&Manager::interfacesAddedHandler, this,
78                       std::placeholders::_1));
79     signals.emplace_back(std::move(matchPtr));
80 
81     // Try to find compatible system types using D-Bus compatible interface.
82     // Note that it might not be supported on this system, or the service that
83     // provides the interface might not be running yet.
84     findCompatibleSystemTypes();
85 
86     // Try to find and load the JSON configuration file
87     loadConfigFile();
88 
89     // Obtain dbus service name
90     bus.request_name(busName);
91 }
92 
93 void Manager::configure()
94 {
95     // Clear any cached data or error history related to hardware devices
96     clearHardwareData();
97 
98     // Verify System object exists; this means config file has been loaded
99     if (system)
100     {
101         // Configure the regulator devices in the system
102         system->configure(services);
103     }
104     else
105     {
106         // Write error message to journal
107         services.getJournal().logError("Unable to configure regulator devices: "
108                                        "Configuration file not loaded");
109 
110         // TODO: Add code to wait for EntityManager to publish the compatible
111         // interface before logging this error.
112 
113         // Log critical error since regulators could not be configured.  Could
114         // cause hardware damage if default regulator settings are very wrong.
115         /*
116         services.getErrorLogging().logConfigFileError(Entry::Level::Critical,
117                                                       services.getJournal());
118         */
119 
120         // Throw InternalFailure to propogate error status to D-Bus client
121         /*
122         throw sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure{};
123         */
124     }
125 }
126 
127 void Manager::interfacesAddedHandler(sdbusplus::message::message& msg)
128 {
129     // Verify message is valid
130     if (!msg)
131     {
132         return;
133     }
134 
135     try
136     {
137         // Read object path for object that was created or had interface added
138         sdbusplus::message::object_path objPath;
139         msg.read(objPath);
140 
141         // Read the dictionary whose keys are interface names and whose values
142         // are dictionaries containing the interface property names and values
143         std::map<std::string,
144                  std::map<std::string, std::variant<std::vector<std::string>>>>
145             intfProp;
146         msg.read(intfProp);
147 
148         // Find the compatible interface, if present
149         auto itIntf = intfProp.find(compatibleIntf);
150         if (itIntf != intfProp.cend())
151         {
152             // Find the Names property of the compatible interface, if present
153             auto itProp = itIntf->second.find(compatibleNamesProp);
154             if (itProp != itIntf->second.cend())
155             {
156                 // Get value of Names property
157                 auto propValue = std::get<0>(itProp->second);
158                 if (!propValue.empty())
159                 {
160                     // Store list of compatible system types
161                     compatibleSystemTypes = propValue;
162 
163                     // Find and load JSON config file based on system types
164                     loadConfigFile();
165                 }
166             }
167         }
168     }
169     catch (const std::exception&)
170     {
171         // Error trying to read interfacesAdded message.  One possible cause
172         // could be a property whose value is not a std::vector<std::string>.
173     }
174 }
175 
176 void Manager::monitor(bool enable)
177 {
178     if (enable)
179     {
180         /* Temporarily comment out until monitoring is supported.
181             Timer timer(eventLoop, std::bind(&Manager::timerExpired, this));
182             // Set timer as a repeating 1sec timer
183             timer.restart(std::chrono::milliseconds(1000));
184             timers.emplace_back(std::move(timer));
185         */
186     }
187     else
188     {
189         /* Temporarily comment out until monitoring is supported.
190             // Delete all timers to disable monitoring
191             timers.clear();
192         */
193 
194         // Verify System object exists; this means config file has been loaded
195         if (system)
196         {
197             // Close the regulator devices in the system.  Monitoring is
198             // normally disabled because the system is being powered off.  The
199             // devices should be closed in case hardware is removed or replaced
200             // while the system is powered off.
201             system->closeDevices(services);
202         }
203     }
204 }
205 
206 void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/,
207                             const struct signalfd_siginfo* /*sigInfo*/)
208 {
209     // Reload the JSON configuration file
210     loadConfigFile();
211 }
212 
213 void Manager::timerExpired()
214 {
215     // TODO Analyze, refresh sensor status, and
216     // collect/update telemetry for each regulator
217 }
218 
219 void Manager::clearHardwareData()
220 {
221     // Clear any cached hardware presence data and VPD values
222     services.getPresenceService().clearCache();
223     services.getVPD().clearCache();
224 
225     // Verify System object exists; this means config file has been loaded
226     if (system)
227     {
228         // Clear any cached hardware data in the System object
229         system->clearCache();
230     }
231 
232     // TODO: Clear error history related to hardware devices
233 }
234 
235 void Manager::findCompatibleSystemTypes()
236 {
237     using namespace phosphor::power::util;
238 
239     try
240     {
241         // Query object mapper for object paths that implement the compatible
242         // interface.  Returns a map of object paths to a map of services names
243         // to their interfaces.
244         DbusSubtree subTree = getSubTree(bus, "/xyz/openbmc_project/inventory",
245                                          compatibleIntf, 0);
246 
247         // Get the first object path
248         auto objectIt = subTree.cbegin();
249         if (objectIt != subTree.cend())
250         {
251             std::string objPath = objectIt->first;
252 
253             // Get the first service name
254             auto serviceIt = objectIt->second.cbegin();
255             if (serviceIt != objectIt->second.cend())
256             {
257                 std::string service = serviceIt->first;
258                 if (!service.empty())
259                 {
260                     // Get compatible system types property value
261                     getProperty(compatibleIntf, compatibleNamesProp, objPath,
262                                 service, bus, compatibleSystemTypes);
263                 }
264             }
265         }
266     }
267     catch (const std::exception&)
268     {
269         // Compatible system types information is not available.  The current
270         // system might not support the interface, or the service that
271         // implements the interface might not be running yet.
272     }
273 }
274 
275 fs::path Manager::findConfigFile()
276 {
277     // Build list of possible base file names
278     std::vector<std::string> fileNames{};
279 
280     // Add possible file names based on compatible system types (if any)
281     for (const std::string& systemType : compatibleSystemTypes)
282     {
283         // Replace all spaces and commas in system type name with underscores
284         std::string fileName{systemType};
285         std::replace(fileName.begin(), fileName.end(), ' ', '_');
286         std::replace(fileName.begin(), fileName.end(), ',', '_');
287 
288         // Append .json suffix and add to list
289         fileName.append(".json");
290         fileNames.emplace_back(fileName);
291     }
292 
293     // Add default file name for systems that don't use compatible interface
294     fileNames.emplace_back(defaultConfigFileName);
295 
296     // Look for a config file with one of the possible base names
297     for (const std::string& fileName : fileNames)
298     {
299         // Check if file exists in test directory
300         fs::path pathName{testConfigFileDir / fileName};
301         if (fs::exists(pathName))
302         {
303             return pathName;
304         }
305 
306         // Check if file exists in standard directory
307         pathName = standardConfigFileDir / fileName;
308         if (fs::exists(pathName))
309         {
310             return pathName;
311         }
312     }
313 
314     // No config file found; return empty path
315     return fs::path{};
316 }
317 
318 void Manager::loadConfigFile()
319 {
320     try
321     {
322         // Find the absolute path to the config file
323         fs::path pathName = findConfigFile();
324         if (!pathName.empty())
325         {
326             // Log info message in journal; config file path is important
327             services.getJournal().logInfo("Loading configuration file " +
328                                           pathName.string());
329 
330             // Parse the config file
331             std::vector<std::unique_ptr<Rule>> rules{};
332             std::vector<std::unique_ptr<Chassis>> chassis{};
333             std::tie(rules, chassis) = config_file_parser::parse(pathName);
334 
335             // Store config file information in a new System object.  The old
336             // System object, if any, is automatically deleted.
337             system =
338                 std::make_unique<System>(std::move(rules), std::move(chassis));
339         }
340     }
341     catch (const std::exception& e)
342     {
343         // Log error messages in journal
344         services.getJournal().logError(exception_utils::getMessages(e));
345         services.getJournal().logError("Unable to load configuration file");
346 
347         // Log error
348         services.getErrorLogging().logConfigFileError(Entry::Level::Error,
349                                                       services.getJournal());
350     }
351 }
352 
353 } // namespace phosphor::power::regulators
354