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