| Tom Joseph | 74f27c7 | 2021-05-16 07:58:53 -0700 | [diff] [blame^] | 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 |