blob: da8ccf3bf4efecbad105a9a6b928543d2117c8c9 [file] [log] [blame]
#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;
}
}