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 memcpy(&_requestHeader, request, sizeof(pldm_msg_hdr));
262 }
263
receive(IO &,int,uint32_t revents,pldm_transport * transport)264 void PLDMInterface::receive(IO& /*io*/, int /*fd*/, uint32_t revents,
265 pldm_transport* transport)
266
267 {
268 if (!(revents & EPOLLIN))
269 {
270 return;
271 }
272
273 void* responseMsg = nullptr;
274 size_t responseSize = 0;
275 ResponseStatus status = ResponseStatus::success;
276
277 pldm_tid_t pldmTID;
278 auto rc = pldm_transport_recv_msg(transport, &pldmTID, &responseMsg,
279 &responseSize);
280 struct pldm_msg_hdr* hdr = (struct pldm_msg_hdr*)responseMsg;
281 if ((pldmTID != _eid) ||
282 !pldm_msg_hdr_correlate_response(&_requestHeader, hdr))
283 {
284 // We got a response to someone else's message. Ignore it.
285 return;
286 }
287 if (rc)
288 {
289 if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
290 {
291 // Due to the MCTP loopback, we may get notified of the message
292 // we just sent.
293 return;
294 }
295
296 auto e = errno;
297 lg2::error("pldm_transport_recv_msg failed, rc = {RC}, errno = {ERRNO}",
298 "RC",
299 static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
300 "ERRNO", e);
301 status = ResponseStatus::failure;
302
303 responseMsg = nullptr;
304 }
305 if (hdr && (hdr->request || hdr->datagram))
306 {
307 free(responseMsg);
308 return;
309 }
310
311 cleanupCmd();
312
313 // Can't use this instance ID anymore.
314 freeIID();
315
316 if (status == ResponseStatus::success)
317 {
318 uint8_t completionCode = 0;
319 auto response = reinterpret_cast<pldm_msg*>(responseMsg);
320
321 auto decodeRC =
322 decode_new_file_resp(response, responseSize, &completionCode);
323 if (decodeRC < 0)
324 {
325 lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
326 decodeRC);
327 status = ResponseStatus::failure;
328 }
329 else
330 {
331 if (completionCode != PLDM_SUCCESS)
332 {
333 lg2::error("Bad PLDM completion code {CODE}", "CODE",
334 completionCode);
335 status = ResponseStatus::failure;
336 }
337 }
338 }
339
340 callResponseFunc(status);
341
342 if (responseMsg)
343 {
344 free(responseMsg);
345 }
346 }
347
receiveTimerExpired()348 void PLDMInterface::receiveTimerExpired()
349 {
350 lg2::error("Timed out waiting for PLDM response");
351
352 // Cleanup, but keep the instance ID because the host didn't
353 // respond so we can still use it.
354 cleanupCmd();
355
356 callResponseFunc(ResponseStatus::failure);
357 }
358
cancelCmd()359 void PLDMInterface::cancelCmd()
360 {
361 freeIID();
362 cleanupCmd();
363 }
364
cleanupCmd()365 void PLDMInterface::cleanupCmd()
366 {
367 _inProgress = false;
368 _source.reset();
369
370 if (_receiveTimer.isEnabled())
371 {
372 _receiveTimer.setEnabled(false);
373 }
374
375 closeFD();
376 }
377
378 } // namespace openpower::pels
379