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