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