PEL: Get PLDM instance ID asynchonously

Start actually getting the PLDM instance ID from the PLDM daemon for use
in sending PLDM commands, and refactor the PLDM interface class to do
this using sd_bus_call_method_async.  After the instance ID is obtained
then the rest of the PLDM command sequence can be run to notify the host
of PELs.

This is necessary because the PLDM daemon may be making a call to the
logging daemon at the same time to create a new OpenBMC event log due
to a host PEL that came down, and there will be a deadlock if the method
call was synchronous.

A new instance ID is needed for every new PLDM command, unless there was
a failure before the host could respond, so the code keeps track of when
it needs to get a new ID.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I0e00d1180363250662bd85f9ec69813ada500931
diff --git a/extensions/openpower-pels/pldm_interface.cpp b/extensions/openpower-pels/pldm_interface.cpp
index 87edb1c..8fa7c18 100644
--- a/extensions/openpower-pels/pldm_interface.cpp
+++ b/extensions/openpower-pels/pldm_interface.cpp
@@ -17,6 +17,7 @@
 
 #include <libpldm/base.h>
 #include <libpldm/file_io.h>
+#include <systemd/sd-bus.h>
 #include <unistd.h>
 
 #include <fstream>
@@ -25,6 +26,21 @@
 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;
@@ -36,6 +52,7 @@
 
 PLDMInterface::~PLDMInterface()
 {
+    sd_bus_unref(_bus);
     closeFD();
 }
 
@@ -84,7 +101,60 @@
     }
 }
 
-CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size)
+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
     {
@@ -92,23 +162,59 @@
 
         open();
 
-        readInstanceID();
-
         registerReceiveCallback();
 
-        doSend(id, size);
+        doSend();
+
+        _receiveTimer.restartOnce(_receiveTimeout);
     }
     catch (const std::exception& e)
     {
-        closeFD();
+        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;
-        _source.reset();
         return CmdStatus::failure;
     }
 
-    _inProgress = true;
-    _receiveTimer.restartOnce(_receiveTimeout);
     return CmdStatus::success;
 }
 
@@ -121,30 +227,16 @@
                   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)
+void PLDMInterface::doSend()
 {
     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
-                            sizeof(id) + sizeof(uint64_t)>
+                            sizeof(_pelID) + sizeof(uint64_t)>
         requestMsg;
 
     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
 
-    auto rc = encode_new_file_req(_instanceID, pelFileType, id, size, request);
+    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));
@@ -173,7 +265,7 @@
     size_t responseSize = 0;
     ResponseStatus status = ResponseStatus::success;
 
-    auto rc = pldm_recv(_eid, fd, _instanceID, &responseMsg, &responseSize);
+    auto rc = pldm_recv(_eid, fd, *_instanceID, &responseMsg, &responseSize);
     if (rc < 0)
     {
         if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH)
@@ -196,10 +288,10 @@
         responseMsg = nullptr;
     }
 
-    _inProgress = false;
-    _receiveTimer.setEnabled(false);
-    closeFD();
-    _source.reset();
+    cleanupCmd();
+
+    // Can't use this instance ID anymore.
+    _instanceID = std::nullopt;
 
     if (status == ResponseStatus::success)
     {
@@ -236,13 +328,22 @@
 void PLDMInterface::receiveTimerExpired()
 {
     log<level::ERR>("Timed out waiting for PLDM response");
-    cancelCmd();
+
+    // 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();