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