| #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 |