1 /** 2 * Copyright © 2020 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 "error_reporter.hpp" 17 18 #include "logging.hpp" 19 #include "psensor.hpp" 20 #include "utility.hpp" 21 22 #include <fmt/format.h> 23 #include <unistd.h> 24 25 #include <phosphor-logging/log.hpp> 26 #include <xyz/openbmc_project/Logging/Create/server.hpp> 27 #include <xyz/openbmc_project/Logging/Entry/server.hpp> 28 29 namespace phosphor::fan::presence 30 { 31 32 using json = nlohmann::json; 33 using namespace phosphor::logging; 34 using namespace sdbusplus::bus::match; 35 using namespace std::literals::string_literals; 36 using namespace std::chrono; 37 namespace fs = std::filesystem; 38 39 const auto itemIface = "xyz.openbmc_project.Inventory.Item"s; 40 const auto invPrefix = "/xyz/openbmc_project/inventory"s; 41 const auto loggingPath = "/xyz/openbmc_project/logging"; 42 const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create"; 43 44 ErrorReporter::ErrorReporter( 45 sdbusplus::bus::bus& bus, 46 const std::vector< 47 std::tuple<Fan, std::vector<std::unique_ptr<PresenceSensor>>>>& fans) : 48 _bus(bus), 49 _event(sdeventplus::Event::get_default()) 50 { 51 // If different methods to check the power state are needed across the 52 // various platforms, the method/class to use could be read out of JSON 53 // or set with a compilation flag. 54 _powerState = std::make_unique<PGoodState>( 55 bus, std::bind(std::mem_fn(&ErrorReporter::powerStateChanged), this, 56 std::placeholders::_1)); 57 58 for (const auto& fan : fans) 59 { 60 const auto& fanData = std::get<0>(fan); 61 62 // Only deal with fans that have an error time defined. 63 if (std::get<std::optional<size_t>>(fanData)) 64 { 65 auto path = invPrefix + std::get<1>(fanData); 66 67 // Register for fan presence changes, get their initial states, 68 // and create the fan missing timers. 69 70 _matches.emplace_back( 71 _bus, rules::propertiesChanged(path, itemIface), 72 std::bind(std::mem_fn(&ErrorReporter::presenceChanged), this, 73 std::placeholders::_1)); 74 75 _fanStates.emplace(path, getPresence(fanData)); 76 77 auto timer = std::make_unique< 78 sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>( 79 _event, 80 std::bind(std::mem_fn(&ErrorReporter::fanMissingTimerExpired), 81 this, path)); 82 83 seconds errorTime{std::get<std::optional<size_t>>(fanData).value()}; 84 85 _fanMissingTimers.emplace( 86 path, std::make_tuple(std::move(timer), std::move(errorTime))); 87 } 88 } 89 90 // If power is already on, check for currently missing fans. 91 if (_powerState->isPowerOn()) 92 { 93 powerStateChanged(true); 94 } 95 } 96 97 void ErrorReporter::presenceChanged(sdbusplus::message::message& msg) 98 { 99 bool present; 100 auto fanPath = msg.get_path(); 101 std::string interface; 102 std::map<std::string, std::variant<bool>> properties; 103 104 msg.read(interface, properties); 105 106 auto presentProp = properties.find("Present"); 107 if (presentProp != properties.end()) 108 { 109 present = std::get<bool>(presentProp->second); 110 if (_fanStates[fanPath] != present) 111 { 112 getLogger().log(fmt::format("Fan {} presence state change to {}", 113 fanPath, present)); 114 115 _fanStates[fanPath] = present; 116 checkFan(fanPath); 117 } 118 } 119 } 120 121 void ErrorReporter::checkFan(const std::string& fanPath) 122 { 123 auto& timer = std::get<0>(_fanMissingTimers[fanPath]); 124 125 if (!_fanStates[fanPath]) 126 { 127 // Fan is missing. If power is on, start the timer. 128 // If power is off, stop a running timer. 129 if (_powerState->isPowerOn()) 130 { 131 timer->restartOnce(std::get<seconds>(_fanMissingTimers[fanPath])); 132 } 133 else if (timer->isEnabled()) 134 { 135 timer->setEnabled(false); 136 } 137 } 138 else 139 { 140 // Fan is present. Stop a running timer. 141 if (timer->isEnabled()) 142 { 143 timer->setEnabled(false); 144 } 145 } 146 } 147 148 void ErrorReporter::fanMissingTimerExpired(const std::string& fanPath) 149 { 150 getLogger().log( 151 fmt::format("Creating event log for missing fan {}", fanPath), 152 Logger::error); 153 154 std::map<std::string, std::string> additionalData; 155 additionalData.emplace("_PID", std::to_string(getpid())); 156 additionalData.emplace("CALLOUT_INVENTORY_PATH", fanPath); 157 158 auto severity = 159 sdbusplus::xyz::openbmc_project::Logging::server::convertForMessage( 160 sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level:: 161 Error); 162 163 // Save our logs to a temp file and get the file descriptor 164 // so it can be passed in as FFDC data. 165 auto logFile = getLogger().saveToTempFile(); 166 util::FileDescriptor fd{-1}; 167 fd.open(logFile, O_RDONLY); 168 169 std::vector<std::tuple< 170 sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat, 171 uint8_t, uint8_t, sdbusplus::message::unix_fd>> 172 ffdc; 173 174 ffdc.emplace_back(sdbusplus::xyz::openbmc_project::Logging::server::Create:: 175 FFDCFormat::Text, 176 0x01, 0x01, fd()); 177 178 try 179 { 180 util::SDBusPlus::lookupAndCallMethod( 181 loggingPath, loggingCreateIface, "CreateWithFFDCFiles", 182 "xyz.openbmc_project.Fan.Error.Missing", severity, additionalData, 183 ffdc); 184 } 185 catch (const util::DBusError& e) 186 { 187 getLogger().log( 188 fmt::format( 189 "Call to create an error log for missing fan {} failed: {}", 190 fanPath, e.what()), 191 Logger::error); 192 fs::remove(logFile); 193 throw; 194 } 195 196 fs::remove(logFile); 197 } 198 199 void ErrorReporter::powerStateChanged(bool powerState) 200 { 201 if (powerState) 202 { 203 // If there are fans already missing, log it. 204 auto missing = std::count_if( 205 _fanStates.begin(), _fanStates.end(), 206 [](const auto& fanState) { return fanState.second == false; }); 207 208 if (missing) 209 { 210 getLogger().log( 211 fmt::format("At power on, there are {} missing fans", missing)); 212 } 213 } 214 215 std::for_each( 216 _fanStates.begin(), _fanStates.end(), 217 [this](const auto& fanState) { this->checkFan(fanState.first); }); 218 } 219 220 } // namespace phosphor::fan::presence 221