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