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