1 #pragma once 2 3 #include "config.h" 4 5 #include "common/types.hpp" 6 #include "pldmd/dbus_impl_requester.hpp" 7 #include "request.hpp" 8 9 #include <libpldm/base.h> 10 #include <libpldm/pldm.h> 11 #include <sys/socket.h> 12 13 #include <function2/function2.hpp> 14 #include <sdbusplus/timer.hpp> 15 #include <sdeventplus/event.hpp> 16 #include <sdeventplus/source/event.hpp> 17 18 #include <cassert> 19 #include <chrono> 20 #include <memory> 21 #include <tuple> 22 #include <unordered_map> 23 24 namespace pldm 25 { 26 27 namespace requester 28 { 29 30 /** @struct RequestKey 31 * 32 * RequestKey uniquely identifies the PLDM request message to match it with the 33 * response and a combination of MCTP endpoint ID, PLDM instance ID, PLDM type 34 * and PLDM command is the key. 35 */ 36 struct RequestKey 37 { 38 mctp_eid_t eid; //!< MCTP endpoint ID 39 uint8_t instanceId; //!< PLDM instance ID 40 uint8_t type; //!< PLDM type 41 uint8_t command; //!< PLDM command 42 43 bool operator==(const RequestKey& e) const 44 { 45 return ((eid == e.eid) && (instanceId == e.instanceId) && 46 (type == e.type) && (command == e.command)); 47 } 48 }; 49 50 /** @struct RequestKeyHasher 51 * 52 * This is a simple hash function, since the instance ID generator API 53 * generates unique instance IDs for MCTP endpoint ID. 54 */ 55 struct RequestKeyHasher 56 { 57 std::size_t operator()(const RequestKey& key) const 58 { 59 return (key.eid << 24 | key.instanceId << 16 | key.type << 8 | 60 key.command); 61 } 62 }; 63 64 using ResponseHandler = fu2::unique_function<void( 65 mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>; 66 67 /** @class Handler 68 * 69 * This class handles the lifecycle of the PLDM request message based on the 70 * instance ID expiration interval, number of request retries and the timeout 71 * waiting for a response. The registered response handlers are invoked with 72 * response once the PLDM responder sends the response. If no response is 73 * received within the instance ID expiration interval or any other failure the 74 * response handler is invoked with the empty response. 75 * 76 * @tparam RequestInterface - Request class type 77 */ 78 template <class RequestInterface> 79 class Handler 80 { 81 public: 82 Handler() = delete; 83 Handler(const Handler&) = delete; 84 Handler(Handler&&) = delete; 85 Handler& operator=(const Handler&) = delete; 86 Handler& operator=(Handler&&) = delete; 87 ~Handler() = default; 88 89 /** @brief Constructor 90 * 91 * @param[in] fd - fd of MCTP communications socket 92 * @param[in] event - reference to PLDM daemon's main event loop 93 * @param[in] requester - reference to Requester object 94 * @param[in] currentSendbuffSize - current send buffer size 95 * @param[in] verbose - verbose tracing flag 96 * @param[in] instanceIdExpiryInterval - instance ID expiration interval 97 * @param[in] numRetries - number of request retries 98 * @param[in] responseTimeOut - time to wait between each retry 99 */ 100 explicit Handler( 101 int fd, sdeventplus::Event& event, pldm::dbus_api::Requester& requester, 102 int currentSendbuffSize, bool verbose, 103 std::chrono::seconds instanceIdExpiryInterval = 104 std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL), 105 uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES), 106 std::chrono::milliseconds responseTimeOut = 107 std::chrono::milliseconds(RESPONSE_TIME_OUT)) : 108 fd(fd), 109 event(event), requester(requester), 110 currentSendbuffSize(currentSendbuffSize), verbose(verbose), 111 instanceIdExpiryInterval(instanceIdExpiryInterval), 112 numRetries(numRetries), responseTimeOut(responseTimeOut) 113 {} 114 115 /** @brief Register a PLDM request message 116 * 117 * @param[in] eid - endpoint ID of the remote MCTP endpoint 118 * @param[in] instanceId - instance ID to match request and response 119 * @param[in] type - PLDM type 120 * @param[in] command - PLDM command 121 * @param[in] requestMsg - PLDM request message 122 * @param[in] responseHandler - Response handler for this request 123 * 124 * @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise 125 */ 126 int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 127 uint8_t command, pldm::Request&& requestMsg, 128 ResponseHandler&& responseHandler) 129 { 130 RequestKey key{eid, instanceId, type, command}; 131 132 auto instanceIdExpiryCallBack = [key, this](void) { 133 if (this->handlers.contains(key)) 134 { 135 std::cerr << "Response not received for the request, instance " 136 "ID expired." 137 << " EID = " << (unsigned)key.eid 138 << " INSTANCE_ID = " << (unsigned)key.instanceId 139 << " TYPE = " << (unsigned)key.type 140 << " COMMAND = " << (unsigned)key.command << "\n"; 141 auto& [request, responseHandler, timerInstance] = 142 this->handlers[key]; 143 request->stop(); 144 auto rc = timerInstance->stop(); 145 if (rc) 146 { 147 std::cerr 148 << "Failed to stop the instance ID expiry timer. RC = " 149 << rc << "\n"; 150 } 151 // Call response handler with an empty response to indicate no 152 // response 153 responseHandler(key.eid, nullptr, 0); 154 this->removeRequestContainer.emplace( 155 key, std::make_unique<sdeventplus::source::Defer>( 156 event, std::bind(&Handler::removeRequestEntry, 157 this, key))); 158 } 159 else 160 { 161 // This condition is not possible, if a response is received 162 // before the instance ID expiry, then the response handler 163 // is executed and the entry will be removed. 164 assert(false); 165 } 166 }; 167 168 auto request = std::make_unique<RequestInterface>( 169 fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut, 170 currentSendbuffSize, verbose); 171 auto timer = std::make_unique<phosphor::Timer>( 172 event.get(), instanceIdExpiryCallBack); 173 174 auto rc = request->start(); 175 if (rc) 176 { 177 requester.markFree(eid, instanceId); 178 std::cerr << "Failure to send the PLDM request message" 179 << "\n"; 180 return rc; 181 } 182 183 try 184 { 185 timer->start(duration_cast<std::chrono::microseconds>( 186 instanceIdExpiryInterval)); 187 } 188 catch (const std::runtime_error& e) 189 { 190 requester.markFree(eid, instanceId); 191 std::cerr << "Failed to start the instance ID expiry timer. RC = " 192 << e.what() << "\n"; 193 return PLDM_ERROR; 194 } 195 196 handlers.emplace(key, std::make_tuple(std::move(request), 197 std::move(responseHandler), 198 std::move(timer))); 199 return rc; 200 } 201 202 /** @brief Handle PLDM response message 203 * 204 * @param[in] eid - endpoint ID of the remote MCTP endpoint 205 * @param[in] instanceId - instance ID to match request and response 206 * @param[in] type - PLDM type 207 * @param[in] command - PLDM command 208 * @param[in] response - PLDM response message 209 * @param[in] respMsgLen - length of the response message 210 */ 211 void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 212 uint8_t command, const pldm_msg* response, 213 size_t respMsgLen) 214 { 215 RequestKey key{eid, instanceId, type, command}; 216 if (handlers.contains(key)) 217 { 218 auto& [request, responseHandler, timerInstance] = handlers[key]; 219 request->stop(); 220 auto rc = timerInstance->stop(); 221 if (rc) 222 { 223 std::cerr 224 << "Failed to stop the instance ID expiry timer. RC = " 225 << rc << "\n"; 226 } 227 responseHandler(eid, response, respMsgLen); 228 requester.markFree(key.eid, key.instanceId); 229 handlers.erase(key); 230 } 231 else 232 { 233 // Got a response for a PLDM request message not registered with the 234 // request handler, so freeing up the instance ID, this can be other 235 // OpenBMC applications relying on PLDM D-Bus apis like 236 // openpower-occ-control and softoff 237 requester.markFree(key.eid, key.instanceId); 238 } 239 } 240 241 private: 242 int fd; //!< file descriptor of MCTP communications socket 243 sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop 244 pldm::dbus_api::Requester& requester; //!< reference to Requester object 245 int currentSendbuffSize; //!< current Send Buffer size 246 bool verbose; //!< verbose tracing flag 247 std::chrono::seconds 248 instanceIdExpiryInterval; //!< Instance ID expiration interval 249 uint8_t numRetries; //!< number of request retries 250 std::chrono::milliseconds 251 responseTimeOut; //!< time to wait between each retry 252 253 /** @brief Container for storing the details of the PLDM request 254 * message, handler for the corresponding PLDM response and the 255 * timer object for the Instance ID expiration 256 */ 257 using RequestValue = 258 std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler, 259 std::unique_ptr<phosphor::Timer>>; 260 261 /** @brief Container for storing the PLDM request entries */ 262 std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers; 263 264 /** @brief Container to store information about the request entries to be 265 * removed after the instance ID timer expires 266 */ 267 std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>, 268 RequestKeyHasher> 269 removeRequestContainer; 270 271 /** @brief Remove request entry for which the instance ID expired 272 * 273 * @param[in] key - key for the Request 274 */ 275 void removeRequestEntry(RequestKey key) 276 { 277 if (removeRequestContainer.contains(key)) 278 { 279 removeRequestContainer[key].reset(); 280 requester.markFree(key.eid, key.instanceId); 281 handlers.erase(key); 282 removeRequestContainer.erase(key); 283 } 284 } 285 }; 286 287 } // namespace requester 288 289 } // namespace pldm 290