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 42 PLDMInterface::~PLDMInterface() 43 { 44 freeIID(); 45 pldm_instance_db_destroy(_pldm_idb); 46 closeFD(); 47 } 48 49 void PLDMInterface::closeFD() 50 { 51 pldm_transport_mctp_demux_destroy(mctpDemux); 52 mctpDemux = nullptr; 53 _fd = -1; 54 pldmTransport = nullptr; 55 } 56 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 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 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 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 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 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 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 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 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 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 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 356 void PLDMInterface::cancelCmd() 357 { 358 freeIID(); 359 cleanupCmd(); 360 } 361 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