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