xref: /openbmc/phosphor-power/phosphor-power-sequencer/src/power_control.cpp (revision 48781aef031d0fcf7d947ceea1848b9dd1047485)
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 "types.hpp"
20 #include "ucd90320_monitor.hpp"
21 
22 #include <fmt/chrono.h>
23 #include <fmt/format.h>
24 
25 #include <phosphor-logging/elog-errors.hpp>
26 #include <phosphor-logging/elog.hpp>
27 #include <phosphor-logging/log.hpp>
28 #include <xyz/openbmc_project/Common/error.hpp>
29 
30 #include <exception>
31 #include <string>
32 
33 using namespace phosphor::logging;
34 
35 namespace phosphor::power::sequencer
36 {
37 
38 const std::string interfaceName = "xyz.openbmc_project.Configuration.UCD90320";
39 const std::string addressPropertyName = "Address";
40 const std::string busPropertyName = "Bus";
41 const std::string namePropertyName = "Name";
42 
43 PowerControl::PowerControl(sdbusplus::bus_t& bus,
44                            const sdeventplus::Event& event) :
45     PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit},
46     bus{bus}, device{std::make_unique<PowerSequencerMonitor>(bus)},
47     match{bus,
48           sdbusplus::bus::match::rules::interfacesAdded() +
49               sdbusplus::bus::match::rules::sender(
50                   "xyz.openbmc_project.EntityManager"),
51           std::bind(&PowerControl::interfacesAddedHandler, this,
52                     std::placeholders::_1)},
53     pgoodWaitTimer{event, std::bind(&PowerControl::onFailureCallback, this)},
54     powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime},
55     timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval}
56 {
57     // Obtain dbus service name
58     bus.request_name(POWER_IFACE);
59 
60     setUpDevice();
61     setUpGpio();
62 }
63 
64 void PowerControl::getDeviceProperties(util::DbusPropertyMap& properties)
65 {
66     uint64_t* i2cBus = nullptr;
67     uint64_t* i2cAddress = nullptr;
68     std::string* name = nullptr;
69 
70     for (const auto& property : properties)
71     {
72         try
73         {
74             if (property.first == busPropertyName)
75             {
76                 i2cBus = std::get_if<uint64_t>(&properties[busPropertyName]);
77             }
78             else if (property.first == addressPropertyName)
79             {
80                 i2cAddress =
81                     std::get_if<uint64_t>(&properties[addressPropertyName]);
82             }
83             else if (property.first == namePropertyName)
84             {
85                 name = std::get_if<std::string>(&properties[namePropertyName]);
86             }
87         }
88         catch (const std::exception&)
89         {}
90     }
91 
92     if (i2cBus && i2cAddress && name && !name->empty())
93     {
94         log<level::DEBUG>(
95             fmt::format(
96                 "Found power sequencer device properties, name: {}, bus: {} addr: {:#02x} ",
97                 *name, *i2cBus, *i2cAddress)
98                 .c_str());
99         // Create device object
100         device = std::make_unique<UCD90320Monitor>(bus, *i2cBus, *i2cAddress);
101         deviceFound = true;
102     }
103 }
104 
105 int PowerControl::getPgood() const
106 {
107     return pgood;
108 }
109 
110 int PowerControl::getPgoodTimeout() const
111 {
112     return timeout.count();
113 }
114 
115 int PowerControl::getState() const
116 {
117     return state;
118 }
119 
120 void PowerControl::interfacesAddedHandler(sdbusplus::message_t& msg)
121 {
122     // Only continue if message is valid and device has not already been found
123     if (!msg || deviceFound)
124     {
125         return;
126     }
127 
128     try
129     {
130         // Read the dbus message
131         sdbusplus::message::object_path objPath;
132         std::map<std::string, std::map<std::string, util::DbusVariant>>
133             interfaces;
134         msg.read(objPath, interfaces);
135 
136         // Find the device interface, if present
137         auto itIntf = interfaces.find(interfaceName);
138         if (itIntf != interfaces.cend())
139         {
140             log<level::INFO>(
141                 fmt::format("InterfacesAdded for: {}", interfaceName).c_str());
142             getDeviceProperties(itIntf->second);
143         }
144     }
145     catch (const std::exception&)
146     {
147         // Error trying to read interfacesAdded message.
148     }
149 }
150 
151 void PowerControl::onFailureCallback()
152 {
153     log<level::INFO>("After onFailure wait");
154 
155     onFailure(false);
156 
157     // Power good has failed, call for chassis hard power off
158     auto method = bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT,
159                                       util::SYSTEMD_INTERFACE, "StartUnit");
160     method.append(util::POWEROFF_TARGET);
161     method.append("replace");
162     bus.call_noreply(method);
163 }
164 
165 void PowerControl::onFailure(bool timeout)
166 {
167     // Call device on failure
168     device->onFailure(timeout, powerSupplyError);
169 }
170 
171 void PowerControl::pollPgood()
172 {
173     if (inStateTransition)
174     {
175         // In transition between power on and off, check for timeout
176         const auto now = std::chrono::steady_clock::now();
177         if (now > pgoodTimeoutTime)
178         {
179             log<level::ERR>(
180                 fmt::format("Power state transition timeout, state: {}", state)
181                     .c_str());
182             inStateTransition = false;
183 
184             if (state)
185             {
186                 // Time out powering on
187                 onFailure(true);
188             }
189             else
190             {
191                 // Time out powering off
192                 std::map<std::string, std::string> additionalData{};
193                 device->logError(
194                     "xyz.openbmc_project.Power.Error.PowerOffTimeout",
195                     additionalData);
196             }
197 
198             failureFound = true;
199             return;
200         }
201     }
202 
203     int pgoodState = pgoodLine.get_value();
204     if (pgoodState != pgood)
205     {
206         // Power good has changed since last read
207         pgood = pgoodState;
208         if (pgoodState == 0)
209         {
210             emitPowerLostSignal();
211         }
212         else
213         {
214             emitPowerGoodSignal();
215             // Clear any errors on the transition to power on
216             powerSupplyError.clear();
217             failureFound = false;
218         }
219         emitPropertyChangedSignal("pgood");
220     }
221     if (pgoodState == state)
222     {
223         // Power good matches requested state
224         inStateTransition = false;
225     }
226     else if (!inStateTransition && (pgoodState == 0) && !failureFound)
227     {
228         // Not in power off state, not changing state, and power good is off
229         log<level::ERR>("Chassis pgood failure");
230         pgoodWaitTimer.restartOnce(std::chrono::seconds(7));
231         failureFound = true;
232     }
233 }
234 
235 void PowerControl::setPgoodTimeout(int t)
236 {
237     if (timeout.count() != t)
238     {
239         timeout = std::chrono::seconds(t);
240         emitPropertyChangedSignal("pgood_timeout");
241     }
242 }
243 
244 void PowerControl::setPowerSupplyError(const std::string& error)
245 {
246     powerSupplyError = error;
247 }
248 
249 void PowerControl::setState(int s)
250 {
251     if (state == s)
252     {
253         log<level::INFO>(
254             fmt::format("Power already at requested state: {}", state).c_str());
255         return;
256     }
257     if (s == 0)
258     {
259         // Wait for two seconds when powering down. This is to allow host and
260         // other BMC applications time to complete power off processing
261         std::this_thread::sleep_for(std::chrono::seconds(2));
262     }
263     else
264     {
265         // If minimum power off time has not passed, wait
266         if (powerOnAllowedTime > std::chrono::steady_clock::now())
267         {
268             log<level::INFO>(
269                 fmt::format(
270                     "Waiting {} seconds until power on allowed",
271                     std::chrono::duration_cast<std::chrono::seconds>(
272                         powerOnAllowedTime - std::chrono::steady_clock::now())
273                         .count())
274                     .c_str());
275         }
276         std::this_thread::sleep_until(powerOnAllowedTime);
277     }
278 
279     log<level::INFO>(fmt::format("setState: {}", s).c_str());
280     powerControlLine.request(
281         {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0});
282     powerControlLine.set_value(s);
283     powerControlLine.release();
284 
285     if (s == 0)
286     {
287         // Set a minimum amount of time to wait before next power on
288         powerOnAllowedTime = std::chrono::steady_clock::now() +
289                              minimumPowerOffTime;
290     }
291 
292     pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout;
293     inStateTransition = true;
294     state = s;
295     emitPropertyChangedSignal("state");
296 }
297 
298 void PowerControl::setUpDevice()
299 {
300     try
301     {
302         auto objects = util::getSubTree(bus, "/", interfaceName, 0);
303 
304         // Search for matching interface in returned objects
305         for (const auto& [path, services] : objects)
306         {
307             auto service = services.begin()->first;
308 
309             if (path.empty() || service.empty())
310             {
311                 continue;
312             }
313 
314             // Get the properties for the device interface
315             auto properties = util::getAllProperties(bus, path, interfaceName,
316                                                      service);
317 
318             getDeviceProperties(properties);
319         }
320     }
321     catch (const std::exception&)
322     {
323         // Interface or property not found. Let the Interfaces Added callback
324         // process the information once the interfaces are added to D-Bus.
325     }
326 }
327 
328 void PowerControl::setUpGpio()
329 {
330     const std::string powerControlLineName = "power-chassis-control";
331     const std::string pgoodLineName = "power-chassis-good";
332 
333     pgoodLine = gpiod::find_line(pgoodLineName);
334     if (!pgoodLine)
335     {
336         std::string errorString{"GPIO line name not found: " + pgoodLineName};
337         log<level::ERR>(errorString.c_str());
338         report<
339             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
340         throw std::runtime_error(errorString);
341     }
342     powerControlLine = gpiod::find_line(powerControlLineName);
343     if (!powerControlLine)
344     {
345         std::string errorString{"GPIO line name not found: " +
346                                 powerControlLineName};
347         log<level::ERR>(errorString.c_str());
348         report<
349             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
350         throw std::runtime_error(errorString);
351     }
352 
353     pgoodLine.request(
354         {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0});
355     int pgoodState = pgoodLine.get_value();
356     pgood = pgoodState;
357     state = pgoodState;
358     log<level::INFO>(fmt::format("Pgood state: {}", pgoodState).c_str());
359 }
360 
361 } // namespace phosphor::power::sequencer
362