xref: /openbmc/phosphor-logging/extensions/openpower-pels/pldm_interface.cpp (revision 40fb54935ce7367636a7156039396ee91cc4d5e2)
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