| #include "side_switch.hpp" | 
 |  | 
 | #include "utils.hpp" | 
 |  | 
 | #include <phosphor-logging/lg2.hpp> | 
 |  | 
 | #include <exception> | 
 | #include <string> | 
 | #include <thread> | 
 | #include <variant> | 
 | #include <vector> | 
 |  | 
 | PHOSPHOR_LOG2_USING; | 
 |  | 
 | bool sideSwitchNeeded(sdbusplus::bus_t& bus) | 
 | { | 
 |     std::string fwRunningVersionPath; | 
 |     uint8_t fwRunningPriority = 0; | 
 |  | 
 |     // Get active image | 
 |     try | 
 |     { | 
 |         std::vector<std::string> paths = | 
 |             utils::getProperty<std::vector<std::string>>( | 
 |                 bus, "/xyz/openbmc_project/software/functional", | 
 |                 "xyz.openbmc_project.Association", "endpoints"); | 
 |         if (paths.size() != 1) | 
 |         { | 
 |             info("side-switch only supports BMC-purpose image systems"); | 
 |             return (false); | 
 |         } | 
 |         fwRunningVersionPath = paths[0]; | 
 |         info("Running firmware version path is {FW_PATH}", "FW_PATH", | 
 |              fwRunningVersionPath); | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         error("failed to retrieve active firmware version: {ERROR}", "ERROR", | 
 |               e); | 
 |         return (false); | 
 |     } | 
 |  | 
 |     // Check if active image has highest priority (0) | 
 |     try | 
 |     { | 
 |         fwRunningPriority = utils::getProperty<uint8_t>( | 
 |             bus, fwRunningVersionPath, | 
 |             "xyz.openbmc_project.Software.RedundancyPriority", "Priority"); | 
 |         info("Running firmware version priority is {FW_PRIORITY}", | 
 |              "FW_PRIORITY", fwRunningPriority); | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         error("failed to read priority from active image: {ERROR}", "ERROR", e); | 
 |         return (false); | 
 |     } | 
 |  | 
 |     // If running at highest priority (0) then no side switch needed | 
 |     if (fwRunningPriority == 0) | 
 |     { | 
 |         info("Running image is at priority 0, no side switch needed"); | 
 |         return (false); | 
 |     } | 
 |  | 
 |     // Need to check if any other BMC images on system have a higher priority | 
 |     std::vector<std::string> allSoftwarePaths; | 
 |     try | 
 |     { | 
 |         auto method = bus.new_method_call( | 
 |             "xyz.openbmc_project.ObjectMapper", | 
 |             "/xyz/openbmc_project/object_mapper", | 
 |             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths"); | 
 |         method.append("/xyz/openbmc_project/software"); | 
 |         method.append(0); // Depth 0 to search all | 
 |         method.append( | 
 |             std::vector<std::string>({"xyz.openbmc_project.Software.Version"})); | 
 |         auto reply = bus.call(method); | 
 |         reply.read(allSoftwarePaths); | 
 |         if (allSoftwarePaths.size() <= 1) | 
 |         { | 
 |             info("only 1 image present in flash so no side switch needed"); | 
 |             return (false); | 
 |         } | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         error("failed to retrieve all firmware versions: {ERROR}", "ERROR", e); | 
 |         return (false); | 
 |     } | 
 |  | 
 |     // Cycle through all firmware images looking for a BMC version that | 
 |     // has a higher priority then our running image | 
 |     for (auto& fwPath : allSoftwarePaths) | 
 |     { | 
 |         if (fwPath == fwRunningVersionPath) | 
 |         { | 
 |             info("{FW_PATH} is the running image, skip", "FW_PATH", fwPath); | 
 |             continue; | 
 |         } | 
 |         try | 
 |         { | 
 |             uint8_t thisPathPri = utils::getProperty<uint8_t>( | 
 |                 bus, fwPath, "xyz.openbmc_project.Software.RedundancyPriority", | 
 |                 "Priority"); | 
 |  | 
 |             if (thisPathPri < fwRunningPriority) | 
 |             { | 
 |                 info( | 
 |                     "{FW_PATH} has a higher priority, {FW_PRIORITY}, then running priority", | 
 |                     "FW_PATH", fwPath, "FW_PRIORITY", thisPathPri); | 
 |                 return (true); | 
 |             } | 
 |         } | 
 |         catch (const std::exception& e) | 
 |         { | 
 |             // This could just be a host firmware image, just keep going | 
 |             info("failed to read a BMC priority from {FW_PATH}: {ERROR}", | 
 |                  "FW_PATH", fwPath, "ERROR", e); | 
 |             continue; | 
 |         } | 
 |     } | 
 |  | 
 |     return (false); | 
 | } | 
 |  | 
 | bool powerOffSystem(sdbusplus::bus_t& bus) | 
 | { | 
 |     try | 
 |     { | 
 |         utils::PropertyValue chassOff = | 
 |             "xyz.openbmc_project.State.Chassis.Transition.Off"; | 
 |         utils::setProperty(bus, "/xyz/openbmc_project/state/chassis0", | 
 |                            "xyz.openbmc_project.State.Chassis", | 
 |                            "RequestedPowerTransition", chassOff); | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         error("chassis off request failed: {ERROR}", "ERROR", e); | 
 |         return (false); | 
 |     } | 
 |  | 
 |     // Now just wait for host and power to turn off | 
 |     // Worst case is a systemd service hangs in power off for 2 minutes so | 
 |     // take that and double it to avoid any timing issues. The user has | 
 |     // requested we switch to the other side, so a lengthy delay is warranted | 
 |     // if needed. On most systems the power off takes 5-15 seconds. | 
 |     for (int i = 0; i < 240; i++) | 
 |     { | 
 |         std::this_thread::sleep_for(std::chrono::milliseconds(1000)); | 
 |         try | 
 |         { | 
 |             // First wait for host to be off | 
 |             auto currentHostState = utils::getProperty<std::string>( | 
 |                 bus, "/xyz/openbmc_project/state/host0", | 
 |                 "xyz.openbmc_project.State.Host", "CurrentHostState"); | 
 |  | 
 |             if (currentHostState == | 
 |                 "xyz.openbmc_project.State.Host.HostState.Off") | 
 |             { | 
 |                 info("host is off"); | 
 |             } | 
 |             else | 
 |             { | 
 |                 continue; | 
 |             } | 
 |  | 
 |             // Then verify chassis power is off | 
 |             auto currentPwrState = utils::getProperty<std::string>( | 
 |                 bus, "/xyz/openbmc_project/state/chassis0", | 
 |                 "xyz.openbmc_project.State.Chassis", "CurrentPowerState"); | 
 |  | 
 |             if (currentPwrState == | 
 |                 "xyz.openbmc_project.State.Chassis.PowerState.Off") | 
 |             { | 
 |                 info("chassis power is off"); | 
 |                 return (true); | 
 |             } | 
 |             else | 
 |             { | 
 |                 continue; | 
 |             } | 
 |         } | 
 |         catch (const std::exception& e) | 
 |         { | 
 |             error("reading chassis power state failed: {ERROR}", "ERROR", e); | 
 |             return (false); | 
 |         } | 
 |     } | 
 |     error("timeout waiting for chassis power to turn off"); | 
 |     return (false); | 
 | } | 
 |  | 
 | bool setAutoPowerRestart(sdbusplus::bus_t& bus) | 
 | { | 
 |     try | 
 |     { | 
 |         // Set the one-time power on policy to AlwaysOn so system auto boots | 
 |         // after BMC reboot | 
 |         utils::PropertyValue restorePolicyOn = | 
 |             "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOn"; | 
 |  | 
 |         utils::setProperty( | 
 |             bus, | 
 |             "/xyz/openbmc_project/control/host0/power_restore_policy/one_time", | 
 |             "xyz.openbmc_project.Control.Power.RestorePolicy", | 
 |             "PowerRestorePolicy", restorePolicyOn); | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         error("setting power policy to always on failed: {ERROR}", "ERROR", e); | 
 |         return (false); | 
 |     } | 
 |     info("RestorePolicy set to AlwaysOn"); | 
 |     return (true); | 
 | } | 
 |  | 
 | bool rebootTheBmc(sdbusplus::bus_t& bus) | 
 | { | 
 |     try | 
 |     { | 
 |         utils::PropertyValue bmcReboot = | 
 |             "xyz.openbmc_project.State.BMC.Transition.Reboot"; | 
 |  | 
 |         utils::setProperty(bus, "/xyz/openbmc_project/state/bmc0", | 
 |                            "xyz.openbmc_project.State.BMC", | 
 |                            "RequestedBMCTransition", bmcReboot); | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         error("rebooting the bmc failed: {ERROR}", "ERROR", e); | 
 |         return (false); | 
 |     } | 
 |     info("BMC reboot initiated"); | 
 |     return (true); | 
 | } | 
 |  | 
 | int main() | 
 | { | 
 |     info("Checking for side switch reboot"); | 
 |  | 
 |     auto bus = sdbusplus::bus::new_default(); | 
 |  | 
 |     if (!sideSwitchNeeded(bus)) | 
 |     { | 
 |         info("Side switch not needed"); | 
 |         return 0; | 
 |     } | 
 |  | 
 |     if (!powerOffSystem(bus)) | 
 |     { | 
 |         error("unable to power off chassis"); | 
 |         return 0; | 
 |     } | 
 |  | 
 |     if (!setAutoPowerRestart(bus)) | 
 |     { | 
 |         error("unable to set the auto power on restart policy"); | 
 |         // system has been powered off, best to at least continue and | 
 |         // switch to new firmware image so continue | 
 |     } | 
 |  | 
 |     if (!rebootTheBmc(bus)) | 
 |     { | 
 |         error("unable to reboot the BMC"); | 
 |         // Return invalid rc to trigger systemd target recovery and appropriate | 
 |         // error logging | 
 |         return -1; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } |