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