xref: /openbmc/phosphor-power/phosphor-power-sequencer/src/power_control.cpp (revision b3ba516f3f418b9b1a98d4bd27210799383262b2)
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 
21 #include <fmt/format.h>
22 #include <sys/types.h>
23 #include <unistd.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}, timer{event, std::bind(&PowerControl::pollPgood, this),
47                     pollInterval}
48 {
49     // Obtain dbus service name
50     bus.request_name(POWER_IFACE);
51 
52     // Subscribe to D-Bus interfacesAdded signal from Entity Manager.  This
53     // notifies us if the interface becomes available later.
54     match = std::make_unique<sdbusplus::bus::match_t>(
55         bus,
56         sdbusplus::bus::match::rules::interfacesAdded() +
57             sdbusplus::bus::match::rules::sender(
58                 "xyz.openbmc_project.EntityManager"),
59         std::bind(&PowerControl::interfacesAddedHandler, this,
60                   std::placeholders::_1));
61     setUpDevice();
62     setUpGpio();
63 }
64 
65 void PowerControl::getDeviceProperties(util::DbusPropertyMap& properties)
66 {
67     uint64_t* i2cBus = nullptr;
68     uint64_t* i2cAddress = nullptr;
69     std::string* name = nullptr;
70 
71     for (const auto& property : properties)
72     {
73         try
74         {
75             if (property.first == busPropertyName)
76             {
77                 i2cBus = std::get_if<uint64_t>(&properties[busPropertyName]);
78             }
79             else if (property.first == addressPropertyName)
80             {
81                 i2cAddress =
82                     std::get_if<uint64_t>(&properties[addressPropertyName]);
83             }
84             else if (property.first == namePropertyName)
85             {
86                 name = std::get_if<std::string>(&properties[namePropertyName]);
87             }
88         }
89         catch (std::exception& e)
90         {}
91     }
92 
93     if (i2cBus && i2cAddress && name && !name->empty())
94     {
95         log<level::INFO>(
96             fmt::format(
97                 "Found power sequencer device properties, name: {}, bus: {} addr: {:#02x} ",
98                 *name, *i2cBus, *i2cAddress)
99                 .c_str());
100         // Create device object
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     // Verify message is valid
122     if (!msg)
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             try
162             {
163                 auto method = bus.new_method_call(
164                     "xyz.openbmc_project.Logging",
165                     "/xyz/openbmc_project/logging",
166                     "xyz.openbmc_project.Logging.Create", "Create");
167 
168                 std::map<std::string, std::string> additionalData;
169                 // Add PID to AdditionalData
170                 additionalData.emplace("_PID", std::to_string(getpid()));
171 
172                 method.append(
173                     state ? "xyz.openbmc_project.Power.Error.PowerOnTimeout"
174                           : "xyz.openbmc_project.Power.Error.PowerOffTimeout",
175                     sdbusplus::xyz::openbmc_project::Logging::server::Entry::
176                         Level::Critical,
177                     additionalData);
178                 bus.call_noreply(method);
179             }
180             catch (const std::exception& e)
181             {
182                 log<level::ERR>(
183                     fmt::format(
184                         "Unable to log timeout error, state: {}, error {}",
185                         state, e.what())
186                         .c_str());
187             }
188 
189             return;
190         }
191     }
192 
193     int pgoodState = pgoodLine.get_value();
194     if (pgoodState != pgood)
195     {
196         // Power good has changed since last read
197         pgood = pgoodState;
198         if (pgoodState == 0)
199         {
200             emitPowerLostSignal();
201         }
202         else
203         {
204             emitPowerGoodSignal();
205         }
206         emitPropertyChangedSignal("pgood");
207     }
208     if (pgoodState == state)
209     {
210         // Power good matches requested state
211         inStateTransition = false;
212     }
213     else if (!inStateTransition && (pgoodState == 0))
214     {
215         // Not in power off state, not changing state, and power good is off
216         // Power good has failed, call for chassis hard power off
217         log<level::ERR>("Chassis pgood failure");
218 
219         auto method =
220             bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT,
221                                 util::SYSTEMD_INTERFACE, "StartUnit");
222         method.append(util::POWEROFF_TARGET);
223         method.append("replace");
224         bus.call_noreply(method);
225     }
226 }
227 
228 void PowerControl::setPgoodTimeout(int t)
229 {
230     if (timeout.count() != t)
231     {
232         timeout = std::chrono::seconds(t);
233         emitPropertyChangedSignal("pgood_timeout");
234     }
235 }
236 
237 void PowerControl::setState(int s)
238 {
239     if (state == s)
240     {
241         log<level::INFO>(
242             fmt::format("Power already at requested state: {}", state).c_str());
243         return;
244     }
245     if (s == 0)
246     {
247         // Wait for two seconds when powering down. This is to allow host and
248         // other BMC applications time to complete power off processing
249         std::this_thread::sleep_for(std::chrono::seconds(2));
250     }
251 
252     log<level::INFO>(fmt::format("setState: {}", s).c_str());
253     powerControlLine.request(
254         {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0});
255     powerControlLine.set_value(s);
256     powerControlLine.release();
257 
258     pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout;
259     inStateTransition = true;
260     state = s;
261     emitPropertyChangedSignal("state");
262 }
263 
264 void PowerControl::setUpDevice()
265 {
266     try
267     {
268         auto objects = util::getSubTree(bus, "/", interfaceName, 0);
269 
270         // Search for matching interface in returned objects
271         for (const auto& [path, services] : objects)
272         {
273             auto service = services.begin()->first;
274 
275             if (path.empty() || service.empty())
276             {
277                 continue;
278             }
279 
280             // Get the properties for the device interface
281             auto properties =
282                 util::getAllProperties(bus, path, interfaceName, service);
283 
284             getDeviceProperties(properties);
285         }
286     }
287     catch (const std::exception& e)
288     {
289         // Interface or property not found. Let the Interfaces Added callback
290         // process the information once the interfaces are added to D-Bus.
291     }
292 }
293 
294 void PowerControl::setUpGpio()
295 {
296     const std::string powerControlLineName = "power-chassis-control";
297     const std::string pgoodLineName = "power-chassis-good";
298 
299     pgoodLine = gpiod::find_line(pgoodLineName);
300     if (!pgoodLine)
301     {
302         std::string errorString{"GPIO line name not found: " + pgoodLineName};
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     powerControlLine = gpiod::find_line(powerControlLineName);
309     if (!powerControlLine)
310     {
311         std::string errorString{"GPIO line name not found: " +
312                                 powerControlLineName};
313         log<level::ERR>(errorString.c_str());
314         report<
315             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
316         throw std::runtime_error(errorString);
317     }
318 
319     pgoodLine.request(
320         {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0});
321     int pgoodState = pgoodLine.get_value();
322     pgood = pgoodState;
323     state = pgoodState;
324     log<level::INFO>(fmt::format("Pgood state: {}", pgoodState).c_str());
325 }
326 
327 } // namespace phosphor::power::sequencer
328