xref: /openbmc/phosphor-fan-presence/presence/error_reporter.cpp (revision 76925cc05ce1297211f07fe54956411ecff4bc9a)
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