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