blob: 2082f696a8c8369cf06d3deb0e164fc5692018a8 [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)>;
65using namespace std::chrono;
66using namespace sdeventplus;
67using namespace sdeventplus::source;
68using namespace pldm::dbus_api;
69using 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 */
82template <class RequestInterface>
83class 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