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 <unistd.h>
21 
22 #include <phosphor-logging/lg2.hpp>
23 
24 #include <fstream>
25 
26 namespace openpower::pels
27 {
28 
29 using namespace sdeventplus;
30 using namespace sdeventplus::source;
31 
32 constexpr auto eidPath = "/usr/share/pldm/host_eid";
33 constexpr mctp_eid_t defaultEIDValue = 9;
34 
35 constexpr uint16_t pelFileType = 0;
36 
~PLDMInterface()37 PLDMInterface::~PLDMInterface()
38 {
39     freeIID();
40     pldm_instance_db_destroy(_pldm_idb);
41     closeFD();
42 }
43 
closeFD()44 void PLDMInterface::closeFD()
45 {
46     if (_fd >= 0)
47     {
48         close(_fd);
49         _fd = -1;
50     }
51 }
52 
readEID()53 void PLDMInterface::readEID()
54 {
55     _eid = defaultEIDValue;
56 
57     std::ifstream eidFile{eidPath};
58     if (!eidFile.good())
59     {
60         lg2::error("Could not open host EID file");
61     }
62     else
63     {
64         std::string eid;
65         eidFile >> eid;
66         if (!eid.empty())
67         {
68             _eid = atoi(eid.c_str());
69         }
70         else
71         {
72             lg2::error("EID file was empty");
73         }
74     }
75 }
76 
open()77 void PLDMInterface::open()
78 {
79     _fd = pldm_open();
80     if (_fd < 0)
81     {
82         auto e = errno;
83         lg2::error("pldm_open failed.  errno = {ERRNO}, rc = {RC}", "ERRNO", e,
84                    "RC", _fd);
85         throw std::runtime_error{"pldm_open failed"};
86     }
87 }
88 
startCommand()89 void PLDMInterface::startCommand()
90 {
91     try
92     {
93         closeFD();
94 
95         open();
96 
97         registerReceiveCallback();
98 
99         doSend();
100 
101         _receiveTimer.restartOnce(_receiveTimeout);
102     }
103     catch (const std::exception& e)
104     {
105         lg2::error("startCommand exception: {ERROR}", "ERROR", e);
106 
107         cleanupCmd();
108 
109         throw;
110     }
111 }
112 
allocIID()113 void PLDMInterface::allocIID()
114 {
115     if (_instanceID)
116     {
117         return;
118     }
119 
120     pldm_instance_id_t iid = 0;
121     auto rc = pldm_instance_id_alloc(_pldm_idb, _eid, &iid);
122 
123     if (rc == -EAGAIN)
124     {
125         throw std::runtime_error("No free instance ids");
126     }
127     else if (rc)
128     {
129         throw std::system_category().default_error_condition(rc);
130     }
131 
132     _instanceID = iid;
133 }
134 
freeIID()135 void PLDMInterface::freeIID()
136 {
137     if (!_instanceID)
138     {
139         return;
140     }
141 
142     auto rc = pldm_instance_id_free(_pldm_idb, _eid, *_instanceID);
143 
144     if (rc == -EINVAL)
145     {
146         throw std::runtime_error("Instance ID " + std::to_string(*_instanceID) +
147                                  " for TID " + std::to_string(_eid) +
148                                  " was not previously allocated");
149     }
150     else if (rc)
151     {
152         throw std::system_category().default_error_condition(rc);
153     }
154 
155     _instanceID = std::nullopt;
156 }
157 
sendNewLogCmd(uint32_t id,uint32_t size)158 CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size)
159 {
160     _pelID = id;
161     _pelSize = size;
162     _inProgress = true;
163 
164     try
165     {
166         // Allocate the instance ID, as needed.
167         if (!_instanceID)
168         {
169             allocIID();
170         }
171         startCommand();
172     }
173     catch (const std::exception& e)
174     {
175         _inProgress = false;
176         return CmdStatus::failure;
177     }
178 
179     return CmdStatus::success;
180 }
181 
registerReceiveCallback()182 void PLDMInterface::registerReceiveCallback()
183 {
184     _source = std::make_unique<IO>(
185         _event, _fd, EPOLLIN,
186         std::bind(std::mem_fn(&PLDMInterface::receive), this,
187                   std::placeholders::_1, std::placeholders::_2,
188                   std::placeholders::_3));
189 }
190 
doSend()191 void PLDMInterface::doSend()
192 {
193     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
194                             sizeof(_pelID) + sizeof(uint64_t)>
195         requestMsg;
196 
197     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
198 
199     auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize,
200                                   request);
201     if (rc != PLDM_SUCCESS)
202     {
203         lg2::error("encode_new_file_req failed, rc = {RC}", "RC", rc);
204         throw std::runtime_error{"encode_new_file_req failed"};
205     }
206 
207     rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size());
208     if (rc < 0)
209     {
210         auto e = errno;
211         lg2::error("pldm_send failed, rc = {RC}, errno = {ERRNO}", "RC", rc,
212                    "ERRNO", e);
213         throw std::runtime_error{"pldm_send failed"};
214     }
215 }
216 
receive(IO &,int fd,uint32_t revents)217 void PLDMInterface::receive(IO& /*io*/, int fd, uint32_t revents)
218 {
219     if (!(revents & EPOLLIN))
220     {
221         return;
222     }
223 
224     uint8_t* responseMsg = nullptr;
225     size_t responseSize = 0;
226     ResponseStatus status = ResponseStatus::success;
227 
228     auto rc = pldm_recv(_eid, fd, *_instanceID, &responseMsg, &responseSize);
229     if (rc < 0)
230     {
231         if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH)
232         {
233             // We got a response to someone else's message. Ignore it.
234             return;
235         }
236         else if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
237         {
238             // Due to the MCTP loopback, we may get notified of the message
239             // we just sent.
240             return;
241         }
242 
243         auto e = errno;
244         lg2::error("pldm_recv failed, rc = {RC}, errno = {ERRNO}", "RC",
245                    static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
246                    "ERRNO", e);
247         status = ResponseStatus::failure;
248 
249         responseMsg = nullptr;
250     }
251 
252     cleanupCmd();
253 
254     // Can't use this instance ID anymore.
255     freeIID();
256 
257     if (status == ResponseStatus::success)
258     {
259         uint8_t completionCode = 0;
260         auto response = reinterpret_cast<pldm_msg*>(responseMsg);
261 
262         auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES,
263                                              &completionCode);
264         if (decodeRC < 0)
265         {
266             lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
267                        decodeRC);
268             status = ResponseStatus::failure;
269         }
270         else
271         {
272             if (completionCode != PLDM_SUCCESS)
273             {
274                 lg2::error("Bad PLDM completion code {CODE}", "CODE",
275                            completionCode);
276                 status = ResponseStatus::failure;
277             }
278         }
279     }
280 
281     callResponseFunc(status);
282 
283     if (responseMsg)
284     {
285         free(responseMsg);
286     }
287 }
288 
receiveTimerExpired()289 void PLDMInterface::receiveTimerExpired()
290 {
291     lg2::error("Timed out waiting for PLDM response");
292 
293     // Cleanup, but keep the instance ID because the host didn't
294     // respond so we can still use it.
295     cleanupCmd();
296 
297     callResponseFunc(ResponseStatus::failure);
298 }
299 
cancelCmd()300 void PLDMInterface::cancelCmd()
301 {
302     freeIID();
303     cleanupCmd();
304 }
305 
cleanupCmd()306 void PLDMInterface::cleanupCmd()
307 {
308     _inProgress = false;
309     _source.reset();
310 
311     if (_receiveTimer.isEnabled())
312     {
313         _receiveTimer.setEnabled(false);
314     }
315 
316     closeFD();
317 }
318 
319 } // namespace openpower::pels
320