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( 53 std::make_pair(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 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 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 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 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 ofs(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 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 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 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 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 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 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