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