blob: 8a4001722fffdc4ffda983b696e8c20aea41beca [file] [log] [blame]
/*
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include "xyz/openbmc_project/Common/error.hpp"
#include <ipmid/api.hpp>
#include <ipmid/utils.hpp>
#include <nlohmann/json.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/timer.hpp>
#include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp>
#include <fstream>
#include <iostream>
#include <regex>
#include <stdexcept>
#include <string_view>
using namespace phosphor::logging;
namespace ipmi::chassis
{
static constexpr const char* buttonIntf = "xyz.openbmc_project.Chassis.Buttons";
const static constexpr char* idButtonPath =
"/xyz/openbmc_project/chassis/buttons/id";
static constexpr const char* powerButtonPath =
"/xyz/openbmc_project/chassis/buttons/power";
static constexpr const char* resetButtonPath =
"/xyz/openbmc_project/chassis/buttons/reset";
static constexpr const char* interruptButtonPath =
"/xyz/openbmc_project/chassis/buttons/nmi";
const static constexpr char* idButtonProp = "ButtonPressed";
const static constexpr char* ledService =
"xyz.openbmc_project.LED.GroupManager";
const static constexpr char* ledIDOnObj =
"/xyz/openbmc_project/led/groups/enclosure_identify";
const static constexpr char* ledIDBlinkObj =
"/xyz/openbmc_project/led/groups/enclosure_identify_blink";
const static constexpr char* ledInterface = "xyz.openbmc_project.Led.Group";
const static constexpr char* ledProp = "Asserted";
enum class ChassisIDState
{
off = 0,
temporary = 1,
indefinite = 2,
};
static ChassisIDState chassisIDState = ChassisIDState::off;
constexpr size_t defaultIdentifyTimeOut = 15;
std::unique_ptr<sdbusplus::Timer> identifyTimer
__attribute__((init_priority(101)));
std::unique_ptr<sdbusplus::bus::match_t> matchPtr
__attribute__((init_priority(101)));
static void registerChassisFunctions() __attribute__((constructor));
static ipmi::ServiceCache LEDService(ledInterface, ledIDBlinkObj);
void enclosureIdentifyLed(const char* objName, bool isIdLedOn)
{
auto bus = getSdBus();
try
{
std::string service = LEDService.getService(*bus);
setDbusProperty(*bus, service, objName, ledInterface, ledProp,
isIdLedOn);
}
catch (const std::exception& e)
{
log<level::ERR>("enclosureIdentifyLed: can't set property",
entry("ERR=%s", e.what()));
}
}
bool getIDState(const char* objName, bool& state)
{
auto bus = getSdBus();
try
{
std::string service = LEDService.getService(*bus);
ipmi::Value enabled = getDbusProperty(*bus, service, objName,
ledInterface, ledProp);
state = std::get<bool>(enabled);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>("Fail to get property", entry("PATH=%s", objName),
entry("ERROR=%s", e.what()));
return false;
}
return true;
}
void enclosureIdentifyLedBlinkOff()
{
chassisIDState = ChassisIDState::off;
enclosureIdentifyLed(ledIDBlinkObj, false);
}
void idButtonPropChanged(sdbusplus::message_t& msg)
{
bool asserted = false;
bool buttonPressed = false;
std::map<std::string, ipmi::Value> props;
std::vector<std::string> inval;
std::string iface;
msg.read(iface, props, inval);
for (const auto& t : props)
{
auto key = t.first;
auto value = t.second;
if (key == idButtonProp)
{
buttonPressed = std::get<bool>(value);
}
break;
}
if (buttonPressed)
{
if (identifyTimer->isRunning())
{
log<level::INFO>("ID timer is running");
}
// make sure timer is stopped
identifyTimer->stop();
if (!getIDState(ledIDBlinkObj, asserted))
{
return;
}
if (asserted)
{
// LED is blinking, turn off the LED
chassisIDState = ChassisIDState::off;
enclosureIdentifyLed(ledIDBlinkObj, false);
enclosureIdentifyLed(ledIDOnObj, false);
}
else
{
// toggle the IED on/off
if (!getIDState(ledIDOnObj, asserted))
{
return;
}
enclosureIdentifyLed(ledIDOnObj, !asserted);
}
}
}
void createIdentifyTimer()
{
if (!identifyTimer)
{
identifyTimer =
std::make_unique<sdbusplus::Timer>(enclosureIdentifyLedBlinkOff);
}
}
ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval,
std::optional<uint8_t> force)
{
uint8_t identifyInterval = interval.value_or(defaultIdentifyTimeOut);
bool forceIdentify = force.value_or(0) & 0x01;
enclosureIdentifyLed(ledIDOnObj, false);
identifyTimer->stop();
if (identifyInterval || forceIdentify)
{
enclosureIdentifyLed(ledIDBlinkObj, true);
if (forceIdentify)
{
chassisIDState = ChassisIDState::indefinite;
return ipmi::responseSuccess();
}
chassisIDState = ChassisIDState::temporary;
// start the timer
auto time = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::seconds(identifyInterval));
identifyTimer->start(time);
}
else
{
chassisIDState = ChassisIDState::off;
enclosureIdentifyLed(ledIDBlinkObj, false);
}
return ipmi::responseSuccess();
}
namespace power_policy
{
/* helper function for Get Chassis Status Command
*/
std::optional<uint2_t> getPowerRestorePolicy()
{
constexpr const char* powerRestorePath =
"/xyz/openbmc_project/control/host0/power_restore_policy";
constexpr const char* powerRestoreIntf =
"xyz.openbmc_project.Control.Power.RestorePolicy";
uint2_t restorePolicy = 0;
std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
try
{
auto service = ipmi::getService(*busp, powerRestoreIntf,
powerRestorePath);
ipmi::Value result =
ipmi::getDbusProperty(*busp, service, powerRestorePath,
powerRestoreIntf, "PowerRestorePolicy");
auto powerRestore = sdbusplus::xyz::openbmc_project::Control::Power::
server::RestorePolicy::convertPolicyFromString(
std::get<std::string>(result));
using namespace sdbusplus::xyz::openbmc_project::Control::Power::server;
switch (powerRestore)
{
case RestorePolicy::Policy::AlwaysOff:
restorePolicy = 0x00;
break;
case RestorePolicy::Policy::Restore:
restorePolicy = 0x01;
break;
case RestorePolicy::Policy::AlwaysOn:
restorePolicy = 0x02;
break;
default:
break;
}
}
catch (const std::exception& e)
{
log<level::ERR>("Failed to fetch PowerRestorePolicy property",
entry("ERROR=%s", e.what()),
entry("PATH=%s", powerRestorePath),
entry("INTERFACE=%s", powerRestoreIntf));
return std::nullopt;
}
return std::make_optional(restorePolicy);
}
/*
* getPowerStatus
* helper function for Get Chassis Status Command
* return - optional value for pgood (no value on error)
*/
std::optional<bool> getPowerStatus()
{
bool powerGood = false;
std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
try
{
constexpr const char* chassisStatePath =
"/xyz/openbmc_project/state/chassis0";
constexpr const char* chassisStateIntf =
"xyz.openbmc_project.State.Chassis";
auto service = ipmi::getService(*busp, chassisStateIntf,
chassisStatePath);
ipmi::Value variant =
ipmi::getDbusProperty(*busp, service, chassisStatePath,
chassisStateIntf, "CurrentPowerState");
std::string powerState = std::get<std::string>(variant);
if (powerState == "xyz.openbmc_project.State.Chassis.PowerState.On")
{
powerGood = true;
}
}
catch (const std::exception& e)
{
log<level::ERR>("Failed to fetch power state property",
entry("ERROR=%s", e.what()));
return std::nullopt;
}
return std::make_optional(powerGood);
}
/*
* getACFailStatus
* helper function for Get Chassis Status Command
* return - bool value for ACFail (false on error)
*/
bool getACFailStatus()
{
constexpr const char* acBootObj =
"/xyz/openbmc_project/control/host0/ac_boot";
constexpr const char* acBootIntf = "xyz.openbmc_project.Common.ACBoot";
std::string acFail;
std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
try
{
auto service = ipmi::getService(*bus, acBootIntf, acBootObj);
ipmi::Value variant = ipmi::getDbusProperty(*bus, service, acBootObj,
acBootIntf, "ACBoot");
acFail = std::get<std::string>(variant);
}
catch (const std::exception& e)
{
log<level::ERR>(
"Failed to fetch ACBoot property", entry("ERROR=%s", e.what()),
entry("PATH=%s", acBootObj), entry("INTERFACE=%s", acBootIntf));
}
return acFail == "True";
}
} // namespace power_policy
static std::optional<bool> getButtonEnabled(const std::string& buttonPath)
{
bool buttonDisabled = false;
std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
try
{
auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath);
ipmi::Value disabled = ipmi::getDbusProperty(
*busp, service, buttonPath, buttonIntf, "ButtonMasked");
buttonDisabled = std::get<bool>(disabled);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>("Fail to get button disabled property",
entry("PATH=%s", buttonPath.c_str()),
entry("ERROR=%s", e.what()));
return std::nullopt;
}
return std::make_optional(buttonDisabled);
}
static bool setButtonEnabled(const std::string& buttonPath, const bool disabled)
{
try
{
auto service = ipmi::getService(*getSdBus(), buttonIntf, buttonPath);
ipmi::setDbusProperty(*getSdBus(), service, buttonPath, buttonIntf,
"ButtonMasked", disabled);
}
catch (const std::exception& e)
{
log<level::ERR>("Failed to set button disabled",
entry("EXCEPTION=%s, REQUEST=%x", e.what(), disabled));
return -1;
}
return 0;
}
static bool getRestartCause(ipmi::Context::ptr& ctx, std::string& restartCause)
{
constexpr const char* restartCausePath =
"/xyz/openbmc_project/control/host0/restart_cause";
constexpr const char* restartCauseIntf =
"xyz.openbmc_project.Control.Host.RestartCause";
std::string service;
boost::system::error_code ec = ipmi::getService(ctx, restartCauseIntf,
restartCausePath, service);
if (!ec)
{
ec = ipmi::getDbusProperty(ctx, service, restartCausePath,
restartCauseIntf, "RestartCause",
restartCause);
}
if (ec)
{
log<level::ERR>("Failed to fetch RestartCause property",
entry("ERROR=%s", ec.message().c_str()),
entry("PATH=%s", restartCausePath),
entry("INTERFACE=%s", restartCauseIntf));
return false;
}
return true;
}
static bool checkIPMIRestartCause(ipmi::Context::ptr& ctx,
bool& ipmiRestartCause)
{
std::string restartCause;
if (!getRestartCause(ctx, restartCause))
{
return false;
}
ipmiRestartCause =
(restartCause ==
"xyz.openbmc_project.State.Host.RestartCause.IpmiCommand");
return true;
}
//----------------------------------------------------------------------
// Get Chassis Status commands
//----------------------------------------------------------------------
ipmi::RspType<bool, // Power is on
bool, // Power overload
bool, // Interlock
bool, // power fault
bool, // power control fault
uint2_t, // power restore policy
bool, // reserved
bool, // AC failed
bool, // last power down caused by a Power overload
bool, // last power down caused by a power interlock
bool, // last power down caused by power fault
bool, // last ‘Power is on’ state was entered via IPMI command
uint3_t, // reserved
bool, // Chassis intrusion active
bool, // Front Panel Lockout active
bool, // Drive Fault
bool, // Cooling/fan fault detected
uint2_t, // Chassis Identify State
bool, // Chassis Identify command and state info supported
bool, // reserved
bool, // Power off button disabled
bool, // Reset button disabled
bool, // Diagnostic Interrupt button disabled
bool, // Standby (sleep) button disabled
bool, // Power off button disable allowed
bool, // Reset button disable allowed
bool, // Diagnostic Interrupt button disable allowed
bool // Standby (sleep) button disable allowed
>
ipmiGetChassisStatus(ipmi::Context::ptr ctx)
{
std::optional<uint2_t> restorePolicy =
power_policy::getPowerRestorePolicy();
std::optional<bool> powerGood = power_policy::getPowerStatus();
if (!restorePolicy || !powerGood)
{
return ipmi::responseUnspecifiedError();
}
// Front Panel Button Capabilities and disable/enable status(Optional)
std::optional<bool> powerButtonReading = getButtonEnabled(powerButtonPath);
// allow disable if the interface is present
bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading);
// default return the button is enabled (not disabled)
bool powerButtonDisabled = false;
if (powerButtonDisableAllow)
{
// return the real value of the button status, if present
powerButtonDisabled = *powerButtonReading;
}
std::optional<bool> resetButtonReading = getButtonEnabled(resetButtonPath);
// allow disable if the interface is present
bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading);
// default return the button is enabled (not disabled)
bool resetButtonDisabled = false;
if (resetButtonDisableAllow)
{
// return the real value of the button status, if present
resetButtonDisabled = *resetButtonReading;
}
std::optional<bool> interruptButtonReading =
getButtonEnabled(interruptButtonPath);
// allow disable if the interface is present
bool interruptButtonDisableAllow =
static_cast<bool>(interruptButtonReading);
// default return the button is enabled (not disabled)
bool interruptButtonDisabled = false;
if (interruptButtonDisableAllow)
{
// return the real value of the button status, if present
interruptButtonDisabled = *interruptButtonReading;
}
bool powerDownAcFailed = power_policy::getACFailStatus();
bool powerStatusIPMI = false;
if (!checkIPMIRestartCause(ctx, powerStatusIPMI))
{
return ipmi::responseUnspecifiedError();
}
bool chassisIntrusionActive = false;
constexpr const char* chassisIntrusionObj =
"/xyz/openbmc_project/Chassis/Intrusion";
constexpr const char* chassisIntrusionInf =
"xyz.openbmc_project.Chassis.Intrusion";
std::string intrusionService;
boost::system::error_code ec = ipmi::getService(
ctx, chassisIntrusionInf, chassisIntrusionObj, intrusionService);
if (ec)
{
log<level::ERR>("Failed to get Chassis Intrusion service",
entry("ERROR=%s", ec.message().c_str()));
}
chassisIntrusionActive = !intrusionService.empty();
// This response has a lot of hard-coded, unsupported fields
// They are set to false or 0
constexpr bool powerOverload = false;
constexpr bool chassisInterlock = false;
constexpr bool powerFault = false;
constexpr bool powerControlFault = false;
constexpr bool powerDownOverload = false;
constexpr bool powerDownInterlock = false;
constexpr bool powerDownPowerFault = false;
constexpr bool frontPanelLockoutActive = false;
constexpr bool driveFault = false;
constexpr bool coolingFanFault = false;
// chassisIdentifySupport set because this command is implemented
constexpr bool chassisIdentifySupport = true;
uint2_t chassisIdentifyState = types::enum_cast<uint2_t>(chassisIDState);
constexpr bool sleepButtonDisabled = false;
constexpr bool sleepButtonDisableAllow = false;
return ipmi::responseSuccess(
*powerGood, powerOverload, chassisInterlock, powerFault,
powerControlFault, *restorePolicy,
false, // reserved
powerDownAcFailed, powerDownOverload, powerDownInterlock,
powerDownPowerFault, powerStatusIPMI,
uint3_t(0), // reserved
chassisIntrusionActive, frontPanelLockoutActive, driveFault,
coolingFanFault, chassisIdentifyState, chassisIdentifySupport,
false, // reserved
powerButtonDisabled, resetButtonDisabled, interruptButtonDisabled,
sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow,
interruptButtonDisableAllow, sleepButtonDisableAllow);
}
static uint4_t getRestartCauseValue(const std::string& cause)
{
uint4_t restartCauseValue = 0;
if (cause == "xyz.openbmc_project.State.Host.RestartCause.Unknown")
{
restartCauseValue = 0x0;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause.IpmiCommand")
{
restartCauseValue = 0x1;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause.ResetButton")
{
restartCauseValue = 0x2;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PowerButton")
{
restartCauseValue = 0x3;
}
else if (cause ==
"xyz.openbmc_project.State.Host.RestartCause.WatchdogTimer")
{
restartCauseValue = 0x4;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause.OEM")
{
restartCauseValue = 0x5;
}
else if (cause ==
"xyz.openbmc_project.State.Host.RestartCause.PowerPolicyAlwaysOn")
{
restartCauseValue = 0x6;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause."
"PowerPolicyPreviousState")
{
restartCauseValue = 0x7;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause.PEFReset")
{
restartCauseValue = 0x8;
}
else if (cause ==
"xyz.openbmc_project.State.Host.RestartCause.PEFPowerCycle")
{
restartCauseValue = 0x9;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause.SoftReset")
{
restartCauseValue = 0xa;
}
else if (cause == "xyz.openbmc_project.State.Host.RestartCause.RTCWakeup")
{
restartCauseValue = 0xb;
}
return restartCauseValue;
}
ipmi::RspType<uint4_t, // Restart Cause
uint4_t, // reserved
uint8_t // channel number
>
ipmiGetSystemRestartCause(ipmi::Context::ptr ctx)
{
std::string restartCauseStr;
if (!getRestartCause(ctx, restartCauseStr))
{
return ipmi::responseUnspecifiedError();
}
constexpr uint4_t reserved = 0;
auto channel = static_cast<uint8_t>(ctx->channel);
return ipmi::responseSuccess(getRestartCauseValue(restartCauseStr),
reserved, channel);
}
ipmi::RspType<> ipmiSetFrontPanelButtonEnables(
bool disablePowerButton, bool disableResetButton,
bool disableInterruptButton, [[maybe_unused]] bool disableSleepButton,
uint4_t reserved)
{
if (reserved)
{
return ipmi::responseInvalidFieldRequest();
}
bool error = false;
error |= setButtonEnabled(powerButtonPath, disablePowerButton);
error |= setButtonEnabled(resetButtonPath, disableResetButton);
error |= setButtonEnabled(interruptButtonPath, disableInterruptButton);
if (error)
{
return ipmi::responseUnspecifiedError();
}
return ipmi::responseSuccess();
}
static void registerChassisFunctions(void)
{
log<level::INFO>("Registering Chassis commands");
createIdentifyTimer();
if (matchPtr == nullptr)
{
using namespace sdbusplus::bus::match::rules;
auto bus = getSdBus();
matchPtr = std::make_unique<sdbusplus::bus::match_t>(
*bus,
sdbusplus::bus::match::rules::propertiesChanged(idButtonPath,
buttonIntf),
std::bind(idButtonPropChanged, std::placeholders::_1));
}
// <Chassis Identify>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
ipmi::chassis::cmdChassisIdentify,
ipmi::Privilege::Operator, ipmiChassisIdentify);
// <Get Chassis Status>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
ipmi::chassis::cmdGetChassisStatus,
ipmi::Privilege::User, ipmiGetChassisStatus);
// <Get System Restart Cause>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
ipmi::chassis::cmdGetSystemRestartCause,
ipmi::Privilege::User, ipmiGetSystemRestartCause);
// <Set Front Panel Enables>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnChassis,
ipmi::chassis::cmdSetFrontPanelButtonEnables,
ipmi::Privilege::Admin,
ipmiSetFrontPanelButtonEnables);
}
} // namespace ipmi::chassis