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