| /** | 
 |  * 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 <systemd/sd-bus.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <fstream> | 
 | #include <phosphor-logging/log.hpp> | 
 |  | 
 | namespace openpower::pels | 
 | { | 
 |  | 
 | namespace service | 
 | { | 
 | constexpr auto pldm = "xyz.openbmc_project.PLDM"; | 
 | } | 
 |  | 
 | namespace object_path | 
 | { | 
 | constexpr auto pldm = "/xyz/openbmc_project/pldm"; | 
 | } | 
 |  | 
 | namespace interface | 
 | { | 
 | constexpr auto pldm_requester = "xyz.openbmc_project.PLDM.Requester"; | 
 | } | 
 |  | 
 | 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() | 
 | { | 
 |     sd_bus_unref(_bus); | 
 |     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{}; | 
 |     } | 
 | } | 
 |  | 
 | void PLDMInterface::instanceIDCallback(sd_bus_message* msg) | 
 | { | 
 |     if (!_inProgress) | 
 |     { | 
 |         // A cancelCmd was run, just return | 
 |         log<level::INFO>( | 
 |             "A command was canceled while waiting for the instance ID"); | 
 |         return; | 
 |     } | 
 |  | 
 |     bool failed = false; | 
 |  | 
 |     auto rc = sd_bus_message_get_errno(msg); | 
 |     if (rc) | 
 |     { | 
 |         log<level::ERR>("GetInstanceId D-Bus method failed", | 
 |                         entry("ERRNO=%d", rc)); | 
 |         failed = true; | 
 |     } | 
 |     else | 
 |     { | 
 |         uint8_t id; | 
 |         rc = sd_bus_message_read_basic(msg, 'y', &id); | 
 |         if (rc < 0) | 
 |         { | 
 |             log<level::ERR>("Could not read instance ID out of message", | 
 |                             entry("ERROR=%d", rc)); | 
 |             failed = true; | 
 |         } | 
 |         else | 
 |         { | 
 |             _instanceID = id; | 
 |         } | 
 |     } | 
 |  | 
 |     if (failed) | 
 |     { | 
 |         _inProgress = false; | 
 |         callResponseFunc(ResponseStatus::failure); | 
 |     } | 
 |     else | 
 |     { | 
 |         startCommand(); | 
 |     } | 
 | } | 
 |  | 
 | int iidCallback(sd_bus_message* msg, void* data, sd_bus_error* /*err*/) | 
 | { | 
 |     auto* interface = static_cast<PLDMInterface*>(data); | 
 |     interface->instanceIDCallback(msg); | 
 |     return 0; | 
 | } | 
 |  | 
 | void PLDMInterface::startCommand() | 
 | { | 
 |     try | 
 |     { | 
 |         closeFD(); | 
 |  | 
 |         open(); | 
 |  | 
 |         registerReceiveCallback(); | 
 |  | 
 |         doSend(); | 
 |  | 
 |         _receiveTimer.restartOnce(_receiveTimeout); | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         cleanupCmd(); | 
 |  | 
 |         callResponseFunc(ResponseStatus::failure); | 
 |     } | 
 | } | 
 |  | 
 | void PLDMInterface::startReadInstanceID() | 
 | { | 
 |     auto rc = sd_bus_call_method_async( | 
 |         _bus, NULL, service::pldm, object_path::pldm, interface::pldm_requester, | 
 |         "GetInstanceId", iidCallback, this, "y", _eid); | 
 |  | 
 |     if (rc < 0) | 
 |     { | 
 |         log<level::ERR>("Error calling sd_bus_call_method_async", | 
 |                         entry("RC=%d", rc), entry("MSG=%s", strerror(-rc))); | 
 |         throw std::exception{}; | 
 |     } | 
 | } | 
 |  | 
 | CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size) | 
 | { | 
 |     _pelID = id; | 
 |     _pelSize = size; | 
 |     _inProgress = true; | 
 |  | 
 |     try | 
 |     { | 
 |         // Kick off the async call to get the instance ID if | 
 |         // necessary, otherwise start the command itself. | 
 |         if (!_instanceID) | 
 |         { | 
 |             startReadInstanceID(); | 
 |         } | 
 |         else | 
 |         { | 
 |             startCommand(); | 
 |         } | 
 |     } | 
 |     catch (const std::exception& e) | 
 |     { | 
 |         _inProgress = false; | 
 |         return CmdStatus::failure; | 
 |     } | 
 |  | 
 |     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::doSend() | 
 | { | 
 |     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) + | 
 |                             sizeof(_pelID) + sizeof(uint64_t)> | 
 |         requestMsg; | 
 |  | 
 |     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); | 
 |  | 
 |     auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize, | 
 |                                   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; | 
 |     } | 
 |  | 
 |     cleanupCmd(); | 
 |  | 
 |     // Can't use this instance ID anymore. | 
 |     _instanceID = std::nullopt; | 
 |  | 
 |     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; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     callResponseFunc(status); | 
 |  | 
 |     if (responseMsg) | 
 |     { | 
 |         free(responseMsg); | 
 |     } | 
 | } | 
 |  | 
 | void PLDMInterface::receiveTimerExpired() | 
 | { | 
 |     log<level::ERR>("Timed out waiting for PLDM response"); | 
 |  | 
 |     // Cleanup, but keep the instance ID because the host didn't | 
 |     // respond so we can still use it. | 
 |     cleanupCmd(); | 
 |  | 
 |     callResponseFunc(ResponseStatus::failure); | 
 | } | 
 |  | 
 | void PLDMInterface::cancelCmd() | 
 | { | 
 |     _instanceID = std::nullopt; | 
 |     cleanupCmd(); | 
 | } | 
 |  | 
 | void PLDMInterface::cleanupCmd() | 
 | { | 
 |     _inProgress = false; | 
 |     _source.reset(); | 
 |  | 
 |     if (_receiveTimer.isEnabled()) | 
 |     { | 
 |         _receiveTimer.setEnabled(false); | 
 |     } | 
 |  | 
 |     closeFD(); | 
 | } | 
 |  | 
 | } // namespace openpower::pels |