1 /**
2 * Copyright © 2021 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "power_control.hpp"
18
19 #include "chassis.hpp"
20 #include "config_file_parser.hpp"
21 #include "format_utils.hpp"
22 #include "types.hpp"
23 #include "utility.hpp"
24
25 #include <exception>
26 #include <format>
27 #include <functional>
28 #include <span>
29 #include <stdexcept>
30 #include <thread>
31 #include <utility>
32
33 namespace phosphor::power::sequencer
34 {
35
36 const std::string powerOnTimeoutError =
37 "xyz.openbmc_project.Power.Error.PowerOnTimeout";
38
39 const std::string powerOffTimeoutError =
40 "xyz.openbmc_project.Power.Error.PowerOffTimeout";
41
42 const std::string shutdownError = "xyz.openbmc_project.Power.Error.Shutdown";
43
PowerControl(sdbusplus::bus_t & bus,const sdeventplus::Event & event)44 PowerControl::PowerControl(sdbusplus::bus_t& bus,
45 const sdeventplus::Event& event) :
46 PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit}, bus{bus},
47 services{bus},
48 pgoodWaitTimer{event, std::bind(&PowerControl::onFailureCallback, this)},
49 powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime},
50 timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval}
51 {
52 // Obtain dbus service name
53 bus.request_name(POWER_IFACE);
54
55 compatSysTypesFinder = std::make_unique<util::CompatibleSystemTypesFinder>(
56 bus, std::bind_front(&PowerControl::compatibleSystemTypesFound, this));
57
58 setUpGpio();
59 }
60
getPgood() const61 int PowerControl::getPgood() const
62 {
63 return pgood;
64 }
65
getPgoodTimeout() const66 int PowerControl::getPgoodTimeout() const
67 {
68 return timeout.count();
69 }
70
getState() const71 int PowerControl::getState() const
72 {
73 return state;
74 }
75
onFailureCallback()76 void PowerControl::onFailureCallback()
77 {
78 services.logInfoMsg("After onFailure wait");
79
80 onFailure(false);
81
82 // Power good has failed, call for chassis hard power off
83 auto method = bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT,
84 util::SYSTEMD_INTERFACE, "StartUnit");
85 method.append(util::POWEROFF_TARGET);
86 method.append("replace");
87 bus.call_noreply(method);
88 }
89
onFailure(bool wasTimeOut)90 void PowerControl::onFailure(bool wasTimeOut)
91 {
92 std::string error;
93 std::map<std::string, std::string> additionalData{};
94
95 // Check if pgood fault occurred on one of the rails being monitored
96 error = findPgoodFault(additionalData);
97
98 // If fault was not isolated to a voltage rail, select a more generic error
99 if (error.empty())
100 {
101 if (!powerSupplyError.empty())
102 {
103 error = powerSupplyError;
104 }
105 else if (wasTimeOut)
106 {
107 error = powerOnTimeoutError;
108 }
109 else
110 {
111 error = shutdownError;
112 }
113 }
114
115 services.logError(error, Entry::Level::Critical, additionalData);
116
117 if (!wasTimeOut)
118 {
119 services.createBMCDump();
120 }
121 }
122
findPgoodFault(std::map<std::string,std::string> & additionalData)123 std::string PowerControl::findPgoodFault(
124 std::map<std::string, std::string>& additionalData)
125 {
126 // Note: This code is temporary. It will be replaced when additional
127 // multi-chassis support is implementated in this application.
128 std::string error{};
129 if (system)
130 {
131 try
132 {
133 for (auto& chassis : system->getChassis())
134 {
135 for (auto& powerSequencer : chassis->getPowerSequencers())
136 {
137 error = powerSequencer->findPgoodFault(
138 services, powerSupplyError, additionalData);
139 if (!error.empty())
140 {
141 return error;
142 }
143 }
144 }
145 }
146 catch (const std::exception& e)
147 {
148 services.logErrorMsg(e.what());
149 additionalData.emplace("ERROR", e.what());
150 }
151 }
152 return error;
153 }
154
pollPgood()155 void PowerControl::pollPgood()
156 {
157 if (inStateTransition)
158 {
159 // In transition between power on and off, check for timeout
160 const auto now = std::chrono::steady_clock::now();
161 if (now > pgoodTimeoutTime)
162 {
163 services.logErrorMsg(std::format(
164 "Power state transition timeout, state: {}", state));
165 inStateTransition = false;
166
167 if (state)
168 {
169 // Time out powering on
170 onFailure(true);
171 }
172 else
173 {
174 // Time out powering off
175 std::map<std::string, std::string> additionalData{};
176 services.logError(powerOffTimeoutError, Entry::Level::Critical,
177 additionalData);
178 }
179
180 failureFound = true;
181 return;
182 }
183 }
184
185 int pgoodState = pgoodLine.get_value();
186 if (pgoodState != pgood)
187 {
188 // Power good has changed since last read
189 pgood = pgoodState;
190 if (pgoodState == 0)
191 {
192 emitPowerLostSignal();
193 }
194 else
195 {
196 emitPowerGoodSignal();
197 // Clear any errors on the transition to power on
198 powerSupplyError.clear();
199 failureFound = false;
200 }
201 emitPropertyChangedSignal("pgood");
202 }
203 if (pgoodState == state)
204 {
205 // Power good matches requested state
206 inStateTransition = false;
207 }
208 else if (!inStateTransition && (pgoodState == 0) && !failureFound)
209 {
210 // Not in power off state, not changing state, and power good is off
211 services.logErrorMsg("Chassis pgood failure");
212 pgoodWaitTimer.restartOnce(std::chrono::seconds(7));
213 failureFound = true;
214 }
215 }
216
setPgoodTimeout(int t)217 void PowerControl::setPgoodTimeout(int t)
218 {
219 if (timeout.count() != t)
220 {
221 timeout = std::chrono::seconds(t);
222 emitPropertyChangedSignal("pgood_timeout");
223 }
224 }
225
setPowerSupplyError(const std::string & error)226 void PowerControl::setPowerSupplyError(const std::string& error)
227 {
228 powerSupplyError = error;
229 }
230
setState(int s)231 void PowerControl::setState(int s)
232 {
233 if (state == s)
234 {
235 services.logInfoMsg(
236 std::format("Power already at requested state: {}", state));
237 return;
238 }
239 if (s == 0)
240 {
241 // Wait for two seconds when powering down. This is to allow host and
242 // other BMC applications time to complete power off processing
243 std::this_thread::sleep_for(std::chrono::seconds(2));
244 }
245 else
246 {
247 // If minimum power off time has not passed, wait
248 if (powerOnAllowedTime > std::chrono::steady_clock::now())
249 {
250 services.logInfoMsg(std::format(
251 "Waiting {} seconds until power on allowed",
252 std::chrono::duration_cast<std::chrono::seconds>(
253 powerOnAllowedTime - std::chrono::steady_clock::now())
254 .count()));
255 }
256 std::this_thread::sleep_until(powerOnAllowedTime);
257 }
258
259 services.logInfoMsg(std::format("setState: {}", s));
260 services.logInfoMsg(std::format("Powering chassis {}", (s ? "on" : "off")));
261 powerControlLine.request(
262 {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0});
263 powerControlLine.set_value(s);
264 powerControlLine.release();
265
266 if (s == 0)
267 {
268 // Set a minimum amount of time to wait before next power on
269 powerOnAllowedTime =
270 std::chrono::steady_clock::now() + minimumPowerOffTime;
271 }
272
273 pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout;
274 inStateTransition = true;
275 state = s;
276 emitPropertyChangedSignal("state");
277 }
278
compatibleSystemTypesFound(const std::vector<std::string> & types)279 void PowerControl::compatibleSystemTypesFound(
280 const std::vector<std::string>& types)
281 {
282 // If we don't already have compatible system types
283 if (compatibleSystemTypes.empty())
284 {
285 std::string typesStr = format_utils::toString(std::span{types});
286 services.logInfoMsg(
287 std::format("Compatible system types found: {}", typesStr));
288
289 // Store compatible system types
290 compatibleSystemTypes = types;
291
292 // Load config file that matches one of the compatible system types
293 loadConfigFile();
294 }
295 }
296
setUpGpio()297 void PowerControl::setUpGpio()
298 {
299 const std::string powerControlLineName = "power-chassis-control";
300 const std::string pgoodLineName = "power-chassis-good";
301
302 pgoodLine = gpiod::find_line(pgoodLineName);
303 if (!pgoodLine)
304 {
305 std::string errorString{"GPIO line name not found: " + pgoodLineName};
306 services.logErrorMsg(errorString);
307 throw std::runtime_error(errorString);
308 }
309 powerControlLine = gpiod::find_line(powerControlLineName);
310 if (!powerControlLine)
311 {
312 std::string errorString{
313 "GPIO line name not found: " + powerControlLineName};
314 services.logErrorMsg(errorString);
315 throw std::runtime_error(errorString);
316 }
317
318 pgoodLine.request(
319 {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0});
320 int pgoodState = pgoodLine.get_value();
321 pgood = pgoodState;
322 state = pgoodState;
323 services.logInfoMsg(std::format("Pgood state: {}", pgoodState));
324 }
325
loadConfigFile()326 void PowerControl::loadConfigFile()
327 {
328 try
329 {
330 std::filesystem::path configFile = findConfigFile();
331 if (!configFile.empty())
332 {
333 std::vector<std::unique_ptr<Chassis>> chassis =
334 config_file_parser::parse(configFile, services);
335 system = std::make_unique<System>(std::move(chassis));
336 }
337 }
338 catch (const std::exception& e)
339 {
340 services.logErrorMsg(std::format(
341 "Unable to parse JSON configuration file: {}", e.what()));
342 }
343 }
344
findConfigFile()345 std::filesystem::path PowerControl::findConfigFile()
346 {
347 // Find config file for current system based on compatible system types
348 std::filesystem::path configFile;
349 if (!compatibleSystemTypes.empty())
350 {
351 try
352 {
353 configFile = config_file_parser::find(compatibleSystemTypes);
354 if (!configFile.empty())
355 {
356 services.logInfoMsg(std::format(
357 "JSON configuration file found: {}", configFile.string()));
358 }
359 }
360 catch (const std::exception& e)
361 {
362 services.logErrorMsg(std::format(
363 "Unable to find JSON configuration file: {}", e.what()));
364 }
365 }
366 return configFile;
367 }
368
369 } // namespace phosphor::power::sequencer
370