1 /**
2  * Copyright © 2019 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 #include "json_parser.hpp"
17 
18 #include "anyof.hpp"
19 #include "fallback.hpp"
20 #include "gpio.hpp"
21 #include "json_config.hpp"
22 #include "sdbusplus.hpp"
23 #include "tach.hpp"
24 
25 #include <nlohmann/json.hpp>
26 #include <phosphor-logging/log.hpp>
27 #include <sdbusplus/bus.hpp>
28 #include <xyz/openbmc_project/Logging/Create/server.hpp>
29 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
30 
31 #include <filesystem>
32 #include <fstream>
33 #include <string>
34 
35 namespace phosphor
36 {
37 namespace fan
38 {
39 namespace presence
40 {
41 
42 using json = nlohmann::json;
43 namespace fs = std::filesystem;
44 using namespace phosphor::logging;
45 
46 policies JsonConfig::_policies;
47 const std::map<std::string, methodHandler> JsonConfig::_methods = {
48     {"tach", method::getTach}, {"gpio", method::getGpio}};
49 const std::map<std::string, rpolicyHandler> JsonConfig::_rpolicies = {
50     {"anyof", rpolicy::getAnyof}, {"fallback", rpolicy::getFallback}};
51 
52 const auto loggingPath = "/xyz/openbmc_project/logging";
53 const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
54 
55 JsonConfig::JsonConfig(sdbusplus::bus_t& bus) : _bus(bus)
56 {}
57 
58 void JsonConfig::start()
59 {
60     using config = fan::JsonConfig;
61 
62     if (!_loaded)
63     {
64         process(config::load(config::getConfFile(confAppName, confFileName)));
65 
66         _loaded = true;
67 
68         for (auto& p : _policies)
69         {
70             p->monitor();
71         }
72     }
73 }
74 
75 const policies& JsonConfig::get()
76 {
77     return _policies;
78 }
79 
80 void JsonConfig::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/,
81                                const struct signalfd_siginfo* /*sigInfo*/)
82 {
83     try
84     {
85         using config = fan::JsonConfig;
86 
87         _reporter.reset();
88 
89         // Load and process the json configuration
90         process(config::load(config::getConfFile(confAppName, confFileName)));
91 
92         for (auto& p : _policies)
93         {
94             p->monitor();
95         }
96         log<level::INFO>("Configuration loaded successfully");
97     }
98     catch (const std::runtime_error& re)
99     {
100         log<level::ERR>("Error loading config, no config changes made",
101                         entry("LOAD_ERROR=%s", re.what()));
102     }
103 }
104 
105 void JsonConfig::process(const json& jsonConf)
106 {
107     policies policies;
108     std::vector<fanPolicy> fans;
109     // Set the expected number of fan entries
110     // to be size of the list of fan json config entries
111     // (Must be done to eliminate vector reallocation of fan references)
112     fans.reserve(jsonConf.size());
113     for (auto& member : jsonConf)
114     {
115         if (!member.contains("name") || !member.contains("path") ||
116             !member.contains("methods") || !member.contains("rpolicy"))
117         {
118             log<level::ERR>("Missing required fan presence properties",
119                             entry("REQUIRED_PROPERTIES=%s",
120                                   "{name, path, methods, rpolicy}"));
121             throw std::runtime_error(
122                 "Missing required fan presence properties");
123         }
124 
125         // Loop thru the configured methods of presence detection
126         std::vector<std::unique_ptr<PresenceSensor>> sensors;
127         for (auto& method : member["methods"].items())
128         {
129             if (!method.value().contains("type"))
130             {
131                 log<level::ERR>(
132                     "Missing required fan presence method type",
133                     entry("FAN_NAME=%s",
134                           member["name"].get<std::string>().c_str()));
135                 throw std::runtime_error(
136                     "Missing required fan presence method type");
137             }
138             // The method type of fan presence detection
139             // (Must have a supported function within the method namespace)
140             auto type = method.value()["type"].get<std::string>();
141             std::transform(type.begin(), type.end(), type.begin(), tolower);
142             auto func = _methods.find(type);
143             if (func != _methods.end())
144             {
145                 // Call function for method type
146                 auto sensor = func->second(fans.size(), method.value());
147                 if (sensor)
148                 {
149                     sensors.emplace_back(std::move(sensor));
150                 }
151             }
152             else
153             {
154                 log<level::ERR>(
155                     "Invalid fan presence method type",
156                     entry("FAN_NAME=%s",
157                           member["name"].get<std::string>().c_str()),
158                     entry("METHOD_TYPE=%s", type.c_str()));
159                 throw std::runtime_error("Invalid fan presence method type");
160             }
161         }
162 
163         // Get the amount of time a fan must be not present before
164         // creating an error.
165         std::optional<size_t> timeUntilError;
166         if (member.contains("fan_missing_error_time"))
167         {
168             timeUntilError = member["fan_missing_error_time"].get<size_t>();
169         }
170 
171         std::unique_ptr<EEPROMDevice> eepromDevice;
172         if (member.contains("eeprom"))
173         {
174             const auto& eeprom = member.at("eeprom");
175             if (!eeprom.contains("bus_address") ||
176                 !eeprom.contains("driver_name") ||
177                 !eeprom.contains("bind_delay_ms"))
178             {
179                 log<level::ERR>(
180                     "Missing address, driver_name, or bind_delay_ms in eeprom "
181                     "section",
182                     entry("FAN_NAME=%s",
183                           member["name"].get<std::string>().c_str()));
184 
185                 throw std::runtime_error("Missing address, driver_name, or "
186                                          "bind_delay_ms in eeprom section");
187             }
188             eepromDevice = std::make_unique<EEPROMDevice>(
189                 eeprom["bus_address"].get<std::string>(),
190                 eeprom["driver_name"].get<std::string>(),
191                 eeprom["bind_delay_ms"].get<size_t>());
192         }
193 
194         auto fan =
195             std::make_tuple(member["name"], member["path"], timeUntilError);
196         // Create a fan object
197         fans.emplace_back(std::make_tuple(fan, std::move(sensors)));
198 
199         // Add fan presence policy
200         auto policy =
201             getPolicy(member["rpolicy"], fans.back(), std::move(eepromDevice));
202         if (policy)
203         {
204             policies.emplace_back(std::move(policy));
205         }
206     }
207 
208     // Success, refresh fans and policies lists
209     _fans.clear();
210     _fans.swap(fans);
211 
212     _policies.clear();
213     _policies.swap(policies);
214 
215     // Create the error reporter class if necessary
216     if (std::any_of(_fans.begin(), _fans.end(), [](const auto& fan) {
217             return std::get<std::optional<size_t>>(std::get<Fan>(fan)) !=
218                    std::nullopt;
219         }))
220     {
221         _reporter = std::make_unique<ErrorReporter>(_bus, _fans);
222     }
223 }
224 
225 std::unique_ptr<RedundancyPolicy>
226     JsonConfig::getPolicy(const json& rpolicy, const fanPolicy& fpolicy,
227                           std::unique_ptr<EEPROMDevice> eepromDevice)
228 {
229     if (!rpolicy.contains("type"))
230     {
231         log<level::ERR>(
232             "Missing required fan presence policy type",
233             entry("FAN_NAME=%s",
234                   std::get<fanPolicyFanPos>(std::get<Fan>(fpolicy)).c_str()),
235             entry("REQUIRED_PROPERTIES=%s", "{type}"));
236         throw std::runtime_error("Missing required fan presence policy type");
237     }
238 
239     // The redundancy policy type for fan presence detection
240     // (Must have a supported function within the rpolicy namespace)
241     auto type = rpolicy["type"].get<std::string>();
242     std::transform(type.begin(), type.end(), type.begin(), tolower);
243     auto func = _rpolicies.find(type);
244     if (func != _rpolicies.end())
245     {
246         // Call function for redundancy policy type and return the policy
247         return func->second(fpolicy, std::move(eepromDevice));
248     }
249     else
250     {
251         log<level::ERR>(
252             "Invalid fan presence policy type",
253             entry("FAN_NAME=%s",
254                   std::get<fanPolicyFanPos>(std::get<Fan>(fpolicy)).c_str()),
255             entry("RPOLICY_TYPE=%s", type.c_str()));
256         throw std::runtime_error("Invalid fan presence methods policy type");
257     }
258 }
259 
260 /**
261  * Methods of fan presence detection function definitions
262  */
263 namespace method
264 {
265 // Get a constructed presence sensor for fan presence detection by tach
266 std::unique_ptr<PresenceSensor> getTach(size_t fanIndex, const json& method)
267 {
268     if (!method.contains("sensors") || method["sensors"].size() == 0)
269     {
270         log<level::ERR>("Missing required tach method properties",
271                         entry("FAN_ENTRY=%d", fanIndex),
272                         entry("REQUIRED_PROPERTIES=%s", "{sensors}"));
273         throw std::runtime_error("Missing required tach method properties");
274     }
275 
276     std::vector<std::string> sensors;
277     for (auto& sensor : method["sensors"])
278     {
279         sensors.emplace_back(sensor.get<std::string>());
280     }
281 
282     return std::make_unique<PolicyAccess<Tach, JsonConfig>>(fanIndex,
283                                                             std::move(sensors));
284 }
285 
286 // Get a constructed presence sensor for fan presence detection by gpio
287 std::unique_ptr<PresenceSensor> getGpio(size_t fanIndex, const json& method)
288 {
289     if (!method.contains("physpath") || !method.contains("devpath") ||
290         !method.contains("key"))
291     {
292         log<level::ERR>(
293             "Missing required gpio method properties",
294             entry("FAN_ENTRY=%d", fanIndex),
295             entry("REQUIRED_PROPERTIES=%s", "{physpath, devpath, key}"));
296         throw std::runtime_error("Missing required gpio method properties");
297     }
298 
299     auto physpath = method["physpath"].get<std::string>();
300     auto devpath = method["devpath"].get<std::string>();
301     auto key = method["key"].get<unsigned int>();
302 
303     try
304     {
305         return std::make_unique<PolicyAccess<Gpio, JsonConfig>>(
306             fanIndex, physpath, devpath, key);
307     }
308     catch (const sdbusplus::exception_t& e)
309     {
310         namespace sdlogging = sdbusplus::xyz::openbmc_project::Logging::server;
311 
312         log<level::ERR>(
313             fmt::format(
314                 "Error creating Gpio device bridge, hardware not detected: {}",
315                 e.what())
316                 .c_str());
317 
318         auto severity =
319             sdlogging::convertForMessage(sdlogging::Entry::Level::Error);
320 
321         std::map<std::string, std::string> additionalData{
322             {"PHYSPATH", physpath},
323             {"DEVPATH", devpath},
324             {"FANINDEX", std::to_string(fanIndex)}};
325 
326         try
327         {
328 
329             util::SDBusPlus::lookupAndCallMethod(
330                 loggingPath, loggingCreateIface, "Create",
331                 "xyz.openbmc_project.Fan.Presence.Error.GPIODeviceUnavailable",
332                 severity, additionalData);
333         }
334         catch (const util::DBusError& e)
335         {
336             log<level::ERR>(fmt::format("Call to create an error log for "
337                                         "presence-sensor failure failed: {}",
338                                         e.what())
339                                 .c_str());
340         }
341 
342         return std::make_unique<PolicyAccess<NullGpio, JsonConfig>>();
343     }
344 }
345 
346 } // namespace method
347 
348 /**
349  * Redundancy policies for fan presence detection function definitions
350  */
351 namespace rpolicy
352 {
353 // Get an `Anyof` redundancy policy for the fan
354 std::unique_ptr<RedundancyPolicy>
355     getAnyof(const fanPolicy& fan, std::unique_ptr<EEPROMDevice> eepromDevice)
356 {
357     std::vector<std::reference_wrapper<PresenceSensor>> pSensors;
358     for (auto& fanSensor : std::get<fanPolicySensorListPos>(fan))
359     {
360         pSensors.emplace_back(*fanSensor);
361     }
362 
363     return std::make_unique<AnyOf>(std::get<fanPolicyFanPos>(fan), pSensors,
364                                    std::move(eepromDevice));
365 }
366 
367 // Get a `Fallback` redundancy policy for the fan
368 std::unique_ptr<RedundancyPolicy>
369     getFallback(const fanPolicy& fan,
370                 std::unique_ptr<EEPROMDevice> eepromDevice)
371 {
372     std::vector<std::reference_wrapper<PresenceSensor>> pSensors;
373     for (auto& fanSensor : std::get<fanPolicySensorListPos>(fan))
374     {
375         // Place in the order given to fallback correctly
376         pSensors.emplace_back(*fanSensor);
377     }
378 
379     return std::make_unique<Fallback>(std::get<fanPolicyFanPos>(fan), pSensors,
380                                       std::move(eepromDevice));
381 }
382 
383 } // namespace rpolicy
384 
385 } // namespace presence
386 } // namespace fan
387 } // namespace phosphor
388