| #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.c_str(), |
| "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.c_str(), |
| "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; |
| } |