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