#include "lamptest.hpp" #include #include namespace phosphor { namespace led { using Json = nlohmann::json; static const fs::path lampTestIndicator = "/var/lib/phosphor-led-manager/lamp-test-running"; bool LampTest::processLEDUpdates(const ActionSet& ledsAssert, const ActionSet& ledsDeAssert) { // If the physical LED status is updated during the lamp test, it should be // saved to Queue, and the queue will be processed after the lamp test is // stopped. if (isLampTestRunning) { // Physical LEDs will be updated during lamp test for (const auto& it : ledsDeAssert) { std::string path = std::string(phyLedPath) + it.name; auto iter = std::find_if( forceUpdateLEDs.begin(), forceUpdateLEDs.end(), [&path](const auto& name) { return name == path; }); if (iter != forceUpdateLEDs.end()) { manager.drivePhysicalLED(path, Layout::Action::Off, it.dutyOn, it.period); } } for (const auto& it : ledsAssert) { std::string path = std::string(phyLedPath) + it.name; auto iter = std::find_if( forceUpdateLEDs.begin(), forceUpdateLEDs.end(), [&path](const auto& name) { return name == path; }); if (iter != forceUpdateLEDs.end()) { manager.drivePhysicalLED(path, it.action, it.dutyOn, it.period); } } updatedLEDsDuringLampTest.emplace( std::make_pair(ledsAssert, ledsDeAssert)); return true; } return false; } void LampTest::stop() { if (!isLampTestRunning) { return; } timer.setEnabled(false); // Stop host lamp test doHostLampTest(false); // Set all the Physical action to Off for (const auto& path : physicalLEDPaths) { auto iter = std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(), [&path](const auto& skip) { return skip == path; }); if (iter != skipUpdateLEDs.end()) { // Skip update physical path continue; } manager.drivePhysicalLED(path, Layout::Action::Off, 0, 0); } if (std::filesystem::exists(lampTestIndicator)) { if (!std::filesystem::remove(lampTestIndicator)) { lg2::error( "Error removing lamp test on indicator file after lamp test execution."); } } isLampTestRunning = false; restorePhysicalLedStates(); } Layout::Action LampTest::getActionFromString(const std::string& str) { Layout::Action action = Layout::Action::Off; if (str == "xyz.openbmc_project.Led.Physical.Action.On") { action = Layout::Action::On; } else if (str == "xyz.openbmc_project.Led.Physical.Action.Blink") { action = Layout::Action::Blink; } return action; } void LampTest::storePhysicalLEDsStates() { physicalLEDStatesPriorToLampTest.clear(); for (const auto& path : physicalLEDPaths) { auto iter = std::find_if( skipUpdateLEDs.begin(), skipUpdateLEDs.end(), [&path](const auto& skipLed) { return skipLed == path; }); if (iter != skipUpdateLEDs.end()) { // Physical LEDs will be skipped continue; } // Reverse intercept path, Get the name of each member of physical led // e.g: path = /xyz/openbmc_project/led/physical/front_fan // name = front_fan sdbusplus::message::object_path object_path(path); auto name = object_path.filename(); if (name.empty()) { lg2::error( "Failed to get the name of member of physical LED path, PATH = {PATH}, NAME = {NAME}", "PATH", path, "NAME", name); continue; } std::string state{}; uint16_t period{}; uint8_t dutyOn{}; try { auto properties = dBusHandler.getAllProperties(path, phyLedIntf); state = std::get(properties["State"]); period = std::get(properties["Period"]); dutyOn = std::get(properties["DutyOn"]); } catch (const sdbusplus::exception_t& e) { lg2::error( "Failed to get All properties, ERROR = {ERROR}, PATH = {PATH}", "ERROR", e, "PATH", path); continue; } phosphor::led::Layout::Action action = getActionFromString(state); if (action != phosphor::led::Layout::Action::Off) { phosphor::led::Layout::LedAction ledAction{ name, action, dutyOn, period, phosphor::led::Layout::Action::On}; physicalLEDStatesPriorToLampTest.emplace(ledAction); } } } void LampTest::start() { if (isLampTestRunning) { // reset the timer and then return timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS)); // Notify host to reset the timer doHostLampTest(true); return; } // Get paths of all the Physical LED objects try { physicalLEDPaths = dBusHandler.getSubTreePaths(phyLedPath, phyLedIntf); } catch (const sdbusplus::exception_t& e) { lg2::error( "Failed to call the SubTreePaths method: {ERROR}, ledPath: {PATH}, ledInterface: {INTERFACE}", "ERROR", e, "PATH", phyLedPath, "INTERFACE", phyLedIntf); return; } // Get physical LEDs states before lamp test storePhysicalLEDsStates(); // restart lamp test, it contains initiate or reset the timer. timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS)); isLampTestRunning = true; // Notify host to start the lamp test doHostLampTest(true); // Create a file to maintain the state across reboots that Lamp test is on. // This is required as there was a scenario where it has been found that // LEDs remains in "on" state if lamp test is triggered and reboot takes // place. const auto ledDirectory = lampTestIndicator.parent_path(); if (!fs::exists(ledDirectory)) { fs::create_directories(ledDirectory); } std::ofstream(lampTestIndicator.c_str()); // Set all the Physical action to On for lamp test for (const auto& path : physicalLEDPaths) { auto iter = std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(), [&path](const auto& skip) { return skip == path; }); if (iter != skipUpdateLEDs.end()) { // Skip update physical path continue; } manager.drivePhysicalLED(path, Layout::Action::On, 0, 0); } } void LampTest::timeOutHandler() { // set the Asserted property of lamp test to false if (!groupObj) { lg2::error("the Group object is nullptr"); throw std::runtime_error("the Group object is nullptr"); } groupObj->asserted(false); } bool LampTest::requestHandler(Group* group, bool value) { if (groupObj == NULL) { groupObj = std::move(group); } if (value) { start(); // Return true in both cases (F -> T && T -> T) return true; } else { if (timer.hasExpired()) { stop(); // Return true as the request to stop the lamptest is handled // successfully. return true; } else if (timer.isEnabled()) { lg2::info( "Lamp test is still running. Cannot force stop the lamp test. Asserted is set back to true."); // Return false as the request to stop lamptest is not handled as // the lamptest is still running. return false; } return false; } } void LampTest::restorePhysicalLedStates() { // restore physical LEDs states before lamp test ActionSet ledsDeAssert{}; manager.driveLEDs(physicalLEDStatesPriorToLampTest, ledsDeAssert); physicalLEDStatesPriorToLampTest.clear(); // restore physical LEDs states during lamp test while (!updatedLEDsDuringLampTest.empty()) { auto& [ledsAssert, ledsDeAssert] = updatedLEDsDuringLampTest.front(); manager.driveLEDs(ledsAssert, ledsDeAssert); updatedLEDsDuringLampTest.pop(); } } void LampTest::doHostLampTest(bool value) { try { PropertyValue assertedValue{value}; dBusHandler.setProperty(HOST_LAMP_TEST_OBJECT, "xyz.openbmc_project.Led.Group", "Asserted", assertedValue); } catch (const sdbusplus::exception_t& e) { lg2::error( "Failed to set Asserted property, ERROR = {ERROR}, PATH = {PATH}", "ERROR", e, "PATH", std::string(HOST_LAMP_TEST_OBJECT)); } } void LampTest::getPhysicalLEDNamesFromJson(const fs::path& path) { if (!fs::exists(path) || fs::is_empty(path)) { lg2::info("The file does not exist or is empty, FILE_PATH = {PATH}", "PATH", path); return; } try { std::ifstream jsonFile(path); auto json = Json::parse(jsonFile); // define the default JSON as empty const std::vector empty{}; auto forceLEDs = json.value("forceLEDs", empty); std::ranges::transform(forceLEDs, std::back_inserter(forceUpdateLEDs), [](const auto& i) { return phyLedPath + i; }); auto skipLEDs = json.value("skipLEDs", empty); std::ranges::transform(skipLEDs, std::back_inserter(skipUpdateLEDs), [](const auto& i) { return phyLedPath + i; }); } catch (const std::exception& e) { lg2::error( "Failed to parse config file, ERROR = {ERROR}, FILE_PATH = {PATH}", "ERROR", e, "PATH", path); } return; } void LampTest::clearLamps() { if (std::filesystem::exists(lampTestIndicator)) { // we need to off all the LEDs. phosphor::led::utils::DBusHandler dBusHandler; std::vector physicalLedPaths = dBusHandler.getSubTreePaths( phosphor::led::phyLedPath, phosphor::led::phyLedIntf); for (const auto& path : physicalLedPaths) { manager.drivePhysicalLED(path, phosphor::led::Layout::Action::Off, 0, 0); } // Also remove the lamp test on indicator file. if (!std::filesystem::remove(lampTestIndicator)) { lg2::error( "Error removing lamp test on indicator file after lamp test execution."); } } } } // namespace led } // namespace phosphor