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