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 21 #include <fmt/format.h> 22 #include <sys/types.h> 23 #include <unistd.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}, timer{event, std::bind(&PowerControl::pollPgood, this), 47 pollInterval} 48 { 49 // Obtain dbus service name 50 bus.request_name(POWER_IFACE); 51 52 // Subscribe to D-Bus interfacesAdded signal from Entity Manager. This 53 // notifies us if the interface becomes available later. 54 match = std::make_unique<sdbusplus::bus::match_t>( 55 bus, 56 sdbusplus::bus::match::rules::interfacesAdded() + 57 sdbusplus::bus::match::rules::sender( 58 "xyz.openbmc_project.EntityManager"), 59 std::bind(&PowerControl::interfacesAddedHandler, this, 60 std::placeholders::_1)); 61 setUpDevice(); 62 setUpGpio(); 63 } 64 65 void PowerControl::getDeviceProperties(util::DbusPropertyMap& properties) 66 { 67 uint64_t* i2cBus = nullptr; 68 uint64_t* i2cAddress = nullptr; 69 std::string* name = nullptr; 70 71 for (const auto& property : properties) 72 { 73 try 74 { 75 if (property.first == busPropertyName) 76 { 77 i2cBus = std::get_if<uint64_t>(&properties[busPropertyName]); 78 } 79 else if (property.first == addressPropertyName) 80 { 81 i2cAddress = 82 std::get_if<uint64_t>(&properties[addressPropertyName]); 83 } 84 else if (property.first == namePropertyName) 85 { 86 name = std::get_if<std::string>(&properties[namePropertyName]); 87 } 88 } 89 catch (std::exception& e) 90 {} 91 } 92 93 if (i2cBus && i2cAddress && name && !name->empty()) 94 { 95 log<level::INFO>( 96 fmt::format( 97 "Found power sequencer device properties, name: {}, bus: {} addr: {:#02x} ", 98 *name, *i2cBus, *i2cAddress) 99 .c_str()); 100 // Create device object 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 // Verify message is valid 122 if (!msg) 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 try 162 { 163 auto method = bus.new_method_call( 164 "xyz.openbmc_project.Logging", 165 "/xyz/openbmc_project/logging", 166 "xyz.openbmc_project.Logging.Create", "Create"); 167 168 std::map<std::string, std::string> additionalData; 169 // Add PID to AdditionalData 170 additionalData.emplace("_PID", std::to_string(getpid())); 171 172 method.append( 173 state ? "xyz.openbmc_project.Power.Error.PowerOnTimeout" 174 : "xyz.openbmc_project.Power.Error.PowerOffTimeout", 175 sdbusplus::xyz::openbmc_project::Logging::server::Entry:: 176 Level::Critical, 177 additionalData); 178 bus.call_noreply(method); 179 } 180 catch (const std::exception& e) 181 { 182 log<level::ERR>( 183 fmt::format( 184 "Unable to log timeout error, state: {}, error {}", 185 state, e.what()) 186 .c_str()); 187 } 188 189 return; 190 } 191 } 192 193 int pgoodState = pgoodLine.get_value(); 194 if (pgoodState != pgood) 195 { 196 // Power good has changed since last read 197 pgood = pgoodState; 198 if (pgoodState == 0) 199 { 200 emitPowerLostSignal(); 201 } 202 else 203 { 204 emitPowerGoodSignal(); 205 } 206 emitPropertyChangedSignal("pgood"); 207 } 208 if (pgoodState == state) 209 { 210 // Power good matches requested state 211 inStateTransition = false; 212 } 213 else if (!inStateTransition && (pgoodState == 0)) 214 { 215 // Not in power off state, not changing state, and power good is off 216 // Power good has failed, call for chassis hard power off 217 log<level::ERR>("Chassis pgood failure"); 218 219 auto method = 220 bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT, 221 util::SYSTEMD_INTERFACE, "StartUnit"); 222 method.append(util::POWEROFF_TARGET); 223 method.append("replace"); 224 bus.call_noreply(method); 225 } 226 } 227 228 void PowerControl::setPgoodTimeout(int t) 229 { 230 if (timeout.count() != t) 231 { 232 timeout = std::chrono::seconds(t); 233 emitPropertyChangedSignal("pgood_timeout"); 234 } 235 } 236 237 void PowerControl::setState(int s) 238 { 239 if (state == s) 240 { 241 log<level::INFO>( 242 fmt::format("Power already at requested state: {}", state).c_str()); 243 return; 244 } 245 if (s == 0) 246 { 247 // Wait for two seconds when powering down. This is to allow host and 248 // other BMC applications time to complete power off processing 249 std::this_thread::sleep_for(std::chrono::seconds(2)); 250 } 251 252 log<level::INFO>(fmt::format("setState: {}", s).c_str()); 253 powerControlLine.request( 254 {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0}); 255 powerControlLine.set_value(s); 256 powerControlLine.release(); 257 258 pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout; 259 inStateTransition = true; 260 state = s; 261 emitPropertyChangedSignal("state"); 262 } 263 264 void PowerControl::setUpDevice() 265 { 266 try 267 { 268 auto objects = util::getSubTree(bus, "/", interfaceName, 0); 269 270 // Search for matching interface in returned objects 271 for (const auto& [path, services] : objects) 272 { 273 auto service = services.begin()->first; 274 275 if (path.empty() || service.empty()) 276 { 277 continue; 278 } 279 280 // Get the properties for the device interface 281 auto properties = 282 util::getAllProperties(bus, path, interfaceName, service); 283 284 getDeviceProperties(properties); 285 } 286 } 287 catch (const std::exception& e) 288 { 289 // Interface or property not found. Let the Interfaces Added callback 290 // process the information once the interfaces are added to D-Bus. 291 } 292 } 293 294 void PowerControl::setUpGpio() 295 { 296 const std::string powerControlLineName = "power-chassis-control"; 297 const std::string pgoodLineName = "power-chassis-good"; 298 299 pgoodLine = gpiod::find_line(pgoodLineName); 300 if (!pgoodLine) 301 { 302 std::string errorString{"GPIO line name not found: " + pgoodLineName}; 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 powerControlLine = gpiod::find_line(powerControlLineName); 309 if (!powerControlLine) 310 { 311 std::string errorString{"GPIO line name not found: " + 312 powerControlLineName}; 313 log<level::ERR>(errorString.c_str()); 314 report< 315 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>(); 316 throw std::runtime_error(errorString); 317 } 318 319 pgoodLine.request( 320 {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0}); 321 int pgoodState = pgoodLine.get_value(); 322 pgood = pgoodState; 323 state = pgoodState; 324 log<level::INFO>(fmt::format("Pgood state: {}", pgoodState).c_str()); 325 } 326 327 } // namespace phosphor::power::sequencer 328