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