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