1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3
4 #include "pldm_interface.hpp"
5
6 #include <libpldm/base.h>
7 #include <libpldm/oem/ibm/file_io.h>
8 #include <libpldm/transport.h>
9 #include <libpldm/transport/mctp-demux.h>
10 #include <poll.h>
11 #include <unistd.h>
12
13 #include <phosphor-logging/lg2.hpp>
14
15 #include <fstream>
16
17 namespace openpower::pels
18 {
19
20 using namespace sdeventplus;
21 using namespace sdeventplus::source;
22 using TerminusID = uint8_t;
23
24 constexpr auto eidPath = "/usr/share/pldm/host_eid";
25 constexpr mctp_eid_t defaultEIDValue = 9;
26 constexpr TerminusID tid = defaultEIDValue;
27
28 constexpr uint16_t pelFileType = 0;
29
~PLDMInterface()30 PLDMInterface::~PLDMInterface()
31 {
32 freeIID();
33 pldm_instance_db_destroy(_pldm_idb);
34 closeFD();
35 }
36
closeFD()37 void PLDMInterface::closeFD()
38 {
39 pldm_transport_mctp_demux_destroy(mctpDemux);
40 mctpDemux = nullptr;
41 _fd = -1;
42 pldmTransport = nullptr;
43 }
44
readEID()45 void PLDMInterface::readEID()
46 {
47 _eid = defaultEIDValue;
48
49 std::ifstream eidFile{eidPath};
50 if (!eidFile.good())
51 {
52 lg2::error("Could not open host EID file");
53 }
54 else
55 {
56 std::string eid;
57 eidFile >> eid;
58 if (!eid.empty())
59 {
60 _eid = atoi(eid.c_str());
61 }
62 else
63 {
64 lg2::error("EID file was empty");
65 }
66 }
67 }
68
open()69 void PLDMInterface::open()
70 {
71 if (pldmTransport)
72 {
73 lg2::error("open: pldmTransport already setup!");
74 throw std::runtime_error{"open failed"};
75 }
76
77 _fd = openMctpDemuxTransport();
78 if (_fd < 0)
79 {
80 auto e = errno;
81 lg2::error("Transport open failed. errno = {ERRNO}, rc = {RC}", "ERRNO",
82 e, "RC", _fd);
83 throw std::runtime_error{"Transport open failed"};
84 }
85 }
86
openMctpDemuxTransport()87 int PLDMInterface::openMctpDemuxTransport()
88 {
89 int rc = pldm_transport_mctp_demux_init(&mctpDemux);
90 if (rc)
91 {
92 lg2::error(
93 "openMctpDemuxTransport: Failed to init MCTP demux transport. rc = {RC}",
94 "RC", rc);
95 return rc;
96 }
97
98 rc = pldm_transport_mctp_demux_map_tid(mctpDemux, tid, tid);
99 if (rc)
100 {
101 lg2::error(
102 "openMctpDemuxTransport: Failed to setup tid to eid mapping. rc = {RC}",
103 "RC", rc);
104 cleanupCmd();
105 return rc;
106 }
107 pldmTransport = pldm_transport_mctp_demux_core(mctpDemux);
108
109 struct pollfd pollfd;
110 rc = pldm_transport_mctp_demux_init_pollfd(pldmTransport, &pollfd);
111 if (rc)
112 {
113 lg2::error("openMctpDemuxTransport: Failed to get pollfd. rc = {RC}",
114 "RC", rc);
115 cleanupCmd();
116 return rc;
117 }
118 return pollfd.fd;
119 }
120
startCommand()121 void PLDMInterface::startCommand()
122 {
123 try
124 {
125 closeFD();
126
127 open();
128
129 registerReceiveCallback();
130
131 doSend();
132
133 _receiveTimer.restartOnce(_receiveTimeout);
134 }
135 catch (const std::exception& e)
136 {
137 lg2::error("startCommand exception: {ERROR}", "ERROR", e);
138
139 cleanupCmd();
140
141 throw;
142 }
143 }
144
allocIID()145 void PLDMInterface::allocIID()
146 {
147 if (_instanceID)
148 {
149 return;
150 }
151
152 pldm_instance_id_t iid = 0;
153 auto rc = pldm_instance_id_alloc(_pldm_idb, _eid, &iid);
154
155 if (rc == -EAGAIN)
156 {
157 throw std::runtime_error("No free instance ids");
158 }
159 else if (rc)
160 {
161 throw std::system_category().default_error_condition(rc);
162 }
163
164 _instanceID = iid;
165 }
166
freeIID()167 void PLDMInterface::freeIID()
168 {
169 if (!_instanceID)
170 {
171 return;
172 }
173
174 auto rc = pldm_instance_id_free(_pldm_idb, _eid, *_instanceID);
175
176 if (rc == -EINVAL)
177 {
178 throw std::runtime_error(
179 "Instance ID " + std::to_string(*_instanceID) + " for TID " +
180 std::to_string(_eid) + " was not previously allocated");
181 }
182 else if (rc)
183 {
184 throw std::system_category().default_error_condition(rc);
185 }
186
187 _instanceID = std::nullopt;
188 }
189
sendNewLogCmd(uint32_t id,uint32_t size)190 CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size)
191 {
192 _pelID = id;
193 _pelSize = size;
194 _inProgress = true;
195
196 try
197 {
198 // Allocate the instance ID, as needed.
199 if (!_instanceID)
200 {
201 allocIID();
202 }
203 startCommand();
204 }
205 catch (const std::exception& e)
206 {
207 _inProgress = false;
208 return CmdStatus::failure;
209 }
210
211 return CmdStatus::success;
212 }
213
registerReceiveCallback()214 void PLDMInterface::registerReceiveCallback()
215 {
216 _source = std::make_unique<IO>(
217 _event, _fd, EPOLLIN,
218 std::bind(std::mem_fn(&PLDMInterface::receive), this,
219 std::placeholders::_1, std::placeholders::_2,
220 std::placeholders::_3, pldmTransport));
221 }
222
doSend()223 void PLDMInterface::doSend()
224 {
225 if (!_instanceID.has_value())
226 {
227 throw std::runtime_error{"No instance ID in PLDMInterface::doSend"};
228 }
229
230 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
231 sizeof(_pelID) + sizeof(uint64_t)>
232 requestMsg;
233
234 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
235
236 auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize,
237 request);
238 if (rc != PLDM_SUCCESS)
239 {
240 lg2::error("encode_new_file_req failed, rc = {RC}", "RC", rc);
241 throw std::runtime_error{"encode_new_file_req failed"};
242 }
243 pldm_tid_t pldmTID = static_cast<pldm_tid_t>(_eid);
244 rc = pldm_transport_send_msg(pldmTransport, pldmTID, requestMsg.data(),
245 requestMsg.size());
246 if (rc < 0)
247 {
248 auto e = errno;
249 lg2::error("pldm_transport_send_msg failed, rc = {RC}, errno = {ERRNO}",
250 "RC", rc, "ERRNO", e);
251 throw std::runtime_error{"pldm_transport_send_msg failed"};
252 }
253
254 memcpy(&_requestHeader, request, sizeof(pldm_msg_hdr));
255 }
256
257 struct Response
258 {
Responseopenpower::pels::Response259 Response(void* r) : response(r) {}
~Responseopenpower::pels::Response260 ~Response()
261 {
262 if (response != nullptr)
263 {
264 free(response);
265 }
266 }
267 void* response = nullptr;
268 };
269
receive(IO &,int,uint32_t revents,pldm_transport * transport)270 void PLDMInterface::receive(IO& /*io*/, int /*fd*/, uint32_t revents,
271 pldm_transport* transport)
272
273 {
274 if (!(revents & EPOLLIN))
275 {
276 return;
277 }
278
279 void* responseMsg = nullptr;
280 size_t responseSize = 0;
281 ResponseStatus status = ResponseStatus::success;
282
283 pldm_tid_t pldmTID;
284 auto rc = pldm_transport_recv_msg(transport, &pldmTID, &responseMsg,
285 &responseSize);
286 struct pldm_msg_hdr* hdr = (struct pldm_msg_hdr*)responseMsg;
287 Response r{responseMsg};
288
289 if (rc == PLDM_REQUESTER_SUCCESS)
290 {
291 if ((pldmTID != _eid) ||
292 !pldm_msg_hdr_correlate_response(&_requestHeader, hdr))
293 {
294 // We got a response to someone else's message. Ignore it.
295 return;
296 }
297 }
298 else
299 {
300 if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
301 {
302 // Due to the MCTP loopback, we may get notified of the message
303 // we just sent.
304 return;
305 }
306
307 auto e = errno;
308 lg2::error("pldm_transport_recv_msg failed, rc = {RC}, errno = {ERRNO}",
309 "RC",
310 static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
311 "ERRNO", e);
312 status = ResponseStatus::failure;
313 }
314 if (hdr && (hdr->request || hdr->datagram))
315 {
316 return;
317 }
318
319 cleanupCmd();
320
321 // Can't use this instance ID anymore.
322 freeIID();
323
324 if (status == ResponseStatus::success)
325 {
326 uint8_t completionCode = 0;
327 auto response = reinterpret_cast<pldm_msg*>(responseMsg);
328
329 auto decodeRC =
330 decode_new_file_resp(response, responseSize, &completionCode);
331 if (decodeRC < 0)
332 {
333 lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
334 decodeRC);
335 status = ResponseStatus::failure;
336 }
337 else
338 {
339 if (completionCode != PLDM_SUCCESS)
340 {
341 lg2::error("Bad PLDM completion code {CODE}", "CODE",
342 completionCode);
343 status = ResponseStatus::failure;
344 }
345 }
346 }
347
348 callResponseFunc(status);
349 }
350
receiveTimerExpired()351 void PLDMInterface::receiveTimerExpired()
352 {
353 lg2::error("Timed out waiting for PLDM response");
354
355 // Cleanup, but keep the instance ID because the host didn't
356 // respond so we can still use it.
357 cleanupCmd();
358
359 callResponseFunc(ResponseStatus::failure);
360 }
361
cancelCmd()362 void PLDMInterface::cancelCmd()
363 {
364 freeIID();
365 cleanupCmd();
366 }
367
cleanupCmd()368 void PLDMInterface::cleanupCmd()
369 {
370 _inProgress = false;
371 _source.reset();
372
373 if (_receiveTimer.isEnabled())
374 {
375 _receiveTimer.setEnabled(false);
376 }
377
378 closeFD();
379 }
380
381 } // namespace openpower::pels
382