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