xref: /openbmc/phosphor-power/phosphor-power-sequencer/src/power_control.cpp (revision 5a8aefe31bff7c0ce23ddee18f7eb2ef74bf624b)
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