oem-ibm: Send the event to host when BIOS attribute changes

IBM has the requirement to send a hot update to host when the
BIOS attribute changes. This patch enables sending an OEM platform
event message to host with the details of the BIOS attributes that
changed on the BMC. Once the host acknowledges the event, then BMC
updates the BaseBiosTable in the bios-config-manager. The host comes
down and reads the changed BIOS attributes by calling the command
GetBIOSAttribute value.

Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
Change-Id: Id1579bfed1967e653da743313c825b765d227681
diff --git a/libpldm/meson.build b/libpldm/meson.build
index c574027..bc31526 100644
--- a/libpldm/meson.build
+++ b/libpldm/meson.build
@@ -27,11 +27,13 @@
 if get_option('oem-ibm').enabled()
   headers += [
     '../oem/ibm/libpldm/file_io.h',
-    '../oem/ibm/libpldm/host.h'
+    '../oem/ibm/libpldm/host.h',
+    '../oem/ibm/libpldm/platform_oem_ibm.h'
   ]
   sources += [
     '../oem/ibm/libpldm/file_io.c',
-    '../oem/ibm/libpldm/host.c'
+    '../oem/ibm/libpldm/host.c',
+    '../oem/ibm/libpldm/platform_oem_ibm.c'
   ]
   libpldm_headers += ['../oem/ibm']
 endif
diff --git a/libpldm/platform.h b/libpldm/platform.h
index bd4d557..1db7aee 100644
--- a/libpldm/platform.h
+++ b/libpldm/platform.h
@@ -33,6 +33,7 @@
 #define PLDM_PLATFORM_EVENT_MESSAGE_MIN_REQ_BYTES 3
 #define PLDM_PLATFORM_EVENT_MESSAGE_STATE_SENSOR_STATE_REQ_BYTES 6
 #define PLDM_PLATFORM_EVENT_MESSAGE_RESP_BYTES 2
+#define PLDM_PLATFORM_EVENT_MESSAGE_FORMAT_VERSION 1
 
 /* Minumum length of senson event data */
 #define PLDM_SENSOR_EVENT_DATA_MIN_LENGTH 5
diff --git a/libpldmresponder/bios.cpp b/libpldmresponder/bios.cpp
index a62d978..fbe6144 100644
--- a/libpldmresponder/bios.cpp
+++ b/libpldmresponder/bios.cpp
@@ -67,7 +67,9 @@
 
 DBusHandler dbusHandler;
 
-Handler::Handler() : biosConfig(BIOS_JSONS_DIR, BIOS_TABLES_DIR, &dbusHandler)
+Handler::Handler(int fd, uint8_t eid, dbus_api::Requester* requester) :
+    biosConfig(BIOS_JSONS_DIR, BIOS_TABLES_DIR, &dbusHandler, fd, eid,
+               requester)
 {
     biosConfig.removeTables();
     biosConfig.buildTables();
diff --git a/libpldmresponder/bios.hpp b/libpldmresponder/bios.hpp
index 9cfb22b..2fbf8ab 100644
--- a/libpldmresponder/bios.hpp
+++ b/libpldmresponder/bios.hpp
@@ -7,6 +7,7 @@
 
 #include "bios_config.hpp"
 #include "bios_table.hpp"
+#include "pldmd/dbus_impl_requester.hpp"
 #include "pldmd/handler.hpp"
 
 #include <stdint.h>
@@ -28,7 +29,13 @@
 class Handler : public CmdHandler
 {
   public:
-    Handler();
+    /** @brief Constructor
+     *
+     *  @param[in] fd - socket descriptor to communicate to host
+     *  @param[in] eid - MCTP EID of host firmware
+     *  @param[in] requester - pointer to Requester object
+     */
+    Handler(int fd, uint8_t eid, dbus_api::Requester* requester);
 
     /** @brief Handler for GetDateTime
      *
diff --git a/libpldmresponder/bios_config.cpp b/libpldmresponder/bios_config.cpp
index e3d89a6..f79984a 100644
--- a/libpldmresponder/bios_config.cpp
+++ b/libpldmresponder/bios_config.cpp
@@ -11,6 +11,10 @@
 #include <fstream>
 #include <iostream>
 
+#ifdef OEM_IBM
+#include "oem/ibm/libpldmresponder/platform_oem_ibm.hpp"
+#endif
+
 namespace pldm
 {
 namespace responder
@@ -34,9 +38,12 @@
 } // namespace
 
 BIOSConfig::BIOSConfig(const char* jsonDir, const char* tableDir,
-                       DBusHandler* const dbusHandler) :
+                       DBusHandler* const dbusHandler, int fd, uint8_t eid,
+                       dbus_api::Requester* requester) :
     jsonDir(jsonDir),
-    tableDir(tableDir), dbusHandler(dbusHandler), isUpdateProperty(false)
+    tableDir(tableDir), dbusHandler(dbusHandler), fd(fd), eid(eid),
+    requester(requester)
+
 {
     fs::create_directories(tableDir);
     constructAttributes();
@@ -70,7 +77,8 @@
     return loadTable(tablePath);
 }
 
-int BIOSConfig::setBIOSTable(uint8_t tableType, const Table& table)
+int BIOSConfig::setBIOSTable(uint8_t tableType, const Table& table,
+                             bool updateBaseBIOSTable)
 {
     fs::path stringTablePath(tableDir / stringTableFile);
     fs::path attrTablePath(tableDir / attrTableFile);
@@ -117,16 +125,16 @@
         }
 
         storeTable(attrValueTablePath, table);
-        isUpdateProperty = true;
     }
     else
     {
         return PLDM_INVALID_BIOS_TABLE_TYPE;
     }
 
-    if (isUpdateProperty)
+    if ((tableType == PLDM_BIOS_ATTR_VAL_TABLE) && updateBaseBIOSTable)
     {
-        isUpdateProperty = false;
+        std::cout << "setBIOSTable:: updateBaseBIOSTableProperty() "
+                  << "\n";
         updateBaseBIOSTableProperty();
     }
 
@@ -633,7 +641,8 @@
     };
 }
 
-int BIOSConfig::setAttrValue(const void* entry, size_t size)
+int BIOSConfig::setAttrValue(const void* entry, size_t size,
+                             bool updateBaseBIOSTable)
 {
     auto attrValueTable = getBIOSTable(PLDM_BIOS_ATTR_VAL_TABLE);
     auto attrTable = getBIOSTable(PLDM_BIOS_ATTR_TABLE);
@@ -692,7 +701,7 @@
         return PLDM_ERROR;
     }
 
-    setBIOSTable(PLDM_BIOS_ATTR_VAL_TABLE, *destTable);
+    setBIOSTable(PLDM_BIOS_ATTR_VAL_TABLE, *destTable, updateBaseBIOSTable);
 
     return PLDM_SUCCESS;
 }
@@ -814,6 +823,8 @@
 void BIOSConfig::constructPendingAttribute(
     const PendingAttributes& pendingAttributes)
 {
+    std::vector<uint16_t> listOfHandles{};
+
     for (auto& attribute : pendingAttributes)
     {
         std::string attributeName = attribute.first;
@@ -849,9 +860,24 @@
         }
 
         entry->attr_handle = htole16(handler);
+        listOfHandles.emplace_back(htole16(handler));
+
         (*iter)->generateAttributeEntry(attributevalue, attrValueEntry);
 
-        setAttrValue(attrValueEntry.data(), attrValueEntry.size());
+        setAttrValue(attrValueEntry.data(), attrValueEntry.size(), false);
+    }
+
+    if (listOfHandles.size())
+    {
+#ifdef OEM_IBM
+        auto rc = pldm::responder::platform::sendBiosAttributeUpdateEvent(
+            fd, eid, requester, listOfHandles);
+        if (rc != PLDM_SUCCESS)
+        {
+            return;
+        }
+#endif
+        updateBaseBIOSTableProperty();
     }
 }
 
diff --git a/libpldmresponder/bios_config.hpp b/libpldmresponder/bios_config.hpp
index b10ca78..4cfa582 100644
--- a/libpldmresponder/bios_config.hpp
+++ b/libpldmresponder/bios_config.hpp
@@ -4,6 +4,7 @@
 
 #include "bios_attribute.hpp"
 #include "bios_table.hpp"
+#include "pldmd/dbus_impl_requester.hpp"
 
 #include <nlohmann/json.hpp>
 
@@ -68,16 +69,23 @@
      *  @param[in] jsonDir - The directory where json file exists
      *  @param[in] tableDir - The directory where the persistent table is placed
      *  @param[in] dbusHandler - Dbus Handler
+     *  @param[in] fd - socket descriptor to communicate to host
+     *  @param[in] eid - MCTP EID of host firmware
+     *  @param[in] requester - pointer to Requester object
      */
     explicit BIOSConfig(const char* jsonDir, const char* tableDir,
-                        DBusHandler* const dbusHandler);
+                        DBusHandler* const dbusHandler, int fd, uint8_t eid,
+                        dbus_api::Requester* requester);
 
     /** @brief Set attribute value on dbus and attribute value table
      *  @param[in] entry - attribute value entry
      *  @param[in] size - size of the attribute value entry
+     *  @param[in] updateBaseBIOSTable - update BaseBIOSTable D-Bus property
+     *                                   if this is set to true
      *  @return pldm_completion_codes
      */
-    int setAttrValue(const void* entry, size_t size);
+    int setAttrValue(const void* entry, size_t size,
+                     bool updateBaseBIOSTable = true);
 
     /** @brief Remove the persistent tables */
     void removeTables();
@@ -96,17 +104,30 @@
      *             {BIOSStringTable=0x0, BIOSAttributeTable=0x1,
      *              BIOSAttributeValueTable=0x2}
      *  @param[in] table - table data
+     *  @param[in] updateBaseBIOSTable - update BaseBIOSTable D-Bus property
+     *                                   if this is set to true
      *  @return pldm_completion_codes
      */
-    int setBIOSTable(uint8_t tableType, const Table& table);
+    int setBIOSTable(uint8_t tableType, const Table& table,
+                     bool updateBaseBIOSTable = true);
 
   private:
     const fs::path jsonDir;
     const fs::path tableDir;
     DBusHandler* const dbusHandler;
-    bool isUpdateProperty;
     BaseBIOSTable baseBIOSTableMaps;
 
+    /** @brief socket descriptor to communicate to host */
+    int fd;
+
+    /** @brief MCTP EID of host firmware */
+    uint8_t eid;
+
+    /** @brief pointer to Requester object, primarily used to access API to
+     *  obtain PLDM instance id.
+     */
+    dbus_api::Requester* requester;
+
     // vector persists all attributes
     using BIOSAttributes = std::vector<std::unique_ptr<BIOSAttribute>>;
     BIOSAttributes biosAttributes;
diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build
index 481c82b..eb86a04 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -32,7 +32,8 @@
     '../oem/ibm/libpldmresponder/file_io_by_type.cpp',
     '../oem/ibm/libpldmresponder/file_io_type_pel.cpp',
     '../oem/ibm/libpldmresponder/file_io_type_dump.cpp',
-    '../oem/ibm/libpldmresponder/file_io_type_cert.cpp'
+    '../oem/ibm/libpldmresponder/file_io_type_cert.cpp',
+    '../oem/ibm/libpldmresponder/platform_oem_ibm.cpp'
   ]
 endif
 
diff --git a/oem/ibm/libpldm/platform_oem_ibm.c b/oem/ibm/libpldm/platform_oem_ibm.c
new file mode 100644
index 0000000..1cf7e86
--- /dev/null
+++ b/oem/ibm/libpldm/platform_oem_ibm.c
@@ -0,0 +1,52 @@
+#include "platform_oem_ibm.h"

+#include "platform.h"

+#include <string.h>

+

+int encode_bios_attribute_update_event_req(uint8_t instance_id,

+					   uint8_t format_version, uint8_t tid,

+					   uint8_t num_handles,

+					   const uint8_t *list_of_handles,

+					   size_t payload_length,

+					   struct pldm_msg *msg)

+{

+	struct pldm_header_info header = {0};

+	int rc = PLDM_SUCCESS;

+

+	header.msg_type = PLDM_REQUEST;

+	header.instance = instance_id;

+	header.pldm_type = PLDM_PLATFORM;

+	header.command = PLDM_PLATFORM_EVENT_MESSAGE;

+

+	if (format_version != 1) {

+		return PLDM_ERROR_INVALID_DATA;

+	}

+

+	if (msg == NULL || list_of_handles == NULL) {

+		return PLDM_ERROR_INVALID_DATA;

+	}

+

+	if (num_handles == 0) {

+		return PLDM_ERROR_INVALID_DATA;

+	}

+

+	if (payload_length !=

+	    (PLDM_PLATFORM_EVENT_MESSAGE_MIN_REQ_BYTES + sizeof(num_handles) +

+	     (num_handles * sizeof(uint16_t)))) {

+		return PLDM_ERROR_INVALID_LENGTH;

+	}

+

+	if ((rc = pack_pldm_header(&header, &(msg->hdr))) > PLDM_SUCCESS) {

+		return rc;

+	}

+

+	struct pldm_bios_attribute_update_event_req *request =

+	    (struct pldm_bios_attribute_update_event_req *)msg->payload;

+	request->format_version = format_version;

+	request->tid = tid;

+	request->event_class = PLDM_EVENT_TYPE_OEM_EVENT_BIOS_ATTRIBUTE_UPDATE;

+	request->num_handles = num_handles;

+	memcpy(request->bios_attribute_handles, list_of_handles,

+	       num_handles * sizeof(uint16_t));

+

+	return PLDM_SUCCESS;

+}
\ No newline at end of file
diff --git a/oem/ibm/libpldm/platform_oem_ibm.h b/oem/ibm/libpldm/platform_oem_ibm.h
new file mode 100644
index 0000000..7d918e7
--- /dev/null
+++ b/oem/ibm/libpldm/platform_oem_ibm.h
@@ -0,0 +1,56 @@
+#ifndef PLATFORM_OEM_IBM_H

+#define PLATFORM_OEM_IBM_H

+

+#ifdef __cplusplus

+extern "C" {

+#endif

+

+#include "base.h"

+#include <stddef.h>

+#include <stdint.h>

+

+enum pldm_event_types_ibm_oem {

+	PLDM_EVENT_TYPE_OEM_EVENT_BIOS_ATTRIBUTE_UPDATE = 0xF0,

+};

+

+/** @struct pldm_bios_attribute_update_event_req

+ *

+ * 	Structure representing PlatformEventMessage command request data for OEM

+ *  event type BIOS attribute update.

+ */

+struct pldm_bios_attribute_update_event_req {

+	uint8_t format_version;

+	uint8_t tid;

+	uint8_t event_class;

+	uint8_t num_handles;

+	uint8_t bios_attribute_handles[1];

+} __attribute__((packed));

+

+/** @brief Encode PlatformEventMessage request data for BIOS attribute update

+ *

+ *  @param[in] instance_id - Message's instance id

+ *  @param[in] format_version - Version of the event format

+ *  @param[in] tid - Terminus ID for the terminus that originated the event

+ *                   message

+ *  @param[in] num_handles - Number of BIOS handles with an update

+ *  @param[in] list_of_handles - Pointer to the list of BIOS attribute handles

+ *  @param[in] payload_length - Length of request message payload

+ *  @param[out] msg - Message will be written to this

+ *

+ *  @return pldm_completion_codes

+ *

+ *  @note  Caller is responsible for memory alloc and dealloc of param

+ *         'msg.payload'

+ */

+int encode_bios_attribute_update_event_req(uint8_t instance_id,

+					   uint8_t format_version, uint8_t tid,

+					   uint8_t num_handles,

+					   const uint8_t *list_of_handles,

+					   size_t payload_length,

+					   struct pldm_msg *msg);

+

+#ifdef __cplusplus

+}

+#endif

+

+#endif /* PLATFORM_OEM_IBM_H */
\ No newline at end of file
diff --git a/oem/ibm/libpldmresponder/platform_oem_ibm.cpp b/oem/ibm/libpldmresponder/platform_oem_ibm.cpp
new file mode 100644
index 0000000..3292004
--- /dev/null
+++ b/oem/ibm/libpldmresponder/platform_oem_ibm.cpp
@@ -0,0 +1,124 @@
+#include "platform_oem_ibm.hpp"

+

+#include "libpldm/platform_oem_ibm.h"

+#include "libpldm/requester/pldm.h"

+

+#include "common/utils.hpp"

+#include "libpldmresponder/pdr.hpp"

+

+#include <iostream>

+

+namespace pldm

+{

+namespace responder

+{

+namespace platform

+{

+

+int sendBiosAttributeUpdateEvent(int fd, uint8_t eid,

+                                 dbus_api::Requester* requester,

+                                 const std::vector<uint16_t>& handles)

+{

+    constexpr auto osStatePath = "/xyz/openbmc_project/state/host0";

+    constexpr auto osStateInterface =

+        "xyz.openbmc_project.State.OperatingSystem.Status";

+    constexpr auto osStateProperty = "OperatingSystemState";

+

+    try

+    {

+        auto propVal = pldm::utils::DBusHandler().getDbusPropertyVariant(

+            osStatePath, osStateProperty, osStateInterface);

+        const auto& currHostState = std::get<std::string>(propVal);

+        if ((currHostState != "xyz.openbmc_project.State.OperatingSystem."

+                              "Status.OSStatus.Standby") &&

+            (currHostState != "xyz.openbmc_project.State.OperatingSystem."

+                              "Status.OSStatus.BootComplete"))

+        {

+            return PLDM_SUCCESS;

+        }

+    }

+    catch (const sdbusplus::exception::SdBusError& e)

+    {

+        std::cerr << "Error in getting current host state, continue ... \n";

+    }

+

+    auto instanceId = requester->getInstanceId(eid);

+

+    std::vector<uint8_t> requestMsg(

+        sizeof(pldm_msg_hdr) + sizeof(pldm_bios_attribute_update_event_req) -

+            1 + (handles.size() * sizeof(uint16_t)),

+        0);

+

+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());

+

+    auto rc = encode_bios_attribute_update_event_req(

+        instanceId, PLDM_PLATFORM_EVENT_MESSAGE_FORMAT_VERSION,

+        pldm::responder::pdr::BmcMctpEid, handles.size(),

+        reinterpret_cast<const uint8_t*>(handles.data()),

+        requestMsg.size() - sizeof(pldm_msg_hdr), request);

+    if (rc != PLDM_SUCCESS)

+    {

+        std::cerr << "Message encode failure 1. PLDM error code = " << std::hex

+                  << std::showbase << rc << "\n";

+        requester->markFree(eid, instanceId);

+        return rc;

+    }

+

+    if (requestMsg.size())

+    {

+        std::ostringstream tempStream;

+        for (int byte : requestMsg)

+        {

+            tempStream << std::setfill('0') << std::setw(2) << std::hex << byte

+                       << " ";

+        }

+        std::cout << tempStream.str() << std::endl;

+    }

+

+    uint8_t* responseMsg = nullptr;

+    size_t responseMsgSize{};

+

+    rc = pldm_send_recv(eid, fd, requestMsg.data(), requestMsg.size(),

+                        &responseMsg, &responseMsgSize);

+    std::unique_ptr<uint8_t, decltype(std::free)*> responseMsgPtr{responseMsg,

+                                                                  std::free};

+    requester->markFree(eid, instanceId);

+

+    if (rc != PLDM_REQUESTER_SUCCESS)

+    {

+        std::cerr << "Failed to send BIOS attribute update event. RC = " << rc

+                  << ", errno = " << errno << "\n";

+        pldm::utils::reportError(

+            "xyz.openbmc_project.bmc.pldm.InternalFailure");

+        return rc;

+    }

+

+    auto responsePtr = reinterpret_cast<struct pldm_msg*>(responseMsgPtr.get());

+    uint8_t completionCode{};

+    uint8_t status{};

+    rc = decode_platform_event_message_resp(

+        responsePtr, responseMsgSize - sizeof(pldm_msg_hdr), &completionCode,

+        &status);

+    if (rc != PLDM_SUCCESS)

+    {

+        std::cerr << "Failed to decode PlatformEventMessage response, rc = "

+                  << rc << "\n";

+        return rc;

+    }

+

+    if (completionCode != PLDM_SUCCESS)

+    {

+        std::cerr << "Failed to send the BIOS attribute update event, rc = "

+                  << (uint32_t)completionCode << "\n";

+        pldm::utils::reportError(

+            "xyz.openbmc_project.bmc.pldm.InternalFailure");

+    }

+

+    return completionCode;

+}

+

+} // namespace platform

+

+} // namespace responder

+

+} // namespace pldm
\ No newline at end of file
diff --git a/oem/ibm/libpldmresponder/platform_oem_ibm.hpp b/oem/ibm/libpldmresponder/platform_oem_ibm.hpp
new file mode 100644
index 0000000..639f1d5
--- /dev/null
+++ b/oem/ibm/libpldmresponder/platform_oem_ibm.hpp
@@ -0,0 +1,34 @@
+#pragma once

+

+#include "pldmd/dbus_impl_requester.hpp"

+

+#include <vector>

+

+namespace pldm

+{

+namespace responder

+{

+namespace platform

+{

+

+/** @brief To send BIOS attribute update event

+ *

+ *  When the attribute value changes for any BIOS attribute, then

+ *  PlatformEventMessage command with OEM event type

+ *  PLDM_EVENT_TYPE_OEM_EVENT_BIOS_ATTRIBUTE_UPDATE is send to host with the

+ *  list of BIOS attribute handles.

+ *

+ *  @param[in] fd - socket descriptor to communicate to host

+ *  @param[in] eid - MCTP EID of host firmware

+ *  @param[in] requester - pointer to Requester object

+ *  @param[in] handles - List of BIOS attribute handles

+ */

+int sendBiosAttributeUpdateEvent(int fd, uint8_t eid,

+                                 dbus_api::Requester* requester,

+                                 const std::vector<uint16_t>& handles);

+

+} // namespace platform

+

+} // namespace responder

+

+} // namespace pldm

diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index 86f6210..1495db4 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -194,7 +194,8 @@
 
     Invoker invoker{};
     invoker.registerHandler(PLDM_BASE, std::make_unique<base::Handler>());
-    invoker.registerHandler(PLDM_BIOS, std::make_unique<bios::Handler>());
+    invoker.registerHandler(PLDM_BIOS, std::make_unique<bios::Handler>(
+                                           sockfd, hostEID, &dbusImplReq));
     auto fruHandler = std::make_unique<fru::Handler>(
         FRU_JSONS_DIR, pdrRepo.get(), entityTree.get());
     // FRU table is built lazily when a FRU command or Get PDR command is
diff --git a/test/libpldmresponder_bios_config_test.cpp b/test/libpldmresponder_bios_config_test.cpp
index e0025fd..837da87 100644
--- a/test/libpldmresponder_bios_config_test.cpp
+++ b/test/libpldmresponder_bios_config_test.cpp
@@ -79,7 +79,8 @@
     ON_CALL(dbusHandler, getDbusPropertyVariant(_, _, _))
         .WillByDefault(Throw(std::exception()));
 
-    BIOSConfig biosConfig("./bios_jsons", tableDir.c_str(), &dbusHandler);
+    BIOSConfig biosConfig("./bios_jsons", tableDir.c_str(), &dbusHandler, 0, 0,
+                          nullptr);
     biosConfig.buildTables();
 
     auto stringTable = biosConfig.getBIOSTable(PLDM_BIOS_STRING_TABLE);
@@ -247,7 +248,8 @@
 {
     MockdBusHandler dbusHandler;
 
-    BIOSConfig biosConfig("./bios_jsons", tableDir.c_str(), &dbusHandler);
+    BIOSConfig biosConfig("./bios_jsons", tableDir.c_str(), &dbusHandler, 0, 0,
+                          nullptr);
     biosConfig.removeTables();
     biosConfig.buildTables();