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