/** * Copyright © 2020 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "json_parser.hpp" #include "conditions.hpp" #include "json_config.hpp" #include "nonzero_speed_trust.hpp" #include "power_interface.hpp" #include "power_off_rule.hpp" #include "tach_sensor.hpp" #include "types.hpp" #include #include #include #include #include #include #include #include namespace phosphor::fan::monitor { using json = nlohmann::json; using namespace phosphor::logging; namespace tClass { // Get a constructed trust group class for a non-zero speed group CreateGroupFunction getNonZeroSpeed(const std::vector& group) { return [group]() { return std::make_unique(std::move(group)); }; } } // namespace tClass const std::map trusts = { {"nonzerospeed", tClass::getNonZeroSpeed}}; const std::map conditions = { {"propertiesmatch", condition::getPropertiesMatch}}; const std::map methods = { {"timebased", MethodMode::timebased}, {"count", MethodMode::count}}; const std::vector getTrustGrps(const json& obj) { std::vector grpFuncs; if (obj.contains("sensor_trust_groups")) { for (auto& stg : obj["sensor_trust_groups"]) { if (!stg.contains("class") || !stg.contains("group")) { // Log error on missing required parameters log( "Missing required fan monitor trust group parameters", entry("REQUIRED_PARAMETERS=%s", "{class, group}")); throw std::runtime_error( "Missing required fan trust group parameters"); } auto tgClass = stg["class"].get(); std::vector group; for (auto& member : stg["group"]) { // Construct list of group members if (!member.contains("name")) { // Log error on missing required parameter log( "Missing required fan monitor trust group member name", entry("CLASS=%s", tgClass.c_str())); throw std::runtime_error( "Missing required fan monitor trust group member name"); } auto in_trust = true; if (member.contains("in_trust")) { in_trust = member["in_trust"].get(); } group.emplace_back(trust::GroupDefinition{ member["name"].get(), in_trust}); } // The class for fan sensor trust groups // (Must have a supported function within the tClass namespace) std::transform(tgClass.begin(), tgClass.end(), tgClass.begin(), tolower); auto handler = trusts.find(tgClass); if (handler != trusts.end()) { // Call function for trust group class grpFuncs.emplace_back(handler->second(group)); } else { // Log error on unsupported trust group class log("Invalid fan monitor trust group class", entry("CLASS=%s", tgClass.c_str())); throw std::runtime_error( "Invalid fan monitor trust group class"); } } } return grpFuncs; } const std::vector getSensorDefs(const json& sensors) { std::vector sensorDefs; for (const auto& sensor : sensors) { if (!sensor.contains("name") || !sensor.contains("has_target")) { // Log error on missing required parameters log( "Missing required fan sensor definition parameters", entry("REQUIRED_PARAMETERS=%s", "{name, has_target}")); throw std::runtime_error( "Missing required fan sensor definition parameters"); } // Target interface is optional and defaults to // 'xyz.openbmc_project.Control.FanSpeed' std::string targetIntf = "xyz.openbmc_project.Control.FanSpeed"; if (sensor.contains("target_interface")) { targetIntf = sensor["target_interface"].get(); } // Target path is optional std::string targetPath; if (sensor.contains("target_path")) { targetPath = sensor["target_path"].get(); } // Factor is optional and defaults to 1 double factor = 1.0; if (sensor.contains("factor")) { factor = sensor["factor"].get(); } // Offset is optional and defaults to 0 int64_t offset = 0; if (sensor.contains("offset")) { offset = sensor["offset"].get(); } // Threshold is optional and defaults to 1 size_t threshold = 1; if (sensor.contains("threshold")) { threshold = sensor["threshold"].get(); } // Ignore being above the allowed max is optional, defaults to not bool ignoreAboveMax = false; if (sensor.contains("ignore_above_max")) { ignoreAboveMax = sensor["ignore_above_max"].get(); } SensorDefinition def{ .name = sensor["name"].get(), .hasTarget = sensor["has_target"].get(), .targetInterface = targetIntf, .targetPath = targetPath, .factor = factor, .offset = offset, .threshold = threshold, .ignoreAboveMax = ignoreAboveMax}; sensorDefs.push_back(std::move(def)); } return sensorDefs; } const std::vector getFanDefs(const json& obj) { std::vector fanDefs; for (const auto& fan : obj["fans"]) { if (!fan.contains("inventory") || !fan.contains("deviation") || !fan.contains("sensors")) { // Log error on missing required parameters log( "Missing required fan monitor definition parameters", entry("REQUIRED_PARAMETERS=%s", "{inventory, deviation, sensors}")); throw std::runtime_error( "Missing required fan monitor definition parameters"); } // Valid deviation range is 0 - 100% auto deviation = fan["deviation"].get(); if (100 < deviation) { auto msg = std::format( "Invalid deviation of {} found, must be between 0 and 100", deviation); log(msg.c_str()); throw std::runtime_error(msg.c_str()); } // Upper deviation defaults to the deviation value and // can also be separately specified. size_t upperDeviation = deviation; if (fan.contains("upper_deviation")) { upperDeviation = fan["upper_deviation"].get(); if (100 < upperDeviation) { auto msg = std::format("Invalid upper_deviation of {} found, must " "be between 0 and 100", upperDeviation); log(msg.c_str()); throw std::runtime_error(msg.c_str()); } } // Construct the sensor definitions for this fan auto sensorDefs = getSensorDefs(fan["sensors"]); // Functional delay is optional and defaults to 0 size_t funcDelay = 0; if (fan.contains("functional_delay")) { funcDelay = fan["functional_delay"].get(); } // Method is optional and defaults to time based functional // determination size_t method = MethodMode::timebased; size_t countInterval = 1; if (fan.contains("method")) { auto methodConf = fan["method"].get(); auto methodFunc = methods.find(methodConf); if (methodFunc != methods.end()) { method = methodFunc->second; } else { // Log error on unsupported method parameter log("Invalid fan method"); throw std::runtime_error("Invalid fan method"); } // Read the count interval value used with the count method. if (method == MethodMode::count) { if (fan.contains("count_interval")) { countInterval = fan["count_interval"].get(); } } } // Timeout defaults to 0 size_t timeout = 0; if (method == MethodMode::timebased) { if (!fan.contains("allowed_out_of_range_time")) { // Log error on missing required parameter log( "Missing required fan monitor definition parameters", entry("REQUIRED_PARAMETER=%s", "{allowed_out_of_range_time}")); throw std::runtime_error( "Missing required fan monitor definition parameters"); } else { timeout = fan["allowed_out_of_range_time"].get(); } } // Monitor start delay is optional and defaults to 0 size_t monitorDelay = 0; if (fan.contains("monitor_start_delay")) { monitorDelay = fan["monitor_start_delay"].get(); } // num_sensors_nonfunc_for_fan_nonfunc is optional and defaults // to zero if not present, meaning the code will not set the // parent fan to nonfunctional based on sensors. size_t nonfuncSensorsCount = 0; if (fan.contains("num_sensors_nonfunc_for_fan_nonfunc")) { nonfuncSensorsCount = fan["num_sensors_nonfunc_for_fan_nonfunc"].get(); } // nonfunc_rotor_error_delay is optional, though it will // default to zero if 'fault_handling' is present. std::optional nonfuncRotorErrorDelay; if (fan.contains("nonfunc_rotor_error_delay")) { nonfuncRotorErrorDelay = fan["nonfunc_rotor_error_delay"].get(); } else if (obj.contains("fault_handling")) { nonfuncRotorErrorDelay = 0; } // fan_missing_error_delay is optional. std::optional fanMissingErrorDelay; if (fan.contains("fan_missing_error_delay")) { fanMissingErrorDelay = fan.at("fan_missing_error_delay").get(); } // Handle optional conditions auto cond = std::optional(); if (fan.contains("condition")) { if (!fan["condition"].contains("name")) { // Log error on missing required parameter log( "Missing required fan monitor condition parameter", entry("REQUIRED_PARAMETER=%s", "{name}")); throw std::runtime_error( "Missing required fan monitor condition parameter"); } auto name = fan["condition"]["name"].get(); // The function for fan monitoring condition // (Must have a supported function within the condition namespace) std::transform(name.begin(), name.end(), name.begin(), tolower); auto handler = conditions.find(name); if (handler != conditions.end()) { cond = handler->second(fan["condition"]); } else { log( "No handler found for configured condition", entry("CONDITION_NAME=%s", name.c_str()), entry("JSON_DUMP=%s", fan["condition"].dump().c_str())); } } // if the fan should be set to functional when plugged in bool setFuncOnPresent = false; if (fan.contains("set_func_on_present")) { setFuncOnPresent = fan["set_func_on_present"].get(); } FanDefinition def{ .name = fan["inventory"].get(), .method = method, .funcDelay = funcDelay, .timeout = timeout, .deviation = deviation, .upperDeviation = upperDeviation, .numSensorFailsForNonfunc = nonfuncSensorsCount, .monitorStartDelay = monitorDelay, .countInterval = countInterval, .nonfuncRotorErrDelay = nonfuncRotorErrorDelay, .fanMissingErrDelay = fanMissingErrorDelay, .sensorList = std::move(sensorDefs), .condition = cond, .funcOnPresent = setFuncOnPresent}; fanDefs.push_back(std::move(def)); } return fanDefs; } PowerRuleState getPowerOffPowerRuleState(const json& powerOffConfig) { // The state is optional and defaults to runtime PowerRuleState ruleState{PowerRuleState::runtime}; if (powerOffConfig.contains("state")) { auto state = powerOffConfig.at("state").get(); if (state == "at_pgood") { ruleState = PowerRuleState::atPgood; } else if (state != "runtime") { auto msg = std::format("Invalid power off state entry {}", state); log(msg.c_str()); throw std::runtime_error(msg.c_str()); } } return ruleState; } std::unique_ptr getPowerOffCause(const json& powerOffConfig) { std::unique_ptr cause; if (!powerOffConfig.contains("count") || !powerOffConfig.contains("cause")) { const auto msg = "Missing 'count' or 'cause' entries in power off config"; log(msg); throw std::runtime_error(msg); } auto count = powerOffConfig.at("count").get(); auto powerOffCause = powerOffConfig.at("cause").get(); const std::map()>> causes{ {"missing_fan_frus", [count]() { return std::make_unique(count); }}, {"nonfunc_fan_rotors", [count]() { return std::make_unique(count); }}, {"fan_frus_with_nonfunc_rotors", [count]() { return std::make_unique(count); }}}; auto it = causes.find(powerOffCause); if (it != causes.end()) { cause = it->second(); } else { auto msg = std::format("Invalid power off cause {} in power off config JSON", powerOffCause); log(msg.c_str()); throw std::runtime_error(msg.c_str()); } return cause; } std::unique_ptr getPowerOffAction(const json& powerOffConfig, std::shared_ptr& powerInterface, PowerOffAction::PrePowerOffFunc& func) { std::unique_ptr action; if (!powerOffConfig.contains("type")) { const auto msg = "Missing 'type' entry in power off config"; log(msg); throw std::runtime_error(msg); } auto type = powerOffConfig.at("type").get(); if (((type == "hard") || (type == "soft")) && !powerOffConfig.contains("delay")) { const auto msg = "Missing 'delay' entry in power off config"; log(msg); throw std::runtime_error(msg); } else if ((type == "epow") && (!powerOffConfig.contains("service_mode_delay") || !powerOffConfig.contains("meltdown_delay"))) { const auto msg = "Missing 'service_mode_delay' or 'meltdown_delay' " "entry in power off config"; log(msg); throw std::runtime_error(msg); } if (type == "hard") { action = std::make_unique( powerOffConfig.at("delay").get(), powerInterface, func); } else if (type == "soft") { action = std::make_unique( powerOffConfig.at("delay").get(), powerInterface, func); } else if (type == "epow") { action = std::make_unique( powerOffConfig.at("service_mode_delay").get(), powerOffConfig.at("meltdown_delay").get(), powerInterface, func); } else { auto msg = std::format("Invalid 'type' entry {} in power off config", type); log(msg.c_str()); throw std::runtime_error(msg.c_str()); } return action; } std::vector> getPowerOffRules( const json& obj, std::shared_ptr& powerInterface, PowerOffAction::PrePowerOffFunc& func) { std::vector> rules; if (!(obj.contains("fault_handling") && obj.at("fault_handling").contains("power_off_config"))) { return rules; } for (const auto& config : obj.at("fault_handling").at("power_off_config")) { auto state = getPowerOffPowerRuleState(config); auto cause = getPowerOffCause(config); auto action = getPowerOffAction(config, powerInterface, func); auto rule = std::make_unique( std::move(state), std::move(cause), std::move(action)); rules.push_back(std::move(rule)); } return rules; } std::optional getNumNonfuncRotorsBeforeError(const json& obj) { std::optional num; if (obj.contains("fault_handling")) { // Defaults to 1 if not present inside of 'fault_handling'. num = obj.at("fault_handling") .value("num_nonfunc_rotors_before_error", 1); } return num; } } // namespace phosphor::fan::monitor