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/include/firmware_inventory.hpp b/include/firmware_inventory.hpp
new file mode 100644
index 0000000..ee27050
--- /dev/null
+++ b/include/firmware_inventory.hpp
@@ -0,0 +1,99 @@
+#pragma once
+#include "smbios_mdrv2.hpp"
+
+#include <sdbusplus/asio/connection.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Inventory/Decorator/Asset/server.hpp>
+#include <xyz/openbmc_project/Inventory/Item/server.hpp>
+#include <xyz/openbmc_project/Software/ExtendedVersion/server.hpp>
+#include <xyz/openbmc_project/Software/Version/server.hpp>
+
+#include <vector>
+
+namespace phosphor
+{
+
+namespace smbios
+{
+namespace utils
+{
+std::vector<std::string> getExistingVersionPaths(sdbusplus::bus_t& bus);
+}
+
+using association =
+    sdbusplus::server::xyz::openbmc_project::association::Definitions;
+using asset =
+    sdbusplus::server::xyz::openbmc_project::inventory::decorator::Asset;
+using item = sdbusplus::server::xyz::openbmc_project::inventory::Item;
+using softwareVersion =
+    sdbusplus::server::xyz::openbmc_project::software::Version;
+using softwareExtendedVersion =
+    sdbusplus::server::xyz::openbmc_project::software::ExtendedVersion;
+
+class FirmwareInventory :
+    sdbusplus::server::object_t<asset, item, association, softwareVersion,
+                                softwareExtendedVersion>
+{
+  public:
+    FirmwareInventory() = delete;
+    ~FirmwareInventory() = default;
+    FirmwareInventory(const FirmwareInventory&) = delete;
+    FirmwareInventory& operator=(const FirmwareInventory&) = delete;
+    FirmwareInventory(FirmwareInventory&&) = default;
+    FirmwareInventory& operator=(FirmwareInventory&&) = default;
+
+    FirmwareInventory(sdbusplus::bus_t& bus, const std::string& objPath,
+                      const uint8_t index, uint8_t* smbiosTableStorage) :
+        sdbusplus::server::object_t<asset, item, association, softwareVersion,
+                                    softwareExtendedVersion>(bus,
+                                                             objPath.c_str()),
+        firmwareInventoryIndex(index), storage(smbiosTableStorage)
+    {
+        firmwareInfoUpdate(smbiosTableStorage);
+    }
+
+    void firmwareInfoUpdate(uint8_t* smbiosTableStorage);
+
+    static std::string checkAndCreateFirmwarePath(
+        uint8_t* dataIn, int index,
+        std::vector<std::string>& existingVersionPaths);
+
+  private:
+    int firmwareInventoryIndex;
+
+    uint8_t* storage;
+
+    struct FirmwareInfo
+    {
+        uint8_t type;
+        uint8_t length;
+        uint16_t handle;
+        uint8_t componentName;
+        uint8_t version;
+        uint8_t versionFormat;
+        uint8_t id;
+        uint8_t idFormat;
+        uint8_t releaseDate;
+        uint8_t manufacturer;
+        uint8_t lowestSupportedVersion;
+        uint64_t imageSize;
+        uint16_t characteristics;
+        uint8_t state;
+        uint8_t numOfAssociatedComponents;
+        uint16_t associatedComponentHandles[1];
+    } __attribute__((packed));
+
+    void firmwareId(const uint8_t positionNum, const uint8_t structLen,
+                    uint8_t* dataIn);
+    void firmwareVersion(const uint8_t positionNum, const uint8_t structLen,
+                         uint8_t* dataIn);
+    void firmwareReleaseDate(const uint8_t positionNum, const uint8_t structLen,
+                             uint8_t* dataIn);
+    void firmwareManufacturer(const uint8_t positionNum,
+                              const uint8_t structLen, uint8_t* dataIn);
+    void firmwareComponentName(const uint8_t positionNum,
+                               const uint8_t structLen, uint8_t* dataIn);
+    static bool getFirmwareInventoryData(uint8_t*& dataIn, int inventoryIndex);
+};
+} // namespace smbios
+} // namespace phosphor
diff --git a/include/mdrv2.hpp b/include/mdrv2.hpp
index 2459a89..c60633c 100644
--- a/include/mdrv2.hpp
+++ b/include/mdrv2.hpp
@@ -17,6 +17,7 @@
 #pragma once
 #include "cpu.hpp"
 #include "dimm.hpp"
+#include "firmware_inventory.hpp"
 #include "pcieslot.hpp"
 #include "smbios_mdrv2.hpp"
 #include "system.hpp"
@@ -62,6 +63,8 @@
     "xyz.openbmc_project.Inventory.Item.System";
 static constexpr const char* boardInterface =
     "xyz.openbmc_project.Inventory.Item.Board";
+static constexpr const char* versionInterface =
+    "xyz.openbmc_project.Software.Version";
 constexpr const int limitEntryLen = 0xff;
 
 // Avoid putting multiple interfaces with same name on same object
@@ -190,10 +193,12 @@
     std::optional<size_t> getTotalDimmSlot(void);
     std::optional<size_t> getTotalPcieSlot(void);
     std::optional<size_t> getTotalTpm(void);
+    std::optional<size_t> getTotalFirmwareInventory(void);
     std::vector<std::unique_ptr<Cpu>> cpus;
     std::vector<std::unique_ptr<Dimm>> dimms;
     std::vector<std::unique_ptr<Pcie>> pcies;
     std::vector<std::unique_ptr<Tpm>> tpms;
+    std::vector<std::unique_ptr<FirmwareInventory>> firmwareCollection;
     std::unique_ptr<System> system;
     std::shared_ptr<sdbusplus::asio::dbus_interface> smbiosInterface;
 
diff --git a/include/smbios_mdrv2.hpp b/include/smbios_mdrv2.hpp
index 2be6dad..c2f1b4b 100644
--- a/include/smbios_mdrv2.hpp
+++ b/include/smbios_mdrv2.hpp
@@ -157,6 +157,13 @@
     uint64_t structTableAddr;
 } __attribute__((packed));
 
+struct StructureHeader
+{
+    uint8_t type;
+    uint8_t length;
+    uint16_t handle;
+} __attribute__((packed));
+
 static constexpr const char* cpuSuffix = "/chassis/motherboard/cpu";
 
 static constexpr const char* dimmSuffix = "/chassis/motherboard/dimm";
@@ -167,6 +174,8 @@
 
 static constexpr const char* tpmSuffix = "/chassis/motherboard/tpm";
 
+static constexpr const char* firmwarePath = "/xyz/openbmc_project/software";
+
 constexpr std::array<SMBIOSVersion, 8> supportedSMBIOSVersions{
     SMBIOSVersion{3, 0}, SMBIOSVersion{3, 2}, SMBIOSVersion{3, 3},
     SMBIOSVersion{3, 4}, SMBIOSVersion{3, 5}, SMBIOSVersion{3, 6},
@@ -192,7 +201,10 @@
     systemEventLogType = 15,
     physicalMemoryArrayType = 16,
     memoryDeviceType = 17,
+    systemPowerSupply = 39,
+    onboardDevicesExtended = 41,
     tpmDeviceType = 43,
+    firmwareInventoryInformationType = 45,
 } SmbiosType;
 
 static constexpr uint8_t separateLen = 2;
@@ -256,6 +268,51 @@
     return nullptr;
 }
 
+static inline uint8_t* smbiosSkipEntryPoint(uint8_t* smbiosDataIn)
+{
+    const std::string anchorString30 = "_SM3_";
+    if (smbiosDataIn == nullptr)
+    {
+        return nullptr;
+    }
+
+    // Jump to starting address of the SMBIOS Structure Table from Entry Point
+    auto anchor = reinterpret_cast<const char*>(smbiosDataIn);
+    if (std::string_view(anchor, anchorString30.length())
+            .compare(anchorString30) == 0)
+    {
+        auto epStructure =
+            reinterpret_cast<const EntryPointStructure30*>(smbiosDataIn);
+        if (epStructure->structTableAddr < mdrSMBIOSSize)
+        {
+            smbiosDataIn += epStructure->structTableAddr;
+        }
+    }
+
+    return smbiosDataIn;
+}
+
+static inline uint8_t* smbiosHandlePtr(uint8_t* smbiosDataIn, uint16_t handle)
+{
+    auto ptr = smbiosSkipEntryPoint(smbiosDataIn);
+    struct StructureHeader* header;
+    while (ptr != nullptr)
+    {
+        header = reinterpret_cast<struct StructureHeader*>(ptr);
+        if (header->length < sizeof(StructureHeader))
+        {
+            return nullptr;
+        }
+
+        if (header->handle == handle)
+        {
+            return ptr;
+        }
+        ptr = smbiosNextPtr(ptr);
+    }
+    return nullptr;
+}
+
 static inline std::string positionToString(uint8_t positionNum,
                                            uint8_t structLen, uint8_t* dataIn)
 {