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         // Log critical error since regulators could not be configured.  Could
111         // cause hardware damage if default regulator settings are very wrong.
112         services.getErrorLogging().logConfigFileError(Entry::Level::Critical,
113                                                       services.getJournal());
114 
115         // Throw InternalFailure to propogate error status to D-Bus client
116         throw sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure{};
117     }
118 }
119 
120 void Manager::interfacesAddedHandler(sdbusplus::message::message& msg)
121 {
122     // Verify message is valid
123     if (!msg)
124     {
125         return;
126     }
127 
128     try
129     {
130         // Read object path for object that was created or had interface added
131         sdbusplus::message::object_path objPath;
132         msg.read(objPath);
133 
134         // Read the dictionary whose keys are interface names and whose values
135         // are dictionaries containing the interface property names and values
136         std::map<std::string,
137                  std::map<std::string, std::variant<std::vector<std::string>>>>
138             intfProp;
139         msg.read(intfProp);
140 
141         // Find the compatible interface, if present
142         auto itIntf = intfProp.find(compatibleIntf);
143         if (itIntf != intfProp.cend())
144         {
145             // Find the Names property of the compatible interface, if present
146             auto itProp = itIntf->second.find(compatibleNamesProp);
147             if (itProp != itIntf->second.cend())
148             {
149                 // Get value of Names property
150                 auto propValue = std::get<0>(itProp->second);
151                 if (!propValue.empty())
152                 {
153                     // Store list of compatible system types
154                     compatibleSystemTypes = propValue;
155 
156                     // Find and load JSON config file based on system types
157                     loadConfigFile();
158                 }
159             }
160         }
161     }
162     catch (const std::exception&)
163     {
164         // Error trying to read interfacesAdded message.  One possible cause
165         // could be a property whose value is not a std::vector<std::string>.
166     }
167 }
168 
169 void Manager::monitor(bool enable)
170 {
171     if (enable)
172     {
173         /* Temporarily comment out until monitoring is supported.
174             Timer timer(eventLoop, std::bind(&Manager::timerExpired, this));
175             // Set timer as a repeating 1sec timer
176             timer.restart(std::chrono::milliseconds(1000));
177             timers.emplace_back(std::move(timer));
178         */
179     }
180     else
181     {
182         /* Temporarily comment out until monitoring is supported.
183             // Delete all timers to disable monitoring
184             timers.clear();
185         */
186 
187         // Verify System object exists; this means config file has been loaded
188         if (system)
189         {
190             // Close the regulator devices in the system.  Monitoring is
191             // normally disabled because the system is being powered off.  The
192             // devices should be closed in case hardware is removed or replaced
193             // while the system is powered off.
194             system->closeDevices(services);
195         }
196     }
197 }
198 
199 void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/,
200                             const struct signalfd_siginfo* /*sigInfo*/)
201 {
202     // Reload the JSON configuration file
203     loadConfigFile();
204 }
205 
206 void Manager::timerExpired()
207 {
208     // TODO Analyze, refresh sensor status, and
209     // collect/update telemetry for each regulator
210 }
211 
212 void Manager::clearHardwareData()
213 {
214     // Clear any cached hardware presence data and VPD values
215     services.getPresenceService().clearCache();
216     services.getVPD().clearCache();
217 
218     // Verify System object exists; this means config file has been loaded
219     if (system)
220     {
221         // Clear any cached hardware data in the System object
222         system->clearCache();
223     }
224 
225     // TODO: Clear error history related to hardware devices
226 }
227 
228 void Manager::findCompatibleSystemTypes()
229 {
230     using namespace phosphor::power::util;
231 
232     try
233     {
234         // Query object mapper for object paths that implement the compatible
235         // interface.  Returns a map of object paths to a map of services names
236         // to their interfaces.
237         DbusSubtree subTree = getSubTree(bus, "/xyz/openbmc_project/inventory",
238                                          compatibleIntf, 0);
239 
240         // Get the first object path
241         auto objectIt = subTree.cbegin();
242         if (objectIt != subTree.cend())
243         {
244             std::string objPath = objectIt->first;
245 
246             // Get the first service name
247             auto serviceIt = objectIt->second.cbegin();
248             if (serviceIt != objectIt->second.cend())
249             {
250                 std::string service = serviceIt->first;
251                 if (!service.empty())
252                 {
253                     // Get compatible system types property value
254                     getProperty(compatibleIntf, compatibleNamesProp, objPath,
255                                 service, bus, compatibleSystemTypes);
256                 }
257             }
258         }
259     }
260     catch (const std::exception&)
261     {
262         // Compatible system types information is not available.  The current
263         // system might not support the interface, or the service that
264         // implements the interface might not be running yet.
265     }
266 }
267 
268 fs::path Manager::findConfigFile()
269 {
270     // Build list of possible base file names
271     std::vector<std::string> fileNames{};
272 
273     // Add possible file names based on compatible system types (if any)
274     for (const std::string& systemType : compatibleSystemTypes)
275     {
276         // Replace all spaces and commas in system type name with underscores
277         std::string fileName{systemType};
278         std::replace(fileName.begin(), fileName.end(), ' ', '_');
279         std::replace(fileName.begin(), fileName.end(), ',', '_');
280 
281         // Append .json suffix and add to list
282         fileName.append(".json");
283         fileNames.emplace_back(fileName);
284     }
285 
286     // Add default file name for systems that don't use compatible interface
287     fileNames.emplace_back(defaultConfigFileName);
288 
289     // Look for a config file with one of the possible base names
290     for (const std::string& fileName : fileNames)
291     {
292         // Check if file exists in test directory
293         fs::path pathName{testConfigFileDir / fileName};
294         if (fs::exists(pathName))
295         {
296             return pathName;
297         }
298 
299         // Check if file exists in standard directory
300         pathName = standardConfigFileDir / fileName;
301         if (fs::exists(pathName))
302         {
303             return pathName;
304         }
305     }
306 
307     // No config file found; return empty path
308     return fs::path{};
309 }
310 
311 void Manager::loadConfigFile()
312 {
313     try
314     {
315         // Find the absolute path to the config file
316         fs::path pathName = findConfigFile();
317         if (!pathName.empty())
318         {
319             // Log info message in journal; config file path is important
320             services.getJournal().logInfo("Loading configuration file " +
321                                           pathName.string());
322 
323             // Parse the config file
324             std::vector<std::unique_ptr<Rule>> rules{};
325             std::vector<std::unique_ptr<Chassis>> chassis{};
326             std::tie(rules, chassis) = config_file_parser::parse(pathName);
327 
328             // Store config file information in a new System object.  The old
329             // System object, if any, is automatically deleted.
330             system =
331                 std::make_unique<System>(std::move(rules), std::move(chassis));
332         }
333     }
334     catch (const std::exception& e)
335     {
336         // Log error messages in journal
337         services.getJournal().logError(exception_utils::getMessages(e));
338         services.getJournal().logError("Unable to load configuration file");
339 
340         // Log error
341         services.getErrorLogging().logConfigFileError(Entry::Level::Error,
342                                                       services.getJournal());
343     }
344 }
345 
346 } // namespace phosphor::power::regulators
347