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 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
226 sizeof(_pelID) + sizeof(uint64_t)>
227 requestMsg;
228
229 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
230
231 auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize,
232 request);
233 if (rc != PLDM_SUCCESS)
234 {
235 lg2::error("encode_new_file_req failed, rc = {RC}", "RC", rc);
236 throw std::runtime_error{"encode_new_file_req failed"};
237 }
238 pldm_tid_t pldmTID = static_cast<pldm_tid_t>(_eid);
239 rc = pldm_transport_send_msg(pldmTransport, pldmTID, requestMsg.data(),
240 requestMsg.size());
241 if (rc < 0)
242 {
243 auto e = errno;
244 lg2::error("pldm_transport_send_msg failed, rc = {RC}, errno = {ERRNO}",
245 "RC", rc, "ERRNO", e);
246 throw std::runtime_error{"pldm_transport_send_msg failed"};
247 }
248
249 memcpy(&_requestHeader, request, sizeof(pldm_msg_hdr));
250 }
251
252 struct Response
253 {
Responseopenpower::pels::Response254 Response(void* r) : response(r) {}
~Responseopenpower::pels::Response255 ~Response()
256 {
257 if (response != nullptr)
258 {
259 free(response);
260 }
261 }
262 void* response = nullptr;
263 };
264
receive(IO &,int,uint32_t revents,pldm_transport * transport)265 void PLDMInterface::receive(IO& /*io*/, int /*fd*/, uint32_t revents,
266 pldm_transport* transport)
267
268 {
269 if (!(revents & EPOLLIN))
270 {
271 return;
272 }
273
274 void* responseMsg = nullptr;
275 size_t responseSize = 0;
276 ResponseStatus status = ResponseStatus::success;
277
278 pldm_tid_t pldmTID;
279 auto rc = pldm_transport_recv_msg(transport, &pldmTID, &responseMsg,
280 &responseSize);
281 struct pldm_msg_hdr* hdr = (struct pldm_msg_hdr*)responseMsg;
282 Response r{responseMsg};
283
284 if (rc == PLDM_REQUESTER_SUCCESS)
285 {
286 if ((pldmTID != _eid) ||
287 !pldm_msg_hdr_correlate_response(&_requestHeader, hdr))
288 {
289 // We got a response to someone else's message. Ignore it.
290 return;
291 }
292 }
293 else
294 {
295 if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
296 {
297 // Due to the MCTP loopback, we may get notified of the message
298 // we just sent.
299 return;
300 }
301
302 auto e = errno;
303 lg2::error("pldm_transport_recv_msg failed, rc = {RC}, errno = {ERRNO}",
304 "RC",
305 static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
306 "ERRNO", e);
307 status = ResponseStatus::failure;
308 }
309 if (hdr && (hdr->request || hdr->datagram))
310 {
311 return;
312 }
313
314 cleanupCmd();
315
316 // Can't use this instance ID anymore.
317 freeIID();
318
319 if (status == ResponseStatus::success)
320 {
321 uint8_t completionCode = 0;
322 auto response = reinterpret_cast<pldm_msg*>(responseMsg);
323
324 auto decodeRC =
325 decode_new_file_resp(response, responseSize, &completionCode);
326 if (decodeRC < 0)
327 {
328 lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
329 decodeRC);
330 status = ResponseStatus::failure;
331 }
332 else
333 {
334 if (completionCode != PLDM_SUCCESS)
335 {
336 lg2::error("Bad PLDM completion code {CODE}", "CODE",
337 completionCode);
338 status = ResponseStatus::failure;
339 }
340 }
341 }
342
343 callResponseFunc(status);
344 }
345
receiveTimerExpired()346 void PLDMInterface::receiveTimerExpired()
347 {
348 lg2::error("Timed out waiting for PLDM response");
349
350 // Cleanup, but keep the instance ID because the host didn't
351 // respond so we can still use it.
352 cleanupCmd();
353
354 callResponseFunc(ResponseStatus::failure);
355 }
356
cancelCmd()357 void PLDMInterface::cancelCmd()
358 {
359 freeIID();
360 cleanupCmd();
361 }
362
cleanupCmd()363 void PLDMInterface::cleanupCmd()
364 {
365 _inProgress = false;
366 _source.reset();
367
368 if (_receiveTimer.isEnabled())
369 {
370 _receiveTimer.setEnabled(false);
371 }
372
373 closeFD();
374 }
375
376 } // namespace openpower::pels
377