1 #pragma once 2 3 #include "config.h" 4 5 #include "common/types.hpp" 6 #include "pldmd/instance_id.hpp" 7 #include "request.hpp" 8 9 #include <libpldm/base.h> 10 #include <libpldm/pldm.h> 11 #include <sys/socket.h> 12 13 #include <function2/function2.hpp> 14 #include <phosphor-logging/lg2.hpp> 15 #include <sdbusplus/timer.hpp> 16 #include <sdeventplus/event.hpp> 17 #include <sdeventplus/source/event.hpp> 18 19 #include <cassert> 20 #include <chrono> 21 #include <memory> 22 #include <tuple> 23 #include <unordered_map> 24 25 PHOSPHOR_LOG2_USING; 26 27 namespace pldm 28 { 29 namespace requester 30 { 31 /** @struct RequestKey 32 * 33 * RequestKey uniquely identifies the PLDM request message to match it with the 34 * response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type 35 * and PLDM command is the key. 36 */ 37 struct RequestKey 38 { 39 mctp_eid_t eid; //!< MCTP endpoint ID 40 uint8_t instanceId; //!< PLDM instance ID 41 uint8_t type; //!< PLDM type 42 uint8_t command; //!< PLDM command 43 44 bool operator==(const RequestKey& e) const 45 { 46 return ((eid == e.eid) && (instanceId == e.instanceId) && 47 (type == e.type) && (command == e.command)); 48 } 49 }; 50 51 /** @struct RequestKeyHasher 52 * 53 * This is a simple hash function, since the instance ID generator API 54 * generates unique instance IDs for MCTP endpoint ID. 55 */ 56 struct RequestKeyHasher 57 { 58 std::size_t operator()(const RequestKey& key) const 59 { 60 return (key.eid << 24 | key.instanceId << 16 | key.type << 8 | 61 key.command); 62 } 63 }; 64 65 using ResponseHandler = fu2::unique_function<void( 66 mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>; 67 68 /** @class Handler 69 * 70 * This class handles the lifecycle of the PLDM request message based on the 71 * instance ID expiration interval, number of request retries and the timeout 72 * waiting for a response. The registered response handlers are invoked with 73 * response once the PLDM responder sends the response. If no response is 74 * received within the instance ID expiration interval or any other failure the 75 * response handler is invoked with the empty response. 76 * 77 * @tparam RequestInterface - Request class type 78 */ 79 template <class RequestInterface> 80 class Handler 81 { 82 public: 83 Handler() = delete; 84 Handler(const Handler&) = delete; 85 Handler(Handler&&) = delete; 86 Handler& operator=(const Handler&) = delete; 87 Handler& operator=(Handler&&) = delete; 88 ~Handler() = default; 89 90 /** @brief Constructor 91 * 92 * @param[in] fd - fd of MCTP communications socket 93 * @param[in] event - reference to PLDM daemon's main event loop 94 * @param[in] instanceIdDb - reference to an InstanceIdDb 95 * @param[in] currentSendbuffSize - current send buffer size 96 * @param[in] verbose - verbose tracing flag 97 * @param[in] instanceIdExpiryInterval - instance ID expiration interval 98 * @param[in] numRetries - number of request retries 99 * @param[in] responseTimeOut - time to wait between each retry 100 */ 101 explicit Handler( 102 int fd, sdeventplus::Event& event, pldm::InstanceIdDb& instanceIdDb, 103 int currentSendbuffSize, bool verbose, 104 std::chrono::seconds instanceIdExpiryInterval = 105 std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL), 106 uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES), 107 std::chrono::milliseconds responseTimeOut = 108 std::chrono::milliseconds(RESPONSE_TIME_OUT)) : 109 fd(fd), 110 event(event), instanceIdDb(instanceIdDb), 111 currentSendbuffSize(currentSendbuffSize), verbose(verbose), 112 instanceIdExpiryInterval(instanceIdExpiryInterval), 113 numRetries(numRetries), responseTimeOut(responseTimeOut) 114 {} 115 116 /** @brief Register a PLDM request message 117 * 118 * @param[in] eid - endpoint ID of the remote MCTP endpoint 119 * @param[in] instanceId - instance ID to match request and response 120 * @param[in] type - PLDM type 121 * @param[in] command - PLDM command 122 * @param[in] requestMsg - PLDM request message 123 * @param[in] responseHandler - Response handler for this request 124 * 125 * @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise 126 */ 127 int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 128 uint8_t command, pldm::Request&& requestMsg, 129 ResponseHandler&& responseHandler) 130 { 131 RequestKey key{eid, instanceId, type, command}; 132 133 auto instanceIdExpiryCallBack = [key, this](void) { 134 if (this->handlers.contains(key)) 135 { 136 error( 137 "Response not received for the request, instance ID expired. EID = {EID} INSTANCE_ID = {INST_ID} TYPE = {REQ_KEY_TYPE} COMMAND = {REQ_KEY_CMD}", 138 "EID", (unsigned)key.eid, "INST_ID", 139 (unsigned)key.instanceId, "REQ_KEY_TYPE", 140 (unsigned)key.type, "REQ_KEY_CMD", (unsigned)key.command); 141 auto& [request, responseHandler, 142 timerInstance] = this->handlers[key]; 143 request->stop(); 144 auto rc = timerInstance->stop(); 145 if (rc) 146 { 147 error( 148 "Failed to stop the instance ID expiry timer. RC = {RC}", 149 "RC", static_cast<int>(rc)); 150 } 151 // Call response handler with an empty response to indicate no 152 // response 153 responseHandler(key.eid, nullptr, 0); 154 this->removeRequestContainer.emplace( 155 key, std::make_unique<sdeventplus::source::Defer>( 156 event, std::bind(&Handler::removeRequestEntry, 157 this, key))); 158 } 159 else 160 { 161 // This condition is not possible, if a response is received 162 // before the instance ID expiry, then the response handler 163 // is executed and the entry will be removed. 164 assert(false); 165 } 166 }; 167 168 if (handlers.contains(key)) 169 { 170 error("The eid:InstanceID {EID}:{IID} is using.", "EID", 171 (unsigned)eid, "IID", (unsigned)instanceId); 172 return PLDM_ERROR; 173 } 174 175 auto request = std::make_unique<RequestInterface>( 176 fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut, 177 currentSendbuffSize, verbose); 178 auto timer = std::make_unique<phosphor::Timer>( 179 event.get(), instanceIdExpiryCallBack); 180 181 auto rc = request->start(); 182 if (rc) 183 { 184 instanceIdDb.free(eid, instanceId); 185 error("Failure to send the PLDM request message"); 186 return rc; 187 } 188 189 try 190 { 191 timer->start(duration_cast<std::chrono::microseconds>( 192 instanceIdExpiryInterval)); 193 } 194 catch (const std::runtime_error& e) 195 { 196 instanceIdDb.free(eid, instanceId); 197 error( 198 "Failed to start the instance ID expiry timer. RC = {ERR_EXCEP}", 199 "ERR_EXCEP", e.what()); 200 return PLDM_ERROR; 201 } 202 203 handlers.emplace(key, std::make_tuple(std::move(request), 204 std::move(responseHandler), 205 std::move(timer))); 206 return rc; 207 } 208 209 /** @brief Handle PLDM response message 210 * 211 * @param[in] eid - endpoint ID of the remote MCTP endpoint 212 * @param[in] instanceId - instance ID to match request and response 213 * @param[in] type - PLDM type 214 * @param[in] command - PLDM command 215 * @param[in] response - PLDM response message 216 * @param[in] respMsgLen - length of the response message 217 */ 218 void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 219 uint8_t command, const pldm_msg* response, 220 size_t respMsgLen) 221 { 222 RequestKey key{eid, instanceId, type, command}; 223 if (handlers.contains(key)) 224 { 225 auto& [request, responseHandler, timerInstance] = handlers[key]; 226 request->stop(); 227 auto rc = timerInstance->stop(); 228 if (rc) 229 { 230 error("Failed to stop the instance ID expiry timer. RC = {RC}", 231 "RC", static_cast<int>(rc)); 232 } 233 responseHandler(eid, response, respMsgLen); 234 instanceIdDb.free(key.eid, key.instanceId); 235 handlers.erase(key); 236 } 237 else 238 { 239 // Got a response for a PLDM request message not registered with the 240 // request handler, so freeing up the instance ID, this can be other 241 // OpenBMC applications relying on PLDM D-Bus apis like 242 // openpower-occ-control and softoff 243 instanceIdDb.free(key.eid, key.instanceId); 244 } 245 } 246 247 private: 248 int fd; //!< file descriptor of MCTP communications socket 249 sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop 250 pldm::InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb 251 int currentSendbuffSize; //!< current Send Buffer size 252 bool verbose; //!< verbose tracing flag 253 std::chrono::seconds 254 instanceIdExpiryInterval; //!< Instance ID expiration interval 255 uint8_t numRetries; //!< number of request retries 256 std::chrono::milliseconds 257 responseTimeOut; //!< time to wait between each retry 258 259 /** @brief Container for storing the details of the PLDM request 260 * message, handler for the corresponding PLDM response and the 261 * timer object for the Instance ID expiration 262 */ 263 using RequestValue = 264 std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler, 265 std::unique_ptr<phosphor::Timer>>; 266 267 /** @brief Container for storing the PLDM request entries */ 268 std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers; 269 270 /** @brief Container to store information about the request entries to be 271 * removed after the instance ID timer expires 272 */ 273 std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>, 274 RequestKeyHasher> 275 removeRequestContainer; 276 277 /** @brief Remove request entry for which the instance ID expired 278 * 279 * @param[in] key - key for the Request 280 */ 281 void removeRequestEntry(RequestKey key) 282 { 283 if (removeRequestContainer.contains(key)) 284 { 285 removeRequestContainer[key].reset(); 286 instanceIdDb.free(key.eid, key.instanceId); 287 handlers.erase(key); 288 removeRequestContainer.erase(key); 289 } 290 } 291 }; 292 293 } // namespace requester 294 295 } // namespace pldm 296