1 /* 2 // Copyright (c) 2019 Intel 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 #include "xyz/openbmc_project/Common/error.hpp" 17 18 #include <fstream> 19 #include <iostream> 20 #include <ipmid/api.hpp> 21 #include <ipmid/utils.hpp> 22 #include <nlohmann/json.hpp> 23 #include <phosphor-logging/elog-errors.hpp> 24 #include <phosphor-logging/log.hpp> 25 #include <regex> 26 #include <sdbusplus/timer.hpp> 27 #include <stdexcept> 28 #include <string_view> 29 #include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp> 30 31 using namespace phosphor::logging; 32 33 namespace ipmi::chassis 34 { 35 static constexpr const char* buttonIntf = "xyz.openbmc_project.Chassis.Buttons"; 36 37 const static constexpr char* idButtonPath = 38 "/xyz/openbmc_project/chassis/buttons/id"; 39 static constexpr const char* powerButtonPath = 40 "/xyz/openbmc_project/chassis/buttons/power"; 41 static constexpr const char* resetButtonPath = 42 "/xyz/openbmc_project/chassis/buttons/reset"; 43 static constexpr const char* interruptButtonPath = 44 "/xyz/openbmc_project/chassis/buttons/nmi"; 45 46 const static constexpr char* idButtonProp = "ButtonPressed"; 47 48 const static constexpr char* ledService = 49 "xyz.openbmc_project.LED.GroupManager"; 50 const static constexpr char* ledIDOnObj = 51 "/xyz/openbmc_project/led/groups/enclosure_identify"; 52 const static constexpr char* ledIDBlinkObj = 53 "/xyz/openbmc_project/led/groups/enclosure_identify_blink"; 54 const static constexpr char* ledInterface = "xyz.openbmc_project.Led.Group"; 55 const static constexpr char* ledProp = "Asserted"; 56 enum class ChassisIDState 57 { 58 off = 0, 59 temporary = 1, 60 indefinite = 2, 61 }; 62 static ChassisIDState chassisIDState = ChassisIDState::off; 63 64 constexpr size_t defaultIdentifyTimeOut = 15; 65 66 std::unique_ptr<phosphor::Timer> identifyTimer 67 __attribute__((init_priority(101))); 68 std::unique_ptr<sdbusplus::bus::match_t> matchPtr 69 __attribute__((init_priority(101))); 70 71 static void registerChassisFunctions() __attribute__((constructor)); 72 73 static ipmi::ServiceCache LEDService(ledInterface, ledIDBlinkObj); 74 75 void enclosureIdentifyLed(const char* objName, bool isIdLedOn) 76 { 77 auto bus = getSdBus(); 78 79 try 80 { 81 std::string service = LEDService.getService(*bus); 82 setDbusProperty(*bus, service, objName, ledInterface, ledProp, 83 isIdLedOn); 84 } 85 catch (const std::exception& e) 86 { 87 log<level::ERR>("enclosureIdentifyLed: can't set property", 88 entry("ERR=%s", e.what())); 89 } 90 } 91 92 bool getIDState(const char* objName, bool& state) 93 { 94 auto bus = getSdBus(); 95 96 try 97 { 98 std::string service = LEDService.getService(*bus); 99 ipmi::Value enabled = 100 getDbusProperty(*bus, service, objName, ledInterface, ledProp); 101 state = std::get<bool>(enabled); 102 } 103 catch (sdbusplus::exception::SdBusError& e) 104 { 105 log<level::ERR>("Fail to get property", entry("PATH=%s", objName), 106 entry("ERROR=%s", e.what())); 107 return false; 108 } 109 return true; 110 } 111 112 void enclosureIdentifyLedBlinkOff() 113 { 114 chassisIDState = ChassisIDState::off; 115 enclosureIdentifyLed(ledIDBlinkObj, false); 116 } 117 118 void idButtonPropChanged(sdbusplus::message::message& msg) 119 { 120 bool asserted = false; 121 bool buttonPressed = false; 122 123 std::map<std::string, ipmi::Value> props; 124 std::vector<std::string> inval; 125 std::string iface; 126 msg.read(iface, props, inval); 127 128 for (const auto& t : props) 129 { 130 auto key = t.first; 131 auto value = t.second; 132 133 if (key == idButtonProp) 134 { 135 buttonPressed = std::get<bool>(value); 136 } 137 break; 138 } 139 140 if (buttonPressed) 141 { 142 if (identifyTimer->isRunning()) 143 { 144 log<level::INFO>("ID timer is running"); 145 } 146 147 // make sure timer is stopped 148 identifyTimer->stop(); 149 150 if (!getIDState(ledIDBlinkObj, asserted)) 151 { 152 return; 153 } 154 155 if (asserted) 156 { 157 // LED is blinking, turn off the LED 158 chassisIDState = ChassisIDState::off; 159 enclosureIdentifyLed(ledIDBlinkObj, false); 160 enclosureIdentifyLed(ledIDOnObj, false); 161 } 162 else 163 { 164 // toggle the IED on/off 165 if (!getIDState(ledIDOnObj, asserted)) 166 { 167 return; 168 } 169 enclosureIdentifyLed(ledIDOnObj, !asserted); 170 } 171 } 172 } 173 174 void createIdentifyTimer() 175 { 176 if (!identifyTimer) 177 { 178 identifyTimer = 179 std::make_unique<phosphor::Timer>(enclosureIdentifyLedBlinkOff); 180 } 181 } 182 183 ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval, 184 std::optional<uint8_t> force) 185 { 186 uint8_t identifyInterval = interval.value_or(defaultIdentifyTimeOut); 187 bool forceIdentify = force.value_or(0) & 0x01; 188 189 enclosureIdentifyLed(ledIDOnObj, false); 190 identifyTimer->stop(); 191 192 if (identifyInterval || forceIdentify) 193 { 194 enclosureIdentifyLed(ledIDBlinkObj, true); 195 if (forceIdentify) 196 { 197 chassisIDState = ChassisIDState::indefinite; 198 return ipmi::responseSuccess(); 199 } 200 chassisIDState = ChassisIDState::temporary; 201 // start the timer 202 auto time = std::chrono::duration_cast<std::chrono::microseconds>( 203 std::chrono::seconds(identifyInterval)); 204 identifyTimer->start(time); 205 } 206 else 207 { 208 chassisIDState = ChassisIDState::off; 209 enclosureIdentifyLed(ledIDBlinkObj, false); 210 } 211 return ipmi::responseSuccess(); 212 } 213 214 namespace power_policy 215 { 216 /* helper function for Get Chassis Status Command 217 */ 218 std::optional<uint2_t> getPowerRestorePolicy() 219 { 220 constexpr const char* powerRestorePath = 221 "/xyz/openbmc_project/control/host0/power_restore_policy"; 222 constexpr const char* powerRestoreIntf = 223 "xyz.openbmc_project.Control.Power.RestorePolicy"; 224 uint2_t restorePolicy = 0; 225 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 226 227 try 228 { 229 auto service = 230 ipmi::getService(*busp, powerRestoreIntf, powerRestorePath); 231 232 ipmi::Value result = 233 ipmi::getDbusProperty(*busp, service, powerRestorePath, 234 powerRestoreIntf, "PowerRestorePolicy"); 235 auto powerRestore = sdbusplus::xyz::openbmc_project::Control::Power:: 236 server::RestorePolicy::convertPolicyFromString( 237 std::get<std::string>(result)); 238 239 using namespace sdbusplus::xyz::openbmc_project::Control::Power::server; 240 switch (powerRestore) 241 { 242 case RestorePolicy::Policy::AlwaysOff: 243 restorePolicy = 0x00; 244 break; 245 case RestorePolicy::Policy::Restore: 246 restorePolicy = 0x01; 247 break; 248 case RestorePolicy::Policy::AlwaysOn: 249 restorePolicy = 0x02; 250 break; 251 } 252 } 253 catch (const std::exception& e) 254 { 255 log<level::ERR>("Failed to fetch PowerRestorePolicy property", 256 entry("ERROR=%s", e.what()), 257 entry("PATH=%s", powerRestorePath), 258 entry("INTERFACE=%s", powerRestoreIntf)); 259 return std::nullopt; 260 } 261 return std::make_optional(restorePolicy); 262 } 263 264 /* 265 * getPowerStatus 266 * helper function for Get Chassis Status Command 267 * return - optional value for pgood (no value on error) 268 */ 269 std::optional<bool> getPowerStatus() 270 { 271 bool powerGood = false; 272 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 273 try 274 { 275 constexpr const char* chassisStatePath = 276 "/xyz/openbmc_project/state/chassis0"; 277 constexpr const char* chassisStateIntf = 278 "xyz.openbmc_project.State.Chassis"; 279 auto service = 280 ipmi::getService(*busp, chassisStateIntf, chassisStatePath); 281 282 ipmi::Value variant = 283 ipmi::getDbusProperty(*busp, service, chassisStatePath, 284 chassisStateIntf, "CurrentPowerState"); 285 std::string powerState = std::get<std::string>(variant); 286 if (powerState == "xyz.openbmc_project.State.Chassis.PowerState.On") 287 { 288 powerGood = true; 289 } 290 } 291 catch (const std::exception& e) 292 { 293 log<level::ERR>("Failed to fetch power state property", 294 entry("ERROR=%s", e.what())); 295 return std::nullopt; 296 } 297 return std::make_optional(powerGood); 298 } 299 300 /* 301 * getACFailStatus 302 * helper function for Get Chassis Status Command 303 * return - bool value for ACFail (false on error) 304 */ 305 bool getACFailStatus() 306 { 307 constexpr const char* acBootObj = 308 "/xyz/openbmc_project/control/host0/ac_boot"; 309 constexpr const char* acBootIntf = "xyz.openbmc_project.Common.ACBoot"; 310 std::string acFail; 311 std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); 312 try 313 { 314 auto service = ipmi::getService(*bus, acBootIntf, acBootObj); 315 316 ipmi::Value variant = ipmi::getDbusProperty(*bus, service, acBootObj, 317 acBootIntf, "ACBoot"); 318 acFail = std::get<std::string>(variant); 319 } 320 catch (const std::exception& e) 321 { 322 log<level::ERR>( 323 "Failed to fetch ACBoot property", entry("ERROR=%s", e.what()), 324 entry("PATH=%s", acBootObj), entry("INTERFACE=%s", acBootIntf)); 325 } 326 return acFail == "True"; 327 } 328 } // namespace power_policy 329 330 static std::optional<bool> getButtonEnabled(const std::string& buttonPath) 331 { 332 bool buttonDisabled = false; 333 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 334 try 335 { 336 auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath); 337 ipmi::Value disabled = ipmi::getDbusProperty( 338 *busp, service, buttonPath, buttonIntf, "ButtonMasked"); 339 buttonDisabled = std::get<bool>(disabled); 340 } 341 catch (sdbusplus::exception::SdBusError& e) 342 { 343 log<level::ERR>("Fail to get button disabled property", 344 entry("PATH=%s", buttonPath.c_str()), 345 entry("ERROR=%s", e.what())); 346 return std::nullopt; 347 } 348 return std::make_optional(buttonDisabled); 349 } 350 351 static bool setButtonEnabled(const std::string& buttonPath, const bool disabled) 352 { 353 try 354 { 355 auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath); 356 ipmi::setDbusProperty(*getSdBus(), service, buttonPath, buttonIntf, 357 "ButtonMasked", disabled); 358 } 359 catch (std::exception& e) 360 { 361 log<level::ERR>("Failed to set button disabled", 362 entry("EXCEPTION=%s, REQUEST=%x", e.what(), disabled)); 363 return -1; 364 } 365 366 return 0; 367 } 368 369 static bool getRestartCause(std::string& restartCause) 370 { 371 constexpr const char* restartCausePath = 372 "/xyz/openbmc_project/control/host0/restart_cause"; 373 constexpr const char* restartCauseIntf = 374 "xyz.openbmc_project.Common.RestartCause"; 375 std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); 376 377 try 378 { 379 auto service = 380 ipmi::getService(*busp, restartCauseIntf, restartCausePath); 381 382 ipmi::Value result = ipmi::getDbusProperty( 383 *busp, service, restartCausePath, restartCauseIntf, "RestartCause"); 384 restartCause = std::get<std::string>(result); 385 } 386 catch (const std::exception& e) 387 { 388 log<level::ERR>("Failed to fetch RestartCause property", 389 entry("ERROR=%s", e.what()), 390 entry("PATH=%s", restartCausePath), 391 entry("INTERFACE=%s", restartCauseIntf)); 392 return false; 393 } 394 return true; 395 } 396 397 static bool checkIPMIRestartCause(bool& ipmiRestartCause) 398 { 399 std::string restartCause; 400 if (!getRestartCause(restartCause)) 401 { 402 return false; 403 } 404 ipmiRestartCause = 405 (restartCause == 406 "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand"); 407 return true; 408 } 409 410 //---------------------------------------------------------------------- 411 // Get Chassis Status commands 412 //---------------------------------------------------------------------- 413 ipmi::RspType<bool, // Power is on 414 bool, // Power overload 415 bool, // Interlock 416 bool, // power fault 417 bool, // power control fault 418 uint2_t, // power restore policy 419 bool, // reserved 420 421 bool, // AC failed 422 bool, // last power down caused by a Power overload 423 bool, // last power down caused by a power interlock 424 bool, // last power down caused by power fault 425 bool, // last ‘Power is on’ state was entered via IPMI command 426 uint3_t, // reserved 427 428 bool, // Chassis intrusion active 429 bool, // Front Panel Lockout active 430 bool, // Drive Fault 431 bool, // Cooling/fan fault detected 432 uint2_t, // Chassis Identify State 433 bool, // Chassis Identify command and state info supported 434 bool, // reserved 435 436 bool, // Power off button disabled 437 bool, // Reset button disabled 438 bool, // Diagnostic Interrupt button disabled 439 bool, // Standby (sleep) button disabled 440 bool, // Power off button disable allowed 441 bool, // Reset button disable allowed 442 bool, // Diagnostic Interrupt button disable allowed 443 bool // Standby (sleep) button disable allowed 444 > 445 ipmiGetChassisStatus() 446 { 447 std::optional<uint2_t> restorePolicy = 448 power_policy::getPowerRestorePolicy(); 449 std::optional<bool> powerGood = power_policy::getPowerStatus(); 450 if (!restorePolicy || !powerGood) 451 { 452 return ipmi::responseUnspecifiedError(); 453 } 454 455 // Front Panel Button Capabilities and disable/enable status(Optional) 456 std::optional<bool> powerButtonReading = getButtonEnabled(powerButtonPath); 457 // allow disable if the interface is present 458 bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading); 459 // default return the button is enabled (not disabled) 460 bool powerButtonDisabled = false; 461 if (powerButtonDisableAllow) 462 { 463 // return the real value of the button status, if present 464 powerButtonDisabled = *powerButtonReading; 465 } 466 467 std::optional<bool> resetButtonReading = getButtonEnabled(resetButtonPath); 468 // allow disable if the interface is present 469 bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading); 470 // default return the button is enabled (not disabled) 471 bool resetButtonDisabled = false; 472 if (resetButtonDisableAllow) 473 { 474 // return the real value of the button status, if present 475 resetButtonDisabled = *resetButtonReading; 476 } 477 478 std::optional<bool> interruptButtonReading = 479 getButtonEnabled(interruptButtonPath); 480 // allow disable if the interface is present 481 bool interruptButtonDisableAllow = 482 static_cast<bool>(interruptButtonReading); 483 // default return the button is enabled (not disabled) 484 bool interruptButtonDisabled = false; 485 if (interruptButtonDisableAllow) 486 { 487 // return the real value of the button status, if present 488 interruptButtonDisabled = *interruptButtonReading; 489 } 490 491 bool powerDownAcFailed = power_policy::getACFailStatus(); 492 493 bool powerStatusIPMI = false; 494 if (!checkIPMIRestartCause(powerStatusIPMI)) 495 { 496 return ipmi::responseUnspecifiedError(); 497 } 498 499 // This response has a lot of hard-coded, unsupported fields 500 // They are set to false or 0 501 constexpr bool powerOverload = false; 502 constexpr bool chassisInterlock = false; 503 constexpr bool powerFault = false; 504 constexpr bool powerControlFault = false; 505 constexpr bool powerDownOverload = false; 506 constexpr bool powerDownInterlock = false; 507 constexpr bool powerDownPowerFault = false; 508 constexpr bool chassisIntrusionActive = false; 509 constexpr bool frontPanelLockoutActive = false; 510 constexpr bool driveFault = false; 511 constexpr bool coolingFanFault = false; 512 // chassisIdentifySupport set because this command is implemented 513 constexpr bool chassisIdentifySupport = true; 514 uint2_t chassisIdentifyState = chassisIDState; 515 constexpr bool sleepButtonDisabled = false; 516 constexpr bool sleepButtonDisableAllow = false; 517 518 return ipmi::responseSuccess( 519 *powerGood, powerOverload, chassisInterlock, powerFault, 520 powerControlFault, *restorePolicy, 521 false, // reserved 522 523 powerDownAcFailed, powerDownOverload, powerDownInterlock, 524 powerDownPowerFault, powerStatusIPMI, 525 uint3_t(0), // reserved 526 527 chassisIntrusionActive, frontPanelLockoutActive, driveFault, 528 coolingFanFault, chassisIdentifyState, chassisIdentifySupport, 529 false, // reserved 530 531 powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled, 532 sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow, 533 interruptButtonDisableAllow, sleepButtonDisableAllow); 534 } 535 536 static uint4_t getRestartCauseValue(const std::string& cause) 537 { 538 uint4_t restartCauseValue = 0; 539 if (cause == "xyz.openbmc_project.State.Host.RestartCause.Unknown") 540 { 541 restartCauseValue = 0x0; 542 } 543 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand") 544 { 545 restartCauseValue = 0x1; 546 } 547 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.ResetButton") 548 { 549 restartCauseValue = 0x2; 550 } 551 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerButton") 552 { 553 restartCauseValue = 0x3; 554 } 555 else if (cause == 556 "xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer") 557 { 558 restartCauseValue = 0x4; 559 } 560 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.OEM") 561 { 562 restartCauseValue = 0x5; 563 } 564 else if (cause == 565 "xyz.openbmc_project.State.Host.RestartCause.PowerPolicyAlwaysOn") 566 { 567 restartCauseValue = 0x6; 568 } 569 else if (cause == "xyz.openbmc_project.State.Host.RestartCause." 570 "PowerPolicyPreviousState") 571 { 572 restartCauseValue = 0x7; 573 } 574 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFReset") 575 { 576 restartCauseValue = 0x8; 577 } 578 else if (cause == 579 "xyz.openbmc_project.State.Host.RestartCause.PEFPowerCycle") 580 { 581 restartCauseValue = 0x9; 582 } 583 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.SoftReset") 584 { 585 restartCauseValue = 0xa; 586 } 587 else if (cause == "xyz.openbmc_project.State.Host.RestartCause.RTCWakeup") 588 { 589 restartCauseValue = 0xb; 590 } 591 return restartCauseValue; 592 } 593 594 ipmi::RspType<uint4_t, // Restart Cause 595 uint4_t, // reserved 596 uint8_t // channel number (not supported) 597 > 598 ipmiGetSystemRestartCause() 599 { 600 std::string restartCauseStr; 601 if (!getRestartCause(restartCauseStr)) 602 { 603 return ipmi::responseUnspecifiedError(); 604 } 605 606 return ipmi::responseSuccess(getRestartCauseValue(restartCauseStr), 0, 0); 607 } 608 609 ipmi::RspType<> ipmiSetFrontPanelButtonEnables(bool disablePowerButton, 610 bool disableResetButton, 611 bool disableInterruptButton, 612 bool disableSleepButton, 613 uint4_t reserved) 614 { 615 bool error = false; 616 617 error |= setButtonEnabled(powerButtonPath, disablePowerButton); 618 error |= setButtonEnabled(resetButtonPath, disableResetButton); 619 error |= setButtonEnabled(interruptButtonPath, disableInterruptButton); 620 621 if (error) 622 { 623 return ipmi::responseUnspecifiedError(); 624 } 625 return ipmi::responseSuccess(); 626 } 627 628 static void registerChassisFunctions(void) 629 { 630 log<level::INFO>("Registering Chassis commands"); 631 632 createIdentifyTimer(); 633 634 if (matchPtr == nullptr) 635 { 636 using namespace sdbusplus::bus::match::rules; 637 auto bus = getSdBus(); 638 639 matchPtr = std::make_unique<sdbusplus::bus::match_t>( 640 *bus, 641 sdbusplus::bus::match::rules::propertiesChanged(idButtonPath, 642 buttonIntf), 643 std::bind(idButtonPropChanged, std::placeholders::_1)); 644 } 645 646 // <Chassis Identify> 647 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 648 ipmi::chassis::cmdChassisIdentify, 649 ipmi::Privilege::Operator, ipmiChassisIdentify); 650 // <Get Chassis Status> 651 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 652 ipmi::chassis::cmdGetChassisStatus, 653 ipmi::Privilege::User, ipmiGetChassisStatus); 654 // <Get System Restart Cause> 655 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 656 ipmi::chassis::cmdGetSystemRestartCause, 657 ipmi::Privilege::User, ipmiGetSystemRestartCause); 658 // <Set Front Panel Enables> 659 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis, 660 ipmi::chassis::cmdSetFrontPanelButtonEnables, 661 ipmi::Privilege::User, 662 ipmiSetFrontPanelButtonEnables); 663 } 664 665 } // namespace ipmi::chassis 666