| #include "watchdog.hpp" |
| |
| #include <cstdint> |
| #include <endian.h> |
| #include <phosphor-logging/elog.hpp> |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <string> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| |
| #include "watchdog_service.hpp" |
| #include "host-ipmid/ipmid-api.h" |
| #include "ipmid.hpp" |
| |
| using phosphor::logging::level; |
| using phosphor::logging::log; |
| using phosphor::logging::report; |
| using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; |
| |
| ipmi_ret_t ipmi_app_watchdog_reset( |
| ipmi_netfn_t netfn, |
| ipmi_cmd_t cmd, |
| ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| // We never return data with this command so immediately get rid of it |
| *data_len = 0; |
| |
| try |
| { |
| WatchdogService wd_service; |
| |
| // Notify the caller if we haven't initialized our timer yet |
| // so it can configure actions and timeouts |
| if (!wd_service.getInitialized()) |
| { |
| return IPMI_WDOG_CC_NOT_INIT; |
| } |
| |
| // The ipmi standard dictates we enable the watchdog during reset |
| wd_service.resetTimeRemaining(true); |
| return IPMI_CC_OK; |
| } |
| catch (const InternalFailure& e) |
| { |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| catch (const std::exception& e) |
| { |
| const std::string e_str = std::string("wd_reset: ") + e.what(); |
| log<level::ERR>(e_str.c_str()); |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| catch (...) |
| { |
| log<level::ERR>("wd_reset: Unknown Error"); |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| } |
| |
| static constexpr uint8_t wd_dont_stop = 0x1 << 6; |
| static constexpr uint8_t wd_timeout_action_mask = 0x3; |
| |
| enum class IpmiAction : uint8_t { |
| None = 0x0, |
| HardReset = 0x1, |
| PowerOff = 0x2, |
| PowerCycle = 0x3, |
| }; |
| |
| /** @brief Converts an IPMI Watchdog Action to DBUS defined action |
| * @param[in] ipmi_action The IPMI Watchdog Action |
| * @return The Watchdog Action that the ipmi_action maps to |
| */ |
| WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action) |
| { |
| switch(ipmi_action) |
| { |
| case IpmiAction::None: |
| { |
| return WatchdogService::Action::None; |
| } |
| case IpmiAction::HardReset: |
| { |
| return WatchdogService::Action::HardReset; |
| } |
| case IpmiAction::PowerOff: |
| { |
| return WatchdogService::Action::PowerOff; |
| } |
| case IpmiAction::PowerCycle: |
| { |
| return WatchdogService::Action::PowerCycle; |
| } |
| default: |
| { |
| throw std::domain_error("IPMI Action is invalid"); |
| } |
| } |
| } |
| |
| struct wd_set_req { |
| uint8_t timer_use; |
| uint8_t timer_action; |
| uint8_t pretimeout; // (seconds) |
| uint8_t expire_flags; |
| uint16_t initial_countdown; // Little Endian (deciseconds) |
| } __attribute__ ((packed)); |
| static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size."); |
| static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER, |
| "wd_get_res can't fit in request buffer."); |
| |
| ipmi_ret_t ipmi_app_watchdog_set( |
| ipmi_netfn_t netfn, |
| ipmi_cmd_t cmd, |
| ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| // Extract the request data |
| if (*data_len < sizeof(wd_set_req)) |
| { |
| *data_len = 0; |
| return IPMI_CC_REQ_DATA_LEN_INVALID; |
| } |
| wd_set_req req; |
| memcpy(&req, request, sizeof(req)); |
| req.initial_countdown = le16toh(req.initial_countdown); |
| *data_len = 0; |
| |
| try |
| { |
| WatchdogService wd_service; |
| // Stop the timer if the don't stop bit is not set |
| if (!(req.timer_use & wd_dont_stop)) |
| { |
| wd_service.setEnabled(false); |
| } |
| |
| // Set the action based on the request |
| const auto ipmi_action = static_cast<IpmiAction>( |
| req.timer_action & wd_timeout_action_mask); |
| wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action)); |
| |
| // Set the new interval and the time remaining deci -> mill seconds |
| const uint64_t interval = req.initial_countdown * 100; |
| wd_service.setInterval(interval); |
| wd_service.setTimeRemaining(interval); |
| |
| // Mark as initialized so that future resets behave correctly |
| wd_service.setInitialized(true); |
| |
| return IPMI_CC_OK; |
| } |
| catch (const std::domain_error &) |
| { |
| return IPMI_CC_INVALID_FIELD_REQUEST; |
| } |
| catch (const InternalFailure& e) |
| { |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| catch (const std::exception& e) |
| { |
| const std::string e_str = std::string("wd_set: ") + e.what(); |
| log<level::ERR>(e_str.c_str()); |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| catch (...) |
| { |
| log<level::ERR>("wd_set: Unknown Error"); |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| } |
| |
| /** @brief Converts a DBUS Watchdog Action to IPMI defined action |
| * @param[in] wd_action The DBUS Watchdog Action |
| * @return The IpmiAction that the wd_action maps to |
| */ |
| IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action) |
| { |
| switch(wd_action) |
| { |
| case WatchdogService::Action::None: |
| { |
| return IpmiAction::None; |
| } |
| case WatchdogService::Action::HardReset: |
| { |
| return IpmiAction::HardReset; |
| } |
| case WatchdogService::Action::PowerOff: |
| { |
| return IpmiAction::PowerOff; |
| } |
| case WatchdogService::Action::PowerCycle: |
| { |
| return IpmiAction::PowerCycle; |
| } |
| default: |
| { |
| // We have no method via IPMI to signal that the action is unknown |
| // or unmappable in some way. |
| // Just ignore the error and return NONE so the host can reconcile. |
| return IpmiAction::None; |
| } |
| } |
| } |
| |
| struct wd_get_res { |
| uint8_t timer_use; |
| uint8_t timer_action; |
| uint8_t pretimeout; |
| uint8_t expire_flags; |
| uint16_t initial_countdown; // Little Endian (deciseconds) |
| uint16_t present_countdown; // Little Endian (deciseconds) |
| } __attribute__ ((packed)); |
| static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size."); |
| static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER, |
| "wd_get_res can't fit in response buffer."); |
| |
| static constexpr uint8_t wd_dont_log = 0x1 << 7; |
| static constexpr uint8_t wd_running = 0x1 << 6; |
| |
| ipmi_ret_t ipmi_app_watchdog_get( |
| ipmi_netfn_t netfn, |
| ipmi_cmd_t cmd, |
| ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| // Assume we will fail and send no data outside the return code |
| *data_len = 0; |
| |
| try |
| { |
| WatchdogService wd_service; |
| WatchdogService::Properties wd_prop = wd_service.getProperties(); |
| |
| // Build and return the response |
| wd_get_res res; |
| res.timer_use = wd_dont_log; |
| res.timer_action = static_cast<uint8_t>( |
| wdActionToIpmiAction(wd_prop.expireAction)); |
| if (wd_prop.enabled) |
| { |
| res.timer_use |= wd_running; |
| } |
| // TODO: Do something about having pretimeout support |
| res.pretimeout = 0; |
| res.expire_flags = 0; |
| // Interval and timeRemaining need converted from milli -> deci seconds |
| res.initial_countdown = htole16(wd_prop.interval / 100); |
| res.present_countdown = htole16(wd_prop.timeRemaining / 100); |
| |
| memcpy(response, &res, sizeof(res)); |
| *data_len = sizeof(res); |
| return IPMI_CC_OK; |
| } |
| catch (const InternalFailure& e) |
| { |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| catch (const std::exception& e) |
| { |
| const std::string e_str = std::string("wd_get: ") + e.what(); |
| log<level::ERR>(e_str.c_str()); |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| catch (...) |
| { |
| log<level::ERR>("wd_get: Unknown Error"); |
| report<InternalFailure>(); |
| return IPMI_CC_UNSPECIFIED_ERROR; |
| } |
| } |