1 #include "side_switch.hpp" 2 3 #include "utils.hpp" 4 5 #include <phosphor-logging/lg2.hpp> 6 7 #include <exception> 8 #include <string> 9 #include <thread> 10 #include <variant> 11 #include <vector> 12 13 PHOSPHOR_LOG2_USING; 14 15 bool sideSwitchNeeded(sdbusplus::bus_t& bus) 16 { 17 18 std::string fwRunningVersionPath; 19 uint8_t fwRunningPriority = 0; 20 21 // Get active image 22 try 23 { 24 std::vector<std::string> paths = 25 utils::getProperty<std::vector<std::string>>( 26 bus, "/xyz/openbmc_project/software/functional", 27 "xyz.openbmc_project.Association", "endpoints"); 28 if (paths.size() != 1) 29 { 30 info("side-switch only supports BMC-purpose image systems"); 31 return (false); 32 } 33 fwRunningVersionPath = paths[0]; 34 info("Running firmware version path is {FW_PATH}", "FW_PATH", 35 fwRunningVersionPath); 36 } 37 catch (const std::exception& e) 38 { 39 error("failed to retrieve active firmware version: {ERROR}", "ERROR", 40 e); 41 return (false); 42 } 43 44 // Check if active image has highest priority (0) 45 try 46 { 47 fwRunningPriority = utils::getProperty<uint8_t>( 48 bus, fwRunningVersionPath.c_str(), 49 "xyz.openbmc_project.Software.RedundancyPriority", "Priority"); 50 info("Running firmware version priority is {FW_PRIORITY}", 51 "FW_PRIORITY", fwRunningPriority); 52 } 53 catch (const std::exception& e) 54 { 55 error("failed to read priority from active image: {ERROR}", "ERROR", e); 56 return (false); 57 } 58 59 // If running at highest priority (0) then no side switch needed 60 if (fwRunningPriority == 0) 61 { 62 info("Running image is at priority 0, no side switch needed"); 63 return (false); 64 } 65 66 // Need to check if any other BMC images on system have a higher priority 67 std::vector<std::string> allSoftwarePaths; 68 try 69 { 70 auto method = bus.new_method_call("xyz.openbmc_project.ObjectMapper", 71 "/xyz/openbmc_project/object_mapper", 72 "xyz.openbmc_project.ObjectMapper", 73 "GetSubTreePaths"); 74 method.append("/xyz/openbmc_project/software"); 75 method.append(0); // Depth 0 to search all 76 method.append( 77 std::vector<std::string>({"xyz.openbmc_project.Software.Version"})); 78 auto reply = bus.call(method); 79 reply.read(allSoftwarePaths); 80 if (allSoftwarePaths.size() <= 1) 81 { 82 info("only 1 image present in flash so no side switch needed"); 83 return (false); 84 } 85 } 86 catch (const std::exception& e) 87 { 88 error("failed to retrieve all firmware versions: {ERROR}", "ERROR", e); 89 return (false); 90 } 91 92 // Cycle through all firmware images looking for a BMC version that 93 // has a higher priority then our running image 94 for (auto& fwPath : allSoftwarePaths) 95 { 96 if (fwPath == fwRunningVersionPath) 97 { 98 info("{FW_PATH} is the running image, skip", "FW_PATH", fwPath); 99 continue; 100 } 101 try 102 { 103 uint8_t thisPathPri = utils::getProperty<uint8_t>( 104 bus, fwPath.c_str(), 105 "xyz.openbmc_project.Software.RedundancyPriority", "Priority"); 106 107 if (thisPathPri < fwRunningPriority) 108 { 109 info( 110 "{FW_PATH} has a higher priority, {FW_PRIORITY}, then running priority", 111 "FW_PATH", fwPath, "FW_PRIORITY", thisPathPri); 112 return (true); 113 } 114 } 115 catch (const std::exception& e) 116 { 117 // This could just be a host firmware image, just keep going 118 info("failed to read a BMC priority from {FW_PATH}: {ERROR}", 119 "FW_PATH", fwPath, "ERROR", e); 120 continue; 121 } 122 } 123 124 return (false); 125 } 126 127 bool powerOffSystem(sdbusplus::bus_t& bus) 128 { 129 130 try 131 { 132 utils::PropertyValue chassOff = 133 "xyz.openbmc_project.State.Chassis.Transition.Off"; 134 utils::setProperty(bus, "/xyz/openbmc_project/state/chassis0", 135 "xyz.openbmc_project.State.Chassis", 136 "RequestedPowerTransition", chassOff); 137 } 138 catch (const std::exception& e) 139 { 140 error("chassis off request failed: {ERROR}", "ERROR", e); 141 return (false); 142 } 143 144 // Now just wait for power to turn off 145 // Worst case is a systemd service hangs in power off for 2 minutes so 146 // take that and double it to avoid any timing issues. The user has 147 // requested we switch to the other side, so a lengthy delay is warranted 148 // if needed. On most systems the power off takes 5-15 seconds. 149 for (int i = 0; i < 240; i++) 150 { 151 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 152 try 153 { 154 auto currentPwrState = utils::getProperty<std::string>( 155 bus, "/xyz/openbmc_project/state/chassis0", 156 "xyz.openbmc_project.State.Chassis", "CurrentPowerState"); 157 158 if (currentPwrState == 159 "xyz.openbmc_project.State.Chassis.PowerState.Off") 160 { 161 info("chassis power is off"); 162 return (true); 163 } 164 } 165 catch (const std::exception& e) 166 { 167 error("reading chassis power state failed: {ERROR}", "ERROR", e); 168 return (false); 169 } 170 } 171 error("timeout waiting for chassis power to turn off"); 172 return (false); 173 } 174 175 bool setAutoPowerRestart(sdbusplus::bus_t& bus) 176 { 177 try 178 { 179 // Set the one-time power on policy to AlwaysOn so system auto boots 180 // after BMC reboot 181 utils::PropertyValue restorePolicyOn = 182 "xyz.openbmc_project.Control.Power.RestorePolicy.Policy.AlwaysOn"; 183 184 utils::setProperty( 185 bus, 186 "/xyz/openbmc_project/control/host0/power_restore_policy/one_time", 187 "xyz.openbmc_project.Control.Power.RestorePolicy", 188 "PowerRestorePolicy", restorePolicyOn); 189 } 190 catch (const std::exception& e) 191 { 192 error("setting power policy to always on failed: {ERROR}", "ERROR", e); 193 return (false); 194 } 195 info("RestorePolicy set to AlwaysOn"); 196 return (true); 197 } 198 199 bool rebootTheBmc(sdbusplus::bus_t& bus) 200 { 201 try 202 { 203 utils::PropertyValue bmcReboot = 204 "xyz.openbmc_project.State.BMC.Transition.Reboot"; 205 206 utils::setProperty(bus, "/xyz/openbmc_project/state/bmc0", 207 "xyz.openbmc_project.State.BMC", 208 "RequestedBMCTransition", bmcReboot); 209 } 210 catch (const std::exception& e) 211 { 212 error("rebooting the bmc failed: {ERROR}", "ERROR", e); 213 return (false); 214 } 215 info("BMC reboot initiated"); 216 return (true); 217 } 218 219 int main() 220 { 221 info("Checking for side switch reboot"); 222 223 auto bus = sdbusplus::bus::new_default(); 224 225 if (!sideSwitchNeeded(bus)) 226 { 227 info("Side switch not needed"); 228 return 0; 229 } 230 231 if (!powerOffSystem(bus)) 232 { 233 error("unable to power off chassis"); 234 return 0; 235 } 236 237 if (!setAutoPowerRestart(bus)) 238 { 239 error("unable to set the auto power on restart policy"); 240 // system has been powered off, best to at least continue and 241 // switch to new firmware image so continue 242 } 243 244 if (!rebootTheBmc(bus)) 245 { 246 error("unable to reboot the BMC"); 247 // Return invalid rc to trigger systemd target recovery and appropriate 248 // error logging 249 return -1; 250 } 251 252 return 0; 253 } 254