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