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