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/format.h> 23 24 #include <phosphor-logging/elog-errors.hpp> 25 #include <phosphor-logging/elog.hpp> 26 #include <phosphor-logging/log.hpp> 27 #include <xyz/openbmc_project/Common/error.hpp> 28 29 #include <exception> 30 #include <string> 31 32 using namespace phosphor::logging; 33 34 namespace phosphor::power::sequencer 35 { 36 37 const std::string interfaceName = "xyz.openbmc_project.Configuration.UCD90320"; 38 const std::string addressPropertyName = "Address"; 39 const std::string busPropertyName = "Bus"; 40 const std::string namePropertyName = "Name"; 41 42 PowerControl::PowerControl(sdbusplus::bus::bus& bus, 43 const sdeventplus::Event& event) : 44 PowerObject{bus, POWER_OBJ_PATH, true}, 45 bus{bus}, device{std::make_unique<PowerSequencerMonitor>(bus)}, 46 match{bus, 47 sdbusplus::bus::match::rules::interfacesAdded() + 48 sdbusplus::bus::match::rules::sender( 49 "xyz.openbmc_project.EntityManager"), 50 std::bind(&PowerControl::interfacesAddedHandler, this, 51 std::placeholders::_1)}, 52 timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval} 53 { 54 // Obtain dbus service name 55 bus.request_name(POWER_IFACE); 56 57 setUpDevice(); 58 setUpGpio(); 59 } 60 61 void PowerControl::getDeviceProperties(util::DbusPropertyMap& properties) 62 { 63 uint64_t* i2cBus = nullptr; 64 uint64_t* i2cAddress = nullptr; 65 std::string* name = nullptr; 66 67 for (const auto& property : properties) 68 { 69 try 70 { 71 if (property.first == busPropertyName) 72 { 73 i2cBus = std::get_if<uint64_t>(&properties[busPropertyName]); 74 } 75 else if (property.first == addressPropertyName) 76 { 77 i2cAddress = 78 std::get_if<uint64_t>(&properties[addressPropertyName]); 79 } 80 else if (property.first == namePropertyName) 81 { 82 name = std::get_if<std::string>(&properties[namePropertyName]); 83 } 84 } 85 catch (std::exception& e) 86 {} 87 } 88 89 if (i2cBus && i2cAddress && name && !name->empty()) 90 { 91 log<level::DEBUG>( 92 fmt::format( 93 "Found power sequencer device properties, name: {}, bus: {} addr: {:#02x} ", 94 *name, *i2cBus, *i2cAddress) 95 .c_str()); 96 // Create device object 97 device = std::make_unique<UCD90320Monitor>(bus, *i2cBus, *i2cAddress); 98 deviceFound = true; 99 } 100 } 101 102 int PowerControl::getPgood() const 103 { 104 return pgood; 105 } 106 107 int PowerControl::getPgoodTimeout() const 108 { 109 return timeout.count(); 110 } 111 112 int PowerControl::getState() const 113 { 114 return state; 115 } 116 117 void PowerControl::interfacesAddedHandler(sdbusplus::message::message& msg) 118 { 119 // Only continue if message is valid and device has not already been found 120 if (!msg || deviceFound) 121 { 122 return; 123 } 124 125 try 126 { 127 // Read the dbus message 128 sdbusplus::message::object_path objPath; 129 std::map<std::string, std::map<std::string, util::DbusVariant>> 130 interfaces; 131 msg.read(objPath, interfaces); 132 133 // Find the device interface, if present 134 auto itIntf = interfaces.find(interfaceName); 135 if (itIntf != interfaces.cend()) 136 { 137 log<level::INFO>( 138 fmt::format("InterfacesAdded for: {}", interfaceName).c_str()); 139 getDeviceProperties(itIntf->second); 140 } 141 } 142 catch (const std::exception&) 143 { 144 // Error trying to read interfacesAdded message. 145 } 146 } 147 148 void PowerControl::pollPgood() 149 { 150 if (inStateTransition) 151 { 152 // In transition between power on and off, check for timeout 153 const auto now = std::chrono::steady_clock::now(); 154 if (now > pgoodTimeoutTime) 155 { 156 log<level::ERR>("ERROR PowerControl: Pgood poll timeout"); 157 inStateTransition = false; 158 159 if (state) 160 { 161 // Time out powering on 162 device->onFailure(true, powerSupplyError); 163 } 164 else 165 { 166 // Time out powering off 167 std::map<std::string, std::string> additionalData{}; 168 device->logError( 169 "xyz.openbmc_project.Power.Error.PowerOffTimeout", 170 additionalData); 171 } 172 173 return; 174 } 175 } 176 177 int pgoodState = pgoodLine.get_value(); 178 if (pgoodState != pgood) 179 { 180 // Power good has changed since last read 181 pgood = pgoodState; 182 if (pgoodState == 0) 183 { 184 emitPowerLostSignal(); 185 } 186 else 187 { 188 emitPowerGoodSignal(); 189 } 190 emitPropertyChangedSignal("pgood"); 191 } 192 if (pgoodState == state) 193 { 194 // Power good matches requested state 195 inStateTransition = false; 196 } 197 else if (!inStateTransition && (pgoodState == 0)) 198 { 199 // Not in power off state, not changing state, and power good is off 200 device->onFailure(false, powerSupplyError); 201 // Power good has failed, call for chassis hard power off 202 log<level::ERR>("Chassis pgood failure"); 203 204 auto method = 205 bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT, 206 util::SYSTEMD_INTERFACE, "StartUnit"); 207 method.append(util::POWEROFF_TARGET); 208 method.append("replace"); 209 bus.call_noreply(method); 210 } 211 } 212 213 void PowerControl::setPgoodTimeout(int t) 214 { 215 if (timeout.count() != t) 216 { 217 timeout = std::chrono::seconds(t); 218 emitPropertyChangedSignal("pgood_timeout"); 219 } 220 } 221 222 void PowerControl::setPowerSupplyError(const std::string& error) 223 { 224 powerSupplyError = error; 225 } 226 227 void PowerControl::setState(int s) 228 { 229 if (state == s) 230 { 231 log<level::INFO>( 232 fmt::format("Power already at requested state: {}", state).c_str()); 233 return; 234 } 235 if (s == 0) 236 { 237 // Wait for two seconds when powering down. This is to allow host and 238 // other BMC applications time to complete power off processing 239 std::this_thread::sleep_for(std::chrono::seconds(2)); 240 } 241 242 log<level::INFO>(fmt::format("setState: {}", s).c_str()); 243 powerControlLine.request( 244 {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0}); 245 powerControlLine.set_value(s); 246 powerControlLine.release(); 247 248 pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout; 249 inStateTransition = true; 250 state = s; 251 emitPropertyChangedSignal("state"); 252 } 253 254 void PowerControl::setUpDevice() 255 { 256 try 257 { 258 auto objects = util::getSubTree(bus, "/", interfaceName, 0); 259 260 // Search for matching interface in returned objects 261 for (const auto& [path, services] : objects) 262 { 263 auto service = services.begin()->first; 264 265 if (path.empty() || service.empty()) 266 { 267 continue; 268 } 269 270 // Get the properties for the device interface 271 auto properties = 272 util::getAllProperties(bus, path, interfaceName, service); 273 274 getDeviceProperties(properties); 275 } 276 } 277 catch (const std::exception& e) 278 { 279 // Interface or property not found. Let the Interfaces Added callback 280 // process the information once the interfaces are added to D-Bus. 281 } 282 } 283 284 void PowerControl::setUpGpio() 285 { 286 const std::string powerControlLineName = "power-chassis-control"; 287 const std::string pgoodLineName = "power-chassis-good"; 288 289 pgoodLine = gpiod::find_line(pgoodLineName); 290 if (!pgoodLine) 291 { 292 std::string errorString{"GPIO line name not found: " + pgoodLineName}; 293 log<level::ERR>(errorString.c_str()); 294 report< 295 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>(); 296 throw std::runtime_error(errorString); 297 } 298 powerControlLine = gpiod::find_line(powerControlLineName); 299 if (!powerControlLine) 300 { 301 std::string errorString{"GPIO line name not found: " + 302 powerControlLineName}; 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 309 pgoodLine.request( 310 {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0}); 311 int pgoodState = pgoodLine.get_value(); 312 pgood = pgoodState; 313 state = pgoodState; 314 log<level::INFO>(fmt::format("Pgood state: {}", pgoodState).c_str()); 315 } 316 317 } // namespace phosphor::power::sequencer 318