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
~PLDMInterface()42 PLDMInterface::~PLDMInterface()
43 {
44 freeIID();
45 pldm_instance_db_destroy(_pldm_idb);
46 closeFD();
47 }
48
closeFD()49 void PLDMInterface::closeFD()
50 {
51 pldm_transport_mctp_demux_destroy(mctpDemux);
52 mctpDemux = nullptr;
53 _fd = -1;
54 pldmTransport = nullptr;
55 }
56
readEID()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
open()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
openMctpDemuxTransport()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
startCommand()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
allocIID()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
freeIID()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
sendNewLogCmd(uint32_t id,uint32_t size)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
registerReceiveCallback()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
doSend()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
receive(IO &,int,uint32_t revents,pldm_transport * transport)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
receiveTimerExpired()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
cancelCmd()356 void PLDMInterface::cancelCmd()
357 {
358 freeIID();
359 cleanupCmd();
360 }
361
cleanupCmd()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