xref: /openbmc/phosphor-bmc-code-mgmt/bmc/side-switch/side_switch.cpp (revision cab87e9cdeeb3e166d6d577511f6be4dc7721aca)
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  
sideSwitchNeeded(sdbusplus::bus_t & bus)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,
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(
70              "xyz.openbmc_project.ObjectMapper",
71              "/xyz/openbmc_project/object_mapper",
72              "xyz.openbmc_project.ObjectMapper", "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, "xyz.openbmc_project.Software.RedundancyPriority",
104                  "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  
powerOffSystem(sdbusplus::bus_t & bus)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  
setAutoPowerRestart(sdbusplus::bus_t & bus)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  
rebootTheBmc(sdbusplus::bus_t & bus)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  
main()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