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
JsonConfig(sdbusplus::bus_t & bus)55 JsonConfig::JsonConfig(sdbusplus::bus_t& bus) : _bus(bus) {}
56
start()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
get()74 const policies& JsonConfig::get()
75 {
76 return _policies;
77 }
78
sighupHandler(sdeventplus::source::Signal &,const struct signalfd_siginfo *)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
process(const json & jsonConf)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 =
194 std::make_tuple(member["name"], member["path"], 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 =
200 getPolicy(member["rpolicy"], fans.back(), 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>
getPolicy(const json & rpolicy,const fanPolicy & fpolicy,std::unique_ptr<EEPROMDevice> eepromDevice)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
getTach(size_t fanIndex,const json & method)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>>(
282 fanIndex, std::move(sensors));
283 }
284
285 // Get a constructed presence sensor for fan presence detection by gpio
getGpio(size_t fanIndex,const json & method)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 std::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>(std::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>
getAnyof(const fanPolicy & fan,std::unique_ptr<EEPROMDevice> eepromDevice)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
getFallback(const fanPolicy & fan,std::unique_ptr<EEPROMDevice> eepromDevice)366 std::unique_ptr<RedundancyPolicy> getFallback(
367 const fanPolicy& fan, std::unique_ptr<EEPROMDevice> eepromDevice)
368 {
369 std::vector<std::reference_wrapper<PresenceSensor>> pSensors;
370 for (auto& fanSensor : std::get<fanPolicySensorListPos>(fan))
371 {
372 // Place in the order given to fallback correctly
373 pSensors.emplace_back(*fanSensor);
374 }
375
376 return std::make_unique<Fallback>(std::get<fanPolicyFanPos>(fan), pSensors,
377 std::move(eepromDevice));
378 }
379
380 } // namespace rpolicy
381
382 } // namespace presence
383 } // namespace fan
384 } // namespace phosphor
385