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