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 using namespace std::chrono; 66 using namespace sdeventplus; 67 using namespace sdeventplus::source; 68 using namespace pldm::dbus_api; 69 using namespace phosphor; 70 71 /** @class Handler 72 * 73 * This class handles the lifecycle of the PLDM request message based on the 74 * instance ID expiration interval, number of request retries and the timeout 75 * waiting for a response. The registered response handlers are invoked with 76 * response once the PLDM responder sends the response. If no response is 77 * received within the instance ID expiration interval or any other failure the 78 * response handler is invoked with the empty response. 79 * 80 * @tparam RequestInterface - Request class type 81 */ 82 template <class RequestInterface> 83 class Handler 84 { 85 86 public: 87 Handler() = delete; 88 Handler(const Handler&) = delete; 89 Handler(Handler&&) = delete; 90 Handler& operator=(const Handler&) = delete; 91 Handler& operator=(Handler&&) = delete; 92 ~Handler() = default; 93 94 /** @brief Constructor 95 * 96 * @param[in] fd - fd of MCTP communications socket 97 * @param[in] event - reference to PLDM daemon's main event loop 98 * @param[in] requester - reference to Requester object 99 * @param[in] instanceIdExpiryInterval - instance ID expiration interval 100 * @param[in] numRetries - number of request retries 101 * @param[in] responseTimeOut - time to wait between each retry 102 */ 103 explicit Handler( 104 int fd, Event& event, Requester& requester, 105 seconds instanceIdExpiryInterval = 106 seconds(INSTANCE_ID_EXPIRATION_INTERVAL), 107 uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES), 108 milliseconds responseTimeOut = milliseconds(RESPONSE_TIME_OUT)) : 109 fd(fd), 110 event(event), requester(requester), 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 = " << key.eid 138 << " INSTANCE_ID = " << key.instanceId 139 << " TYPE = " << key.type 140 << " COMMAND = " << 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 auto timer = std::make_unique<phosphor::Timer>( 171 event.get(), instanceIdExpiryCallBack); 172 173 auto rc = request->start(); 174 if (rc != PLDM_SUCCESS) 175 { 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<microseconds>(instanceIdExpiryInterval)); 184 } 185 catch (const std::runtime_error& e) 186 { 187 std::cerr << "Failed to start the instance ID expiry timer. RC = " 188 << e.what() << "\n"; 189 return PLDM_ERROR; 190 } 191 192 handlers.emplace(key, std::make_tuple(std::move(request), 193 std::move(responseHandler), 194 std::move(timer))); 195 return rc; 196 } 197 198 /** @brief Handle PLDM response message 199 * 200 * @param[in] eid - endpoint ID of the remote MCTP endpoint 201 * @param[in] instanceId - instance ID to match request and response 202 * @param[in] type - PLDM type 203 * @param[in] command - PLDM command 204 * @param[in] response - PLDM response message 205 * @param[in] respMsgLen - length of the response message 206 */ 207 void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 208 uint8_t command, const pldm_msg* response, 209 size_t respMsgLen) 210 { 211 RequestKey key{eid, instanceId, type, command}; 212 if (handlers.contains(key)) 213 { 214 auto& [request, responseHandler, timerInstance] = handlers[key]; 215 request->stop(); 216 auto rc = timerInstance->stop(); 217 if (rc) 218 { 219 std::cerr 220 << "Failed to stop the instance ID expiry timer. RC = " 221 << rc << "\n"; 222 } 223 responseHandler(eid, response, respMsgLen); 224 requester.markFree(key.eid, key.instanceId); 225 handlers.erase(key); 226 } 227 else 228 { 229 // Got a response for a PLDM request message not registered with the 230 // request handler, so freeing up the instance ID, this can be other 231 // OpenBMC applications relying on PLDM D-Bus apis like 232 // openpower-occ-control and softoff 233 requester.markFree(key.eid, key.instanceId); 234 } 235 } 236 237 private: 238 int fd; //!< file descriptor of MCTP communications socket 239 Event& event; //!< reference to PLDM daemon's main event loop 240 Requester& requester; //!< reference to Requester object 241 seconds instanceIdExpiryInterval; //!< Instance ID expiration interval 242 uint8_t numRetries; //!< number of request retries 243 milliseconds responseTimeOut; //!< time to wait between each retry 244 245 /** @brief Container for storing the details of the PLDM request message, 246 * handler for the corresponding PLDM response and the timer object 247 * for the Instance ID expiration 248 */ 249 using RequestValue = std::tuple<std::unique_ptr<RequestInterface>, 250 ResponseHandler, std::unique_ptr<Timer>>; 251 252 /** @brief Container for storing the PLDM request entries */ 253 std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers; 254 255 /** @brief Container to store information about the request entries to be 256 * removed after the instance ID timer expires 257 */ 258 std::unordered_map<RequestKey, std::unique_ptr<Defer>, RequestKeyHasher> 259 removeRequestContainer; 260 261 /** @brief Remove request entry for which the instance ID expired 262 * 263 * @param[in] key - key for the Request 264 */ 265 void removeRequestEntry(RequestKey key) 266 { 267 if (removeRequestContainer.contains(key)) 268 { 269 removeRequestContainer[key].reset(); 270 requester.markFree(key.eid, key.instanceId); 271 handlers.erase(key); 272 removeRequestContainer.erase(key); 273 } 274 } 275 }; 276 277 } // namespace requester 278 279 } // namespace pldm 280