xref: /openbmc/phosphor-bmc-code-mgmt/side-switch/side_switch.cpp (revision bf2bb2b11c4d70811e8745c2f4c411896c744111)
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