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] instanceIdExpiryInterval - instance ID expiration interval 95 * @param[in] numRetries - number of request retries 96 * @param[in] responseTimeOut - time to wait between each retry 97 */ 98 explicit Handler( 99 int fd, sdeventplus::Event& event, pldm::dbus_api::Requester& requester, 100 std::chrono::seconds instanceIdExpiryInterval = 101 std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL), 102 uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES), 103 std::chrono::milliseconds responseTimeOut = 104 std::chrono::milliseconds(RESPONSE_TIME_OUT)) : 105 fd(fd), 106 event(event), requester(requester), 107 instanceIdExpiryInterval(instanceIdExpiryInterval), 108 numRetries(numRetries), responseTimeOut(responseTimeOut) 109 {} 110 111 /** @brief Register a PLDM request message 112 * 113 * @param[in] eid - endpoint ID of the remote MCTP endpoint 114 * @param[in] instanceId - instance ID to match request and response 115 * @param[in] type - PLDM type 116 * @param[in] command - PLDM command 117 * @param[in] requestMsg - PLDM request message 118 * @param[in] responseHandler - Response handler for this request 119 * 120 * @return return PLDM_SUCCESS on success and PLDM_ERROR otherwise 121 */ 122 int registerRequest(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 123 uint8_t command, pldm::Request&& requestMsg, 124 ResponseHandler&& responseHandler) 125 { 126 RequestKey key{eid, instanceId, type, command}; 127 128 auto instanceIdExpiryCallBack = [key, this](void) { 129 if (this->handlers.contains(key)) 130 { 131 std::cerr << "Response not received for the request, instance " 132 "ID expired." 133 << " EID = " << (unsigned)key.eid 134 << " INSTANCE_ID = " << (unsigned)key.instanceId 135 << " TYPE = " << (unsigned)key.type 136 << " COMMAND = " << (unsigned)key.command << "\n"; 137 auto& [request, responseHandler, timerInstance] = 138 this->handlers[key]; 139 request->stop(); 140 auto rc = timerInstance->stop(); 141 if (rc) 142 { 143 std::cerr 144 << "Failed to stop the instance ID expiry timer. RC = " 145 << rc << "\n"; 146 } 147 // Call response handler with an empty response to indicate no 148 // response 149 responseHandler(key.eid, nullptr, 0); 150 this->removeRequestContainer.emplace( 151 key, std::make_unique<sdeventplus::source::Defer>( 152 event, std::bind(&Handler::removeRequestEntry, 153 this, key))); 154 } 155 else 156 { 157 // This condition is not possible, if a response is received 158 // before the instance ID expiry, then the response handler 159 // is executed and the entry will be removed. 160 assert(false); 161 } 162 }; 163 164 auto request = std::make_unique<RequestInterface>( 165 fd, eid, event, std::move(requestMsg), numRetries, responseTimeOut); 166 auto timer = std::make_unique<phosphor::Timer>( 167 event.get(), instanceIdExpiryCallBack); 168 169 auto rc = request->start(); 170 if (rc) 171 { 172 requester.markFree(eid, instanceId); 173 std::cerr << "Failure to send the PLDM request message" 174 << "\n"; 175 return rc; 176 } 177 178 try 179 { 180 timer->start(duration_cast<std::chrono::microseconds>( 181 instanceIdExpiryInterval)); 182 } 183 catch (const std::runtime_error& e) 184 { 185 requester.markFree(eid, instanceId); 186 std::cerr << "Failed to start the instance ID expiry timer. RC = " 187 << e.what() << "\n"; 188 return PLDM_ERROR; 189 } 190 191 handlers.emplace(key, std::make_tuple(std::move(request), 192 std::move(responseHandler), 193 std::move(timer))); 194 return rc; 195 } 196 197 /** @brief Handle PLDM response message 198 * 199 * @param[in] eid - endpoint ID of the remote MCTP endpoint 200 * @param[in] instanceId - instance ID to match request and response 201 * @param[in] type - PLDM type 202 * @param[in] command - PLDM command 203 * @param[in] response - PLDM response message 204 * @param[in] respMsgLen - length of the response message 205 */ 206 void handleResponse(mctp_eid_t eid, uint8_t instanceId, uint8_t type, 207 uint8_t command, const pldm_msg* response, 208 size_t respMsgLen) 209 { 210 RequestKey key{eid, instanceId, type, command}; 211 if (handlers.contains(key)) 212 { 213 auto& [request, responseHandler, timerInstance] = handlers[key]; 214 request->stop(); 215 auto rc = timerInstance->stop(); 216 if (rc) 217 { 218 std::cerr 219 << "Failed to stop the instance ID expiry timer. RC = " 220 << rc << "\n"; 221 } 222 responseHandler(eid, response, respMsgLen); 223 requester.markFree(key.eid, key.instanceId); 224 handlers.erase(key); 225 } 226 else 227 { 228 // Got a response for a PLDM request message not registered with the 229 // request handler, so freeing up the instance ID, this can be other 230 // OpenBMC applications relying on PLDM D-Bus apis like 231 // openpower-occ-control and softoff 232 requester.markFree(key.eid, key.instanceId); 233 } 234 } 235 236 private: 237 int fd; //!< file descriptor of MCTP communications socket 238 sdeventplus::Event& event; //!< reference to PLDM daemon's main event loop 239 pldm::dbus_api::Requester& requester; //!< reference to Requester object 240 std::chrono::seconds 241 instanceIdExpiryInterval; //!< Instance ID expiration interval 242 uint8_t numRetries; //!< number of request retries 243 std::chrono::milliseconds 244 responseTimeOut; //!< time to wait between each retry 245 246 /** @brief Container for storing the details of the PLDM request message, 247 * handler for the corresponding PLDM response and the timer object 248 * for the Instance ID expiration 249 */ 250 using RequestValue = 251 std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler, 252 std::unique_ptr<phosphor::Timer>>; 253 254 /** @brief Container for storing the PLDM request entries */ 255 std::unordered_map<RequestKey, RequestValue, RequestKeyHasher> handlers; 256 257 /** @brief Container to store information about the request entries to be 258 * removed after the instance ID timer expires 259 */ 260 std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>, 261 RequestKeyHasher> 262 removeRequestContainer; 263 264 /** @brief Remove request entry for which the instance ID expired 265 * 266 * @param[in] key - key for the Request 267 */ 268 void removeRequestEntry(RequestKey key) 269 { 270 if (removeRequestContainer.contains(key)) 271 { 272 removeRequestContainer[key].reset(); 273 requester.markFree(key.eid, key.instanceId); 274 handlers.erase(key); 275 removeRequestContainer.erase(key); 276 } 277 } 278 }; 279 280 } // namespace requester 281 282 } // namespace pldm 283