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