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 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 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 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 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 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 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 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 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 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 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 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