xref: /openbmc/phosphor-led-manager/manager/lamptest/lamptest.cpp (revision 858e5731ff4a058bcf79094f4b948d0b36d127b7)
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