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