1 /*
2 // Copyright (c) 2019 Intel 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 
17 #include "callback_manager.hpp"
18 
19 #include <boost/asio/io_context.hpp>
20 #include <boost/asio/steady_timer.hpp>
21 #include <boost/container/flat_map.hpp>
22 #include <sdbusplus/asio/connection.hpp>
23 #include <sdbusplus/asio/object_server.hpp>
24 
25 #include <variant>
26 
27 constexpr const char* fatalLedPath =
28     "/xyz/openbmc_project/led/groups/status_critical";
29 constexpr const char* criticalLedPath =
30     "/xyz/openbmc_project/led/groups/status_non_critical";
31 constexpr const char* warningLedPath =
32     "/xyz/openbmc_project/led/groups/status_degraded";
33 constexpr const char* okLedPath = "/xyz/openbmc_project/led/groups/status_ok";
34 
35 constexpr const char* ledIface = "xyz.openbmc_project.Led.Group";
36 constexpr const char* ledAssertProp = "Asserted";
37 constexpr const char* ledManagerBusname =
38     "xyz.openbmc_project.LED.GroupManager";
39 
40 std::unique_ptr<AssociationManager> associationManager;
41 
42 enum class StatusSetting
43 {
44     none,
45     ok,
46     warn,
47     critical,
48     fatal
49 };
50 
51 constexpr const bool debug = false;
52 
53 // final led state tracking
54 StatusSetting currentPriority = StatusSetting::none;
55 
56 // maps of <object-path, <property, asserted>>
57 boost::container::flat_map<std::string,
58                            boost::container::flat_map<std::string, bool>>
59     fatalAssertMap;
60 boost::container::flat_map<std::string,
61                            boost::container::flat_map<std::string, bool>>
62     criticalAssertMap;
63 boost::container::flat_map<std::string,
64                            boost::container::flat_map<std::string, bool>>
65     warningAssertMap;
66 
67 std::vector<std::string> assertedInMap(
68     const boost::container::flat_map<
69         std::string, boost::container::flat_map<std::string, bool>>& map)
70 {
71     std::vector<std::string> ret;
72     // if any of the properties are true, return true
73     for (const auto& pair : map)
74     {
75         for (const auto& item : pair.second)
76         {
77             if (item.second)
78             {
79                 ret.push_back(pair.first);
80             }
81         }
82     }
83     return ret;
84 }
85 
86 void updateLedStatus(std::shared_ptr<sdbusplus::asio::connection>& conn,
87                      bool forceRefresh = false)
88 {
89     std::vector<std::string> fatalVector = assertedInMap(fatalAssertMap);
90     bool fatal = fatalVector.size();
91 
92     std::vector<std::string> criticalVector = assertedInMap(criticalAssertMap);
93     bool critical = criticalVector.size();
94 
95     std::vector<std::string> warningVector = assertedInMap(warningAssertMap);
96     bool warn = warningVector.size();
97 
98     associationManager->setLocalAssociations(fatalVector, criticalVector,
99                                              warningVector);
100 
101     StatusSetting last = currentPriority;
102     std::vector<std::pair<std::string, std::variant<bool>>> ledsToSet;
103     if (forceRefresh)
104     {
105         ledsToSet.push_back(std::make_pair(fatalLedPath, false));
106         ledsToSet.push_back(std::make_pair(criticalLedPath, false));
107         ledsToSet.push_back(std::make_pair(warningLedPath, false));
108         ledsToSet.push_back(std::make_pair(okLedPath, false));
109         for (const auto& ledPair : ledsToSet)
110         {
111             conn->async_method_call(
112                 [ledPair](const boost::system::error_code ec) {
113                     if (ec)
114                     {
115                         std::cerr << "Cannot set " << ledPair.first << " to "
116                                   << std::boolalpha
117                                   << std::get<bool>(ledPair.second) << "\n";
118                     }
119                     if constexpr (debug)
120                     {
121                         std::cerr << "Set " << ledPair.first << " to "
122                                   << std::boolalpha
123                                   << std::get<bool>(ledPair.second) << "\n";
124                     }
125                 },
126                 ledManagerBusname, ledPair.first,
127                 "org.freedesktop.DBus.Properties", "Set", ledIface,
128                 ledAssertProp, ledPair.second);
129         }
130     }
131     if (fatal)
132     {
133         currentPriority = StatusSetting::fatal;
134     }
135     else if (critical)
136     {
137         currentPriority = StatusSetting::critical;
138     }
139     else if (warn)
140     {
141         currentPriority = StatusSetting::warn;
142     }
143     else
144     {
145         currentPriority = StatusSetting::ok;
146     }
147 
148     if (last != currentPriority || forceRefresh)
149     {
150         switch (currentPriority)
151         {
152             case (StatusSetting::fatal):
153             {
154                 ledsToSet.push_back(std::make_pair(fatalLedPath, true));
155                 ledsToSet.push_back(std::make_pair(criticalLedPath, false));
156                 ledsToSet.push_back(std::make_pair(warningLedPath, false));
157                 ledsToSet.push_back(std::make_pair(okLedPath, false));
158                 break;
159             }
160             case (StatusSetting::critical):
161             {
162                 ledsToSet.push_back(std::make_pair(fatalLedPath, false));
163                 ledsToSet.push_back(std::make_pair(criticalLedPath, true));
164                 ledsToSet.push_back(std::make_pair(warningLedPath, false));
165                 ledsToSet.push_back(std::make_pair(okLedPath, false));
166                 break;
167             }
168             case (StatusSetting::warn):
169             {
170                 ledsToSet.push_back(std::make_pair(fatalLedPath, false));
171                 ledsToSet.push_back(std::make_pair(criticalLedPath, false));
172                 ledsToSet.push_back(std::make_pair(warningLedPath, true));
173                 ledsToSet.push_back(std::make_pair(okLedPath, false));
174                 break;
175             }
176             case (StatusSetting::ok):
177             {
178                 ledsToSet.push_back(std::make_pair(fatalLedPath, false));
179                 ledsToSet.push_back(std::make_pair(criticalLedPath, false));
180                 ledsToSet.push_back(std::make_pair(warningLedPath, false));
181                 ledsToSet.push_back(std::make_pair(okLedPath, true));
182                 break;
183             }
184         }
185     }
186 
187     for (const auto& ledPair : ledsToSet)
188     {
189         conn->async_method_call(
190             [ledPair](const boost::system::error_code ec) {
191                 if (ec)
192                 {
193                     std::cerr << "Cannot set " << ledPair.first << " to "
194                               << std::boolalpha
195                               << std::get<bool>(ledPair.second) << "\n";
196                 }
197                 if constexpr (debug)
198                 {
199                     std::cerr << "Set " << ledPair.first << " to "
200                               << std::boolalpha
201                               << std::get<bool>(ledPair.second) << "\n";
202                 }
203             },
204             ledManagerBusname, ledPair.first, "org.freedesktop.DBus.Properties",
205             "Set", ledIface, ledAssertProp, ledPair.second);
206     }
207 }
208 
209 void createThresholdMatch(std::shared_ptr<sdbusplus::asio::connection>& conn)
210 {
211 
212     static sdbusplus::bus::match_t match(
213         static_cast<sdbusplus::bus_t&>(*conn),
214         "type='signal',member='ThresholdAsserted'",
215         [&conn](sdbusplus::message_t& message) {
216             std::string sensorName;
217             std::string thresholdInterface;
218             std::string event;
219             bool assert;
220             double assertValue;
221 
222             try
223             {
224                 message.read(sensorName, thresholdInterface, event, assert,
225                              assertValue);
226             }
227             catch (sdbusplus::exception_t&)
228             {
229                 return;
230             }
231             if constexpr (debug)
232             {
233                 std::cerr << "Threshold callback: SensorName = " << sensorName
234                           << ", Event = " << event << ", Asserted = " << assert
235                           << "\n";
236             }
237 
238             if (event == "CriticalAlarmLow")
239             {
240                 criticalAssertMap[message.get_path()]["low"] = assert;
241             }
242             else if (event == "CriticalAlarmHigh")
243             {
244                 criticalAssertMap[message.get_path()]["high"] = assert;
245             }
246             else if (event == "WarningAlarmLow")
247             {
248                 warningAssertMap[message.get_path()]["low"] = assert;
249             }
250             else if (event == "WarningAlarmHigh")
251             {
252                 warningAssertMap[message.get_path()]["high"] = assert;
253             }
254 
255             associationManager->setSensorAssociations(
256                 assertedInMap(criticalAssertMap),
257                 assertedInMap(warningAssertMap));
258 
259             updateLedStatus(conn);
260         });
261 }
262 
263 void createAssociationMatch(std::shared_ptr<sdbusplus::asio::connection>& conn)
264 {
265     static sdbusplus::bus::match_t match(
266         static_cast<sdbusplus::bus_t&>(*conn),
267         "type='signal',interface='org.freedesktop.DBus.Properties',"
268         "arg0namespace='" +
269             std::string(associationIface) + "'",
270         [&conn](sdbusplus::message_t& message) {
271             if (message.get_path() == rootPath)
272             {
273                 return; // it's us
274             }
275             std::string objectName;
276             boost::container::flat_map<std::string,
277                                        std::variant<std::vector<Association>>>
278                 values;
279             try
280             {
281                 message.read(objectName, values);
282             }
283             catch (sdbusplus::exception_t&)
284             {
285                 return;
286             }
287 
288             if constexpr (debug)
289             {
290                 std::cerr << "Association callback " << message.get_path()
291                           << "\n";
292             }
293 
294             auto findAssociations = values.find("Associations");
295             if (findAssociations == values.end())
296             {
297                 return;
298             }
299             const std::vector<Association>* associations =
300                 std::get_if<std::vector<Association>>(
301                     &findAssociations->second);
302 
303             if (associations == nullptr)
304             {
305                 std::cerr << "Illegal Association on " << message.get_path()
306                           << "\n";
307                 return;
308             }
309 
310             bool localWarning = false;
311             bool localCritical = false;
312             bool globalWarning = false;
313             bool globalCritical = false;
314 
315             for (const auto& [forward, reverse, path] : *associations)
316             {
317                 if (path == rootPath)
318                 {
319                     globalWarning = globalWarning ? true : reverse == "warning";
320                     globalCritical =
321                         globalCritical ? true : reverse == "critical";
322 
323                     if constexpr (1)
324                     {
325                         std::cerr << "got global ";
326                     }
327                 }
328                 else
329                 {
330                     localWarning = localWarning ? true : reverse == "warning";
331                     localCritical =
332                         localCritical ? true : reverse == "critical";
333                 }
334                 if (globalCritical && localCritical)
335                 {
336                     break;
337                 }
338             }
339 
340             bool fatal = globalCritical && localCritical;
341             bool critical = globalWarning && localCritical;
342             bool warning = globalWarning && !critical;
343 
344             fatalAssertMap[message.get_path()]["association"] = fatal;
345             criticalAssertMap[message.get_path()]["association"] = critical;
346             warningAssertMap[message.get_path()]["association"] = warning;
347 
348             updateLedStatus(conn);
349         });
350 }
351 
352 int main(int argc, char** argv)
353 {
354     boost::asio::io_context io;
355     auto conn = std::make_shared<sdbusplus::asio::connection>(io);
356     conn->request_name("xyz.openbmc_project.CallbackManager");
357     sdbusplus::asio::object_server objServer(conn);
358     std::shared_ptr<sdbusplus::asio::dbus_interface> rootIface =
359         objServer.add_interface(rootPath,
360                                 "xyz.openbmc_project.CallbackManager");
361     rootIface->register_method("RetriggerLEDUpdate",
362                                [&conn]() { updateLedStatus(conn, true); });
363     rootIface->initialize();
364 
365     std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
366         objServer.add_interface(rootPath, globalInventoryIface);
367     inventoryIface->initialize();
368 
369     associationManager = std::make_unique<AssociationManager>(objServer, conn);
370 
371     createThresholdMatch(conn);
372     createAssociationMatch(conn);
373     updateLedStatus(conn);
374 
375     io.run();
376 
377     return 0;
378 }
379