smbios-mdr: Expose Firmware Inventory Information

SMBIOS Type 45 defines the data structure to expose Firmware
Inventory information. Added patch which exposes the same
on the DBus.

Introduced fw-inventory-dbus meson option which by default,
will be disabled. When enabled fw-inventory is exposed to dbus

Introduced expose-firmware-component-name meson option which
by default, will be disabled. When enabled fw-component name is
exposed as dbus object

Tested:
1) No crash is seen when firmware is flashed with change
2) Firmware Inventory information is available under dbus tree
```
busctl tree xyz.openbmc_project.Smbios.MDR_V2
`- /xyz
  `- /xyz/openbmc_project
    |- /xyz/openbmc_project/Smbios
    | `- /xyz/openbmc_project/Smbios/MDR_V2
    `- /xyz/openbmc_project/software
      `- /xyz/openbmc_project/software/UEFI
```

Change-Id: If5b367f21dedc0addef4f7b1d4c6dac6a5dc17c2
Signed-off-by: Prithvi Pai <ppai@nvidia.com>
diff --git a/src/firmware_inventory.cpp b/src/firmware_inventory.cpp
new file mode 100644
index 0000000..30053d5
--- /dev/null
+++ b/src/firmware_inventory.cpp
@@ -0,0 +1,218 @@
+#include "firmware_inventory.hpp"
+
+#include "mdrv2.hpp"
+
+#include <boost/algorithm/string.hpp>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <regex>
+#include <sstream>
+
+namespace phosphor
+{
+namespace smbios
+{
+namespace utils
+{
+std::vector<std::string> getExistingVersionPaths(sdbusplus::bus_t& bus)
+{
+    std::vector<std::string> existingVersionPaths;
+
+    auto getVersionPaths = bus.new_method_call(
+        phosphor::smbios::mapperBusName, phosphor::smbios::mapperPath,
+        phosphor::smbios::mapperInterface, "GetSubTreePaths");
+    getVersionPaths.append(firmwarePath);
+    getVersionPaths.append(0);
+    getVersionPaths.append(
+        std::array<std::string, 1>({phosphor::smbios::versionInterface}));
+
+    try
+    {
+        auto reply = bus.call(getVersionPaths);
+        reply.read(existingVersionPaths);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to query version objects. ERROR={E}", "E", e.what());
+        existingVersionPaths.clear();
+    }
+
+    return existingVersionPaths;
+}
+} // namespace utils
+
+bool FirmwareInventory::getFirmwareInventoryData(uint8_t*& dataIn,
+                                                 int inventoryIndex)
+{
+    dataIn = getSMBIOSTypePtr(dataIn, firmwareInventoryInformationType);
+    if (dataIn == nullptr)
+    {
+        return false;
+    }
+    for (uint8_t index = 0; index < inventoryIndex; index++)
+    {
+        dataIn = smbiosNextPtr(dataIn);
+        if (dataIn == nullptr)
+        {
+            return false;
+        }
+        dataIn = getSMBIOSTypePtr(dataIn, firmwareInventoryInformationType);
+        if (dataIn == nullptr)
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
+void FirmwareInventory::firmwareInfoUpdate(uint8_t* smbiosTableStorage)
+{
+    uint8_t* dataIn = smbiosTableStorage;
+    if (!getFirmwareInventoryData(dataIn, firmwareInventoryIndex))
+    {
+        lg2::info("Failed to get data for firmware inventory index {I}", "I",
+                  firmwareInventoryIndex);
+        return;
+    }
+
+    auto firmwareInfo = reinterpret_cast<struct FirmwareInfo*>(dataIn);
+
+    firmwareComponentName(firmwareInfo->componentName, firmwareInfo->length,
+                          dataIn);
+    firmwareVersion(firmwareInfo->version, firmwareInfo->length, dataIn);
+    firmwareId(firmwareInfo->id, firmwareInfo->length, dataIn);
+    firmwareReleaseDate(firmwareInfo->releaseDate, firmwareInfo->length,
+                        dataIn);
+    firmwareManufacturer(firmwareInfo->manufacturer, firmwareInfo->length,
+                         dataIn);
+    present(true);
+    purpose(softwareVersion::VersionPurpose::Other);
+
+    std::vector<std::tuple<std::string, std::string, std::string>> assocs;
+    assocs.emplace_back("software_version", "functional",
+                        "/xyz/openbmc_project/software");
+    association::associations(assocs);
+}
+
+std::string FirmwareInventory::checkAndCreateFirmwarePath(
+    uint8_t* dataIn, int inventoryIndex,
+    std::vector<std::string>& existingVersionPaths)
+{
+    if (!getFirmwareInventoryData(dataIn, inventoryIndex))
+    {
+        lg2::info("Failed to get data for firmware inventory index {I}", "I",
+                  inventoryIndex);
+        return "";
+    }
+    auto firmwareInfo = reinterpret_cast<struct FirmwareInfo*>(dataIn);
+    std::string firmwareId =
+        positionToString(firmwareInfo->id, firmwareInfo->length, dataIn);
+    auto firmwareName = positionToString(firmwareInfo->componentName,
+                                         firmwareInfo->length, dataIn);
+    std::string firmwareObjPath = "";
+#ifdef EXPOSE_FW_COMPONENT_NAME
+    firmwareObjPath = firmwareName;
+#else
+    firmwareObjPath = firmwareId;
+#endif
+    if (firmwareInfo->numOfAssociatedComponents > 0)
+    {
+        for (int i = 0; i < firmwareInfo->numOfAssociatedComponents; i++)
+        {
+            auto component = smbiosHandlePtr(
+                dataIn, firmwareInfo->associatedComponentHandles[i]);
+            if (component == nullptr)
+            {
+                continue;
+            }
+
+            auto header = reinterpret_cast<struct StructureHeader*>(component);
+            switch (header->type)
+            {
+                case processorsType:
+                case systemSlots:
+                case onboardDevicesExtended:
+                {
+                    auto designation = positionToString(
+                        component[4], header->length, component);
+                    if (!designation.empty())
+                    {
+                        firmwareObjPath.append("_").append(designation);
+                    }
+                    break;
+                }
+                case systemPowerSupply:
+                {
+                    auto location = positionToString(component[5],
+                                                     header->length, component);
+                    if (!location.empty())
+                    {
+                        firmwareObjPath.append("_").append(location);
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+    }
+    if (firmwareObjPath.empty())
+    {
+        firmwareObjPath = "firmware" + std::to_string(inventoryIndex);
+    }
+    boost::algorithm::trim_right(firmwareObjPath);
+    firmwareObjPath =
+        std::regex_replace(firmwareObjPath, std::regex("[^a-zA-Z0-9_/]+"), "_");
+
+    auto eqObjName = [firmwareObjPath](std::string s) {
+        std::filesystem::path p(s);
+        return p.filename().compare(firmwareObjPath) == 0;
+    };
+    if (std::find_if(existingVersionPaths.begin(), existingVersionPaths.end(),
+                     std::move(eqObjName)) != existingVersionPaths.end())
+    {
+        return "";
+    }
+    std::string path = firmwarePath;
+    path.append("/").append(firmwareObjPath);
+    return path;
+}
+
+void FirmwareInventory::firmwareComponentName(
+    const uint8_t positionNum, const uint8_t structLen, uint8_t* dataIn)
+{
+    std::string result = positionToString(positionNum, structLen, dataIn);
+    prettyName(result);
+}
+
+void FirmwareInventory::firmwareVersion(
+    const uint8_t positionNum, const uint8_t structLen, uint8_t* dataIn)
+{
+    std::string result = positionToString(positionNum, structLen, dataIn);
+    version(result);
+}
+
+void FirmwareInventory::firmwareId(const uint8_t positionNum,
+                                   const uint8_t structLen, uint8_t* dataIn)
+{
+    std::string result = positionToString(positionNum, structLen, dataIn);
+    extendedVersion(result);
+}
+
+void FirmwareInventory::firmwareReleaseDate(
+    const uint8_t positionNum, const uint8_t structLen, uint8_t* dataIn)
+{
+    std::string result = positionToString(positionNum, structLen, dataIn);
+    buildDate(result);
+}
+
+void FirmwareInventory::firmwareManufacturer(
+    const uint8_t positionNum, const uint8_t structLen, uint8_t* dataIn)
+{
+    std::string result = positionToString(positionNum, structLen, dataIn);
+    manufacturer(result);
+}
+} // namespace smbios
+} // namespace phosphor
diff --git a/src/mdrv2.cpp b/src/mdrv2.cpp
index f33fbc1..8395c94 100644
--- a/src/mdrv2.cpp
+++ b/src/mdrv2.cpp
@@ -651,6 +651,39 @@
     }
 #endif
 
+#ifdef FIRMWARE_INVENTORY_DBUS
+
+    auto existingVersionPaths = utils::getExistingVersionPaths(*bus);
+    num = getTotalFirmwareInventory();
+    if (!num)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "get firmware inventory failed");
+        existingVersionPaths.clear();
+        return;
+    }
+
+    // In case the new size is smaller than old, trim the vector
+    if (*num < firmwareCollection.size())
+    {
+        firmwareCollection.resize(*num);
+    }
+    for (unsigned int index = 0; index < *num; index++)
+    {
+        auto path = FirmwareInventory::checkAndCreateFirmwarePath(
+            smbiosDir.dir[smbiosDirIndex].dataStorage, index,
+            existingVersionPaths);
+        if (path.empty())
+        {
+            continue;
+        }
+        firmwareCollection.emplace_back(
+            std::make_unique<phosphor::smbios::FirmwareInventory>(
+                *bus, path, index, smbiosDir.dir[smbiosDirIndex].dataStorage));
+    }
+
+#endif
+
     system.reset();
     system = std::make_unique<System>(bus, smbiosInventoryPath + systemSuffix,
                                       smbiosDir.dir[smbiosDirIndex].dataStorage,
@@ -800,6 +833,40 @@
     return num;
 }
 
+std::optional<size_t> MDRV2::getTotalFirmwareInventory()
+{
+    uint8_t* dataIn = smbiosDir.dir[smbiosDirIndex].dataStorage;
+    size_t num = 0;
+
+    if (dataIn == nullptr)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Fail to get firmware inventory - no storage data");
+        return std::nullopt;
+    }
+
+    while (1)
+    {
+        dataIn = getSMBIOSTypePtr(dataIn, firmwareInventoryInformationType);
+        if (dataIn == nullptr)
+        {
+            break;
+        }
+        num++;
+        dataIn = smbiosNextPtr(dataIn);
+        if (dataIn == nullptr)
+        {
+            break;
+        }
+        if (num >= limitEntryLen)
+        {
+            break;
+        }
+    }
+
+    return num;
+}
+
 bool MDRV2::checkSMBIOSVersion(uint8_t* dataIn)
 {
     const std::string anchorString21 = "_SM_";
diff --git a/src/meson.build b/src/meson.build
index 3a2bc17..d94397e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -19,6 +19,14 @@
     cpp_args_smbios += ['-DTPM_DBUS']
 endif
 
+if get_option('firmware-inventory-dbus').allowed()
+    cpp_args_smbios += ['-DFIRMWARE_INVENTORY_DBUS']
+endif
+
+if get_option('expose-firmware-component-name').allowed()
+    cpp_args_smbios += ['-DEXPOSE_FW_COMPONENT_NAME']
+endif
+
 executable(
     'smbiosmdrv2app',
     'mdrv2.cpp',
@@ -28,6 +36,7 @@
     'system.cpp',
     'pcieslot.cpp',
     'tpm.cpp',
+    'firmware_inventory.cpp',
     cpp_args: cpp_args_smbios,
     dependencies: [
         boost_dep,