xref: /openbmc/phosphor-led-manager/manager/manager.cpp (revision 68e204c6154634830c9f435ada5bd8c381539dcc)
1 #include "config.h"
2 
3 #include "manager.hpp"
4 
5 #include <phosphor-logging/lg2.hpp>
6 #include <sdbusplus/exception.hpp>
7 #include <xyz/openbmc_project/Led/Physical/server.hpp>
8 
9 #include <algorithm>
10 #include <chrono>
11 #include <iostream>
12 #include <string>
13 
14 namespace phosphor
15 {
16 namespace led
17 {
18 
19 // apply the led action to the map
20 static void applyGroupAction(std::map<LedName, Layout::LedAction>& newState,
21                              Layout::LedAction action)
22 {
23     if (!newState.contains(action.name))
24     {
25         newState[action.name] = action;
26         return;
27     }
28 
29     auto currentAction = newState[action.name];
30 
31     const bool hasPriority = currentAction.priority.has_value();
32 
33     if (hasPriority && currentAction.action == action.priority)
34     {
35         // if the current action is already the priority action,
36         // we cannot override it
37         return;
38     }
39 
40     newState[action.name] = action;
41 }
42 
43 // create the resulting new map from all currently asserted groups
44 static auto getNewMapWithGroupPriorities(
45     std::set<const Layout::GroupLayout*, Layout::CompareGroupLayout> sorted)
46     -> std::map<LedName, Layout::LedAction>
47 {
48     std::map<LedName, Layout::LedAction> newState;
49 
50     // update the new map with the desired state
51     for (const auto* it : sorted)
52     {
53         // apply all led actions of that group to the map
54         for (const Layout::LedAction& action : it->actionSet)
55         {
56             newState[action.name] = action;
57         }
58     }
59     return newState;
60 }
61 
62 static std::map<LedName, Layout::LedAction> getNewMapWithLEDPriorities(
63     std::set<const Layout::GroupLayout*> assertedGroups)
64 {
65     std::map<LedName, Layout::LedAction> newState;
66     // update the new map with the desired state
67     for (const Layout::GroupLayout* it : assertedGroups)
68     {
69         // apply all led actions of that group to the map
70         for (const Layout::LedAction& action : it->actionSet)
71         {
72             applyGroupAction(newState, action);
73         }
74     }
75     return newState;
76 }
77 
78 // create the resulting new map from all currently asserted groups
79 std::map<LedName, Layout::LedAction> Manager::getNewMap(
80     std::set<const Layout::GroupLayout*> assertedGroups)
81 {
82     std::map<LedName, Layout::LedAction> newState;
83 
84     std::set<const Layout::GroupLayout*, Layout::CompareGroupLayout> sorted;
85 
86     bool groupPriorities = false;
87 
88     for (const Layout::GroupLayout* it : assertedGroups)
89     {
90         sorted.insert(it);
91 
92         if (it->priority != 0)
93         {
94             groupPriorities = true;
95         }
96     }
97 
98     if (groupPriorities)
99     {
100         newState = getNewMapWithGroupPriorities(sorted);
101     }
102     else
103     {
104         newState = getNewMapWithLEDPriorities(assertedGroups);
105     }
106 
107     return newState;
108 }
109 
110 // Assert -or- De-assert
111 bool Manager::setGroupState(const std::string& path, bool assert,
112                             ActionSet& ledsAssert, ActionSet& ledsDeAssert)
113 {
114     if (assert)
115     {
116         assertedGroups.insert(&ledMap.at(path));
117     }
118     else
119     {
120         if (assertedGroups.contains(&ledMap.at(path)))
121         {
122             assertedGroups.erase(&ledMap.at(path));
123         }
124     }
125 
126     // create the new map from the asserted groups
127     auto newState = getNewMap(assertedGroups);
128 
129     // the ledsAssert are those that are in the new map and change state
130     // + those in the new map and not in the old map
131     for (const auto& [name, action] : newState)
132     {
133         if (ledStateMap.contains(name))
134         {
135             // check if the led action has changed
136             auto& currentAction = ledStateMap[name];
137 
138             if (currentAction.action == action.action)
139             {
140                 continue;
141             }
142         }
143 
144         ledsAssert.insert(action);
145     }
146 
147     // the ledsDeAssert are those in the old map but not in the new map
148     for (const auto& [name, action] : ledStateMap)
149     {
150         if (!newState.contains(name))
151         {
152             ledsDeAssert.insert(action);
153         }
154     }
155 
156     ledStateMap = newState;
157 
158     // If we survive, then set the state accordingly.
159     return assert;
160 }
161 
162 void Manager::setLampTestCallBack(
163     std::function<bool(ActionSet& ledsAssert, ActionSet& ledsDeAssert)>
164         callBack)
165 {
166     lampTestCallBack = callBack;
167 }
168 
169 /** @brief Run through the map and apply action on the LEDs */
170 void Manager::driveLEDs(ActionSet& ledsAssert, ActionSet& ledsDeAssert)
171 {
172     if constexpr (USE_LAMP_TEST)
173     {
174         // Use the lampTestCallBack method and trigger the callback method in
175         // the lamp test(processLEDUpdates), in this way, all lamp test
176         // operations are performed in the lamp test class.
177         if (lampTestCallBack(ledsAssert, ledsDeAssert))
178         {
179             return;
180         }
181     }
182 
183     ActionSet newReqChangedLeds;
184     std::vector<std::pair<ActionSet&, ActionSet&>> actionsVec = {
185         {reqLedsAssert, ledsAssert}, {reqLedsDeAssert, ledsDeAssert}};
186 
187     timer.setEnabled(false);
188     std::set_union(ledsAssert.begin(), ledsAssert.end(), ledsDeAssert.begin(),
189                    ledsDeAssert.end(),
190                    std::inserter(newReqChangedLeds, newReqChangedLeds.begin()),
191                    ledLess);
192 
193     // prepare reqLedsAssert & reqLedsDeAssert
194     for (auto pair : actionsVec)
195     {
196         ActionSet tmpSet;
197 
198         // Discard current required LED actions, if these LEDs have new actions
199         // in newReqChangedLeds.
200         std::set_difference(pair.first.begin(), pair.first.end(),
201                             newReqChangedLeds.begin(), newReqChangedLeds.end(),
202                             std::inserter(tmpSet, tmpSet.begin()), ledLess);
203 
204         // Union the remaining LED actions with new LED actions.
205         pair.first.clear();
206         std::set_union(tmpSet.begin(), tmpSet.end(), pair.second.begin(),
207                        pair.second.end(),
208                        std::inserter(pair.first, pair.first.begin()), ledLess);
209     }
210 
211     driveLedsHandler();
212     return;
213 }
214 
215 // Calls into driving physical LED post choosing the action
216 int Manager::drivePhysicalLED(const std::string& objPath, Layout::Action action,
217                               uint8_t dutyOn, uint16_t period)
218 {
219     try
220     {
221         // If Blink, set its property
222         if (action == Layout::Action::Blink)
223         {
224             PropertyValue dutyOnValue{dutyOn};
225             PropertyValue periodValue{period};
226 
227             phosphor::led::utils::DBusHandler::setProperty(
228                 objPath, phyLedIntf, "DutyOn", dutyOnValue);
229             phosphor::led::utils::DBusHandler::setProperty(
230                 objPath, phyLedIntf, "Period", periodValue);
231         }
232 
233         PropertyValue actionValue{getPhysicalAction(action)};
234         phosphor::led::utils::DBusHandler::setProperty(objPath, phyLedIntf,
235                                                        "State", actionValue);
236     }
237     catch (const sdbusplus::exception_t& e)
238     {
239         // This can be a really spammy event log, so we rate limit it to once an
240         // hour per LED.
241         auto now = std::chrono::steady_clock::now();
242 
243         if (auto it = physicalLEDErrors.find(objPath);
244             it != physicalLEDErrors.end())
245         {
246             using namespace std::literals::chrono_literals;
247             if ((now - it->second) < 1h)
248             {
249                 return -1;
250             }
251         }
252 
253         lg2::error(
254             "Error setting property for physical LED, ERROR = {ERROR}, OBJECT_PATH = {PATH}",
255             "ERROR", e, "PATH", objPath);
256         physicalLEDErrors[objPath] = now;
257         return -1;
258     }
259 
260     return 0;
261 }
262 
263 /** @brief Returns action string based on enum */
264 std::string Manager::getPhysicalAction(Layout::Action action)
265 {
266     namespace server = sdbusplus::xyz::openbmc_project::Led::server;
267 
268     // TODO: openbmc/phosphor-led-manager#5
269     //    Somehow need to use the generated Action enum than giving one
270     //    in ledlayout.
271     if (action == Layout::Action::On)
272     {
273         return server::convertForMessage(server::Physical::Action::On);
274     }
275     else if (action == Layout::Action::Blink)
276     {
277         return server::convertForMessage(server::Physical::Action::Blink);
278     }
279     else
280     {
281         return server::convertForMessage(server::Physical::Action::Off);
282     }
283 }
284 
285 void Manager::driveLedsHandler(void)
286 {
287     ActionSet failedLedsAssert;
288     ActionSet failedLedsDeAssert;
289 
290     // This order of LED operation is important.
291     for (const auto& it : reqLedsDeAssert)
292     {
293         std::string objPath = std::string(phyLedPath) + it.name;
294         lg2::debug("De-Asserting LED, NAME = {NAME}, ACTION = {ACTION}", "NAME",
295                    it.name, "ACTION", it.action);
296         if (drivePhysicalLED(objPath, Layout::Action::Off, it.dutyOn,
297                              it.period) != 0)
298         {
299             failedLedsDeAssert.insert(it);
300         }
301     }
302 
303     for (const auto& it : reqLedsAssert)
304     {
305         std::string objPath = std::string(phyLedPath) + it.name;
306         lg2::debug("Asserting LED, NAME = {NAME}, ACTION = {ACTION}", "NAME",
307                    it.name, "ACTION", it.action);
308         if (drivePhysicalLED(objPath, it.action, it.dutyOn, it.period) != 0)
309         {
310             failedLedsAssert.insert(it);
311         }
312     }
313 
314     reqLedsAssert = failedLedsAssert;
315     reqLedsDeAssert = failedLedsDeAssert;
316 
317     if (reqLedsDeAssert.empty() && reqLedsAssert.empty())
318     {
319         timer.setEnabled(false);
320     }
321     else
322     {
323         timer.restartOnce(std::chrono::seconds(1));
324     }
325 
326     return;
327 }
328 
329 } // namespace led
330 } // namespace phosphor
331