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