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