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