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