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