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