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::bus& bus, 44 const sdeventplus::Event& event) : 45 PowerObject{bus, POWER_OBJ_PATH, true}, 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 powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime}, 54 timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval} 55 { 56 // Obtain dbus service name 57 bus.request_name(POWER_IFACE); 58 59 setUpDevice(); 60 setUpGpio(); 61 } 62 63 void PowerControl::getDeviceProperties(util::DbusPropertyMap& properties) 64 { 65 uint64_t* i2cBus = nullptr; 66 uint64_t* i2cAddress = nullptr; 67 std::string* name = nullptr; 68 69 for (const auto& property : properties) 70 { 71 try 72 { 73 if (property.first == busPropertyName) 74 { 75 i2cBus = std::get_if<uint64_t>(&properties[busPropertyName]); 76 } 77 else if (property.first == addressPropertyName) 78 { 79 i2cAddress = 80 std::get_if<uint64_t>(&properties[addressPropertyName]); 81 } 82 else if (property.first == namePropertyName) 83 { 84 name = std::get_if<std::string>(&properties[namePropertyName]); 85 } 86 } 87 catch (std::exception& e) 88 {} 89 } 90 91 if (i2cBus && i2cAddress && name && !name->empty()) 92 { 93 log<level::DEBUG>( 94 fmt::format( 95 "Found power sequencer device properties, name: {}, bus: {} addr: {:#02x} ", 96 *name, *i2cBus, *i2cAddress) 97 .c_str()); 98 // Create device object 99 device = std::make_unique<UCD90320Monitor>(bus, *i2cBus, *i2cAddress); 100 deviceFound = true; 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 // Only continue if message is valid and device has not already been found 122 if (!msg || deviceFound) 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 if (state) 162 { 163 // Time out powering on 164 device->onFailure(true, powerSupplyError); 165 } 166 else 167 { 168 // Time out powering off 169 std::map<std::string, std::string> additionalData{}; 170 device->logError( 171 "xyz.openbmc_project.Power.Error.PowerOffTimeout", 172 additionalData); 173 } 174 175 failureFound = true; 176 return; 177 } 178 } 179 180 int pgoodState = pgoodLine.get_value(); 181 if (pgoodState != pgood) 182 { 183 // Power good has changed since last read 184 pgood = pgoodState; 185 if (pgoodState == 0) 186 { 187 emitPowerLostSignal(); 188 } 189 else 190 { 191 emitPowerGoodSignal(); 192 // Clear any errors on the transition to power on 193 powerSupplyError.clear(); 194 failureFound = false; 195 } 196 emitPropertyChangedSignal("pgood"); 197 } 198 if (pgoodState == state) 199 { 200 // Power good matches requested state 201 inStateTransition = false; 202 } 203 else if (!inStateTransition && (pgoodState == 0) && !failureFound) 204 { 205 // Not in power off state, not changing state, and power good is off 206 device->onFailure(false, powerSupplyError); 207 failureFound = true; 208 // Power good has failed, call for chassis hard power off 209 log<level::ERR>("Chassis pgood failure"); 210 211 auto method = 212 bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT, 213 util::SYSTEMD_INTERFACE, "StartUnit"); 214 method.append(util::POWEROFF_TARGET); 215 method.append("replace"); 216 bus.call_noreply(method); 217 } 218 } 219 220 void PowerControl::setPgoodTimeout(int t) 221 { 222 if (timeout.count() != t) 223 { 224 timeout = std::chrono::seconds(t); 225 emitPropertyChangedSignal("pgood_timeout"); 226 } 227 } 228 229 void PowerControl::setPowerSupplyError(const std::string& error) 230 { 231 powerSupplyError = error; 232 } 233 234 void PowerControl::setState(int s) 235 { 236 if (state == s) 237 { 238 log<level::INFO>( 239 fmt::format("Power already at requested state: {}", state).c_str()); 240 return; 241 } 242 if (s == 0) 243 { 244 // Wait for two seconds when powering down. This is to allow host and 245 // other BMC applications time to complete power off processing 246 std::this_thread::sleep_for(std::chrono::seconds(2)); 247 } 248 else 249 { 250 // If minimum power off time has not passed, wait 251 if (powerOnAllowedTime > std::chrono::steady_clock::now()) 252 { 253 log<level::INFO>( 254 fmt::format( 255 "Waiting {} seconds until power on allowed", 256 std::chrono::duration_cast<std::chrono::seconds>( 257 powerOnAllowedTime - std::chrono::steady_clock::now()) 258 .count()) 259 .c_str()); 260 } 261 std::this_thread::sleep_until(powerOnAllowedTime); 262 } 263 264 log<level::INFO>(fmt::format("setState: {}", s).c_str()); 265 powerControlLine.request( 266 {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0}); 267 powerControlLine.set_value(s); 268 powerControlLine.release(); 269 270 if (s == 0) 271 { 272 // Set a minimum amount of time to wait before next power on 273 powerOnAllowedTime = 274 std::chrono::steady_clock::now() + minimumPowerOffTime; 275 } 276 277 pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout; 278 inStateTransition = true; 279 state = s; 280 emitPropertyChangedSignal("state"); 281 } 282 283 void PowerControl::setUpDevice() 284 { 285 try 286 { 287 auto objects = util::getSubTree(bus, "/", interfaceName, 0); 288 289 // Search for matching interface in returned objects 290 for (const auto& [path, services] : objects) 291 { 292 auto service = services.begin()->first; 293 294 if (path.empty() || service.empty()) 295 { 296 continue; 297 } 298 299 // Get the properties for the device interface 300 auto properties = 301 util::getAllProperties(bus, path, interfaceName, service); 302 303 getDeviceProperties(properties); 304 } 305 } 306 catch (const std::exception& e) 307 { 308 // Interface or property not found. Let the Interfaces Added callback 309 // process the information once the interfaces are added to D-Bus. 310 } 311 } 312 313 void PowerControl::setUpGpio() 314 { 315 const std::string powerControlLineName = "power-chassis-control"; 316 const std::string pgoodLineName = "power-chassis-good"; 317 318 pgoodLine = gpiod::find_line(pgoodLineName); 319 if (!pgoodLine) 320 { 321 std::string errorString{"GPIO line name not found: " + pgoodLineName}; 322 log<level::ERR>(errorString.c_str()); 323 report< 324 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>(); 325 throw std::runtime_error(errorString); 326 } 327 powerControlLine = gpiod::find_line(powerControlLineName); 328 if (!powerControlLine) 329 { 330 std::string errorString{"GPIO line name not found: " + 331 powerControlLineName}; 332 log<level::ERR>(errorString.c_str()); 333 report< 334 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>(); 335 throw std::runtime_error(errorString); 336 } 337 338 pgoodLine.request( 339 {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0}); 340 int pgoodState = pgoodLine.get_value(); 341 pgood = pgoodState; 342 state = pgoodState; 343 log<level::INFO>(fmt::format("Pgood state: {}", pgoodState).c_str()); 344 } 345 346 } // namespace phosphor::power::sequencer 347