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 
receive(IO &,int,uint32_t revents,pldm_transport * transport)262 void PLDMInterface::receive(IO& /*io*/, int /*fd*/, uint32_t revents,
263                             pldm_transport* transport)
264 
265 {
266     if (!(revents & EPOLLIN))
267     {
268         return;
269     }
270 
271     void* responseMsg = nullptr;
272     size_t responseSize = 0;
273     ResponseStatus status = ResponseStatus::success;
274 
275     pldm_tid_t pldmTID;
276     auto rc = pldm_transport_recv_msg(transport, &pldmTID, &responseMsg,
277                                       &responseSize);
278     if (pldmTID != _eid)
279     {
280         // We got a response to someone else's message. Ignore it.
281         return;
282     }
283     if (rc)
284     {
285         if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
286         {
287             // Due to the MCTP loopback, we may get notified of the message
288             // we just sent.
289             return;
290         }
291 
292         auto e = errno;
293         lg2::error("pldm_transport_recv_msg failed, rc = {RC}, errno = {ERRNO}",
294                    "RC",
295                    static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
296                    "ERRNO", e);
297         status = ResponseStatus::failure;
298 
299         responseMsg = nullptr;
300     }
301 
302     cleanupCmd();
303 
304     // Can't use this instance ID anymore.
305     freeIID();
306 
307     if (status == ResponseStatus::success)
308     {
309         uint8_t completionCode = 0;
310         auto response = reinterpret_cast<pldm_msg*>(responseMsg);
311 
312         auto decodeRC =
313             decode_new_file_resp(response, responseSize, &completionCode);
314         if (decodeRC < 0)
315         {
316             lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
317                        decodeRC);
318             status = ResponseStatus::failure;
319         }
320         else
321         {
322             if (completionCode != PLDM_SUCCESS)
323             {
324                 lg2::error("Bad PLDM completion code {CODE}", "CODE",
325                            completionCode);
326                 status = ResponseStatus::failure;
327             }
328         }
329     }
330 
331     callResponseFunc(status);
332 
333     if (responseMsg)
334     {
335         free(responseMsg);
336     }
337 }
338 
receiveTimerExpired()339 void PLDMInterface::receiveTimerExpired()
340 {
341     lg2::error("Timed out waiting for PLDM response");
342 
343     // Cleanup, but keep the instance ID because the host didn't
344     // respond so we can still use it.
345     cleanupCmd();
346 
347     callResponseFunc(ResponseStatus::failure);
348 }
349 
cancelCmd()350 void PLDMInterface::cancelCmd()
351 {
352     freeIID();
353     cleanupCmd();
354 }
355 
cleanupCmd()356 void PLDMInterface::cleanupCmd()
357 {
358     _inProgress = false;
359     _source.reset();
360 
361     if (_receiveTimer.isEnabled())
362     {
363         _receiveTimer.setEnabled(false);
364     }
365 
366     closeFD();
367 }
368 
369 } // namespace openpower::pels
370