1 #include "watchdog.hpp" 2 3 #include <cstdint> 4 #include <endian.h> 5 #include <phosphor-logging/elog.hpp> 6 #include <phosphor-logging/elog-errors.hpp> 7 #include <phosphor-logging/log.hpp> 8 #include <string> 9 #include <xyz/openbmc_project/Common/error.hpp> 10 11 #include "watchdog_service.hpp" 12 #include "host-ipmid/ipmid-api.h" 13 #include "ipmid.hpp" 14 15 using phosphor::logging::level; 16 using phosphor::logging::log; 17 using phosphor::logging::report; 18 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 19 20 ipmi_ret_t ipmi_app_watchdog_reset( 21 ipmi_netfn_t netfn, 22 ipmi_cmd_t cmd, 23 ipmi_request_t request, 24 ipmi_response_t response, 25 ipmi_data_len_t data_len, 26 ipmi_context_t context) 27 { 28 // We never return data with this command so immediately get rid of it 29 *data_len = 0; 30 31 try 32 { 33 WatchdogService wd_service; 34 35 // Notify the caller if we haven't initialized our timer yet 36 // so it can configure actions and timeouts 37 if (!wd_service.getInitialized()) 38 { 39 return IPMI_WDOG_CC_NOT_INIT; 40 } 41 42 // The ipmi standard dictates we enable the watchdog during reset 43 wd_service.resetTimeRemaining(true); 44 return IPMI_CC_OK; 45 } 46 catch (const InternalFailure& e) 47 { 48 report<InternalFailure>(); 49 return IPMI_CC_UNSPECIFIED_ERROR; 50 } 51 catch (const std::exception& e) 52 { 53 const std::string e_str = std::string("wd_reset: ") + e.what(); 54 log<level::ERR>(e_str.c_str()); 55 report<InternalFailure>(); 56 return IPMI_CC_UNSPECIFIED_ERROR; 57 } 58 catch (...) 59 { 60 log<level::ERR>("wd_reset: Unknown Error"); 61 report<InternalFailure>(); 62 return IPMI_CC_UNSPECIFIED_ERROR; 63 } 64 } 65 66 static constexpr uint8_t wd_dont_stop = 0x1 << 6; 67 static constexpr uint8_t wd_timeout_action_mask = 0x3; 68 69 enum class IpmiAction : uint8_t { 70 None = 0x0, 71 HardReset = 0x1, 72 PowerOff = 0x2, 73 PowerCycle = 0x3, 74 }; 75 76 /** @brief Converts an IPMI Watchdog Action to DBUS defined action 77 * @param[in] ipmi_action The IPMI Watchdog Action 78 * @return The Watchdog Action that the ipmi_action maps to 79 */ 80 WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action) 81 { 82 switch(ipmi_action) 83 { 84 case IpmiAction::None: 85 { 86 return WatchdogService::Action::None; 87 } 88 case IpmiAction::HardReset: 89 { 90 return WatchdogService::Action::HardReset; 91 } 92 case IpmiAction::PowerOff: 93 { 94 return WatchdogService::Action::PowerOff; 95 } 96 case IpmiAction::PowerCycle: 97 { 98 return WatchdogService::Action::PowerCycle; 99 } 100 default: 101 { 102 throw std::domain_error("IPMI Action is invalid"); 103 } 104 } 105 } 106 107 struct wd_set_req { 108 uint8_t timer_use; 109 uint8_t timer_action; 110 uint8_t pretimeout; // (seconds) 111 uint8_t expire_flags; 112 uint16_t initial_countdown; // Little Endian (deciseconds) 113 } __attribute__ ((packed)); 114 static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size."); 115 static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER, 116 "wd_get_res can't fit in request buffer."); 117 118 ipmi_ret_t ipmi_app_watchdog_set( 119 ipmi_netfn_t netfn, 120 ipmi_cmd_t cmd, 121 ipmi_request_t request, 122 ipmi_response_t response, 123 ipmi_data_len_t data_len, 124 ipmi_context_t context) 125 { 126 // Extract the request data 127 if (*data_len < sizeof(wd_set_req)) 128 { 129 *data_len = 0; 130 return IPMI_CC_REQ_DATA_LEN_INVALID; 131 } 132 wd_set_req req; 133 memcpy(&req, request, sizeof(req)); 134 req.initial_countdown = le16toh(req.initial_countdown); 135 *data_len = 0; 136 137 try 138 { 139 WatchdogService wd_service; 140 // Stop the timer if the don't stop bit is not set 141 if (!(req.timer_use & wd_dont_stop)) 142 { 143 wd_service.setEnabled(false); 144 } 145 146 // Set the action based on the request 147 const auto ipmi_action = static_cast<IpmiAction>( 148 req.timer_action & wd_timeout_action_mask); 149 wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action)); 150 151 // Set the new interval and the time remaining deci -> mill seconds 152 const uint64_t interval = req.initial_countdown * 100; 153 wd_service.setInterval(interval); 154 wd_service.setTimeRemaining(interval); 155 156 // Mark as initialized so that future resets behave correctly 157 wd_service.setInitialized(true); 158 159 return IPMI_CC_OK; 160 } 161 catch (const std::domain_error &) 162 { 163 return IPMI_CC_INVALID_FIELD_REQUEST; 164 } 165 catch (const InternalFailure& e) 166 { 167 report<InternalFailure>(); 168 return IPMI_CC_UNSPECIFIED_ERROR; 169 } 170 catch (const std::exception& e) 171 { 172 const std::string e_str = std::string("wd_set: ") + e.what(); 173 log<level::ERR>(e_str.c_str()); 174 report<InternalFailure>(); 175 return IPMI_CC_UNSPECIFIED_ERROR; 176 } 177 catch (...) 178 { 179 log<level::ERR>("wd_set: Unknown Error"); 180 report<InternalFailure>(); 181 return IPMI_CC_UNSPECIFIED_ERROR; 182 } 183 } 184 185 /** @brief Converts a DBUS Watchdog Action to IPMI defined action 186 * @param[in] wd_action The DBUS Watchdog Action 187 * @return The IpmiAction that the wd_action maps to 188 */ 189 IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action) 190 { 191 switch(wd_action) 192 { 193 case WatchdogService::Action::None: 194 { 195 return IpmiAction::None; 196 } 197 case WatchdogService::Action::HardReset: 198 { 199 return IpmiAction::HardReset; 200 } 201 case WatchdogService::Action::PowerOff: 202 { 203 return IpmiAction::PowerOff; 204 } 205 case WatchdogService::Action::PowerCycle: 206 { 207 return IpmiAction::PowerCycle; 208 } 209 default: 210 { 211 // We have no method via IPMI to signal that the action is unknown 212 // or unmappable in some way. 213 // Just ignore the error and return NONE so the host can reconcile. 214 return IpmiAction::None; 215 } 216 } 217 } 218 219 struct wd_get_res { 220 uint8_t timer_use; 221 uint8_t timer_action; 222 uint8_t pretimeout; 223 uint8_t expire_flags; 224 uint16_t initial_countdown; // Little Endian (deciseconds) 225 uint16_t present_countdown; // Little Endian (deciseconds) 226 } __attribute__ ((packed)); 227 static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size."); 228 static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER, 229 "wd_get_res can't fit in response buffer."); 230 231 static constexpr uint8_t wd_dont_log = 0x1 << 7; 232 static constexpr uint8_t wd_running = 0x1 << 6; 233 234 ipmi_ret_t ipmi_app_watchdog_get( 235 ipmi_netfn_t netfn, 236 ipmi_cmd_t cmd, 237 ipmi_request_t request, 238 ipmi_response_t response, 239 ipmi_data_len_t data_len, 240 ipmi_context_t context) 241 { 242 // Assume we will fail and send no data outside the return code 243 *data_len = 0; 244 245 try 246 { 247 WatchdogService wd_service; 248 WatchdogService::Properties wd_prop = wd_service.getProperties(); 249 250 // Build and return the response 251 wd_get_res res; 252 res.timer_use = wd_dont_log; 253 res.timer_action = static_cast<uint8_t>( 254 wdActionToIpmiAction(wd_prop.expireAction)); 255 if (wd_prop.enabled) 256 { 257 res.timer_use |= wd_running; 258 } 259 // TODO: Do something about having pretimeout support 260 res.pretimeout = 0; 261 res.expire_flags = 0; 262 // Interval and timeRemaining need converted from milli -> deci seconds 263 res.initial_countdown = htole16(wd_prop.interval / 100); 264 res.present_countdown = htole16(wd_prop.timeRemaining / 100); 265 266 memcpy(response, &res, sizeof(res)); 267 *data_len = sizeof(res); 268 return IPMI_CC_OK; 269 } 270 catch (const InternalFailure& e) 271 { 272 report<InternalFailure>(); 273 return IPMI_CC_UNSPECIFIED_ERROR; 274 } 275 catch (const std::exception& e) 276 { 277 const std::string e_str = std::string("wd_get: ") + e.what(); 278 log<level::ERR>(e_str.c_str()); 279 report<InternalFailure>(); 280 return IPMI_CC_UNSPECIFIED_ERROR; 281 } 282 catch (...) 283 { 284 log<level::ERR>("wd_get: Unknown Error"); 285 report<InternalFailure>(); 286 return IPMI_CC_UNSPECIFIED_ERROR; 287 } 288 } 289