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