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