#include "watchdog.hpp" #include #include #include #include #include #include #include #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(); return IPMI_CC_UNSPECIFIED_ERROR; } catch (const std::exception& e) { const std::string e_str = std::string("wd_reset: ") + e.what(); log(e_str.c_str()); report(); return IPMI_CC_UNSPECIFIED_ERROR; } catch (...) { log("wd_reset: Unknown Error"); report(); 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( 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(); return IPMI_CC_UNSPECIFIED_ERROR; } catch (const std::exception& e) { const std::string e_str = std::string("wd_set: ") + e.what(); log(e_str.c_str()); report(); return IPMI_CC_UNSPECIFIED_ERROR; } catch (...) { log("wd_set: Unknown Error"); report(); 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( 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(); return IPMI_CC_UNSPECIFIED_ERROR; } catch (const std::exception& e) { const std::string e_str = std::string("wd_get: ") + e.what(); log(e_str.c_str()); report(); return IPMI_CC_UNSPECIFIED_ERROR; } catch (...) { log("wd_get: Unknown Error"); report(); return IPMI_CC_UNSPECIFIED_ERROR; } }