blob: 2af4ade4c0b980be42571f4a5f6d6696733dc8ec [file] [log] [blame]
Tom Joseph74f27c72021-05-16 07:58:53 -07001#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
23namespace pldm
24{
25
26namespace 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 */
35struct 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 */
54struct 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
63using ResponseHandler = fu2::unique_function<void(
64 mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)>;
Tom Joseph74f27c72021-05-16 07:58:53 -070065
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 */
77template <class RequestInterface>
78class 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(
Brad Bishop5079ac42021-08-19 18:35:06 -040099 int fd, sdeventplus::Event& event, pldm::dbus_api::Requester& requester,
100 std::chrono::seconds instanceIdExpiryInterval =
101 std::chrono::seconds(INSTANCE_ID_EXPIRATION_INTERVAL),
Tom Joseph74f27c72021-05-16 07:58:53 -0700102 uint8_t numRetries = static_cast<uint8_t>(NUMBER_OF_REQUEST_RETRIES),
Brad Bishop5079ac42021-08-19 18:35:06 -0400103 std::chrono::milliseconds responseTimeOut =
104 std::chrono::milliseconds(RESPONSE_TIME_OUT)) :
Tom Joseph74f27c72021-05-16 07:58:53 -0700105 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."
Manojkiran Eda394fac62021-07-22 15:58:29 +0530133 << " EID = " << (unsigned)key.eid
134 << " INSTANCE_ID = " << (unsigned)key.instanceId
135 << " TYPE = " << (unsigned)key.type
136 << " COMMAND = " << (unsigned)key.command << "\n";
Tom Joseph74f27c72021-05-16 07:58:53 -0700137 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();
Tom Josepha5ed6582021-06-17 22:08:47 -0700170 if (rc)
Tom Joseph74f27c72021-05-16 07:58:53 -0700171 {
Tom Josepha5ed6582021-06-17 22:08:47 -0700172 requester.markFree(eid, instanceId);
Tom Joseph74f27c72021-05-16 07:58:53 -0700173 std::cerr << "Failure to send the PLDM request message"
174 << "\n";
175 return rc;
176 }
177
178 try
179 {
Brad Bishop5079ac42021-08-19 18:35:06 -0400180 timer->start(duration_cast<std::chrono::microseconds>(
181 instanceIdExpiryInterval));
Tom Joseph74f27c72021-05-16 07:58:53 -0700182 }
183 catch (const std::runtime_error& e)
184 {
Tom Josepha5ed6582021-06-17 22:08:47 -0700185 requester.markFree(eid, instanceId);
Tom Joseph74f27c72021-05-16 07:58:53 -0700186 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:
Brad Bishop5079ac42021-08-19 18:35:06 -0400237 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
Tom Joseph74f27c72021-05-16 07:58:53 -0700245
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 */
Brad Bishop5079ac42021-08-19 18:35:06 -0400250 using RequestValue =
251 std::tuple<std::unique_ptr<RequestInterface>, ResponseHandler,
252 std::unique_ptr<phosphor::Timer>>;
Tom Joseph74f27c72021-05-16 07:58:53 -0700253
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 */
Brad Bishop5079ac42021-08-19 18:35:06 -0400260 std::unordered_map<RequestKey, std::unique_ptr<sdeventplus::source::Defer>,
261 RequestKeyHasher>
Tom Joseph74f27c72021-05-16 07:58:53 -0700262 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