1 #include "lamptest.hpp"
2
3 #include <phosphor-logging/lg2.hpp>
4
5 #include <algorithm>
6
7 namespace phosphor
8 {
9 namespace led
10 {
11
12 using Json = nlohmann::json;
13 static const fs::path lampTestIndicator =
14 "/var/lib/phosphor-led-manager/lamp-test-running";
15
processLEDUpdates(const ActionSet & ledsAssert,const ActionSet & ledsDeAssert)16 bool LampTest::processLEDUpdates(const ActionSet& ledsAssert,
17 const ActionSet& ledsDeAssert)
18 {
19 // If the physical LED status is updated during the lamp test, it should be
20 // saved to Queue, and the queue will be processed after the lamp test is
21 // stopped.
22 if (isLampTestRunning)
23 {
24 // Physical LEDs will be updated during lamp test
25 for (const auto& it : ledsDeAssert)
26 {
27 std::string path = std::string(phyLedPath) + it.name;
28 auto iter = std::find_if(
29 forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
30 [&path](const auto& name) { return name == path; });
31
32 if (iter != forceUpdateLEDs.end())
33 {
34 manager.drivePhysicalLED(path, Layout::Action::Off, it.dutyOn,
35 it.period);
36 }
37 }
38
39 for (const auto& it : ledsAssert)
40 {
41 std::string path = std::string(phyLedPath) + it.name;
42 auto iter = std::find_if(
43 forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
44 [&path](const auto& name) { return name == path; });
45
46 if (iter != forceUpdateLEDs.end())
47 {
48 manager.drivePhysicalLED(path, it.action, it.dutyOn, it.period);
49 }
50 }
51
52 updatedLEDsDuringLampTest.emplace(
53 std::make_pair(ledsAssert, ledsDeAssert));
54 return true;
55 }
56 return false;
57 }
58
stop()59 void LampTest::stop()
60 {
61 if (!isLampTestRunning)
62 {
63 return;
64 }
65
66 timer.setEnabled(false);
67
68 // Stop host lamp test
69 doHostLampTest(false);
70
71 // Set all the Physical action to Off
72 for (const auto& path : physicalLEDPaths)
73 {
74 auto iter =
75 std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
76 [&path](const auto& skip) { return skip == path; });
77
78 if (iter != skipUpdateLEDs.end())
79 {
80 // Skip update physical path
81 continue;
82 }
83
84 manager.drivePhysicalLED(path, Layout::Action::Off, 0, 0);
85 }
86
87 if (std::filesystem::exists(lampTestIndicator))
88 {
89 if (!std::filesystem::remove(lampTestIndicator))
90 {
91 lg2::error(
92 "Error removing lamp test on indicator file after lamp test execution.");
93 }
94 }
95
96 isLampTestRunning = false;
97 restorePhysicalLedStates();
98 }
99
getActionFromString(const std::string & str)100 Layout::Action LampTest::getActionFromString(const std::string& str)
101 {
102 Layout::Action action = Layout::Action::Off;
103
104 if (str == "xyz.openbmc_project.Led.Physical.Action.On")
105 {
106 action = Layout::Action::On;
107 }
108 else if (str == "xyz.openbmc_project.Led.Physical.Action.Blink")
109 {
110 action = Layout::Action::Blink;
111 }
112
113 return action;
114 }
115
storePhysicalLEDsStates()116 void LampTest::storePhysicalLEDsStates()
117 {
118 physicalLEDStatesPriorToLampTest.clear();
119
120 for (const auto& path : physicalLEDPaths)
121 {
122 auto iter = std::find_if(
123 skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
124 [&path](const auto& skipLed) { return skipLed == path; });
125
126 if (iter != skipUpdateLEDs.end())
127 {
128 // Physical LEDs will be skipped
129 continue;
130 }
131
132 // Reverse intercept path, Get the name of each member of physical led
133 // e.g: path = /xyz/openbmc_project/led/physical/front_fan
134 // name = front_fan
135 sdbusplus::message::object_path object_path(path);
136 auto name = object_path.filename();
137 if (name.empty())
138 {
139 lg2::error(
140 "Failed to get the name of member of physical LED path, PATH = {PATH}, NAME = {NAME}",
141 "PATH", path, "NAME", name);
142 continue;
143 }
144
145 std::string state{};
146 uint16_t period{};
147 uint8_t dutyOn{};
148 try
149 {
150 auto properties = dBusHandler.getAllProperties(path, phyLedIntf);
151
152 state = std::get<std::string>(properties["State"]);
153 period = std::get<uint16_t>(properties["Period"]);
154 dutyOn = std::get<uint8_t>(properties["DutyOn"]);
155 }
156 catch (const sdbusplus::exception_t& e)
157 {
158 lg2::error(
159 "Failed to get All properties, ERROR = {ERROR}, PATH = {PATH}",
160 "ERROR", e, "PATH", path);
161 continue;
162 }
163
164 phosphor::led::Layout::Action action = getActionFromString(state);
165 if (action != phosphor::led::Layout::Action::Off)
166 {
167 phosphor::led::Layout::LedAction ledAction{
168 name, action, dutyOn, period,
169 phosphor::led::Layout::Action::On};
170 physicalLEDStatesPriorToLampTest.emplace(ledAction);
171 }
172 }
173 }
174
start()175 void LampTest::start()
176 {
177 if (isLampTestRunning)
178 {
179 // reset the timer and then return
180 timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
181
182 // Notify host to reset the timer
183 doHostLampTest(true);
184
185 return;
186 }
187
188 // Get paths of all the Physical LED objects
189 try
190 {
191 physicalLEDPaths = dBusHandler.getSubTreePaths(phyLedPath, phyLedIntf);
192 }
193 catch (const sdbusplus::exception_t& e)
194 {
195 lg2::error(
196 "Failed to call the SubTreePaths method: {ERROR}, ledPath: {PATH}, ledInterface: {INTERFACE}",
197 "ERROR", e, "PATH", phyLedPath, "INTERFACE", phyLedIntf);
198 return;
199 }
200
201 // Get physical LEDs states before lamp test
202 storePhysicalLEDsStates();
203
204 // restart lamp test, it contains initiate or reset the timer.
205 timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
206 isLampTestRunning = true;
207
208 // Notify host to start the lamp test
209 doHostLampTest(true);
210
211 // Create a file to maintain the state across reboots that Lamp test is on.
212 // This is required as there was a scenario where it has been found that
213 // LEDs remains in "on" state if lamp test is triggered and reboot takes
214 // place.
215 const auto ledDirectory = lampTestIndicator.parent_path();
216
217 if (!fs::exists(ledDirectory))
218 {
219 fs::create_directories(ledDirectory);
220 }
221
222 std::ofstream(lampTestIndicator.c_str());
223
224 // Set all the Physical action to On for lamp test
225 for (const auto& path : physicalLEDPaths)
226 {
227 auto iter =
228 std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
229 [&path](const auto& skip) { return skip == path; });
230
231 if (iter != skipUpdateLEDs.end())
232 {
233 // Skip update physical path
234 continue;
235 }
236
237 manager.drivePhysicalLED(path, Layout::Action::On, 0, 0);
238 }
239 }
240
timeOutHandler()241 void LampTest::timeOutHandler()
242 {
243 // set the Asserted property of lamp test to false
244 if (!groupObj)
245 {
246 lg2::error("the Group object is nullptr");
247 throw std::runtime_error("the Group object is nullptr");
248 }
249
250 groupObj->asserted(false);
251 }
252
requestHandler(Group * group,bool value)253 bool LampTest::requestHandler(Group* group, bool value)
254 {
255 if (groupObj == NULL)
256 {
257 groupObj = std::move(group);
258 }
259
260 if (value)
261 {
262 start();
263
264 // Return true in both cases (F -> T && T -> T)
265 return true;
266 }
267 else
268 {
269 if (timer.hasExpired())
270 {
271 stop();
272
273 // Return true as the request to stop the lamptest is handled
274 // successfully.
275 return true;
276 }
277 else if (timer.isEnabled())
278 {
279 lg2::info(
280 "Lamp test is still running. Cannot force stop the lamp test. Asserted is set back to true.");
281
282 // Return false as the request to stop lamptest is not handled as
283 // the lamptest is still running.
284 return false;
285 }
286 return false;
287 }
288 }
289
restorePhysicalLedStates()290 void LampTest::restorePhysicalLedStates()
291 {
292 // restore physical LEDs states before lamp test
293 ActionSet ledsDeAssert{};
294 manager.driveLEDs(physicalLEDStatesPriorToLampTest, ledsDeAssert);
295 physicalLEDStatesPriorToLampTest.clear();
296
297 // restore physical LEDs states during lamp test
298 while (!updatedLEDsDuringLampTest.empty())
299 {
300 auto& [ledsAssert, ledsDeAssert] = updatedLEDsDuringLampTest.front();
301 manager.driveLEDs(ledsAssert, ledsDeAssert);
302 updatedLEDsDuringLampTest.pop();
303 }
304 }
305
doHostLampTest(bool value)306 void LampTest::doHostLampTest(bool value)
307 {
308 try
309 {
310 PropertyValue assertedValue{value};
311 dBusHandler.setProperty(HOST_LAMP_TEST_OBJECT,
312 "xyz.openbmc_project.Led.Group", "Asserted",
313 assertedValue);
314 }
315 catch (const sdbusplus::exception_t& e)
316 {
317 lg2::error(
318 "Failed to set Asserted property, ERROR = {ERROR}, PATH = {PATH}",
319 "ERROR", e, "PATH", std::string(HOST_LAMP_TEST_OBJECT));
320 }
321 }
322
getPhysicalLEDNamesFromJson(const fs::path & path)323 void LampTest::getPhysicalLEDNamesFromJson(const fs::path& path)
324 {
325 if (!fs::exists(path) || fs::is_empty(path))
326 {
327 lg2::info("The file does not exist or is empty, FILE_PATH = {PATH}",
328 "PATH", path);
329 return;
330 }
331
332 try
333 {
334 std::ifstream jsonFile(path);
335 auto json = Json::parse(jsonFile);
336
337 // define the default JSON as empty
338 const std::vector<std::string> empty{};
339 auto forceLEDs = json.value("forceLEDs", empty);
340 std::ranges::transform(forceLEDs, std::back_inserter(forceUpdateLEDs),
341 [](const auto& i) { return phyLedPath + i; });
342
343 auto skipLEDs = json.value("skipLEDs", empty);
344 std::ranges::transform(skipLEDs, std::back_inserter(skipUpdateLEDs),
345 [](const auto& i) { return phyLedPath + i; });
346 }
347 catch (const std::exception& e)
348 {
349 lg2::error(
350 "Failed to parse config file, ERROR = {ERROR}, FILE_PATH = {PATH}",
351 "ERROR", e, "PATH", path);
352 }
353 return;
354 }
355
clearLamps()356 void LampTest::clearLamps()
357 {
358 if (std::filesystem::exists(lampTestIndicator))
359 {
360 // we need to off all the LEDs.
361 phosphor::led::utils::DBusHandler dBusHandler;
362 std::vector<std::string> physicalLedPaths = dBusHandler.getSubTreePaths(
363 phosphor::led::phyLedPath, phosphor::led::phyLedIntf);
364
365 for (const auto& path : physicalLedPaths)
366 {
367 manager.drivePhysicalLED(path, phosphor::led::Layout::Action::Off,
368 0, 0);
369 }
370
371 // Also remove the lamp test on indicator file.
372 if (!std::filesystem::remove(lampTestIndicator))
373 {
374 lg2::error(
375 "Error removing lamp test on indicator file after lamp test execution.");
376 }
377 }
378 }
379 } // namespace led
380 } // namespace phosphor
381