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