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