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