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/lg2.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 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         lg2::error("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             lg2::error("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         lg2::error("pldm_open failed.  errno = {ERRNO}, rc = {RC}", "ERRNO", e,
99                    "RC", _fd);
100         throw std::runtime_error{"pldm_open failed"};
101     }
102 }
103 
104 void PLDMInterface::instanceIDCallback(sd_bus_message* msg)
105 {
106     if (!_inProgress)
107     {
108         lg2::info("A command was canceled while waiting for the instance ID");
109         return;
110     }
111 
112     bool failed = false;
113 
114     auto rc = sd_bus_message_get_errno(msg);
115     if (rc)
116     {
117         lg2::error("GetInstanceId D-Bus method failed, rc = {RC}", "RC", rc);
118         failed = true;
119     }
120     else
121     {
122         uint8_t id;
123         rc = sd_bus_message_read_basic(msg, 'y', &id);
124         if (rc < 0)
125         {
126             lg2::error("Could not read instance ID out of message, rc = {RC}",
127                        "RC", rc);
128             failed = true;
129         }
130         else
131         {
132             _instanceID = id;
133         }
134     }
135 
136     if (failed)
137     {
138         _inProgress = false;
139         callResponseFunc(ResponseStatus::failure);
140     }
141     else
142     {
143         try
144         {
145             startCommand();
146         }
147         catch (const std::exception& e)
148         {
149             callResponseFunc(ResponseStatus::failure);
150         }
151     }
152 }
153 
154 int iidCallback(sd_bus_message* msg, void* data, sd_bus_error* /*err*/)
155 {
156     auto* interface = static_cast<PLDMInterface*>(data);
157     interface->instanceIDCallback(msg);
158     return 0;
159 }
160 
161 void PLDMInterface::startCommand()
162 {
163     try
164     {
165         closeFD();
166 
167         open();
168 
169         registerReceiveCallback();
170 
171         doSend();
172 
173         _receiveTimer.restartOnce(_receiveTimeout);
174     }
175     catch (const std::exception& e)
176     {
177         lg2::error("startCommand exception: {ERROR}", "ERROR", e);
178 
179         cleanupCmd();
180 
181         throw;
182     }
183 }
184 
185 void PLDMInterface::startReadInstanceID()
186 {
187     auto rc = sd_bus_call_method_async(
188         _bus, NULL, service::pldm, object_path::pldm, interface::pldm_requester,
189         "GetInstanceId", iidCallback, this, "y", _eid);
190 
191     if (rc < 0)
192     {
193         lg2::error(
194             "Error calling sd_bus_call_method_async, rc = {RC}, msg = {MSG}",
195             "RC", rc, "MSG", strerror(-rc));
196         throw std::runtime_error{"sd_bus_call_method_async failed"};
197     }
198 }
199 
200 CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size)
201 {
202     _pelID = id;
203     _pelSize = size;
204     _inProgress = true;
205 
206     try
207     {
208         // Kick off the async call to get the instance ID if
209         // necessary, otherwise start the command itself.
210         if (!_instanceID)
211         {
212             startReadInstanceID();
213         }
214         else
215         {
216             startCommand();
217         }
218     }
219     catch (const std::exception& e)
220     {
221         _inProgress = false;
222         return CmdStatus::failure;
223     }
224 
225     return CmdStatus::success;
226 }
227 
228 void PLDMInterface::registerReceiveCallback()
229 {
230     _source = std::make_unique<IO>(
231         _event, _fd, EPOLLIN,
232         std::bind(std::mem_fn(&PLDMInterface::receive), this,
233                   std::placeholders::_1, std::placeholders::_2,
234                   std::placeholders::_3));
235 }
236 
237 void PLDMInterface::doSend()
238 {
239     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
240                             sizeof(_pelID) + sizeof(uint64_t)>
241         requestMsg;
242 
243     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
244 
245     auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize,
246                                   request);
247     if (rc != PLDM_SUCCESS)
248     {
249         lg2::error("encode_new_file_req failed, rc = {RC}", "RC", rc);
250         throw std::runtime_error{"encode_new_file_req failed"};
251     }
252 
253     rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size());
254     if (rc < 0)
255     {
256         auto e = errno;
257         lg2::error("pldm_send failed, rc = {RC}, errno = {ERRNO}", "RC", rc,
258                    "ERRNO", e);
259         throw std::runtime_error{"pldm_send failed"};
260     }
261 }
262 
263 void PLDMInterface::receive(IO& /*io*/, int fd, uint32_t revents)
264 {
265     if (!(revents & EPOLLIN))
266     {
267         return;
268     }
269 
270     uint8_t* responseMsg = nullptr;
271     size_t responseSize = 0;
272     ResponseStatus status = ResponseStatus::success;
273 
274     auto rc = pldm_recv(_eid, fd, *_instanceID, &responseMsg, &responseSize);
275     if (rc < 0)
276     {
277         if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH)
278         {
279             // We got a response to someone else's message. Ignore it.
280             return;
281         }
282         else if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
283         {
284             // Due to the MCTP loopback, we may get notified of the message
285             // we just sent.
286             return;
287         }
288 
289         auto e = errno;
290         lg2::error("pldm_recv failed, rc = {RC}, errno = {ERRNO}", "RC",
291                    static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
292                    "ERRNO", e);
293         status = ResponseStatus::failure;
294 
295         responseMsg = nullptr;
296     }
297 
298     cleanupCmd();
299 
300     // Can't use this instance ID anymore.
301     _instanceID = std::nullopt;
302 
303     if (status == ResponseStatus::success)
304     {
305         uint8_t completionCode = 0;
306         auto response = reinterpret_cast<pldm_msg*>(responseMsg);
307 
308         auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES,
309                                              &completionCode);
310         if (decodeRC < 0)
311         {
312             lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
313                        decodeRC);
314             status = ResponseStatus::failure;
315         }
316         else
317         {
318             if (completionCode != PLDM_SUCCESS)
319             {
320                 lg2::error("Bad PLDM completion code {CODE}", "CODE",
321                            completionCode);
322                 status = ResponseStatus::failure;
323             }
324         }
325     }
326 
327     callResponseFunc(status);
328 
329     if (responseMsg)
330     {
331         free(responseMsg);
332     }
333 }
334 
335 void PLDMInterface::receiveTimerExpired()
336 {
337     lg2::error("Timed out waiting for PLDM response");
338 
339     // Cleanup, but keep the instance ID because the host didn't
340     // respond so we can still use it.
341     cleanupCmd();
342 
343     callResponseFunc(ResponseStatus::failure);
344 }
345 
346 void PLDMInterface::cancelCmd()
347 {
348     _instanceID = std::nullopt;
349     cleanupCmd();
350 }
351 
352 void PLDMInterface::cleanupCmd()
353 {
354     _inProgress = false;
355     _source.reset();
356 
357     if (_receiveTimer.isEnabled())
358     {
359         _receiveTimer.setEnabled(false);
360     }
361 
362     closeFD();
363 }
364 
365 } // namespace openpower::pels
366