oem:ibm :Implement SetFruRecordTable

The commit implements the setFruRecordTable command
and also updates the DBus property for the IBM cable cards
for which the host sends a setFruRecordTable command.

Tested: using pldmtool

Signed-off-by: Pavithra Barithaya <pavithra.b@ibm.com>
Change-Id: I70e4f85f627577d8ca1bc90447b10e9e2e8e7ccd
diff --git a/libpldmresponder/fru.cpp b/libpldmresponder/fru.cpp
index 8a04199..58c3316 100644
--- a/libpldmresponder/fru.cpp
+++ b/libpldmresponder/fru.cpp
@@ -429,6 +429,24 @@
     return PLDM_SUCCESS;
 }
 
+int FruImpl::setFRUTable(const std::vector<uint8_t>& fruData)
+{
+    auto record =
+        reinterpret_cast<const pldm_fru_record_data_format*>(fruData.data());
+    if (record)
+    {
+        if (oemFruHandler && record->record_type == PLDM_FRU_RECORD_TYPE_OEM)
+        {
+            auto rc = oemFruHandler->processOEMFRUTable(fruData);
+            if (!rc)
+            {
+                return PLDM_SUCCESS;
+            }
+        }
+    }
+    return PLDM_ERROR_UNSUPPORTED_PLDM_CMD;
+}
+
 namespace fru
 {
 Response Handler::getFRURecordTableMetadata(const pldm_msg* request,
@@ -539,6 +557,44 @@
     return response;
 }
 
+Response Handler::setFRURecordTable(const pldm_msg* request,
+                                    size_t payloadLength)
+{
+    uint32_t transferHandle{};
+    uint8_t transferOpFlag{};
+    struct variable_field fruData;
+
+    auto rc = decode_set_fru_record_table_req(
+        request, payloadLength, &transferHandle, &transferOpFlag, &fruData);
+
+    if (rc != PLDM_SUCCESS)
+    {
+        return ccOnlyResponse(request, rc);
+    }
+
+    Table table(fruData.ptr, fruData.ptr + fruData.length);
+    rc = impl.setFRUTable(table);
+    if (rc != PLDM_SUCCESS)
+    {
+        return ccOnlyResponse(request, rc);
+    }
+
+    Response response(sizeof(pldm_msg_hdr) +
+                      PLDM_SET_FRU_RECORD_TABLE_RESP_BYTES);
+    struct pldm_msg* responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    rc = encode_set_fru_record_table_resp(
+        request->hdr.instance_id, PLDM_SUCCESS, 0 /* nextDataTransferHandle */,
+        response.size() - sizeof(pldm_msg_hdr), responsePtr);
+
+    if (rc != PLDM_SUCCESS)
+    {
+        return ccOnlyResponse(request, rc);
+    }
+
+    return response;
+}
+
 } // namespace fru
 
 } // namespace responder
diff --git a/libpldmresponder/fru.hpp b/libpldmresponder/fru.hpp
index da19d00..20bc509 100644
--- a/libpldmresponder/fru.hpp
+++ b/libpldmresponder/fru.hpp
@@ -1,6 +1,8 @@
 #pragma once
 
 #include "fru_parser.hpp"
+#include "libpldmresponder/pdr_utils.hpp"
+#include "oem_handler.hpp"
 #include "pldmd/handler.hpp"
 
 #include <libpldm/fru.h>
@@ -59,13 +61,16 @@
      *  @param[in] entityTree - opaque pointer to the entity association tree
      *  @param[in] bmcEntityTree - opaque pointer to bmc's entity association
      *                             tree
+     *  @param[in] oemFruHandler - OEM fru handler
      */
     FruImpl(const std::string& configPath,
             const std::filesystem::path& fruMasterJsonPath, pldm_pdr* pdrRepo,
             pldm_entity_association_tree* entityTree,
-            pldm_entity_association_tree* bmcEntityTree) :
+            pldm_entity_association_tree* bmcEntityTree,
+            pldm::responder::oem_fru::Handler* oemFruHandler) :
         parser(configPath, fruMasterJsonPath),
-        pdrRepo(pdrRepo), entityTree(entityTree), bmcEntityTree(bmcEntityTree)
+        pdrRepo(pdrRepo), entityTree(entityTree), bmcEntityTree(bmcEntityTree),
+        oemFruHandler(oemFruHandler)
     {}
 
     /** @brief Total length of the FRU table in bytes, this includes the pad
@@ -188,6 +193,14 @@
      */
     std::vector<uint8_t> tableResize();
 
+    /* @brief set FRU Record Table
+     *
+     * @param[in] fruData - the data of the fru
+     *
+     * @return PLDM completion code
+     */
+    int setFRUTable(const std::vector<uint8_t>& fruData);
+
   private:
     uint16_t nextRSI()
     {
@@ -211,6 +224,7 @@
     pldm_pdr* pdrRepo;
     pldm_entity_association_tree* entityTree;
     pldm_entity_association_tree* bmcEntityTree;
+    pldm::responder::oem_fru::Handler* oemFruHandler;
 
     std::map<dbus::ObjectPath, pldm_entity_node*> objToEntityNode{};
 
@@ -240,8 +254,10 @@
     Handler(const std::string& configPath,
             const std::filesystem::path& fruMasterJsonPath, pldm_pdr* pdrRepo,
             pldm_entity_association_tree* entityTree,
-            pldm_entity_association_tree* bmcEntityTree) :
-        impl(configPath, fruMasterJsonPath, pdrRepo, entityTree, bmcEntityTree)
+            pldm_entity_association_tree* bmcEntityTree,
+            pldm::responder::oem_fru::Handler* oemFruHandler) :
+        impl(configPath, fruMasterJsonPath, pdrRepo, entityTree, bmcEntityTree,
+             oemFruHandler)
     {
         handlers.emplace(
             PLDM_GET_FRU_RECORD_TABLE_METADATA,
@@ -258,6 +274,11 @@
             [this](pldm_tid_t, const pldm_msg* request, size_t payloadLength) {
             return this->getFRURecordByOption(request, payloadLength);
         });
+        handlers.emplace(
+            PLDM_SET_FRU_RECORD_TABLE,
+            [this](pldm_tid_t, const pldm_msg* request, size_t payloadLength) {
+            return this->setFRURecordTable(request, payloadLength);
+        });
     }
 
     /** @brief Handler for Get FRURecordTableMetadata
@@ -309,6 +330,17 @@
     Response getFRURecordByOption(const pldm_msg* request,
                                   size_t payloadLength);
 
+    /** @brief Handler for SetFRURecordTable
+     *
+     *  @param[in] request - Request message
+     *  @param[in] payloadLength - Request payload length
+     *
+     *  @return PLDM response message
+     */
+    Response setFRURecordTable(const pldm_msg* request, size_t payloadLength);
+
+    using Table = std::vector<uint8_t>;
+
   private:
     FruImpl impl;
 };
diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build
index 156e3d1..50bafe3 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -46,6 +46,7 @@
     '../oem/ibm/libpldmresponder/file_io_type_dump.cpp',
     '../oem/ibm/libpldmresponder/file_io_type_cert.cpp',
     '../oem/ibm/libpldmresponder/platform_oem_ibm.cpp',
+    '../oem/ibm/libpldmresponder/fru_oem_ibm.cpp',
     '../oem/ibm/libpldmresponder/oem_ibm_handler.cpp',
     '../oem/ibm/libpldmresponder/inband_code_update.cpp',
     '../oem/ibm/requester/dbus_to_file_handler.cpp',
diff --git a/libpldmresponder/oem_handler.hpp b/libpldmresponder/oem_handler.hpp
index 3fcfeb0..542f823 100644
--- a/libpldmresponder/oem_handler.hpp
+++ b/libpldmresponder/oem_handler.hpp
@@ -118,6 +118,27 @@
 
 } // namespace oem_platform
 
+namespace oem_fru
+{
+
+class Handler : public CmdHandler
+{
+  public:
+    Handler() {}
+
+    /** @brief Process OEM FRU record
+     *
+     * @param[in] fruData - the data of the fru
+     *
+     * @return success or failure
+     */
+    virtual int processOEMFRUTable(const std::vector<uint8_t>& fruData) = 0;
+
+    virtual ~Handler() = default;
+};
+
+} // namespace oem_fru
+
 } // namespace responder
 
 } // namespace pldm
diff --git a/libpldmresponder/test/libpldmresponder_fru_test.cpp b/libpldmresponder/test/libpldmresponder_fru_test.cpp
index d654fca..2d7b7ec 100644
--- a/libpldmresponder/test/libpldmresponder_fru_test.cpp
+++ b/libpldmresponder/test/libpldmresponder_fru_test.cpp
@@ -117,7 +117,7 @@
 
     pldm::responder::FruImpl mockedFruHandler(
         FRU_JSONS_DIR, "./fru_jsons/fru_master/fru_master.json", pdrRepo.get(),
-        entityTree.get(), bmcEntityTree.get());
+        entityTree.get(), bmcEntityTree.get(), nullptr);
 
     pldm_entity systemEntity{0x2d01, 1, 0};
     pldm_entity chassisEntity{0x2d, 1, 1};
@@ -170,7 +170,7 @@
     InterfaceMap iface = {{"xyz.openbmc_project.Inventory.Item.Chassis", {}}};
     pldm::responder::FruImpl mockedFruHandler(
         FRU_JSONS_DIR, "./fru_jsons/fru_master/fru_master.json", pdrRepo.get(),
-        entityTree.get(), bmcEntityTree.get());
+        entityTree.get(), bmcEntityTree.get(), nullptr);
 
     // Good path
     auto entityPtr = mockedFruHandler.getEntityByObjectPath(iface);
diff --git a/oem/ibm/libpldmresponder/fru_oem_ibm.cpp b/oem/ibm/libpldmresponder/fru_oem_ibm.cpp
new file mode 100644
index 0000000..805d0ec
--- /dev/null
+++ b/oem/ibm/libpldmresponder/fru_oem_ibm.cpp
@@ -0,0 +1,152 @@
+#include "fru_oem_ibm.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <ranges>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+namespace responder
+{
+namespace oem_ibm_fru
+{
+
+void pldm::responder::oem_ibm_fru::Handler::setIBMFruHandler(
+    pldm::responder::fru::Handler* handler)
+{
+    fruHandler = handler;
+}
+
+int pldm::responder::oem_ibm_fru::Handler::processOEMFRUTable(
+    const std::vector<uint8_t>& fruData)
+{
+    uint8_t dataSize = 0;
+    const uint8_t* data = fruData.data();
+
+    while (dataSize < fruData.size())
+    {
+        auto record =
+            reinterpret_cast<const pldm_fru_record_data_format*>(data);
+        if (!record)
+        {
+            return PLDM_ERROR_INVALID_DATA;
+        }
+
+        auto& entityAssociationMap = getAssociateEntityMap();
+        uint16_t fruRSI = le16toh(record->record_set_id);
+
+        dataSize += sizeof(pldm_fru_record_data_format) -
+                    sizeof(pldm_fru_record_tlv);
+        data += dataSize;
+
+        for ([[maybe_unused]] const auto& i :
+             std::views::iota(0, (int)record->num_fru_fields))
+        {
+            auto tlv = reinterpret_cast<const pldm_fru_record_tlv*>(data);
+            if (!tlv)
+            {
+                return PLDM_ERROR_INVALID_DATA;
+            }
+
+            if (tlv->type == PLDM_OEM_FRU_FIELD_TYPE_PCIE_CONFIG_SPACE_DATA)
+            {
+                auto pcieData =
+                    reinterpret_cast<const PcieConfigSpaceData*>(tlv->value);
+
+                if (!pcieData)
+                {
+                    return PLDM_ERROR_INVALID_DATA;
+                }
+                auto vendorId = std::to_string(htole16(pcieData->vendorId));
+                auto deviceId = std::to_string(htole16(pcieData->deviceId));
+                auto revisionId = std::to_string(pcieData->revisionId);
+
+                std::stringstream ss;
+
+                for (const auto& ele : pcieData->classCode)
+                {
+                    ss << std::setfill('0') << std::setw(2) << std::hex << ele;
+                }
+                std::string classCode = ss.str();
+
+                auto subSystemVendorId =
+                    std::to_string(htole16(pcieData->subSystemVendorId));
+                auto subSystemId =
+                    std::to_string(htole16(pcieData->subSystemId));
+
+                updateDBusProperty(fruRSI, entityAssociationMap, vendorId,
+                                   deviceId, revisionId, classCode,
+                                   subSystemVendorId, subSystemId);
+            }
+            // length of tlv is removed from the structure pldm_fru_record_tlv
+            // and the new tlv length is added back.
+            dataSize += sizeof(pldm_fru_record_tlv) - sizeof(uint8_t) +
+                        tlv->length;
+            data += dataSize;
+        }
+    }
+
+    return PLDM_SUCCESS;
+}
+
+void Handler::updateDBusProperty(
+    uint16_t fruRSI, const AssociatedEntityMap& fruAssociationMap,
+    const std::string& vendorId, const std::string& deviceId,
+    const std::string& revisionId, const std::string& classCode,
+    const std::string& subSystemVendorId, const std::string& subSystemId)
+{
+    uint16_t entityType{};
+    uint16_t entityInstanceNum{};
+    uint16_t containerId{};
+    uint16_t terminusHandle{};
+    const pldm_pdr_record* record{};
+
+    record = pldm_pdr_fru_record_set_find_by_rsi(
+        pdrRepo, fruRSI, &terminusHandle, &entityType, &entityInstanceNum,
+        &containerId);
+
+    if (record)
+    {
+        for (const auto& [key, value] : fruAssociationMap)
+        {
+            if (entityInstanceNum == value.entity_instance_num &&
+                entityType == value.entity_type &&
+                containerId == value.entity_container_id)
+            {
+                dbus_map_update(key, "Function0VendorId", vendorId);
+                dbus_map_update(key, "Function0DeviceId", deviceId);
+                dbus_map_update(key, "Function0RevisionId", revisionId);
+                dbus_map_update(key, "Function0ClassCode", classCode);
+                dbus_map_update(key, "Function0SubsystemVendorId",
+                                subSystemVendorId);
+                dbus_map_update(key, "Function0SubsystemId", subSystemId);
+            }
+        }
+    }
+}
+
+void Handler::dbus_map_update(const std::string& adapterObjPath,
+                              const std::string& propertyName,
+                              const std::string& propValue)
+{
+    pldm::utils::PropertyValue value = propValue;
+    pldm::utils::DBusMapping dbusMapping;
+    dbusMapping.objectPath = adapterObjPath;
+    dbusMapping.interface = "xyz.openbmc_project.Inventory.Item.PCIeDevice";
+    dbusMapping.propertyName = propertyName;
+    dbusMapping.propertyType = "string";
+    try
+    {
+        pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
+    }
+    catch (const std::exception& e)
+    {
+        error("Failed to set '{PROPERTY}' property: {ERROR}", "PROPERTY",
+              propertyName, "ERROR", e);
+    }
+}
+} // namespace oem_ibm_fru
+} // namespace responder
+} // namespace pldm
diff --git a/oem/ibm/libpldmresponder/fru_oem_ibm.hpp b/oem/ibm/libpldmresponder/fru_oem_ibm.hpp
new file mode 100644
index 0000000..1fa7bda
--- /dev/null
+++ b/oem/ibm/libpldmresponder/fru_oem_ibm.hpp
@@ -0,0 +1,98 @@
+#pragma once
+
+#include "common/utils.hpp"
+#include "libpldmresponder/fru.hpp"
+#include "libpldmresponder/oem_handler.hpp"
+
+#include <libpldm/oem/ibm/fru.h>
+
+namespace pldm
+{
+namespace responder
+{
+using ObjectPath = std::string;
+using AssociatedEntityMap = std::map<ObjectPath, pldm_entity>;
+
+namespace oem_ibm_fru
+{
+
+// structure of the PCIE config space data
+struct PcieConfigSpaceData
+{
+    uint16_t vendorId;
+    uint16_t deviceId;
+    uint32_t first_reserved;
+    uint8_t revisionId;
+    std::array<uint8_t, 3> classCode;
+    uint32_t second_reserved[8];
+    uint16_t subSystemVendorId;
+    uint16_t subSystemId;
+    uint32_t last_reserved[4];
+
+} __attribute__((packed));
+
+class Handler : public oem_fru::Handler
+{
+  public:
+    Handler(pldm_pdr* repo) : pdrRepo(repo) {}
+
+    /** @brief Method to set the fru handler in the
+     *    oem_ibm_handler class
+     *
+     *  @param[in] handler - pointer to PLDM platform handler
+     */
+    void setIBMFruHandler(pldm::responder::fru::Handler* handler);
+
+    /** @brief Process OEM FRU table
+     *
+     *  @param[in] fruData - the data of the fru records
+     *
+     *  @return success or failure
+     */
+    int processOEMFRUTable(const std::vector<uint8_t>& fruData);
+
+    virtual const AssociatedEntityMap& getAssociateEntityMap()
+    {
+        return fruHandler->getAssociateEntityMap();
+    }
+
+    ~Handler() = default;
+
+  private:
+    /** @brief pointer to BMC's primary PDR repo */
+    const pldm_pdr* pdrRepo;
+
+    pldm::responder::fru::Handler* fruHandler; //!< pointer to PLDM fru handler
+
+    /** @brief update the DBus property
+     *
+     *  @param[in] fruRSI - fru record set identifier
+     *  @param[in] fruAssociationMap - the dbus path to pldm entity stored while
+     *                                 creating the pldm fru records
+     *  @param[in] vendorId - the vendor ID
+     *  @param[in] deviceId - the device ID
+     *  @param[in] revisionId - the revision ID
+     *  @param[in] classCode - the class Code
+     *  @param[in] subSystemVendorId - the subSystemVendor ID
+     *  @param[in] subSystemId - the subSystem ID
+     */
+    void updateDBusProperty(
+        uint16_t fruRSI, const AssociatedEntityMap& fruAssociationMap,
+        const std::string& vendorId, const std::string& deviceId,
+        const std::string& revisionId, const std::string& classCode,
+        const std::string& subSystemVendorId, const std::string& subSystemId);
+
+    /** @brief DBus Map update
+     *
+     *  @param[in] adapterObjectPath - the fru object path
+     *  @param[in] propertyName - the fru property name
+     *  @param[in] propValue - the fru property value
+     */
+    void dbus_map_update(const std::string& adapterObjectPath,
+                         const std::string& propertyName,
+                         const std::string& propValue);
+};
+
+} // namespace oem_ibm_fru
+} // namespace responder
+} // namespace pldm
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index f510f75..e2ca648 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -62,6 +62,7 @@
 
 #ifdef OEM_IBM
 #include "libpldmresponder/file_io.hpp"
+#include "libpldmresponder/fru_oem_ibm.hpp"
 #include "libpldmresponder/oem_ibm_handler.hpp"
 #endif
 
@@ -227,6 +228,8 @@
     std::unique_ptr<oem_platform::Handler> oemPlatformHandler{};
     std::unique_ptr<platform_config::Handler> platformConfigHandler{};
     platformConfigHandler = std::make_unique<platform_config::Handler>();
+    std::unique_ptr<oem_fru::Handler> oemFruHandler{};
+
 #ifdef OEM_IBM
     std::unique_ptr<pldm::responder::CodeUpdate> codeUpdate =
         std::make_unique<pldm::responder::CodeUpdate>(&dbusHandler);
@@ -235,6 +238,7 @@
         &dbusHandler, codeUpdate.get(), pldmTransport.getEventSource(), hostEID,
         instanceIdDb, event, &reqHandler);
     codeUpdate->setOemPlatformHandler(oemPlatformHandler.get());
+    oemFruHandler = std::make_unique<oem_ibm_fru::Handler>(pdrRepo.get());
     invoker.registerHandler(PLDM_OEM, std::make_unique<oem_ibm::Handler>(
                                           oemPlatformHandler.get(),
                                           pldmTransport.getEventSource(),
@@ -260,9 +264,11 @@
     auto biosHandler = std::make_unique<bios::Handler>(
         pldmTransport.getEventSource(), hostEID, &instanceIdDb, &reqHandler,
         platformConfigHandler.get());
+
     auto fruHandler = std::make_unique<fru::Handler>(
         FRU_JSONS_DIR, FRU_MASTER_JSON, pdrRepo.get(), entityTree.get(),
-        bmcEntityTree.get());
+        bmcEntityTree.get(), oemFruHandler.get());
+
     // FRU table is built lazily when a FRU command or Get PDR command is
     // handled. To enable building FRU table, the FRU handler is passed to the
     // Platform handler.
@@ -276,6 +282,11 @@
         dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>(
             oemPlatformHandler.get());
     oemIbmPlatformHandler->setPlatformHandler(platformHandler.get());
+
+    pldm::responder::oem_ibm_fru::Handler* oemIbmFruHandler =
+        dynamic_cast<pldm::responder::oem_ibm_fru::Handler*>(
+            oemFruHandler.get());
+    oemIbmFruHandler->setIBMFruHandler(fruHandler.get());
 #endif
 
     invoker.registerHandler(PLDM_BIOS, std::move(biosHandler));