platform-mc: discovery FRU data from terminus

As [1], `pldmd` will host Fru D-Bus inventory object path and Fru
`xyz.openbmc_project.Inventory.Decorator.*` D-Bus interfaces of one PLDM
terminus.
[1] https://github.com/openbmc/docs/blob/master/designs/pldm-stack.md#processing-pldm-fru-information-sent-down-by-the-host-firmware

Support getting FRU data from terminus in the terminus discovery phase
and expose FRU data to the Fru D-Bus properties in
`xyz.openbmc_project.Inventory.Decorator.Asset`,
`xyz.openbmc_project.Inventory.Decorator.AssetTag`,
`xyz.openbmc_project.Inventory.Decorator.Compatible` and
`xyz.openbmc_project.Inventory.Decorator.Revision` interfaces of
`xyz/openbmc_project/FruPldm/<Terminus_Name>` D-Bus object path which is
created by `xyz.openbmc_project.PLDM` D-Bus service. The object path
will be available until the endpoint EID is removed from the MCTP
D-Bus.

```
busctl introspect xyz.openbmc_project.PLDM  /xyz/openbmc_project/inventory/system/board/S0
NAME                                               TYPE      SIGNATURE RESULT/VALUE                       FLAGS
org.freedesktop.DBus.Introspectable                interface -         -                                  -
.Introspect                                        method    -         s                                  -
org.freedesktop.DBus.Peer                          interface -         -                                  -
.GetMachineId                                      method    -         s                                  -
.Ping                                              method    -         -                                  -
org.freedesktop.DBus.Properties                    interface -         -                                  -
.Get                                               method    ss        v                                  -
.GetAll                                            method    s         a{sv}                              -
.Set                                               method    ssv       -                                  -
.PropertiesChanged                                 signal    sa{sv}as  -                                  -
xyz.openbmc_project.Inventory.Decorator.Asset      interface -         -                                  -
.BuildDate                                         property  s         ""                                 emits-change writable
.Manufacturer                                      property  s         ""                                 emits-change writable
.Model                                             property  s         "00014003"                         emits-change writable
.PartNumber                                        property  s         ""                                 emits-change writable
.SerialNumber                                      property  s         "000000218"                        emits-change writable
.SparePartNumber                                   property  s         ""                                 emits-change writable
.SubModel                                          property  s         ""                                 emits-change writable
xyz.openbmc_project.Inventory.Decorator.AssetTag   interface -         -                                  -
.AssetTag                                          property  s         ""                                 emits-change writable
xyz.openbmc_project.Inventory.Decorator.Compatible interface -         -                                  -
.Names                                             property  as        0                                  emits-change writable
xyz.openbmc_project.Inventory.Decorator.Revision   interface -         -                                  -
.Version                                           property  s         "x.x.00008.004"                    emits-change writable
xyz.openbmc_project.Inventory.Item.Board           interface -         -                                  -

```
Signed-off-by: Dung Cao <dung@os.amperecomputing.com>
Signed-off-by: Thu Nguyen <thu@os.amperecomputing.com>
Change-Id: I4f8869f8fee0fc0f8a5a5670d9a42fc7f48cc798
diff --git a/platform-mc/terminus.cpp b/platform-mc/terminus.cpp
index 70d1112..99ba92b 100644
--- a/platform-mc/terminus.cpp
+++ b/platform-mc/terminus.cpp
@@ -2,6 +2,7 @@
 
 #include "libpldm/platform.h"
 
+#include "dbus_impl_fru.hpp"
 #include "terminus_manager.hpp"
 
 #include <common/utils.hpp>
@@ -92,11 +93,18 @@
         return false;
     }
 
+    /* inventory object is created */
+    if (inventoryItemBoardInft)
+    {
+        return false;
+    }
+
     inventoryPath = "/xyz/openbmc_project/inventory/system/board/" + tName;
     try
     {
-        inventoryItemBoardInft = std::make_unique<InventoryItemBoardIntf>(
-            utils::DBusHandler::getBus(), inventoryPath.c_str());
+        inventoryItemBoardInft =
+            std::make_unique<pldm::dbus_api::PldmEntityReq>(
+                utils::DBusHandler::getBus(), inventoryPath.c_str());
         return true;
     }
     catch (const sdbusplus::exception_t& e)
@@ -216,7 +224,8 @@
 
     if (createInventoryPath(terminusName))
     {
-        lg2::error("Terminus ID {TID}: Created Inventory path.", "TID", tid);
+        lg2::error("Terminus ID {TID}: Created Inventory path {PATH}.", "TID",
+                   tid, "PATH", inventoryPath);
     }
 
     for (auto pdr : numericSensorPdrs)
@@ -569,5 +578,134 @@
 
     return nullptr;
 }
+
+/** @brief Check if a pointer is go through end of table
+ *  @param[in] table - pointer to FRU record table
+ *  @param[in] p - pointer to each record of FRU record table
+ *  @param[in] tableSize - FRU table size
+ */
+static bool isTableEnd(const uint8_t* table, const uint8_t* p,
+                       const size_t tableSize)
+{
+    auto offset = p - table;
+    return (tableSize - offset) < sizeof(struct pldm_fru_record_data_format);
+}
+
+void Terminus::updateInventoryWithFru(const uint8_t* fruData,
+                                      const size_t fruLen)
+{
+    auto tmp = getTerminusName();
+    if (!tmp || tmp.value().empty())
+    {
+        lg2::error(
+            "Terminus ID {TID}: Failed to update Inventory with Fru Data - error : Terminus name is empty.",
+            "TID", tid);
+        return;
+    }
+
+    if (createInventoryPath(static_cast<std::string>(tmp.value())))
+    {
+        lg2::info("Terminus ID {TID}: Created Inventory path.", "TID", tid);
+    }
+
+    auto ptr = fruData;
+    while (!isTableEnd(fruData, ptr, fruLen))
+    {
+        auto record = reinterpret_cast<const pldm_fru_record_data_format*>(ptr);
+        ptr += sizeof(pldm_fru_record_data_format) -
+               sizeof(pldm_fru_record_tlv);
+
+        if (!record->num_fru_fields)
+        {
+            lg2::error(
+                "Invalid number of fields {NUM} of Record ID Type {TYPE} of terminus {TID}",
+                "NUM", record->num_fru_fields, "TYPE", record->record_type,
+                "TID", tid);
+            return;
+        }
+
+        if (record->record_type != PLDM_FRU_RECORD_TYPE_GENERAL)
+        {
+            lg2::error(
+                "Does not support Fru Record ID Type {TYPE} of terminus {TID}",
+                "TYPE", record->record_type, "TID", tid);
+
+            for ([[maybe_unused]] const auto& idx :
+                 std::views::iota(0, static_cast<int>(record->num_fru_fields)))
+            {
+                auto tlv = reinterpret_cast<const pldm_fru_record_tlv*>(ptr);
+                ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length;
+            }
+            continue;
+        }
+        /* FRU General record type */
+        for ([[maybe_unused]] const auto& idx :
+             std::views::iota(0, static_cast<int>(record->num_fru_fields)))
+        {
+            auto tlv = reinterpret_cast<const pldm_fru_record_tlv*>(ptr);
+            std::string fruField{};
+            if (tlv->type != PLDM_FRU_FIELD_TYPE_IANA)
+            {
+                auto strOptional =
+                    pldm::utils::fruFieldValuestring(tlv->value, tlv->length);
+                if (!strOptional)
+                {
+                    ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length;
+                    continue;
+                }
+                fruField = strOptional.value();
+
+                if (fruField.empty())
+                {
+                    ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length;
+                    continue;
+                }
+            }
+
+            switch (tlv->type)
+            {
+                case PLDM_FRU_FIELD_TYPE_MODEL:
+                    inventoryItemBoardInft->model(fruField);
+                    break;
+                case PLDM_FRU_FIELD_TYPE_PN:
+                    inventoryItemBoardInft->partNumber(fruField);
+                    break;
+                case PLDM_FRU_FIELD_TYPE_SN:
+                    inventoryItemBoardInft->serialNumber(fruField);
+                    break;
+                case PLDM_FRU_FIELD_TYPE_MANUFAC:
+                    inventoryItemBoardInft->manufacturer(fruField);
+                    break;
+                case PLDM_FRU_FIELD_TYPE_NAME:
+                    inventoryItemBoardInft->names({fruField});
+                    break;
+                case PLDM_FRU_FIELD_TYPE_VERSION:
+                    inventoryItemBoardInft->version(fruField);
+                    break;
+                case PLDM_FRU_FIELD_TYPE_ASSET_TAG:
+                    inventoryItemBoardInft->assetTag(fruField);
+                    break;
+                case PLDM_FRU_FIELD_TYPE_VENDOR:
+                case PLDM_FRU_FIELD_TYPE_CHASSIS:
+                case PLDM_FRU_FIELD_TYPE_SKU:
+                case PLDM_FRU_FIELD_TYPE_DESC:
+                case PLDM_FRU_FIELD_TYPE_EC_LVL:
+                case PLDM_FRU_FIELD_TYPE_OTHER:
+                    break;
+                case PLDM_FRU_FIELD_TYPE_IANA:
+                    auto iana =
+                        pldm::utils::fruFieldParserU32(tlv->value, tlv->length);
+                    if (!iana)
+                    {
+                        ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length;
+                        continue;
+                    }
+                    break;
+            }
+            ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length;
+        }
+    }
+}
+
 } // namespace platform_mc
 } // namespace pldm