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/dbus_impl_fru.cpp b/platform-mc/dbus_impl_fru.cpp
new file mode 100644
index 0000000..c3076b0
--- /dev/null
+++ b/platform-mc/dbus_impl_fru.cpp
@@ -0,0 +1,61 @@
+#include "dbus_impl_fru.hpp"
+
+#include <iostream>
+
+namespace pldm
+{
+namespace dbus_api
+{
+
+std::string PldmEntityReq::partNumber(std::string value)
+{
+    return assetserver::partNumber(value);
+}
+
+std::string PldmEntityReq::serialNumber(std::string value)
+{
+    return assetserver::serialNumber(value);
+}
+
+std::string PldmEntityReq::manufacturer(std::string value)
+{
+    return assetserver::manufacturer(value);
+}
+
+std::string PldmEntityReq::buildDate(std::string value)
+{
+    return assetserver::buildDate(value);
+}
+
+std::string PldmEntityReq::model(std::string value)
+{
+    return assetserver::model(value);
+}
+
+std::string PldmEntityReq::subModel(std::string value)
+{
+    return assetserver::subModel(value);
+}
+
+std::string PldmEntityReq::sparePartNumber(std::string value)
+{
+    return assetserver::sparePartNumber(value);
+}
+
+std::string PldmEntityReq::assetTag(std::string value)
+{
+    return assettagserver::assetTag(value);
+}
+
+std::string PldmEntityReq::version(std::string value)
+{
+    return revisionserver::version(value);
+}
+
+std::vector<std::string> PldmEntityReq::names(std::vector<std::string> values)
+{
+    return compatibleserver::names(values);
+}
+
+} // namespace dbus_api
+} // namespace pldm
diff --git a/platform-mc/dbus_impl_fru.hpp b/platform-mc/dbus_impl_fru.hpp
new file mode 100644
index 0000000..0eba375
--- /dev/null
+++ b/platform-mc/dbus_impl_fru.hpp
@@ -0,0 +1,96 @@
+#pragma once
+
+#include "xyz/openbmc_project/Inventory/Decorator/Asset/server.hpp"
+#include "xyz/openbmc_project/Inventory/Decorator/AssetTag/server.hpp"
+#include "xyz/openbmc_project/Inventory/Decorator/Compatible/server.hpp"
+#include "xyz/openbmc_project/Inventory/Decorator/Revision/server.hpp"
+#include "xyz/openbmc_project/Inventory/Item/Board/server.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+
+#include <map>
+
+namespace pldm
+{
+namespace dbus_api
+{
+
+using assetserver =
+    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::Asset;
+using assettagserver =
+    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::AssetTag;
+using revisionserver =
+    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::Revision;
+using compatibleserver =
+    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::Compatible;
+using boardserver =
+    sdbusplus::xyz::openbmc_project::Inventory::Item::server::Board;
+
+using AssetIntf = sdbusplus::server::object::object<assetserver>;
+using AssetTagIntf = sdbusplus::server::object::object<assettagserver>;
+using RevisionIntf = sdbusplus::server::object::object<revisionserver>;
+using CompatibleIntf = sdbusplus::server::object::object<compatibleserver>;
+using BoardIntf = sdbusplus::server::object::object<boardserver>;
+
+/** @class PldmEntityRequester
+ *  @brief OpenBMC PLDM Inventory entity implementation.
+ *  @details A concrete implementation for the PLDM Inventory entity DBus APIs.
+ */
+class PldmEntityReq :
+    public AssetIntf,
+    public AssetTagIntf,
+    public RevisionIntf,
+    public CompatibleIntf,
+    public BoardIntf
+{
+  public:
+    PldmEntityReq() = delete;
+    PldmEntityReq(const PldmEntityReq&) = delete;
+    PldmEntityReq& operator=(const PldmEntityReq&) = delete;
+    PldmEntityReq(PldmEntityReq&&) = delete;
+    PldmEntityReq& operator=(PldmEntityReq&&) = delete;
+    virtual ~PldmEntityReq() = default;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     */
+    PldmEntityReq(sdbusplus::bus_t& bus, const std::string& path) :
+        AssetIntf(bus, path.c_str()), AssetTagIntf(bus, path.c_str()),
+        RevisionIntf(bus, path.c_str()), CompatibleIntf(bus, path.c_str()),
+        BoardIntf(bus, path.c_str()) {};
+
+    /** @brief Set value of partNumber in Decorator.Asset */
+    std::string partNumber(std::string value);
+
+    /** @brief Set value of serialNumber in Decorator.Asset */
+    std::string serialNumber(std::string value);
+
+    /** @brief Set value of manufacturer in Decorator.Asset */
+    std::string manufacturer(std::string value);
+
+    /** @brief Set value of buildDate in Decorator.Asset */
+    std::string buildDate(std::string value);
+
+    /** @brief Set value of model in Decorator.Asset */
+    std::string model(std::string value);
+
+    /** @brief Set value of subModel in Decorator.Asset */
+    std::string subModel(std::string value);
+
+    /** @brief Set value of sparePartNumber in Decorator.Asset */
+    std::string sparePartNumber(std::string value);
+
+    /** @brief Set value of assetTag in Decorator.AssetTag */
+    std::string assetTag(std::string value);
+
+    /** @brief Set value of version in Decorator.Revision */
+    std::string version(std::string value);
+
+    /** @brief Set value of names in in Decorator.Compatible */
+    std::vector<std::string> names(std::vector<std::string> values);
+};
+
+} // namespace dbus_api
+} // namespace pldm
diff --git a/platform-mc/platform_manager.cpp b/platform-mc/platform_manager.cpp
index 4725c63..2b11ef6 100644
--- a/platform-mc/platform_manager.cpp
+++ b/platform-mc/platform_manager.cpp
@@ -22,6 +22,39 @@
             continue;
         }
 
+        /* Get Fru */
+        uint16_t totalTableRecords = 0;
+        if (terminus->doesSupportCommand(PLDM_FRU,
+                                         PLDM_GET_FRU_RECORD_TABLE_METADATA))
+        {
+            auto rc =
+                co_await getFRURecordTableMetadata(tid, &totalTableRecords);
+            if (rc)
+            {
+                lg2::error(
+                    "Failed to get FRU Metadata for terminus {TID}, error {ERROR}",
+                    "TID", tid, "ERROR", rc);
+            }
+            if (!totalTableRecords)
+            {
+                lg2::info("Fru record table meta data has 0 records");
+            }
+        }
+
+        std::vector<uint8_t> fruData{};
+        if ((totalTableRecords != 0) &&
+            terminus->doesSupportCommand(PLDM_FRU, PLDM_GET_FRU_RECORD_TABLE))
+        {
+            auto rc =
+                co_await getFRURecordTables(tid, totalTableRecords, fruData);
+            if (rc)
+            {
+                lg2::error(
+                    "Failed to get Fru Record table for terminus {TID}, error {ERROR}",
+                    "TID", tid, "ERROR", rc);
+            }
+        }
+
         if (terminus->doesSupportCommand(PLDM_PLATFORM, PLDM_GET_PDR))
         {
             auto rc = co_await getPDRs(terminus);
@@ -36,6 +69,15 @@
             terminus->parseTerminusPDRs();
         }
 
+        /**
+         * Need terminus name from PDRs before updating Inventory object with
+         * Fru data
+         */
+        if (fruData.size())
+        {
+            updateInventoryWithFru(tid, fruData.data(), fruData.size());
+        }
+
         uint16_t terminusMaxBufferSize = terminus->maxBufferSize;
         if (!terminus->doesSupportCommand(PLDM_PLATFORM,
                                           PLDM_EVENT_MESSAGE_BUFFER_SIZE))
@@ -561,5 +603,211 @@
 
     co_return completionCode;
 }
+
+exec::task<int>
+    PlatformManager::getFRURecordTableMetadata(pldm_tid_t tid, uint16_t* total)
+{
+    Request request(
+        sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_METADATA_REQ_BYTES);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+
+    auto rc = encode_get_fru_record_table_metadata_req(
+        0, requestMsg, PLDM_GET_FRU_RECORD_TABLE_METADATA_REQ_BYTES);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to encode request GetFRURecordTableMetadata for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    const pldm_msg* responseMsg = nullptr;
+    size_t responseLen = 0;
+
+    rc = co_await terminusManager.sendRecvPldmMsg(tid, request, &responseMsg,
+                                                  &responseLen);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to send GetFRURecordTableMetadata message for terminus {TID}, error {RC}",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    uint8_t completionCode = 0;
+    if (responseMsg == nullptr || !responseLen)
+    {
+        lg2::error(
+            "No response data for GetFRURecordTableMetadata for terminus {TID}",
+            "TID", tid);
+        co_return rc;
+    }
+
+    uint8_t fru_data_major_version, fru_data_minor_version;
+    uint32_t fru_table_maximum_size, fru_table_length;
+    uint16_t total_record_set_identifiers;
+    uint32_t checksum;
+    rc = decode_get_fru_record_table_metadata_resp(
+        responseMsg, responseLen, &completionCode, &fru_data_major_version,
+        &fru_data_minor_version, &fru_table_maximum_size, &fru_table_length,
+        &total_record_set_identifiers, total, &checksum);
+
+    if (rc)
+    {
+        lg2::error(
+            "Failed to decode response GetFRURecordTableMetadata for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    if (completionCode != PLDM_SUCCESS)
+    {
+        lg2::error(
+            "Error : GetFRURecordTableMetadata for terminus ID {TID}, complete code {CC}.",
+            "TID", tid, "CC", completionCode);
+        co_return rc;
+    }
+
+    co_return rc;
+}
+
+exec::task<int> PlatformManager::getFRURecordTable(
+    pldm_tid_t tid, const uint32_t dataTransferHndl,
+    const uint8_t transferOpFlag, uint32_t* nextDataTransferHndl,
+    uint8_t* transferFlag, size_t* responseCnt,
+    std::vector<uint8_t>& recordData)
+{
+    Request request(sizeof(pldm_msg_hdr) + PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+
+    auto rc = encode_get_fru_record_table_req(
+        0, dataTransferHndl, transferOpFlag, requestMsg,
+        PLDM_GET_FRU_RECORD_TABLE_REQ_BYTES);
+    if (rc != PLDM_SUCCESS)
+    {
+        lg2::error(
+            "Failed to encode request GetFRURecordTable for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    const pldm_msg* responseMsg = nullptr;
+    size_t responseLen = 0;
+
+    rc = co_await terminusManager.sendRecvPldmMsg(tid, request, &responseMsg,
+                                                  &responseLen);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to send GetFRURecordTable message for terminus {TID}, error {RC}",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    uint8_t completionCode = 0;
+    if (responseMsg == nullptr || !responseLen)
+    {
+        lg2::error("No response data for GetFRURecordTable for terminus {TID}",
+                   "TID", tid);
+        co_return rc;
+    }
+
+    auto responsePtr = reinterpret_cast<const struct pldm_msg*>(responseMsg);
+    rc = decode_get_fru_record_table_resp(
+        responsePtr, responseLen - sizeof(pldm_msg_hdr), &completionCode,
+        nextDataTransferHndl, transferFlag, recordData.data(), responseCnt);
+
+    if (rc)
+    {
+        lg2::error(
+            "Failed to decode response GetFRURecordTable for terminus ID {TID}, error {RC} ",
+            "TID", tid, "RC", rc);
+        co_return rc;
+    }
+
+    if (completionCode != PLDM_SUCCESS)
+    {
+        lg2::error(
+            "Error : GetFRURecordTable for terminus ID {TID}, complete code {CC}.",
+            "TID", tid, "CC", completionCode);
+        co_return rc;
+    }
+
+    co_return rc;
+}
+
+void PlatformManager::updateInventoryWithFru(
+    pldm_tid_t tid, const uint8_t* fruData, const size_t fruLen)
+{
+    if (tid == PLDM_TID_RESERVED || !termini.contains(tid) || !termini[tid])
+    {
+        lg2::error("Invalid terminus {TID}", "TID", tid);
+        return;
+    }
+
+    termini[tid]->updateInventoryWithFru(fruData, fruLen);
+}
+
+exec::task<int> PlatformManager::getFRURecordTables(
+    pldm_tid_t tid, const uint16_t& totalTableRecords,
+    std::vector<uint8_t>& fruData)
+{
+    if (!totalTableRecords)
+    {
+        lg2::info("Fru record table has 0 records");
+        co_return PLDM_ERROR;
+    }
+
+    uint32_t dataTransferHndl = 0;
+    uint32_t nextDataTransferHndl = 0;
+    uint8_t transferFlag = 0;
+    uint8_t transferOpFlag = PLDM_GET_FIRSTPART;
+    size_t responseCnt = 0;
+    std::vector<uint8_t> recvBuf(PLDM_PLATFORM_GETPDR_MAX_RECORD_BYTES);
+
+    size_t fruLength = 0;
+    std::vector<uint8_t> receivedFru(0);
+    do
+    {
+        auto rc = co_await getFRURecordTable(
+            tid, dataTransferHndl, transferOpFlag, &nextDataTransferHndl,
+            &transferFlag, &responseCnt, recvBuf);
+
+        if (rc)
+        {
+            lg2::error(
+                "Failed to get Fru Record Data for terminus {TID}, error: {RC}, first part of data handle {RECORD}",
+                "TID", tid, "RC", rc, "RECORD", dataTransferHndl);
+            co_return rc;
+        }
+
+        receivedFru.insert(receivedFru.end(), recvBuf.begin(),
+                           recvBuf.begin() + responseCnt);
+        fruLength += responseCnt;
+        if (transferFlag == PLDM_PLATFORM_TRANSFER_START_AND_END ||
+            transferFlag == PLDM_PLATFORM_TRANSFER_END)
+        {
+            break;
+        }
+
+        // multipart transfer
+        dataTransferHndl = nextDataTransferHndl;
+        transferOpFlag = PLDM_GET_NEXTPART;
+
+    } while (nextDataTransferHndl != 0);
+
+    if (fruLength != receivedFru.size())
+    {
+        lg2::error(
+            "Size of Fru Record Data {SIZE} for terminus {TID} is different the responded size {RSPSIZE}.",
+            "SIZE", receivedFru.size(), "RSPSIZE", fruLength);
+        co_return PLDM_ERROR_INVALID_LENGTH;
+    }
+
+    fruData = receivedFru;
+
+    co_return PLDM_SUCCESS;
+}
+
 } // namespace platform_mc
 } // namespace pldm
diff --git a/platform-mc/platform_manager.hpp b/platform-mc/platform_manager.hpp
index 549a4e7..d16af75 100644
--- a/platform-mc/platform_manager.hpp
+++ b/platform-mc/platform_manager.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "libpldm/fru.h"
 #include "libpldm/platform.h"
 #include "libpldm/pldm.h"
 
@@ -138,6 +139,49 @@
         bitfield8_t& synchronyConfigurationSupported,
         uint8_t& numerEventClassReturned, std::vector<uint8_t>& eventClass);
 
+    /** @brief Get FRU Record Tables from remote MCTP Endpoint
+     *
+     *  @param[in] tid - Destination TID
+     *  @param[in] total - Total number of record in table
+     *  @param[out] fruData - Returned fru record table data
+     */
+    exec::task<int> getFRURecordTables(pldm_tid_t tid, const uint16_t& total,
+                                       std::vector<uint8_t>& fruData);
+
+    /** @brief Fetch FRU Record Data from terminus
+     *
+     *  @param[in] tid - Destination TID
+     *  @param[in] dataTransferHndl - Data transfer handle
+     *  @param[in] transferOpFlag - Transfer Operation Flag
+     *  @param[out] nextDataTransferHndl - Next data transfer handle
+     *  @param[out] transferFlag - Transfer flag
+     *  @param[out] responseCnt - Response count of record data
+     *  @param[out] recordData - Returned record data
+     *
+     *  @return coroutine return_value - PLDM completion code
+     */
+    exec::task<int> getFRURecordTable(
+        pldm_tid_t tid, const uint32_t dataTransferHndl,
+        const uint8_t transferOpFlag, uint32_t* nextDataTransferHndl,
+        uint8_t* transferFlag, size_t* responseCnt,
+        std::vector<uint8_t>& recordData);
+
+    /** @brief Get FRU Record Table Metadata from remote MCTP Endpoint
+     *
+     *  @param[in] tid - Destination TID
+     *  @param[out] total - Total number of record in table
+     */
+    exec::task<int> getFRURecordTableMetadata(pldm_tid_t tid, uint16_t* total);
+
+    /** @brief Parse record data from FRU table
+     *
+     *  @param[in] tid - Destination TID
+     *  @param[in] fruData - pointer to FRU record table
+     *  @param[in] fruLen - FRU table length
+     */
+    void updateInventoryWithFru(pldm_tid_t tid, const uint8_t* fruData,
+                                const size_t fruLen);
+
     /** reference of TerminusManager for sending PLDM request to terminus*/
     TerminusManager& terminusManager;
 
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
diff --git a/platform-mc/terminus.hpp b/platform-mc/terminus.hpp
index e818d32..10a8f55 100644
--- a/platform-mc/terminus.hpp
+++ b/platform-mc/terminus.hpp
@@ -1,15 +1,16 @@
 #pragma once
 
+#include "libpldm/fru.h"
 #include "libpldm/platform.h"
 
 #include "common/types.hpp"
+#include "dbus_impl_fru.hpp"
 #include "numeric_sensor.hpp"
 #include "requester/handler.hpp"
 #include "terminus.hpp"
 
 #include <sdbusplus/server/object.hpp>
 #include <sdeventplus/event.hpp>
-#include <xyz/openbmc_project/Inventory/Item/Board/server.hpp>
 
 #include <algorithm>
 #include <bitset>
@@ -34,8 +35,6 @@
 using SensorAuxiliaryNames = std::tuple<
     SensorId, SensorCnt,
     std::vector<std::vector<std::pair<NameLanguageTag, SensorName>>>>;
-using InventoryItemBoardIntf = sdbusplus::server::object_t<
-    sdbusplus::xyz::openbmc_project::Inventory::Item::server::Board>;
 
 /** @struct EntityKey
  *
@@ -149,6 +148,13 @@
         return terminusName;
     }
 
+    /** @brief Parse record data from FRU table
+     *
+     *  @param[in] fruData - pointer to FRU record table
+     *  @param[in] fruLen - FRU table length
+     */
+    void updateInventoryWithFru(const uint8_t* fruData, const size_t fruLen);
+
     /** @brief A list of PDRs fetched from Terminus */
     std::vector<std::vector<uint8_t>> pdrs{};
 
@@ -298,8 +304,9 @@
 
     /** @brief Terminus name */
     EntityName terminusName{};
-    /* @brief The pointer of iventory D-Bus interface for the terminus */
-    std::unique_ptr<InventoryItemBoardIntf> inventoryItemBoardInft = nullptr;
+    /* @brief The pointer of inventory D-Bus interface for the terminus */
+    std::unique_ptr<pldm::dbus_api::PldmEntityReq> inventoryItemBoardInft =
+        nullptr;
 
     /* @brief Inventory D-Bus object path of the terminus */
     std::string inventoryPath;
diff --git a/platform-mc/test/meson.build b/platform-mc/test/meson.build
index 0e8bc87..e8ea6b7 100644
--- a/platform-mc/test/meson.build
+++ b/platform-mc/test/meson.build
@@ -4,6 +4,7 @@
         '../terminus.cpp',
         '../platform_manager.cpp',
         '../manager.cpp',
+        '../dbus_impl_fru.cpp',
         '../sensor_manager.cpp',
         '../numeric_sensor.cpp',
         '../event_manager.cpp',