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
applyGroupAction(std::map<LedName,Layout::LedAction> & newState,Layout::LedAction action)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
getNewMapWithGroupPriorities(std::set<const Layout::GroupLayout *,Layout::CompareGroupLayout> sorted)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
getNewMapWithLEDPriorities(std::set<const Layout::GroupLayout * > assertedGroups)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
getNewMap(std::set<const Layout::GroupLayout * > assertedGroups)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
setGroupState(const std::string & path,bool assert,ActionSet & ledsAssert,ActionSet & ledsDeAssert)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
setLampTestCallBack(std::function<bool (ActionSet & ledsAssert,ActionSet & ledsDeAssert)> callBack)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 */
driveLEDs(ActionSet & ledsAssert,ActionSet & ledsDeAssert)170 void Manager::driveLEDs(ActionSet& ledsAssert, ActionSet& ledsDeAssert)
171 {
172 #ifdef USE_LAMP_TEST
173 // Use the lampTestCallBack method and trigger the callback method in the
174 // lamp test(processLEDUpdates), in this way, all lamp test operations
175 // are performed in the lamp test class.
176 if (lampTestCallBack(ledsAssert, ledsDeAssert))
177 {
178 return;
179 }
180 #endif
181 ActionSet newReqChangedLeds;
182 std::vector<std::pair<ActionSet&, ActionSet&>> actionsVec = {
183 {reqLedsAssert, ledsAssert}, {reqLedsDeAssert, ledsDeAssert}};
184
185 timer.setEnabled(false);
186 std::set_union(ledsAssert.begin(), ledsAssert.end(), ledsDeAssert.begin(),
187 ledsDeAssert.end(),
188 std::inserter(newReqChangedLeds, newReqChangedLeds.begin()),
189 ledLess);
190
191 // prepare reqLedsAssert & reqLedsDeAssert
192 for (auto pair : actionsVec)
193 {
194 ActionSet tmpSet;
195
196 // Discard current required LED actions, if these LEDs have new actions
197 // in newReqChangedLeds.
198 std::set_difference(pair.first.begin(), pair.first.end(),
199 newReqChangedLeds.begin(), newReqChangedLeds.end(),
200 std::inserter(tmpSet, tmpSet.begin()), ledLess);
201
202 // Union the remaining LED actions with new LED actions.
203 pair.first.clear();
204 std::set_union(tmpSet.begin(), tmpSet.end(), pair.second.begin(),
205 pair.second.end(),
206 std::inserter(pair.first, pair.first.begin()), ledLess);
207 }
208
209 driveLedsHandler();
210 return;
211 }
212
213 // Calls into driving physical LED post choosing the action
drivePhysicalLED(const std::string & objPath,Layout::Action action,uint8_t dutyOn,uint16_t period)214 int Manager::drivePhysicalLED(const std::string& objPath, Layout::Action action,
215 uint8_t dutyOn, uint16_t period)
216 {
217 try
218 {
219 // If Blink, set its property
220 if (action == Layout::Action::Blink)
221 {
222 PropertyValue dutyOnValue{dutyOn};
223 PropertyValue periodValue{period};
224
225 phosphor::led::utils::DBusHandler::setProperty(
226 objPath, phyLedIntf, "DutyOn", dutyOnValue);
227 phosphor::led::utils::DBusHandler::setProperty(
228 objPath, phyLedIntf, "Period", periodValue);
229 }
230
231 PropertyValue actionValue{getPhysicalAction(action)};
232 phosphor::led::utils::DBusHandler::setProperty(objPath, phyLedIntf,
233 "State", actionValue);
234 }
235 catch (const sdbusplus::exception_t& e)
236 {
237 // This can be a really spammy event log, so we rate limit it to once an
238 // hour per LED.
239 auto now = std::chrono::steady_clock::now();
240
241 if (auto it = physicalLEDErrors.find(objPath);
242 it != physicalLEDErrors.end())
243 {
244 using namespace std::literals::chrono_literals;
245 if ((now - it->second) < 1h)
246 {
247 return -1;
248 }
249 }
250
251 lg2::error(
252 "Error setting property for physical LED, ERROR = {ERROR}, OBJECT_PATH = {PATH}",
253 "ERROR", e, "PATH", objPath);
254 physicalLEDErrors[objPath] = now;
255 return -1;
256 }
257
258 return 0;
259 }
260
261 /** @brief Returns action string based on enum */
getPhysicalAction(Layout::Action action)262 std::string Manager::getPhysicalAction(Layout::Action action)
263 {
264 namespace server = sdbusplus::xyz::openbmc_project::Led::server;
265
266 // TODO: openbmc/phosphor-led-manager#5
267 // Somehow need to use the generated Action enum than giving one
268 // in ledlayout.
269 if (action == Layout::Action::On)
270 {
271 return server::convertForMessage(server::Physical::Action::On);
272 }
273 else if (action == Layout::Action::Blink)
274 {
275 return server::convertForMessage(server::Physical::Action::Blink);
276 }
277 else
278 {
279 return server::convertForMessage(server::Physical::Action::Off);
280 }
281 }
282
driveLedsHandler(void)283 void Manager::driveLedsHandler(void)
284 {
285 ActionSet failedLedsAssert;
286 ActionSet failedLedsDeAssert;
287
288 // This order of LED operation is important.
289 for (const auto& it : reqLedsDeAssert)
290 {
291 std::string objPath = std::string(phyLedPath) + it.name;
292 lg2::debug("De-Asserting LED, NAME = {NAME}, ACTION = {ACTION}", "NAME",
293 it.name, "ACTION", it.action);
294 if (drivePhysicalLED(objPath, Layout::Action::Off, it.dutyOn,
295 it.period) != 0)
296 {
297 failedLedsDeAssert.insert(it);
298 }
299 }
300
301 for (const auto& it : reqLedsAssert)
302 {
303 std::string objPath = std::string(phyLedPath) + it.name;
304 lg2::debug("Asserting LED, NAME = {NAME}, ACTION = {ACTION}", "NAME",
305 it.name, "ACTION", it.action);
306 if (drivePhysicalLED(objPath, it.action, it.dutyOn, it.period) != 0)
307 {
308 failedLedsAssert.insert(it);
309 }
310 }
311
312 reqLedsAssert = failedLedsAssert;
313 reqLedsDeAssert = failedLedsDeAssert;
314
315 if (reqLedsDeAssert.empty() && reqLedsAssert.empty())
316 {
317 timer.setEnabled(false);
318 }
319 else
320 {
321 timer.restartOnce(std::chrono::seconds(1));
322 }
323
324 return;
325 }
326
327 } // namespace led
328 } // namespace phosphor
329