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