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 
42 PLDMInterface::~PLDMInterface()
43 {
44     freeIID();
45     pldm_instance_db_destroy(_pldm_idb);
46     closeFD();
47 }
48 
49 void PLDMInterface::closeFD()
50 {
51     pldm_transport_mctp_demux_destroy(mctpDemux);
52     mctpDemux = nullptr;
53     _fd = -1;
54     pldmTransport = nullptr;
55 }
56 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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     struct pldm_msg_hdr* hdr = (struct pldm_msg_hdr*)responseMsg;
279     if (pldmTID != _eid)
280     {
281         // We got a response to someone else's message. Ignore it.
282         return;
283     }
284     if (rc)
285     {
286         if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
287         {
288             // Due to the MCTP loopback, we may get notified of the message
289             // we just sent.
290             return;
291         }
292 
293         auto e = errno;
294         lg2::error("pldm_transport_recv_msg failed, rc = {RC}, errno = {ERRNO}",
295                    "RC",
296                    static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
297                    "ERRNO", e);
298         status = ResponseStatus::failure;
299 
300         responseMsg = nullptr;
301     }
302     if (hdr && (hdr->request || hdr->datagram))
303     {
304         free(responseMsg);
305         return;
306     }
307 
308     cleanupCmd();
309 
310     // Can't use this instance ID anymore.
311     freeIID();
312 
313     if (status == ResponseStatus::success)
314     {
315         uint8_t completionCode = 0;
316         auto response = reinterpret_cast<pldm_msg*>(responseMsg);
317 
318         auto decodeRC =
319             decode_new_file_resp(response, responseSize, &completionCode);
320         if (decodeRC < 0)
321         {
322             lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
323                        decodeRC);
324             status = ResponseStatus::failure;
325         }
326         else
327         {
328             if (completionCode != PLDM_SUCCESS)
329             {
330                 lg2::error("Bad PLDM completion code {CODE}", "CODE",
331                            completionCode);
332                 status = ResponseStatus::failure;
333             }
334         }
335     }
336 
337     callResponseFunc(status);
338 
339     if (responseMsg)
340     {
341         free(responseMsg);
342     }
343 }
344 
345 void PLDMInterface::receiveTimerExpired()
346 {
347     lg2::error("Timed out waiting for PLDM response");
348 
349     // Cleanup, but keep the instance ID because the host didn't
350     // respond so we can still use it.
351     cleanupCmd();
352 
353     callResponseFunc(ResponseStatus::failure);
354 }
355 
356 void PLDMInterface::cancelCmd()
357 {
358     freeIID();
359     cleanupCmd();
360 }
361 
362 void PLDMInterface::cleanupCmd()
363 {
364     _inProgress = false;
365     _source.reset();
366 
367     if (_receiveTimer.isEnabled())
368     {
369         _receiveTimer.setEnabled(false);
370     }
371 
372     closeFD();
373 }
374 
375 } // namespace openpower::pels
376