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