1 #include "button_handler.hpp" 2 3 #include "config.hpp" 4 #include "gpio.hpp" 5 #include "power_button_profile_factory.hpp" 6 7 #include <phosphor-logging/lg2.hpp> 8 #include <xyz/openbmc_project/Chassis/Buttons/Power/server.hpp> 9 #include <xyz/openbmc_project/State/Chassis/server.hpp> 10 #include <xyz/openbmc_project/State/Host/server.hpp> 11 12 #include <fstream> 13 #include <iostream> 14 #include <string> 15 16 namespace phosphor 17 { 18 namespace button 19 { 20 21 namespace sdbusRule = sdbusplus::bus::match::rules; 22 using namespace sdbusplus::xyz::openbmc_project::State::server; 23 using namespace sdbusplus::xyz::openbmc_project::Chassis::Buttons::server; 24 25 const std::map<std::string, Chassis::Transition> chassisPwrCtls = { 26 {"chassis-on", Chassis::Transition::On}, 27 {"chassis-off", Chassis::Transition::Off}, 28 {"chassis-cycle", Chassis::Transition::PowerCycle}}; 29 30 constexpr auto chassisIface = "xyz.openbmc_project.State.Chassis"; 31 constexpr auto hostIface = "xyz.openbmc_project.State.Host"; 32 constexpr auto powerButtonIface = "xyz.openbmc_project.Chassis.Buttons.Power"; 33 constexpr auto idButtonIface = "xyz.openbmc_project.Chassis.Buttons.ID"; 34 constexpr auto resetButtonIface = "xyz.openbmc_project.Chassis.Buttons.Reset"; 35 constexpr auto ledGroupIface = "xyz.openbmc_project.Led.Group"; 36 constexpr auto ledGroupBasePath = "/xyz/openbmc_project/led/groups/"; 37 constexpr auto hostSelectorIface = 38 "xyz.openbmc_project.Chassis.Buttons.HostSelector"; 39 constexpr auto debugHostSelectorIface = 40 "xyz.openbmc_project.Chassis.Buttons.Button"; 41 42 constexpr auto propertyIface = "org.freedesktop.DBus.Properties"; 43 constexpr auto mapperIface = "xyz.openbmc_project.ObjectMapper"; 44 45 constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper"; 46 constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper"; 47 constexpr auto BMC_POSITION = 0; 48 49 std::vector<std::map<uint16_t, Chassis::Transition>> multiPwrBtnActConf; 50 51 Handler::Handler(sdbusplus::bus_t& bus) : bus(bus) 52 { 53 /* So far, there are two modes for multi-host power control 54 - host select button mode, e.g.: Yosemite V2 55 only one power button with host select switch, 56 which's interface for handling target host, 57 in the case, hostSelectButtonMode = true 58 - multi power button mode, e.g.: Greatlakes 59 each slot/sled has its own power button, 60 in the case, hostSelectButtonMode = false */ 61 hostSelectButtonMode = 62 !getService(HS_DBUS_OBJECT_NAME, hostSelectorIface).empty(); 63 size_t powerButtonCount = 1; 64 if (!hostSelectButtonMode) 65 { 66 powerButtonCount = phosphor::button::numberOfChassis(); 67 } 68 69 std::ifstream gpios{gpioDefFile}; 70 auto configDefJson = nlohmann::json::parse(gpios, nullptr, true); 71 nlohmann::json gpioDefs = configDefJson["gpio_definitions"]; 72 73 for (const auto& gpioConfig : gpioDefs) 74 { 75 if (gpioConfig.contains("multi-action")) 76 { 77 std::map<uint16_t, Chassis::Transition> mapEntry; 78 const auto& multiActCfg = gpioConfig["multi-action"]; 79 for (const auto& ActCfg : multiActCfg) 80 { 81 auto chassisPwrCtl = chassisPwrCtls.find(ActCfg["action"]); 82 if (chassisPwrCtl != chassisPwrCtls.end()) 83 { 84 auto duration = ActCfg["duration"].get<uint16_t>(); 85 mapEntry[duration] = chassisPwrCtl->second; 86 } 87 else 88 { 89 lg2::error("unknown power button action"); 90 } 91 } 92 multiPwrBtnActConf.emplace_back(mapEntry); 93 } 94 else 95 { 96 isButtonMultiActionSupport = false; 97 break; 98 } 99 } 100 101 try 102 { 103 if (!getService(POWER_DBUS_OBJECT_NAME, powerButtonIface).empty()) 104 { 105 lg2::info("Starting power button handler"); 106 107 // Check for a custom handler 108 powerButtonProfile = 109 PowerButtonProfileFactory::instance().createProfile(bus); 110 111 if (!powerButtonProfile) 112 { 113 powerButtonReleased = std::make_unique<sdbusplus::bus::match_t>( 114 bus, 115 sdbusRule::type::signal() + sdbusRule::member("Released") + 116 sdbusRule::path(POWER_DBUS_OBJECT_NAME) + 117 sdbusRule::interface(powerButtonIface), 118 std::bind(std::mem_fn(&Handler::powerReleased), this, 119 std::placeholders::_1)); 120 } 121 } 122 123 if (!hostSelectButtonMode && isButtonMultiActionSupport) 124 { 125 lg2::info("Starting multi power button handler"); 126 // The index, 'countIter', starts at 1 and increments, 127 // representing slot_1 through slot_N. 128 for (size_t countIter = 1; countIter <= powerButtonCount; 129 countIter++) 130 { 131 std::unique_ptr<sdbusplus::bus::match_t> 132 multiPowerReleaseMatch = 133 std::make_unique<sdbusplus::bus::match_t>( 134 bus, 135 sdbusRule::type::signal() + 136 sdbusRule::member("Released") + 137 sdbusRule::path(POWER_DBUS_OBJECT_NAME + 138 std::to_string(countIter)) + 139 sdbusRule::interface(powerButtonIface), 140 std::bind(std::mem_fn(&Handler::powerReleased), 141 this, std::placeholders::_1)); 142 multiPowerButtonReleased.emplace_back( 143 std::move(multiPowerReleaseMatch)); 144 } 145 } 146 } 147 catch (const sdbusplus::exception_t& e) 148 { 149 lg2::error("Error creating power button handler: {ERROR}", "ERROR", e); 150 } 151 152 try 153 { 154 if (!getService(ID_DBUS_OBJECT_NAME, idButtonIface).empty()) 155 { 156 lg2::info("Registering ID button handler"); 157 idButtonReleased = std::make_unique<sdbusplus::bus::match_t>( 158 bus, 159 sdbusRule::type::signal() + sdbusRule::member("Released") + 160 sdbusRule::path(ID_DBUS_OBJECT_NAME) + 161 sdbusRule::interface(idButtonIface), 162 std::bind(std::mem_fn(&Handler::idReleased), this, 163 std::placeholders::_1)); 164 } 165 } 166 catch (const sdbusplus::exception_t& e) 167 { 168 // The button wasn't implemented 169 } 170 171 try 172 { 173 if (!getService(RESET_DBUS_OBJECT_NAME, resetButtonIface).empty()) 174 { 175 lg2::info("Registering reset button handler"); 176 resetButtonReleased = std::make_unique<sdbusplus::bus::match_t>( 177 bus, 178 sdbusRule::type::signal() + sdbusRule::member("Released") + 179 sdbusRule::path(RESET_DBUS_OBJECT_NAME) + 180 sdbusRule::interface(resetButtonIface), 181 std::bind(std::mem_fn(&Handler::resetReleased), this, 182 std::placeholders::_1)); 183 } 184 } 185 catch (const sdbusplus::exception_t& e) 186 { 187 // The button wasn't implemented 188 } 189 try 190 { 191 if (!getService(DBG_HS_DBUS_OBJECT_NAME, debugHostSelectorIface) 192 .empty()) 193 { 194 lg2::info("Registering debug host selector button handler"); 195 debugHSButtonReleased = std::make_unique<sdbusplus::bus::match_t>( 196 bus, 197 sdbusRule::type::signal() + sdbusRule::member("Released") + 198 sdbusRule::path(DBG_HS_DBUS_OBJECT_NAME) + 199 sdbusRule::interface(debugHostSelectorIface), 200 std::bind(std::mem_fn(&Handler::debugHostSelectorReleased), 201 this, std::placeholders::_1)); 202 } 203 } 204 catch (const sdbusplus::exception_t& e) 205 { 206 // The button wasn't implemented 207 } 208 } 209 bool Handler::isMultiHost() 210 { 211 if (numberOfChassis() != 1) 212 { 213 return true; 214 } 215 else 216 { 217 return (hostSelectButtonMode); 218 } 219 } 220 std::string Handler::getService(const std::string& path, 221 const std::string& interface) const 222 { 223 auto method = bus.new_method_call(mapperService, mapperObjPath, mapperIface, 224 "GetObject"); 225 method.append(path, std::vector{interface}); 226 try 227 { 228 auto result = bus.call(method); 229 std::map<std::string, std::vector<std::string>> objectData; 230 result.read(objectData); 231 return objectData.begin()->first; 232 } 233 catch (const sdbusplus::exception_t& e) 234 { 235 return std::string(); 236 } 237 } 238 size_t Handler::getHostSelectorValue() 239 { 240 auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface); 241 242 if (HSService.empty()) 243 { 244 lg2::info("Host selector dbus object not available"); 245 throw std::invalid_argument("Host selector dbus object not available"); 246 } 247 248 try 249 { 250 auto method = bus.new_method_call( 251 HSService.c_str(), HS_DBUS_OBJECT_NAME, propertyIface, "Get"); 252 method.append(hostSelectorIface, "Position"); 253 auto result = bus.call(method); 254 255 std::variant<size_t> HSPositionVariant; 256 result.read(HSPositionVariant); 257 258 auto position = std::get<size_t>(HSPositionVariant); 259 return position; 260 } 261 catch (const sdbusplus::exception_t& e) 262 { 263 lg2::error("Error reading host selector position: {ERROR}", "ERROR", e); 264 throw; 265 } 266 } 267 bool Handler::poweredOn(size_t hostNumber) const 268 { 269 auto hostObjectName = HOST_STATE_OBJECT_NAME + std::to_string(hostNumber); 270 auto service = getService(hostObjectName.c_str(), hostIface); 271 auto method = bus.new_method_call(service.c_str(), hostObjectName.c_str(), 272 propertyIface, "Get"); 273 method.append(hostIface, "CurrentHostState"); 274 auto result = bus.call(method); 275 276 std::variant<std::string> state; 277 result.read(state); 278 279 return Host::HostState::Off != 280 Host::convertHostStateFromString(std::get<std::string>(state)); 281 } 282 283 void Handler::handlePowerEvent(PowerEvent powerEventType, 284 const std::string& objectPath, 285 std::chrono::microseconds duration) 286 { 287 std::string objPathName; 288 std::string dbusIfaceName; 289 std::string transitionName; 290 std::variant<Host::Transition, Chassis::Transition> transition; 291 292 size_t hostNumber = 0; 293 std::string hostNumStr = 294 objectPath.substr(std::string(POWER_DBUS_OBJECT_NAME).length()); 295 auto isMultiHostSystem = isMultiHost(); 296 297 if (hostSelectButtonMode) 298 { 299 hostNumber = getHostSelectorValue(); 300 lg2::info("Multi-host system detected : {POSITION}", "POSITION", 301 hostNumber); 302 303 hostNumStr = std::to_string(hostNumber); 304 305 // ignore power and reset button events if BMC is selected. 306 if (isMultiHostSystem && (hostNumber == BMC_POSITION) && 307 (powerEventType != PowerEvent::powerReleased) && 308 (duration <= LONG_PRESS_TIME_MS)) 309 { 310 lg2::info( 311 "handlePowerEvent : BMC selected on multi-host system. ignoring power and reset button events..."); 312 return; 313 } 314 } 315 316 switch (powerEventType) 317 { 318 case PowerEvent::powerReleased: 319 { 320 for (const auto& iter : multiPwrBtnActConf[stoi(hostNumStr) - 1]) 321 { 322 if (duration > std::chrono::milliseconds(iter.first)) 323 { 324 dbusIfaceName = chassisIface; 325 transitionName = "RequestedPowerTransition"; 326 objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr; 327 transition = iter.second; 328 } 329 } 330 break; 331 332 if (duration <= LONG_PRESS_TIME_MS) 333 { 334 objPathName = HOST_STATE_OBJECT_NAME + hostNumStr; 335 dbusIfaceName = hostIface; 336 transitionName = "RequestedHostTransition"; 337 338 transition = Host::Transition::On; 339 340 if (poweredOn(hostNumber)) 341 { 342 transition = Host::Transition::Off; 343 } 344 lg2::info("handlePowerEvent : Handle power button press "); 345 break; 346 } 347 else 348 { 349 dbusIfaceName = chassisIface; 350 transitionName = "RequestedPowerTransition"; 351 objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr; 352 transition = Chassis::Transition::Off; 353 354 /* multi host system : 355 hosts (1 to N) - host shutdown 356 bmc (0) - sled cycle 357 single host system : 358 host(0) - host shutdown 359 */ 360 if (isMultiHostSystem && (hostNumber == BMC_POSITION)) 361 { 362 #if CHASSIS_SYSTEM_RESET_ENABLED 363 objPathName = CHASSISSYSTEM_STATE_OBJECT_NAME + hostNumStr; 364 transition = Chassis::Transition::PowerCycle; 365 #else 366 return; 367 #endif 368 } 369 else if (!poweredOn(hostNumber)) 370 { 371 lg2::info( 372 "Power is off so ignoring long power button press"); 373 return; 374 } 375 lg2::info("handlePowerEvent : handle long power button press"); 376 break; 377 } 378 } 379 case PowerEvent::resetReleased: 380 { 381 objPathName = HOST_STATE_OBJECT_NAME + hostNumStr; 382 dbusIfaceName = hostIface; 383 transitionName = "RequestedHostTransition"; 384 385 if (!poweredOn(hostNumber)) 386 { 387 lg2::info("Power is off so ignoring reset button press"); 388 return; 389 } 390 391 lg2::info("Handling reset button press"); 392 #ifdef ENABLE_RESET_BUTTON_DO_WARM_REBOOT 393 transition = Host::Transition::ForceWarmReboot; 394 #else 395 transition = Host::Transition::Reboot; 396 #endif 397 break; 398 } 399 default: 400 { 401 lg2::error("{EVENT} is invalid power event. skipping...", "EVENT", 402 powerEventType); 403 404 return; 405 } 406 } 407 auto service = getService(objPathName.c_str(), dbusIfaceName); 408 auto method = bus.new_method_call(service.c_str(), objPathName.c_str(), 409 propertyIface, "Set"); 410 method.append(dbusIfaceName, transitionName, transition); 411 bus.call(method); 412 } 413 414 void Handler::powerReleased(sdbusplus::message_t& msg) 415 { 416 try 417 { 418 uint64_t time; 419 msg.read(time); 420 421 handlePowerEvent(PowerEvent::powerReleased, msg.get_path(), 422 std::chrono::microseconds(time)); 423 } 424 catch (const sdbusplus::exception_t& e) 425 { 426 lg2::error("Failed power state change on a power button press: {ERROR}", 427 "ERROR", e); 428 } 429 } 430 431 void Handler::resetReleased(sdbusplus::message_t& msg) 432 { 433 try 434 { 435 // No need to calculate duration, set to 0. 436 handlePowerEvent(PowerEvent::resetReleased, msg.get_path(), 437 std::chrono::microseconds(0)); 438 } 439 catch (const sdbusplus::exception_t& e) 440 { 441 lg2::error("Failed power state change on a reset button press: {ERROR}", 442 "ERROR", e); 443 } 444 } 445 446 void Handler::idReleased(sdbusplus::message_t& /* msg */) 447 { 448 std::string groupPath{ledGroupBasePath}; 449 groupPath += ID_LED_GROUP; 450 451 auto service = getService(groupPath, ledGroupIface); 452 453 if (service.empty()) 454 { 455 lg2::info("No found {GROUP} during ID button press:", "GROUP", 456 groupPath); 457 return; 458 } 459 460 try 461 { 462 auto method = bus.new_method_call(service.c_str(), groupPath.c_str(), 463 propertyIface, "Get"); 464 method.append(ledGroupIface, "Asserted"); 465 auto result = bus.call(method); 466 467 std::variant<bool> state; 468 result.read(state); 469 470 state = !std::get<bool>(state); 471 472 lg2::info( 473 "Changing ID LED group state on ID LED press, GROUP = {GROUP}, STATE = {STATE}", 474 "GROUP", groupPath, "STATE", std::get<bool>(state)); 475 476 method = bus.new_method_call(service.c_str(), groupPath.c_str(), 477 propertyIface, "Set"); 478 479 method.append(ledGroupIface, "Asserted", state); 480 result = bus.call(method); 481 } 482 catch (const sdbusplus::exception_t& e) 483 { 484 lg2::error("Error toggling ID LED group on ID button press: {ERROR}", 485 "ERROR", e); 486 } 487 } 488 489 void Handler::increaseHostSelectorPosition() 490 { 491 try 492 { 493 auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface); 494 495 if (HSService.empty()) 496 { 497 lg2::error("Host selector service not available"); 498 return; 499 } 500 501 auto method = 502 bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME, 503 phosphor::button::propertyIface, "GetAll"); 504 method.append(phosphor::button::hostSelectorIface); 505 auto result = bus.call(method); 506 std::unordered_map<std::string, std::variant<size_t>> properties; 507 result.read(properties); 508 509 auto maxPosition = std::get<size_t>(properties.at("MaxPosition")); 510 auto position = std::get<size_t>(properties.at("Position")); 511 512 std::variant<size_t> HSPositionVariant = 513 (position < maxPosition) ? (position + 1) : 0; 514 515 method = bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME, 516 phosphor::button::propertyIface, "Set"); 517 method.append(phosphor::button::hostSelectorIface, "Position"); 518 519 method.append(HSPositionVariant); 520 result = bus.call(method); 521 } 522 catch (const sdbusplus::exception_t& e) 523 { 524 lg2::error("Error modifying host selector position : {ERROR}", "ERROR", 525 e); 526 } 527 } 528 529 void Handler::debugHostSelectorReleased(sdbusplus::message_t& /* msg */) 530 { 531 try 532 { 533 increaseHostSelectorPosition(); 534 } 535 catch (const sdbusplus::exception_t& e) 536 { 537 lg2::error( 538 "Failed power process debug host selector button press : {ERROR}", 539 "ERROR", e); 540 } 541 } 542 543 } // namespace button 544 } // namespace phosphor 545