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