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 "config_file_parser.hpp"
20 #include "format_utils.hpp"
21 #include "types.hpp"
22 #include "ucd90160_device.hpp"
23 #include "ucd90320_device.hpp"
24 #include "utility.hpp"
25 
26 #include <exception>
27 #include <format>
28 #include <functional>
29 #include <map>
30 #include <span>
31 #include <stdexcept>
32 #include <thread>
33 #include <utility>
34 
35 namespace phosphor::power::sequencer
36 {
37 
38 const std::string powerOnTimeoutError =
39     "xyz.openbmc_project.Power.Error.PowerOnTimeout";
40 
41 const std::string powerOffTimeoutError =
42     "xyz.openbmc_project.Power.Error.PowerOffTimeout";
43 
44 const std::string shutdownError = "xyz.openbmc_project.Power.Error.Shutdown";
45 
46 PowerControl::PowerControl(sdbusplus::bus_t& bus,
47                            const sdeventplus::Event& event) :
48     PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit},
49     bus{bus}, services{bus},
50     pgoodWaitTimer{event, std::bind(&PowerControl::onFailureCallback, this)},
51     powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime},
52     timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval}
53 {
54     // Obtain dbus service name
55     bus.request_name(POWER_IFACE);
56 
57     compatSysTypesFinder = std::make_unique<util::CompatibleSystemTypesFinder>(
58         bus, std::bind_front(&PowerControl::compatibleSystemTypesFound, this));
59 
60     deviceFinder = std::make_unique<DeviceFinder>(
61         bus, std::bind_front(&PowerControl::deviceFound, this));
62 
63     setUpGpio();
64 }
65 
66 int PowerControl::getPgood() const
67 {
68     return pgood;
69 }
70 
71 int PowerControl::getPgoodTimeout() const
72 {
73     return timeout.count();
74 }
75 
76 int PowerControl::getState() const
77 {
78     return state;
79 }
80 
81 void PowerControl::onFailureCallback()
82 {
83     services.logInfoMsg("After onFailure wait");
84 
85     onFailure(false);
86 
87     // Power good has failed, call for chassis hard power off
88     auto method = bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT,
89                                       util::SYSTEMD_INTERFACE, "StartUnit");
90     method.append(util::POWEROFF_TARGET);
91     method.append("replace");
92     bus.call_noreply(method);
93 }
94 
95 void PowerControl::onFailure(bool wasTimeOut)
96 {
97     std::string error;
98     std::map<std::string, std::string> additionalData{};
99 
100     // Check if pgood fault occurred on rail monitored by power sequencer device
101     if (device)
102     {
103         try
104         {
105             error = device->findPgoodFault(services, powerSupplyError,
106                                            additionalData);
107         }
108         catch (const std::exception& e)
109         {
110             services.logErrorMsg(e.what());
111             additionalData.emplace("ERROR", e.what());
112         }
113     }
114 
115     // If fault was not isolated to a voltage rail, select a more generic error
116     if (error.empty())
117     {
118         if (!powerSupplyError.empty())
119         {
120             error = powerSupplyError;
121         }
122         else if (wasTimeOut)
123         {
124             error = powerOnTimeoutError;
125         }
126         else
127         {
128             error = shutdownError;
129         }
130     }
131 
132     services.logError(error, Entry::Level::Critical, additionalData);
133 
134     if (!wasTimeOut)
135     {
136         services.createBMCDump();
137     }
138 }
139 
140 void PowerControl::pollPgood()
141 {
142     if (inStateTransition)
143     {
144         // In transition between power on and off, check for timeout
145         const auto now = std::chrono::steady_clock::now();
146         if (now > pgoodTimeoutTime)
147         {
148             services.logErrorMsg(std::format(
149                 "Power state transition timeout, state: {}", state));
150             inStateTransition = false;
151 
152             if (state)
153             {
154                 // Time out powering on
155                 onFailure(true);
156             }
157             else
158             {
159                 // Time out powering off
160                 std::map<std::string, std::string> additionalData{};
161                 services.logError(powerOffTimeoutError, Entry::Level::Critical,
162                                   additionalData);
163             }
164 
165             failureFound = true;
166             return;
167         }
168     }
169 
170     int pgoodState = pgoodLine.get_value();
171     if (pgoodState != pgood)
172     {
173         // Power good has changed since last read
174         pgood = pgoodState;
175         if (pgoodState == 0)
176         {
177             emitPowerLostSignal();
178         }
179         else
180         {
181             emitPowerGoodSignal();
182             // Clear any errors on the transition to power on
183             powerSupplyError.clear();
184             failureFound = false;
185         }
186         emitPropertyChangedSignal("pgood");
187     }
188     if (pgoodState == state)
189     {
190         // Power good matches requested state
191         inStateTransition = false;
192     }
193     else if (!inStateTransition && (pgoodState == 0) && !failureFound)
194     {
195         // Not in power off state, not changing state, and power good is off
196         services.logErrorMsg("Chassis pgood failure");
197         pgoodWaitTimer.restartOnce(std::chrono::seconds(7));
198         failureFound = true;
199     }
200 }
201 
202 void PowerControl::setPgoodTimeout(int t)
203 {
204     if (timeout.count() != t)
205     {
206         timeout = std::chrono::seconds(t);
207         emitPropertyChangedSignal("pgood_timeout");
208     }
209 }
210 
211 void PowerControl::setPowerSupplyError(const std::string& error)
212 {
213     powerSupplyError = error;
214 }
215 
216 void PowerControl::setState(int s)
217 {
218     if (state == s)
219     {
220         services.logInfoMsg(
221             std::format("Power already at requested state: {}", state));
222         return;
223     }
224     if (s == 0)
225     {
226         // Wait for two seconds when powering down. This is to allow host and
227         // other BMC applications time to complete power off processing
228         std::this_thread::sleep_for(std::chrono::seconds(2));
229     }
230     else
231     {
232         // If minimum power off time has not passed, wait
233         if (powerOnAllowedTime > std::chrono::steady_clock::now())
234         {
235             services.logInfoMsg(std::format(
236                 "Waiting {} seconds until power on allowed",
237                 std::chrono::duration_cast<std::chrono::seconds>(
238                     powerOnAllowedTime - std::chrono::steady_clock::now())
239                     .count()));
240         }
241         std::this_thread::sleep_until(powerOnAllowedTime);
242     }
243 
244     services.logInfoMsg(std::format("setState: {}", s));
245     services.logInfoMsg(std::format("Powering chassis {}", (s ? "on" : "off")));
246     powerControlLine.request(
247         {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0});
248     powerControlLine.set_value(s);
249     powerControlLine.release();
250 
251     if (s == 0)
252     {
253         // Set a minimum amount of time to wait before next power on
254         powerOnAllowedTime = std::chrono::steady_clock::now() +
255                              minimumPowerOffTime;
256     }
257 
258     pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout;
259     inStateTransition = true;
260     state = s;
261     emitPropertyChangedSignal("state");
262 }
263 
264 void PowerControl::compatibleSystemTypesFound(
265     const std::vector<std::string>& types)
266 {
267     // If we don't already have compatible system types
268     if (compatibleSystemTypes.empty())
269     {
270         std::string typesStr = format_utils::toString(std::span{types});
271         services.logInfoMsg(
272             std::format("Compatible system types found: {}", typesStr));
273 
274         // Store compatible system types
275         compatibleSystemTypes = types;
276 
277         // Load config file and create device object if possible
278         loadConfigFileAndCreateDevice();
279     }
280 }
281 
282 void PowerControl::deviceFound(const DeviceProperties& properties)
283 {
284     // If we don't already have device properties
285     if (!deviceProperties)
286     {
287         services.logInfoMsg(std::format(
288             "Power sequencer device found: type={}, name={}, bus={:d}, address={:#02x}",
289             properties.type, properties.name, properties.bus,
290             properties.address));
291 
292         // Store device properties
293         deviceProperties = properties;
294 
295         // Load config file and create device object if possible
296         loadConfigFileAndCreateDevice();
297     }
298 }
299 
300 void PowerControl::setUpGpio()
301 {
302     const std::string powerControlLineName = "power-chassis-control";
303     const std::string pgoodLineName = "power-chassis-good";
304 
305     pgoodLine = gpiod::find_line(pgoodLineName);
306     if (!pgoodLine)
307     {
308         std::string errorString{"GPIO line name not found: " + pgoodLineName};
309         services.logErrorMsg(errorString);
310         throw std::runtime_error(errorString);
311     }
312     powerControlLine = gpiod::find_line(powerControlLineName);
313     if (!powerControlLine)
314     {
315         std::string errorString{"GPIO line name not found: " +
316                                 powerControlLineName};
317         services.logErrorMsg(errorString);
318         throw std::runtime_error(errorString);
319     }
320 
321     pgoodLine.request(
322         {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0});
323     int pgoodState = pgoodLine.get_value();
324     pgood = pgoodState;
325     state = pgoodState;
326     services.logInfoMsg(std::format("Pgood state: {}", pgoodState));
327 }
328 
329 void PowerControl::loadConfigFileAndCreateDevice()
330 {
331     // If compatible system types and device properties have been found
332     if (!compatibleSystemTypes.empty() && deviceProperties)
333     {
334         // Find the JSON configuration file
335         std::filesystem::path configFile = findConfigFile();
336         if (!configFile.empty())
337         {
338             // Parse the JSON configuration file
339             std::vector<std::unique_ptr<Rail>> rails;
340             if (parseConfigFile(configFile, rails))
341             {
342                 // Create the power sequencer device object
343                 createDevice(std::move(rails));
344             }
345         }
346     }
347 }
348 
349 std::filesystem::path PowerControl::findConfigFile()
350 {
351     // Find config file for current system based on compatible system types
352     std::filesystem::path configFile;
353     if (!compatibleSystemTypes.empty())
354     {
355         try
356         {
357             configFile = config_file_parser::find(compatibleSystemTypes);
358             if (!configFile.empty())
359             {
360                 services.logInfoMsg(std::format(
361                     "JSON configuration file found: {}", configFile.string()));
362             }
363         }
364         catch (const std::exception& e)
365         {
366             services.logErrorMsg(std::format(
367                 "Unable to find JSON configuration file: {}", e.what()));
368         }
369     }
370     return configFile;
371 }
372 
373 bool PowerControl::parseConfigFile(const std::filesystem::path& configFile,
374                                    std::vector<std::unique_ptr<Rail>>& rails)
375 {
376     // Parse JSON configuration file
377     bool wasParsed{false};
378     try
379     {
380         rails = config_file_parser::parse(configFile);
381         wasParsed = true;
382     }
383     catch (const std::exception& e)
384     {
385         services.logErrorMsg(std::format(
386             "Unable to parse JSON configuration file: {}", e.what()));
387     }
388     return wasParsed;
389 }
390 
391 void PowerControl::createDevice(std::vector<std::unique_ptr<Rail>> rails)
392 {
393     // Create power sequencer device based on device properties
394     if (deviceProperties)
395     {
396         try
397         {
398             if (deviceProperties->type == UCD90160Device::deviceName)
399             {
400                 device = std::make_unique<UCD90160Device>(
401                     std::move(rails), services, deviceProperties->bus,
402                     deviceProperties->address);
403             }
404             else if (deviceProperties->type == UCD90320Device::deviceName)
405             {
406                 device = std::make_unique<UCD90320Device>(
407                     std::move(rails), services, deviceProperties->bus,
408                     deviceProperties->address);
409             }
410             else
411             {
412                 throw std::runtime_error{std::format(
413                     "Unsupported device type: {}", deviceProperties->type)};
414             }
415             services.logInfoMsg(std::format(
416                 "Power sequencer device created: {}", device->getName()));
417         }
418         catch (const std::exception& e)
419         {
420             services.logErrorMsg(
421                 std::format("Unable to create device object: {}", e.what()));
422         }
423     }
424 }
425 
426 } // namespace phosphor::power::sequencer
427