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/async.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 <deque> 20 #include <functional> 21 #include <memory> 22 #include <mutex> 23 #include <queue> 24 #include <tuple> 25 #include <unordered_map> 26 27 PHOSPHOR_LOG2_USING; 28 29 namespace pldm 30 { 31 namespace requester 32 { 33 /** @struct RequestKey 34 * 35 * RequestKey uniquely identifies the PLDM request message to match it with the 36 * response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type 37 * and PLDM command is the key. 38 */ 39 struct RequestKey 40 { 41 mctp_eid_t eid; //!< MCTP endpoint ID 42 uint8_t instanceId; //!< PLDM instance ID 43 uint8_t type; //!< PLDM type 44 uint8_t command; //!< PLDM command 45 46 bool operator==(const RequestKey& e) const 47 { 48 return ((eid == e.eid) && (instanceId == e.instanceId) && 49 (type == e.type) && (command == e.command)); 50 } 51 }; 52 53 /** @struct RequestKeyHasher 54 * 55 * This is a simple hash function, since the instance ID generator API 56 * generates unique instance IDs for MCTP endpoint ID. 57 */ 58 struct RequestKeyHasher 59 { 60 std::size_t operator()(const RequestKey& key) const 61 { 62 return (key.eid << 24 | key.instanceId << 16 | key.type << 8 | 63 key.command); 64 } 65 }; 66 67 using ResponseHandler = std::function<void( 68 mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>; 69 70 /** @brief The response from SendRecvMsg with coroutine API 71 * 72 * The response when registers PLDM request message using the SendRecvMsg 73 * with coroutine API. 74 * Responded tuple includes <CompleteCode, ResponseMgs, ResponseMsgLength> 75 * Value: [PLDM_ERROR, _, _] if registerRequest fails. 76 * [PLDM_ERROR_NOT_READY, nullptr, 0] if timed out. 77 * [PLDM_SUCCESS, ResponseMsg, ResponseMsgLength] if succeeded 78 */ 79 using SendRecvCoResp = std::tuple<int, const pldm_msg*, size_t>; 80 81 /** @struct RegisteredRequest 82 * 83 * This struct is used to store the registered request to one endpoint. 84 */ 85 struct RegisteredRequest 86 { 87 RequestKey key; //!< Responder MCTP endpoint ID 88 std::vector<uint8_t> reqMsg; //!< Request messages queue 89 ResponseHandler responseHandler; //!< Waiting for response flag 90 }; 91 92 /** @struct EndpointMessageQueue 93 * 94 * This struct is used to save the list of request messages of one endpoint and 95 * the existing of the request message to the endpoint with its' EID. 96 */ 97 struct EndpointMessageQueue 98 { 99 mctp_eid_t eid; //!< Responder MCTP endpoint ID 100 std::deque<std::shared_ptr<RegisteredRequest>> requestQueue; //!< Queue 101 bool activeRequest; //!< Waiting for response flag 102 103 bool operator==(const mctp_eid_t& mctpEid) const 104 { 105 return (eid == mctpEid); 106 } 107 }; 108 109 /** @class Handler 110 * 111 * This class handles the lifecycle of the PLDM request message based on the 112 * instance ID expiration interval, number of request retries and the timeout 113 * waiting for a response. The registered response handlers are invoked with 114 * response once the PLDM responder sends the response. If no response is 115 * received within the instance ID expiration interval or any other failure the 116 * response handler is invoked with the empty response. 117 * 118 * @tparam RequestInterface - Request class type 119 */ 120 template <class RequestInterface> 121 class Handler 122 { 123 public: 124 Handler() = delete; 125 Handler(const Handler&) = delete; 126 Handler(Handler&&) = delete; 127 Handler& operator=(const Handler&) = delete; 128 Handler& operator=(Handler&&) = delete; 129 ~Handler() = default; 130 131 /** @brief Constructor 132 * 133 * @param[in] pldm_transport - PLDM requester 134 * @param[in] event - reference to PLDM daemon's main event loop 135 * @param[in] instanceIdDb - reference to an InstanceIdDb 136 * @param[in] verbose - verbose tracing flag 137 * @param[in] instanceIdExpiryInterval - instance ID expiration interval 138 * @param[in] numRetries - number of request retries 139 * @param[in] responseTimeOut - time to wait between each retry 140 */ 141 explicit Handler( 142 PldmTransport* pldmTransport, sdeventplus::Event& event, 143 pldm::InstanceIdDb& instanceIdDb, bool verbose, 144 std::chrono::seconds instanceIdExpiryInterval = 145 std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL), 146 uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES), 147 std::chrono::milliseconds responseTimeOut = 148 std::chrono::milliseconds(RESPONSE_TIME_OUT)) : 149 pldmTransport(pldmTransport), event(event), instanceIdDb(instanceIdDb), 150 verbose(verbose), instanceIdExpiryInterval(instanceIdExpiryInterval), 151 numRetries(numRetries), responseTimeOut(responseTimeOut) 152 {} 153 154 void instanceIdExpiryCallBack(RequestKey key) 155 { 156 auto eid = key.eid; 157 if (this->handlers.contains(key)) 158 { 159 info( 160 "Instance ID expiry for EID '{EID}' using InstanceID '{INSTANCEID}'", 161 "EID", key.eid, "INSTANCEID", key.instanceId); 162 auto& [request, responseHandler, 163 timerInstance] = this->handlers[key]; 164 request->stop(); 165 auto rc = timerInstance->stop(); 166 if (rc) 167 { 168 error( 169 "Failed to stop the instance ID expiry timer, response code '{RC}'", 170 "RC", rc); 171 } 172 // Call response handler with an empty response to indicate no 173 // response 174 responseHandler(eid, nullptr, 0); 175 this->removeRequestContainer.emplace( 176 key, 177 std::make_unique<sdeventplus::source::Defer>( 178 event, std::bind(&Handler::removeRequestEntry, this, key))); 179 endpointMessageQueues[eid]->activeRequest = false; 180 181 /* try to send new request if the endpoint is free */ 182 pollEndpointQueue(eid); 183 } 184 else 185 { 186 // This condition is not possible, if a response is received 187 // before the instance ID expiry, then the response handler 188 // is executed and the entry will be removed. 189 assert(false); 190 } 191 } 192 193 /** @brief Send the remaining PLDM request messages in endpoint queue 194 * 195 * @param[in] eid - endpoint ID of the remote MCTP endpoint 196 */ 197 int pollEndpointQueue(mctp_eid_t eid) 198 { 199 if (endpointMessageQueues[eid]->activeRequest || 200 endpointMessageQueues[eid]->requestQueue.empty()) 201 { 202 return PLDM_SUCCESS; 203 } 204 205 endpointMessageQueues[eid]->activeRequest = true; 206 auto requestMsg = endpointMessageQueues[eid]->requestQueue.front(); 207 endpointMessageQueues[eid]->requestQueue.pop_front(); 208 209 auto request = std::make_unique<RequestInterface>( 210 pldmTransport, requestMsg->key.eid, event, 211 std::move(requestMsg->reqMsg), numRetries, responseTimeOut, 212 verbose); 213 auto timer = std::make_unique<sdbusplus::Timer>( 214 event.get(), std::bind(&Handler::instanceIdExpiryCallBack, this, 215 requestMsg->key)); 216 217 auto rc = request->start(); 218 if (rc) 219 { 220 instanceIdDb.free(requestMsg->key.eid, requestMsg->key.instanceId); 221 error( 222 "Failure to send the PLDM request message for polling endpoint queue, response code '{RC}'", 223 "RC", rc); 224 endpointMessageQueues[eid]->activeRequest = false; 225 return rc; 226 } 227 228 try 229 { 230 timer->start(duration_cast<std::chrono::microseconds>( 231 instanceIdExpiryInterval)); 232 } 233 catch (const std::runtime_error& e) 234 { 235 instanceIdDb.free(requestMsg->key.eid, requestMsg->key.instanceId); 236 error( 237 "Failed to start the instance ID expiry timer, error - {ERROR}", 238 "ERROR", e); 239 endpointMessageQueues[eid]->activeRequest = false; 240 return PLDM_ERROR; 241 } 242 243 handlers.emplace(requestMsg->key, 244 std::make_tuple(std::move(request), 245 std::move(requestMsg->responseHandler), 246 std::move(timer))); 247 return PLDM_SUCCESS; 248 } 249 250 /** @brief Register a PLDM request message 251 * 252 * @param[in] eid - endpoint ID of the remote MCTP endpoint 253 * @param[in] instanceId - instance ID to match request and response 254 * @param[in] type - PLDM type 255 * @param[in] command - PLDM command 256 * @param[in] requestMsg - PLDM request message 257 * @param[in] responseHandler - Response handler for this request 258 * 259 * @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise 260 */ 261 int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 262 uint8_t command, pldm::Request&& requestMsg, 263 ResponseHandler&& responseHandler) 264 { 265 RequestKey key{eid, instanceId, type, command}; 266 267 if (handlers.contains(key)) 268 { 269 error( 270 "Register request for EID '{EID}' is using InstanceID '{INSTANCEID}'", 271 "EID", eid, "INSTANCEID", instanceId); 272 return PLDM_ERROR; 273 } 274 275 auto inputRequest = std::make_shared<RegisteredRequest>( 276 key, std::move(requestMsg), std::move(responseHandler)); 277 if (endpointMessageQueues.contains(eid)) 278 { 279 endpointMessageQueues[eid]->requestQueue.push_back(inputRequest); 280 } 281 else 282 { 283 std::deque<std::shared_ptr<RegisteredRequest>> reqQueue; 284 reqQueue.push_back(inputRequest); 285 endpointMessageQueues[eid] = 286 std::make_shared<EndpointMessageQueue>(eid, reqQueue, false); 287 } 288 289 /* try to send new request if the endpoint is free */ 290 pollEndpointQueue(eid); 291 292 return PLDM_SUCCESS; 293 } 294 295 /** @brief Unregister a PLDM request message 296 * 297 * @param[in] eid - endpoint ID of the remote MCTP endpoint 298 * @param[in] instanceId - instance ID to match request and response 299 * @param[in] type - PLDM type 300 * @param[in] command - PLDM command 301 * 302 * @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise 303 */ 304 int unregisterRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 305 uint8_t command) 306 { 307 RequestKey key{eid, instanceId, type, command}; 308 309 /* handlers only contain key when the message is already sent */ 310 if (handlers.contains(key)) 311 { 312 auto& [request, responseHandler, timerInstance] = handlers[key]; 313 request->stop(); 314 auto rc = timerInstance->stop(); 315 if (rc) 316 { 317 error( 318 "Failed to stop the instance ID expiry timer, response code '{RC}'", 319 "RC", static_cast<int>(rc)); 320 } 321 322 instanceIdDb.free(key.eid, key.instanceId); 323 handlers.erase(key); 324 endpointMessageQueues[eid]->activeRequest = false; 325 /* try to send new request if the endpoint is free */ 326 pollEndpointQueue(eid); 327 328 return PLDM_SUCCESS; 329 } 330 else 331 { 332 if (!endpointMessageQueues.contains(eid)) 333 { 334 error( 335 "Can't find request for EID '{EID}' is using InstanceID '{INSTANCEID}' in Endpoint message Queue", 336 "EID", (unsigned)eid, "INSTANCEID", (unsigned)instanceId); 337 return PLDM_ERROR; 338 } 339 auto requestMsg = endpointMessageQueues[eid]->requestQueue; 340 /* Find the registered request in the requestQueue */ 341 for (auto it = requestMsg.begin(); it != requestMsg.end();) 342 { 343 auto msg = *it; 344 if (msg->key == key) 345 { 346 // erase and get the next valid iterator 347 it = endpointMessageQueues[eid]->requestQueue.erase(it); 348 instanceIdDb.free(key.eid, key.instanceId); 349 return PLDM_SUCCESS; 350 } 351 else 352 { 353 ++it; // increment iterator only if not erasing 354 } 355 } 356 } 357 358 return PLDM_ERROR; 359 } 360 361 /** @brief Handle PLDM response message 362 * 363 * @param[in] eid - endpoint ID of the remote MCTP endpoint 364 * @param[in] instanceId - instance ID to match request and response 365 * @param[in] type - PLDM type 366 * @param[in] command - PLDM command 367 * @param[in] response - PLDM response message 368 * @param[in] respMsgLen - length of the response message 369 */ 370 void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 371 uint8_t command, const pldm_msg* response, 372 size_t respMsgLen) 373 { 374 RequestKey key{eid, instanceId, type, command}; 375 if (handlers.contains(key) && !removeRequestContainer.contains(key)) 376 { 377 auto& [request, responseHandler, timerInstance] = handlers[key]; 378 request->stop(); 379 auto rc = timerInstance->stop(); 380 if (rc) 381 { 382 error( 383 "Failed to stop the instance ID expiry timer, response code '{RC}'", 384 "RC", rc); 385 } 386 responseHandler(eid, response, respMsgLen); 387 instanceIdDb.free(key.eid, key.instanceId); 388 handlers.erase(key); 389 390 endpointMessageQueues[eid]->activeRequest = false; 391 /* try to send new request if the endpoint is free */ 392 pollEndpointQueue(eid); 393 } 394 else 395 { 396 // Got a response for a PLDM request message not registered with the 397 // request handler, so freeing up the instance ID, this can be other 398 // OpenBMC applications relying on PLDM D-Bus apis like 399 // openpower-occ-control and softoff 400 instanceIdDb.free(key.eid, key.instanceId); 401 } 402 } 403 404 /** @brief Wrap registerRequest with coroutine API. 405 * 406 * @return Return [PLDM_ERROR, _, _] if registerRequest fails. 407 * Return [PLDM_ERROR_NOT_READY, nullptr, 0] if timed out. 408 * Return [PLDM_SUCCESS, resp, len] if succeeded 409 */ 410 stdexec::sender_of<stdexec::set_value_t(SendRecvCoResp)> auto 411 sendRecvMsg(mctp_eid_t eid, pldm::Request&& request); 412 413 private: 414 PldmTransport* pldmTransport; //!< PLDM transport object 415 sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop 416 pldm::InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb 417 bool verbose; //!< verbose tracing flag 418 std::chrono::seconds 419 instanceIdExpiryInterval; //!< Instance ID expiration interval 420 uint8_t numRetries; //!< number of request retries 421 std::chrono::milliseconds 422 responseTimeOut; //!< time to wait between each retry 423 424 /** @brief Container for storing the details of the PLDM request 425 * message, handler for the corresponding PLDM response and the 426 * timer object for the Instance ID expiration 427 */ 428 using RequestValue = 429 std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler, 430 std::unique_ptr<sdbusplus::Timer>>; 431 432 // Manage the requests of responders base on MCTP EID 433 std::map<mctp_eid_t, std::shared_ptr<EndpointMessageQueue>> 434 endpointMessageQueues; 435 436 /** @brief Container for storing the PLDM request entries */ 437 std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers; 438 439 /** @brief Container to store information about the request entries to be 440 * removed after the instance ID timer expires 441 */ 442 std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>, 443 RequestKeyHasher> 444 removeRequestContainer; 445 446 /** @brief Remove request entry for which the instance ID expired 447 * 448 * @param[in] key - key for the Request 449 */ 450 void removeRequestEntry(RequestKey key) 451 { 452 if (removeRequestContainer.contains(key)) 453 { 454 removeRequestContainer[key].reset(); 455 instanceIdDb.free(key.eid, key.instanceId); 456 handlers.erase(key); 457 removeRequestContainer.erase(key); 458 } 459 } 460 }; 461 462 /** @class SendRecvMsgOperation 463 * 464 * Represents the state and logic for a single send/receive message operation 465 * 466 * @tparam RequestInterface - Request class type 467 * @tparam stdexec::receiver - Execute receiver 468 */ 469 template <class RequestInterface, stdexec::receiver R> 470 struct SendRecvMsgOperation 471 { 472 SendRecvMsgOperation() = delete; 473 474 explicit SendRecvMsgOperation(Handler<RequestInterface>& handler, 475 mctp_eid_t eid, pldm::Request&& request, 476 R&& r) : 477 handler(handler), request(std::move(request)), receiver(std::move(r)) 478 { 479 auto requestMsg = 480 reinterpret_cast<const pldm_msg*>(this->request.data()); 481 requestKey = RequestKey{ 482 eid, 483 requestMsg->hdr.instance_id, 484 requestMsg->hdr.type, 485 requestMsg->hdr.command, 486 }; 487 response = nullptr; 488 respMsgLen = 0; 489 } 490 491 /** @brief Checks if the operation has been requested to stop. 492 * If so, it sets the state to stopped.Registers the request with 493 * the handler. If registration fails, sets an error on the 494 * receiver. If stopping is possible, sets up a stop callback. 495 * 496 * @param[in] op - operation request 497 * 498 * @return Execute errors 499 */ 500 friend void tag_invoke(stdexec::start_t, SendRecvMsgOperation& op) noexcept 501 { 502 auto stopToken = stdexec::get_stop_token(stdexec::get_env(op.receiver)); 503 504 // operation already cancelled 505 if (stopToken.stop_requested()) 506 { 507 return stdexec::set_stopped(std::move(op.receiver)); 508 } 509 510 using namespace std::placeholders; 511 auto rc = op.handler.registerRequest( 512 op.requestKey.eid, op.requestKey.instanceId, op.requestKey.type, 513 op.requestKey.command, std::move(op.request), 514 std::bind(&SendRecvMsgOperation::onComplete, &op, _1, _2, _3)); 515 if (rc) 516 { 517 return stdexec::set_value(std::move(op.receiver), rc, 518 static_cast<const pldm_msg*>(nullptr), 519 static_cast<size_t>(0)); 520 } 521 522 if (stopToken.stop_possible()) 523 { 524 op.stopCallback.emplace( 525 std::move(stopToken), 526 std::bind(&SendRecvMsgOperation::onStop, &op)); 527 } 528 } 529 530 /** @brief Unregisters the request and sets the state to stopped on the 531 * receiver. 532 */ 533 void onStop() 534 { 535 handler.unregisterRequest(requestKey.eid, requestKey.instanceId, 536 requestKey.type, requestKey.command); 537 return stdexec::set_stopped(std::move(receiver)); 538 } 539 540 /** @brief This function resets the stop callback. Validates the response 541 * and sets either an error or a value on the receiver. 542 * 543 * @param[in] eid - endpoint ID of the remote MCTP endpoint 544 * @param[in] response - PLDM response message 545 * @param[in] respMsgLen - length of the response message 546 * 547 * @return PLDM completion code 548 */ 549 void onComplete(mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen) 550 { 551 stopCallback.reset(); 552 assert(eid == this->requestKey.eid); 553 auto rc = PLDM_SUCCESS; 554 if (!response && !respMsgLen) 555 { 556 rc = PLDM_ERROR_NOT_READY; 557 } 558 return stdexec::set_value(std::move(receiver), static_cast<int>(rc), 559 response, respMsgLen); 560 } 561 562 private: 563 /** @brief Reference to a Handler object that manages the request/response 564 * logic. 565 */ 566 requester::Handler<RequestInterface>& handler; 567 568 /** @brief Stores information about the request such as eid, instanceId, 569 * type, and command. 570 */ 571 RequestKey requestKey; 572 573 /** @brief The request message to be sent. 574 */ 575 pldm::Request request; 576 577 /** @brief The response message for the sent request message. 578 */ 579 const pldm_msg* response; 580 581 /** @brief The length of response message for the sent request message. 582 */ 583 size_t respMsgLen; 584 585 /** @brief The receiver to be notified with the result of the operation. 586 */ 587 R receiver; 588 589 /** @brief An optional callback that handles stopping the operation if 590 * requested. 591 */ 592 std::optional<typename stdexec::stop_token_of_t< 593 stdexec::env_of_t<R>>::template callback_type<std::function<void()>>> 594 stopCallback = std::nullopt; 595 }; 596 597 /** @class SendRecvMsgSender 598 * 599 * Represents the single message sender 600 * 601 * @tparam RequestInterface - Request class type 602 */ 603 template <class RequestInterface> 604 struct SendRecvMsgSender 605 { 606 using is_sender = void; 607 608 SendRecvMsgSender() = delete; 609 610 explicit SendRecvMsgSender(requester::Handler<RequestInterface>& handler, 611 mctp_eid_t eid, pldm::Request&& request) : 612 handler(handler), eid(eid), request(std::move(request)) 613 {} 614 615 friend auto tag_invoke(stdexec::get_completion_signatures_t, 616 const SendRecvMsgSender&, auto) 617 -> stdexec::completion_signatures< 618 stdexec::set_value_t(int, const pldm_msg*, size_t), 619 stdexec::set_stopped_t()>; 620 621 /** @brief Execute the sending the request message */ 622 template <stdexec::receiver R> 623 friend auto tag_invoke(stdexec::connect_t, SendRecvMsgSender&& self, R r) 624 { 625 return SendRecvMsgOperation<RequestInterface, R>( 626 self.handler, self.eid, std::move(self.request), std::move(r)); 627 } 628 629 private: 630 /** @brief Reference to a Handler object that manages the request/response 631 * logic. 632 */ 633 requester::Handler<RequestInterface>& handler; 634 635 /** @brief MCTP Endpoint ID of request message */ 636 mctp_eid_t eid; 637 638 /** @brief Request message */ 639 pldm::Request request; 640 }; 641 642 /** @brief Wrap registerRequest with coroutine API. 643 * 644 * @param[in] eid - endpoint ID of the remote MCTP endpoint 645 * @param[in] request - PLDM request message 646 * 647 * @return Return [PLDM_ERROR, _, _] if registerRequest fails. 648 * Return [PLDM_ERROR_NOT_READY, nullptr, 0] if timed out. 649 * Return [PLDM_SUCCESS, resp, len] if succeeded 650 */ 651 template <class RequestInterface> 652 stdexec::sender_of<stdexec::set_value_t(SendRecvCoResp)> auto 653 Handler<RequestInterface>::sendRecvMsg(mctp_eid_t eid, 654 pldm::Request&& request) 655 { 656 return SendRecvMsgSender(*this, eid, std::move(request)) | 657 stdexec::then([](int rc, const pldm_msg* resp, size_t respLen) { 658 return std::make_tuple(rc, resp, respLen); 659 }); 660 } 661 662 } // namespace requester 663 664 } // namespace pldm 665