PEL: Add PLDM command handler

Derive a PLDMInterface class from the HostInterface class to implement
sending the 'new file available' PLDM command to notify the host of the
ID and size of PELs.

The command response is received asynchronously by watching for activity
on the MCTP file descriptor that was used for the command.  If a
response isn't received in 10 seconds, it will be considered a failure.

Both on response success and failure the class will call the registered
callback function and pass it the result.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ica00da590628cc81114a48e2831a436dc2115269
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