diff options
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/openpower-pels/openpower-pels.mk | 7 | ||||
-rw-r--r-- | extensions/openpower-pels/pldm_interface.cpp | 279 | ||||
-rw-r--r-- | extensions/openpower-pels/pldm_interface.hpp | 160 |
3 files changed, 446 insertions, 0 deletions
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk index 220668f..e510b42 100644 --- a/extensions/openpower-pels/openpower-pels.mk +++ b/extensions/openpower-pels/openpower-pels.mk @@ -2,12 +2,19 @@ phosphor_log_manager_SOURCES += \ extensions/openpower-pels/entry_points.cpp \ extensions/openpower-pels/host_notifier.cpp \ extensions/openpower-pels/manager.cpp \ + extensions/openpower-pels/pldm_interface.cpp \ extensions/openpower-pels/repository.cpp \ extensions/openpower-pels/user_data.cpp phosphor_log_manager_LDADD = \ libpel.la +phosphor_log_manager_LDFLAGS += \ + $(LIBPLDM_LIBS) + +phosphor_log_manager_CFLAGS = \ + $(LIBPLDM_CFLAGS) + noinst_LTLIBRARIES = libpel.la libpel_la_SOURCES = \ diff --git a/extensions/openpower-pels/pldm_interface.cpp b/extensions/openpower-pels/pldm_interface.cpp new file mode 100644 index 0000000..8e60315 --- /dev/null +++ b/extensions/openpower-pels/pldm_interface.cpp @@ -0,0 +1,279 @@ +/** + * 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 diff --git a/extensions/openpower-pels/pldm_interface.hpp b/extensions/openpower-pels/pldm_interface.hpp new file mode 100644 index 0000000..0d679a1 --- /dev/null +++ b/extensions/openpower-pels/pldm_interface.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include "host_interface.hpp" + +#include <libpldm/pldm.h> + +#include <chrono> +#include <memory> +#include <sdeventplus/clock.hpp> +#include <sdeventplus/source/io.hpp> +#include <sdeventplus/utility/timer.hpp> + +namespace openpower::pels +{ + +/** + * @class PLDMInterface + * + * This class handles sending the 'new file available' PLDM + * command to the host to notify it of a new PEL's ID and size. + * + * The command response is asynchronous. + */ +class PLDMInterface : public HostInterface +{ + public: + PLDMInterface() = delete; + PLDMInterface(const PLDMInterface&) = default; + PLDMInterface& operator=(const PLDMInterface&) = default; + PLDMInterface(PLDMInterface&&) = default; + PLDMInterface& operator=(PLDMInterface&&) = default; + + /** + * @brief Constructor + * + * @param[in] event - The sd_event object pointer + * @param[in] dataIface - The DataInterface object + */ + PLDMInterface(sd_event* event, DataInterfaceBase& dataIface) : + HostInterface(event, dataIface), + _receiveTimer( + event, + std::bind(std::mem_fn(&PLDMInterface::receiveTimerExpired), this)) + { + readEID(); + } + + /** + * @brief Destructor + */ + ~PLDMInterface(); + + /** + * @brief Kicks off the send of the 'new file available' command + * to send up the ID and size of the new PEL. + * + * @param[in] id - The PEL ID + * @param[in] size - The PEL size in bytes + * + * @return CmdStatus - the success/fail status of the send + */ + CmdStatus sendNewLogCmd(uint32_t id, uint32_t size) override; + + /** + * @brief Cancels waiting for a command response + */ + void cancelCmd() override; + + private: + /** + * @brief The asynchronous callback for getting the response + * of the 'new file available' command. + * + * Calls the response callback that is registered. + * + * @param[in] io - The event source object + * @param[in] fd - The FD used + * @param[in] revents - The event bits + */ + void receive(sdeventplus::source::IO& io, int fd, + uint32_t revents) override; + + /** + * @brief Function called when the receive timer expires. + * + * This is considered a failure and so will invoke the + * registered response callback function with a failure + * indication. + */ + void receiveTimerExpired(); + + /** + * @brief Configures the sdeventplus::source::IO object to + * call receive() on EPOLLIN activity on the PLDM FD + * which is used for command responses. + */ + void registerReceiveCallback(); + + /** + * @brief Reads the MCTP endpoint ID out of a file + */ + void readEID(); + + /** + * @brief Opens the PLDM file descriptor + */ + void open(); + + /** + * @brief Reads the PLDM instance ID to use for the upcoming + * command. + */ + void readInstanceID(); + + /** + * @brief Encodes and sends the PLDM 'new file available' cmd + * + * @param[in] id - The PEL ID + * @param[in] size - The PEL size in bytes + */ + void doSend(uint32_t id, uint32_t size); + + /** + * @brief Closes the PLDM file descriptor + */ + void closeFD(); + + /** + * @brief The MCTP endpoint ID + */ + mctp_eid_t _eid; + + /** + * @brief The PLDM instance ID of the current command + */ + uint8_t _instanceID; + + /** + * @brief The PLDM command file descriptor for the current command + */ + int _fd = -1; + + /** + * @brief The event object for handling callbacks on the PLDM FD + */ + std::unique_ptr<sdeventplus::source::IO> _source; + + /** + * @brief A timer to only allow a certain amount of time for the + * async PLDM receive before it is considered a failure. + */ + sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _receiveTimer; + + /** + * @brief The command timeout value + */ + const std::chrono::milliseconds _receiveTimeout{10000}; +}; + +} // namespace openpower::pels |