blob: 99ce739186450370052d7b5dc88e975969f0f49f [file] [log] [blame]
#include "button_handler.hpp"
#include "config.hpp"
#include "gpio.hpp"
#include "power_button_profile_factory.hpp"
#include <phosphor-logging/lg2.hpp>
#include <xyz/openbmc_project/Chassis/Buttons/Power/server.hpp>
#include <xyz/openbmc_project/State/Chassis/server.hpp>
#include <xyz/openbmc_project/State/Host/server.hpp>
#include <fstream>
#include <iostream>
#include <string>
namespace phosphor
{
namespace button
{
namespace sdbusRule = sdbusplus::bus::match::rules;
using namespace sdbusplus::xyz::openbmc_project::State::server;
using namespace sdbusplus::xyz::openbmc_project::Chassis::Buttons::server;
const std::map<std::string, Chassis::Transition> chassisPwrCtls = {
{"chassis-on", Chassis::Transition::On},
{"chassis-off", Chassis::Transition::Off},
{"chassis-cycle", Chassis::Transition::PowerCycle}};
constexpr auto chassisIface = "xyz.openbmc_project.State.Chassis";
constexpr auto hostIface = "xyz.openbmc_project.State.Host";
constexpr auto powerButtonIface = "xyz.openbmc_project.Chassis.Buttons.Power";
constexpr auto idButtonIface = "xyz.openbmc_project.Chassis.Buttons.ID";
constexpr auto resetButtonIface = "xyz.openbmc_project.Chassis.Buttons.Reset";
constexpr auto ledGroupIface = "xyz.openbmc_project.Led.Group";
constexpr auto ledGroupBasePath = "/xyz/openbmc_project/led/groups/";
constexpr auto hostSelectorIface =
"xyz.openbmc_project.Chassis.Buttons.HostSelector";
constexpr auto debugHostSelectorIface =
"xyz.openbmc_project.Chassis.Buttons.Button";
constexpr auto propertyIface = "org.freedesktop.DBus.Properties";
constexpr auto mapperIface = "xyz.openbmc_project.ObjectMapper";
constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
constexpr auto BMC_POSITION = 0;
std::vector<std::map<uint16_t, Chassis::Transition>> multiPwrBtnActConf;
Handler::Handler(sdbusplus::bus_t& bus) : bus(bus)
{
/* So far, there are two modes for multi-host power control
- host select button mode, e.g.: Yosemite V2
only one power button with host select switch,
which's interface for handling target host,
in the case, hostSelectButtonMode = true
- multi power button mode, e.g.: Greatlakes
each slot/sled has its own power button,
in the case, hostSelectButtonMode = false */
hostSelectButtonMode =
!getService(HS_DBUS_OBJECT_NAME, hostSelectorIface).empty();
size_t powerButtonCount = 1;
if (!hostSelectButtonMode)
{
powerButtonCount = phosphor::button::numberOfChassis();
}
std::ifstream gpios{gpioDefFile};
auto configDefJson = nlohmann::json::parse(gpios, nullptr, true);
nlohmann::json gpioDefs = configDefJson["gpio_definitions"];
for (const auto& gpioConfig : gpioDefs)
{
if (gpioConfig.contains("multi-action"))
{
std::map<uint16_t, Chassis::Transition> mapEntry;
const auto& multiActCfg = gpioConfig["multi-action"];
for (const auto& ActCfg : multiActCfg)
{
auto chassisPwrCtl = chassisPwrCtls.find(ActCfg["action"]);
if (chassisPwrCtl != chassisPwrCtls.end())
{
auto duration = ActCfg["duration"].get<uint16_t>();
mapEntry[duration] = chassisPwrCtl->second;
}
else
{
lg2::error("unknown power button action");
}
}
multiPwrBtnActConf.emplace_back(mapEntry);
}
else
{
isButtonMultiActionSupport = false;
break;
}
}
try
{
if (!getService(POWER_DBUS_OBJECT_NAME, powerButtonIface).empty())
{
lg2::info("Starting power button handler");
// Check for a custom handler
powerButtonProfile =
PowerButtonProfileFactory::instance().createProfile(bus);
if (!powerButtonProfile)
{
powerButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusRule::type::signal() + sdbusRule::member("Released") +
sdbusRule::path(POWER_DBUS_OBJECT_NAME) +
sdbusRule::interface(powerButtonIface),
std::bind(std::mem_fn(&Handler::powerReleased), this,
std::placeholders::_1));
}
}
if (!hostSelectButtonMode && isButtonMultiActionSupport)
{
lg2::info("Starting multi power button handler");
// The index, 'countIter', starts at 1 and increments,
// representing slot_1 through slot_N.
for (size_t countIter = 1; countIter <= powerButtonCount;
countIter++)
{
std::unique_ptr<sdbusplus::bus::match_t>
multiPowerReleaseMatch =
std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusRule::type::signal() +
sdbusRule::member("Released") +
sdbusRule::path(POWER_DBUS_OBJECT_NAME +
std::to_string(countIter)) +
sdbusRule::interface(powerButtonIface),
std::bind(std::mem_fn(&Handler::powerReleased),
this, std::placeholders::_1));
multiPowerButtonReleased.emplace_back(
std::move(multiPowerReleaseMatch));
}
}
}
catch (const sdbusplus::exception_t& e)
{
lg2::error("Error creating power button handler: {ERROR}", "ERROR", e);
}
try
{
if (!getService(ID_DBUS_OBJECT_NAME, idButtonIface).empty())
{
lg2::info("Registering ID button handler");
idButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusRule::type::signal() + sdbusRule::member("Released") +
sdbusRule::path(ID_DBUS_OBJECT_NAME) +
sdbusRule::interface(idButtonIface),
std::bind(std::mem_fn(&Handler::idReleased), this,
std::placeholders::_1));
}
}
catch (const sdbusplus::exception_t& e)
{
// The button wasn't implemented
}
try
{
if (!getService(RESET_DBUS_OBJECT_NAME, resetButtonIface).empty())
{
lg2::info("Registering reset button handler");
resetButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusRule::type::signal() + sdbusRule::member("Released") +
sdbusRule::path(RESET_DBUS_OBJECT_NAME) +
sdbusRule::interface(resetButtonIface),
std::bind(std::mem_fn(&Handler::resetReleased), this,
std::placeholders::_1));
}
}
catch (const sdbusplus::exception_t& e)
{
// The button wasn't implemented
}
try
{
if (!getService(DBG_HS_DBUS_OBJECT_NAME, debugHostSelectorIface)
.empty())
{
lg2::info("Registering debug host selector button handler");
debugHSButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusRule::type::signal() + sdbusRule::member("Released") +
sdbusRule::path(DBG_HS_DBUS_OBJECT_NAME) +
sdbusRule::interface(debugHostSelectorIface),
std::bind(std::mem_fn(&Handler::debugHostSelectorReleased),
this, std::placeholders::_1));
}
}
catch (const sdbusplus::exception_t& e)
{
// The button wasn't implemented
}
}
bool Handler::isMultiHost()
{
if (numberOfChassis() != 1)
{
return true;
}
else
{
return (hostSelectButtonMode);
}
}
std::string Handler::getService(const std::string& path,
const std::string& interface) const
{
auto method = bus.new_method_call(mapperService, mapperObjPath, mapperIface,
"GetObject");
method.append(path, std::vector{interface});
try
{
auto result = bus.call(method);
std::map<std::string, std::vector<std::string>> objectData;
result.read(objectData);
return objectData.begin()->first;
}
catch (const sdbusplus::exception_t& e)
{
return std::string();
}
}
size_t Handler::getHostSelectorValue()
{
auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
if (HSService.empty())
{
lg2::info("Host selector dbus object not available");
throw std::invalid_argument("Host selector dbus object not available");
}
try
{
auto method = bus.new_method_call(
HSService.c_str(), HS_DBUS_OBJECT_NAME, propertyIface, "Get");
method.append(hostSelectorIface, "Position");
auto result = bus.call(method);
std::variant<size_t> HSPositionVariant;
result.read(HSPositionVariant);
auto position = std::get<size_t>(HSPositionVariant);
return position;
}
catch (const sdbusplus::exception_t& e)
{
lg2::error("Error reading host selector position: {ERROR}", "ERROR", e);
throw;
}
}
bool Handler::poweredOn(size_t hostNumber) const
{
auto hostObjectName = HOST_STATE_OBJECT_NAME + std::to_string(hostNumber);
auto service = getService(hostObjectName.c_str(), hostIface);
auto method = bus.new_method_call(service.c_str(), hostObjectName.c_str(),
propertyIface, "Get");
method.append(hostIface, "CurrentHostState");
auto result = bus.call(method);
std::variant<std::string> state;
result.read(state);
return Host::HostState::Off !=
Host::convertHostStateFromString(std::get<std::string>(state));
}
void Handler::handlePowerEvent(PowerEvent powerEventType,
const std::string& objectPath,
std::chrono::microseconds duration)
{
std::string objPathName;
std::string dbusIfaceName;
std::string transitionName;
std::variant<Host::Transition, Chassis::Transition> transition;
size_t hostNumber = 0;
std::string hostNumStr =
objectPath.substr(std::string(POWER_DBUS_OBJECT_NAME).length());
auto isMultiHostSystem = isMultiHost();
if (hostSelectButtonMode)
{
hostNumber = getHostSelectorValue();
lg2::info("Multi-host system detected : {POSITION}", "POSITION",
hostNumber);
hostNumStr = std::to_string(hostNumber);
// ignore power and reset button events if BMC is selected.
if (isMultiHostSystem && (hostNumber == BMC_POSITION) &&
(powerEventType != PowerEvent::powerReleased) &&
(duration <= LONG_PRESS_TIME_MS))
{
lg2::info(
"handlePowerEvent : BMC selected on multi-host system. ignoring power and reset button events...");
return;
}
}
switch (powerEventType)
{
case PowerEvent::powerReleased:
{
for (const auto& iter : multiPwrBtnActConf[stoi(hostNumStr) - 1])
{
if (duration > std::chrono::milliseconds(iter.first))
{
dbusIfaceName = chassisIface;
transitionName = "RequestedPowerTransition";
objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
transition = iter.second;
}
}
break;
if (duration <= LONG_PRESS_TIME_MS)
{
objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
dbusIfaceName = hostIface;
transitionName = "RequestedHostTransition";
transition = Host::Transition::On;
if (poweredOn(hostNumber))
{
transition = Host::Transition::Off;
}
lg2::info("handlePowerEvent : Handle power button press ");
break;
}
else
{
dbusIfaceName = chassisIface;
transitionName = "RequestedPowerTransition";
objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
transition = Chassis::Transition::Off;
/* multi host system :
hosts (1 to N) - host shutdown
bmc (0) - sled cycle
single host system :
host(0) - host shutdown
*/
if (isMultiHostSystem && (hostNumber == BMC_POSITION))
{
#if CHASSIS_SYSTEM_RESET_ENABLED
objPathName = CHASSISSYSTEM_STATE_OBJECT_NAME + hostNumStr;
transition = Chassis::Transition::PowerCycle;
#else
return;
#endif
}
else if (!poweredOn(hostNumber))
{
lg2::info(
"Power is off so ignoring long power button press");
return;
}
lg2::info("handlePowerEvent : handle long power button press");
break;
}
}
case PowerEvent::resetReleased:
{
objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
dbusIfaceName = hostIface;
transitionName = "RequestedHostTransition";
if (!poweredOn(hostNumber))
{
lg2::info("Power is off so ignoring reset button press");
return;
}
lg2::info("Handling reset button press");
#ifdef ENABLE_RESET_BUTTON_DO_WARM_REBOOT
transition = Host::Transition::ForceWarmReboot;
#else
transition = Host::Transition::Reboot;
#endif
break;
}
default:
{
lg2::error("{EVENT} is invalid power event. skipping...", "EVENT",
powerEventType);
return;
}
}
auto service = getService(objPathName.c_str(), dbusIfaceName);
auto method = bus.new_method_call(service.c_str(), objPathName.c_str(),
propertyIface, "Set");
method.append(dbusIfaceName, transitionName, transition);
bus.call(method);
}
void Handler::powerReleased(sdbusplus::message_t& msg)
{
try
{
uint64_t time;
msg.read(time);
handlePowerEvent(PowerEvent::powerReleased, msg.get_path(),
std::chrono::microseconds(time));
}
catch (const sdbusplus::exception_t& e)
{
lg2::error("Failed power state change on a power button press: {ERROR}",
"ERROR", e);
}
}
void Handler::resetReleased(sdbusplus::message_t& msg)
{
try
{
// No need to calculate duration, set to 0.
handlePowerEvent(PowerEvent::resetReleased, msg.get_path(),
std::chrono::microseconds(0));
}
catch (const sdbusplus::exception_t& e)
{
lg2::error("Failed power state change on a reset button press: {ERROR}",
"ERROR", e);
}
}
void Handler::idReleased(sdbusplus::message_t& /* msg */)
{
std::string groupPath{ledGroupBasePath};
groupPath += ID_LED_GROUP;
auto service = getService(groupPath, ledGroupIface);
if (service.empty())
{
lg2::info("No found {GROUP} during ID button press:", "GROUP",
groupPath);
return;
}
try
{
auto method = bus.new_method_call(service.c_str(), groupPath.c_str(),
propertyIface, "Get");
method.append(ledGroupIface, "Asserted");
auto result = bus.call(method);
std::variant<bool> state;
result.read(state);
state = !std::get<bool>(state);
lg2::info(
"Changing ID LED group state on ID LED press, GROUP = {GROUP}, STATE = {STATE}",
"GROUP", groupPath, "STATE", std::get<bool>(state));
method = bus.new_method_call(service.c_str(), groupPath.c_str(),
propertyIface, "Set");
method.append(ledGroupIface, "Asserted", state);
result = bus.call(method);
}
catch (const sdbusplus::exception_t& e)
{
lg2::error("Error toggling ID LED group on ID button press: {ERROR}",
"ERROR", e);
}
}
void Handler::increaseHostSelectorPosition()
{
try
{
auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
if (HSService.empty())
{
lg2::error("Host selector service not available");
return;
}
auto method =
bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
phosphor::button::propertyIface, "GetAll");
method.append(phosphor::button::hostSelectorIface);
auto result = bus.call(method);
std::unordered_map<std::string, std::variant<size_t>> properties;
result.read(properties);
auto maxPosition = std::get<size_t>(properties.at("MaxPosition"));
auto position = std::get<size_t>(properties.at("Position"));
std::variant<size_t> HSPositionVariant =
(position < maxPosition) ? (position + 1) : 0;
method = bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
phosphor::button::propertyIface, "Set");
method.append(phosphor::button::hostSelectorIface, "Position");
method.append(HSPositionVariant);
result = bus.call(method);
}
catch (const sdbusplus::exception_t& e)
{
lg2::error("Error modifying host selector position : {ERROR}", "ERROR",
e);
}
}
void Handler::debugHostSelectorReleased(sdbusplus::message_t& /* msg */)
{
try
{
increaseHostSelectorPosition();
}
catch (const sdbusplus::exception_t& e)
{
lg2::error(
"Failed power process debug host selector button press : {ERROR}",
"ERROR", e);
}
}
} // namespace button
} // namespace phosphor