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