|  | /** | 
|  | * Copyright © 2019 IBM Corporation | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  | #include "pldm_interface.hpp" | 
|  |  | 
|  | #include <libpldm/base.h> | 
|  | #include <libpldm/file_io.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include <fstream> | 
|  | #include <phosphor-logging/log.hpp> | 
|  |  | 
|  | namespace openpower::pels | 
|  | { | 
|  |  | 
|  | using namespace phosphor::logging; | 
|  | using namespace sdeventplus; | 
|  | using namespace sdeventplus::source; | 
|  |  | 
|  | constexpr auto eidPath = "/usr/share/pldm/host_eid"; | 
|  | constexpr mctp_eid_t defaultEIDValue = 9; | 
|  |  | 
|  | constexpr uint16_t pelFileType = 0; | 
|  |  | 
|  | PLDMInterface::~PLDMInterface() | 
|  | { | 
|  | closeFD(); | 
|  | } | 
|  |  | 
|  | void PLDMInterface::closeFD() | 
|  | { | 
|  | if (_fd >= 0) | 
|  | { | 
|  | close(_fd); | 
|  | _fd = -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | void PLDMInterface::readEID() | 
|  | { | 
|  | _eid = defaultEIDValue; | 
|  |  | 
|  | std::ifstream eidFile{eidPath}; | 
|  | if (!eidFile.good()) | 
|  | { | 
|  | log<level::ERR>("Could not open host EID file"); | 
|  | } | 
|  | else | 
|  | { | 
|  | std::string eid; | 
|  | eidFile >> eid; | 
|  | if (!eid.empty()) | 
|  | { | 
|  | _eid = atoi(eid.c_str()); | 
|  | } | 
|  | else | 
|  | { | 
|  | log<level::ERR>("EID file was empty"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void PLDMInterface::open() | 
|  | { | 
|  | _fd = pldm_open(); | 
|  | if (_fd < 0) | 
|  | { | 
|  | auto e = errno; | 
|  | log<level::ERR>("pldm_open failed", entry("ERRNO=%d", e), | 
|  | entry("RC=%d\n", _fd)); | 
|  | throw std::exception{}; | 
|  | } | 
|  | } | 
|  |  | 
|  | CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size) | 
|  | { | 
|  | try | 
|  | { | 
|  | closeFD(); | 
|  |  | 
|  | open(); | 
|  |  | 
|  | readInstanceID(); | 
|  |  | 
|  | registerReceiveCallback(); | 
|  |  | 
|  | doSend(id, size); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | closeFD(); | 
|  |  | 
|  | _inProgress = false; | 
|  | _source.reset(); | 
|  | return CmdStatus::failure; | 
|  | } | 
|  |  | 
|  | _inProgress = true; | 
|  | _receiveTimer.restartOnce(_receiveTimeout); | 
|  | return CmdStatus::success; | 
|  | } | 
|  |  | 
|  | void PLDMInterface::registerReceiveCallback() | 
|  | { | 
|  | _source = std::make_unique<IO>( | 
|  | _event, _fd, EPOLLIN, | 
|  | std::bind(std::mem_fn(&PLDMInterface::receive), this, | 
|  | std::placeholders::_1, std::placeholders::_2, | 
|  | std::placeholders::_3)); | 
|  | } | 
|  |  | 
|  | void PLDMInterface::readInstanceID() | 
|  | { | 
|  | try | 
|  | { | 
|  | _instanceID = _dataIface.getPLDMInstanceID(_eid); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | log<level::ERR>( | 
|  | "Failed to get instance ID from PLDM Requester D-Bus daemon", | 
|  | entry("ERROR=%s", e.what())); | 
|  | throw; | 
|  | } | 
|  | } | 
|  |  | 
|  | void PLDMInterface::doSend(uint32_t id, uint32_t size) | 
|  | { | 
|  | std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) + | 
|  | sizeof(id) + sizeof(size)> | 
|  | requestMsg; | 
|  |  | 
|  | auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); | 
|  |  | 
|  | auto rc = encode_new_file_req(_instanceID, pelFileType, id, size, request); | 
|  | if (rc != PLDM_SUCCESS) | 
|  | { | 
|  | log<level::ERR>("encode_new_file_req failed", entry("RC=%d", rc)); | 
|  | throw std::exception{}; | 
|  | } | 
|  |  | 
|  | rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size()); | 
|  | if (rc < 0) | 
|  | { | 
|  | auto e = errno; | 
|  | log<level::ERR>("pldm_send failed", entry("RC=%d", rc), | 
|  | entry("ERRNO=%d", e)); | 
|  |  | 
|  | throw std::exception{}; | 
|  | } | 
|  | } | 
|  |  | 
|  | void PLDMInterface::receive(IO& io, int fd, uint32_t revents) | 
|  | { | 
|  | if (!(revents & EPOLLIN)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint8_t* responseMsg = nullptr; | 
|  | size_t responseSize = 0; | 
|  | ResponseStatus status = ResponseStatus::success; | 
|  |  | 
|  | auto rc = pldm_recv(_eid, fd, _instanceID, &responseMsg, &responseSize); | 
|  | if (rc < 0) | 
|  | { | 
|  | if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH) | 
|  | { | 
|  | // We got a response to someone else's message. Ignore it. | 
|  | return; | 
|  | } | 
|  | else if (rc == PLDM_REQUESTER_NOT_RESP_MSG) | 
|  | { | 
|  | // Due to the MCTP loopback, we may get notified of the message | 
|  | // we just sent. | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto e = errno; | 
|  | log<level::ERR>("pldm_recv failed", entry("RC=%d", rc), | 
|  | entry("ERRNO=%d", e)); | 
|  | status = ResponseStatus::failure; | 
|  |  | 
|  | responseMsg = nullptr; | 
|  | } | 
|  |  | 
|  | _inProgress = false; | 
|  | _receiveTimer.setEnabled(false); | 
|  | closeFD(); | 
|  | _source.reset(); | 
|  |  | 
|  | if (status == ResponseStatus::success) | 
|  | { | 
|  | uint8_t completionCode = 0; | 
|  | auto response = reinterpret_cast<pldm_msg*>(responseMsg); | 
|  |  | 
|  | auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES, | 
|  | &completionCode); | 
|  | if (decodeRC < 0) | 
|  | { | 
|  | log<level::ERR>("decode_new_file_resp failed", | 
|  | entry("RC=%d", decodeRC)); | 
|  | status = ResponseStatus::failure; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (completionCode != PLDM_SUCCESS) | 
|  | { | 
|  | log<level::ERR>("Bad PLDM completion code", | 
|  | entry("COMPLETION_CODE=%d", completionCode)); | 
|  | status = ResponseStatus::failure; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (_responseFunc) | 
|  | { | 
|  | try | 
|  | { | 
|  | (*_responseFunc)(status); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | log<level::ERR>("PLDM response callback threw an exception", | 
|  | entry("ERROR=%s", e.what())); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (responseMsg) | 
|  | { | 
|  | free(responseMsg); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PLDMInterface::receiveTimerExpired() | 
|  | { | 
|  | log<level::ERR>("Timed out waiting for PLDM response"); | 
|  | cancelCmd(); | 
|  |  | 
|  | if (_responseFunc) | 
|  | { | 
|  | try | 
|  | { | 
|  | (*_responseFunc)(ResponseStatus::failure); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | log<level::ERR>("PLDM response callback threw an exception", | 
|  | entry("ERROR=%s", e.what())); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void PLDMInterface::cancelCmd() | 
|  | { | 
|  | _inProgress = false; | 
|  | _source.reset(); | 
|  |  | 
|  | if (_receiveTimer.isEnabled()) | 
|  | { | 
|  | _receiveTimer.setEnabled(false); | 
|  | } | 
|  |  | 
|  | closeFD(); | 
|  | } | 
|  |  | 
|  | } // namespace openpower::pels |