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