add firmware inventory support

Add support to display the firmware version using the Firmware version
register from EM configuration. The version will be exposed as
xyz.openbmc_project.Software.Version interface on the Dbus.

Tested:
Unit Test -
```
> meson test -t 10 -C builddir/ --print-errorlogs --wrapper="valgrind --error-exitcode=1" test_firmware
ninja: Entering directory `/host/repos/Modbus/phosphor-modbus/builddir'
ninja: no work to do.
1/1 test_firmware        OK               3.37s

Ok:                1
Fail:              0
```

Tested on Qemu -
```
> busctl tree xyz.openbmc_project.ModbusRTU
`- /xyz
  `- /xyz/openbmc_project
    |- /xyz/openbmc_project/inventory_source
    | |- /xyz/openbmc_project/inventory_source/Heat_Exchanger_12_DevTTYUSB0
    | |- /xyz/openbmc_project/inventory_source/Heat_Exchanger_12_DevTTYUSB1
    | |- /xyz/openbmc_project/inventory_source/Reservoir_Pumping_Unit_12_DevTTYUSB0
    | `- /xyz/openbmc_project/inventory_source/Reservoir_Pumping_Unit_12_DevTTYUSB1
    |- /xyz/openbmc_project/sensors
    | `- /xyz/openbmc_project/sensors/temperature
    |   |- /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB0_RPU_Coolant_Inlet_Temp_C
    |   |- /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB0_RPU_Coolant_Outlet_Temp_C
    |   |- /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB1_RPU_Coolant_Inlet_Temp_C
    |   `- /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB1_RPU_Coolant_Outlet_Temp_C
    `- /xyz/openbmc_project/software
      |- /xyz/openbmc_project/software/Reservoir_Pumping_Unit_12_DevTTYUSB0_RPU_PLC_FW_Revision_9071
      `- /xyz/openbmc_project/software/Reservoir_Pumping_Unit_12_DevTTYUSB1_RPU_PLC_FW_Revision_8053

> busctl  introspect xyz.openbmc_project.ModbusRTU /xyz/openbmc_project/software/Reservoir_Pumping_Unit_12_DevTTYUSB0_RPU_PLC_FW_Revision_9071
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.Association.Definitions interface -         -                                        -
.Associations                               property  a(sss)    1 "running" "ran_on" "/xyz/openbmc_pr... emits-change writable
xyz.openbmc_project.Software.Activation     interface -         -                                        -
.Activation                                 property  s         "xyz.openbmc_project.Software.Activat... emits-change writable
.RequestedActivation                        property  s         "xyz.openbmc_project.Software.Activat... emits-change writable
xyz.openbmc_project.Software.Version        interface -         -                                        -
.Purpose                                    property  s         "xyz.openbmc_project.Software.Version... emits-change writable
.Version                                    property  s         "ABABABAB"                               emits-change writable
```

Change-Id: I985e12ef88547585cca93569b083f347e74a8695
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/rtu/firmware/device_firmware.cpp b/rtu/firmware/device_firmware.cpp
new file mode 100644
index 0000000..7626076
--- /dev/null
+++ b/rtu/firmware/device_firmware.cpp
@@ -0,0 +1,119 @@
+#include "device_firmware.hpp"
+
+#include "device/base_device.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace phosphor::modbus::rtu::device
+{
+
+PHOSPHOR_LOG2_USING;
+
+static auto getRandomId() -> long int
+{
+    struct timespec ts;
+    clock_gettime(CLOCK_REALTIME, &ts);
+    unsigned int seed = ts.tv_nsec ^ getpid();
+    srandom(seed);
+    return random() % 10000;
+}
+
+static auto getObjectPath(const config_intf::Config& config)
+    -> sdbusplus::message::object_path
+{
+    for (const auto& firmwareRegister : config.firmwareRegisters)
+    {
+        if (firmwareRegister.type == config_intf::FirmwareRegisterType::version)
+        {
+            if (firmwareRegister.name.empty())
+            {
+                return sdbusplus::message::object_path(
+                           FirmwareIntf::namespace_path) /
+                       std::format("{}_{}", config.name, getRandomId());
+            }
+            else
+            {
+                return sdbusplus::message::object_path(
+                           FirmwareIntf::namespace_path) /
+                       std::format("{}_{}_{}", config.name,
+                                   firmwareRegister.name, getRandomId());
+            }
+        }
+    }
+
+    throw std::runtime_error(
+        "No firmware version register found for " + config.name);
+}
+
+constexpr FirmwareIntf::Version::properties_t initVersion{
+    "Unknown", FirmwareIntf::VersionPurpose::Other};
+constexpr FirmwareIntf::Activation::properties_t initActivation{
+    FirmwareIntf::Activations::NotReady,
+    FirmwareIntf::RequestedActivations::None};
+constexpr FirmwareIntf::Definitions::properties_t initAssociations{};
+
+DeviceFirmware::DeviceFirmware(sdbusplus::async::context& ctx,
+                               const config_intf::Config& config,
+                               PortIntf& serialPort) :
+    objectPath(getObjectPath(config)),
+    currentFirmware(
+        std::make_unique<FirmwareIntf>(ctx, objectPath.str.c_str(), initVersion,
+                                       initActivation, initAssociations)),
+    config(config), serialPort(serialPort)
+{
+    currentFirmware->Version::emit_added();
+    currentFirmware->Activation::emit_added();
+    currentFirmware->Definitions::emit_added();
+
+    info("Device firmware {NAME} created successfully", "NAME", config.name);
+}
+
+auto DeviceFirmware::readVersionRegister() -> sdbusplus::async::task<void>
+{
+    const auto it = std::find_if(
+        config.firmwareRegisters.begin(), config.firmwareRegisters.end(),
+        [](const config_intf::FirmwareRegister& firmwareRegister) {
+            return firmwareRegister.type ==
+                   config_intf::FirmwareRegisterType::version;
+        });
+
+    if (it == config.firmwareRegisters.end())
+    {
+        error("No firmware version register found for {NAME}", "NAME",
+              config.name);
+        co_return;
+    }
+
+    const config_intf::FirmwareRegister& versionRegister = *it;
+
+    auto registers = std::vector<uint16_t>(versionRegister.size);
+    auto ret = co_await serialPort.readHoldingRegisters(
+        config.address, versionRegister.offset, config.baudRate, config.parity,
+        registers);
+    if (!ret)
+    {
+        error("Failed to read holding registers {NAME} for {DEVICE_ADDRESS}",
+              "NAME", versionRegister.name, "DEVICE_ADDRESS", config.address);
+        co_return;
+    }
+
+    std::string strValue = "";
+
+    for (const auto& value : registers)
+    {
+        strValue += static_cast<char>((value >> 8) & 0xFF);
+        strValue += static_cast<char>(value & 0xFF);
+    }
+
+    currentFirmware->version(strValue);
+    currentFirmware->activation(FirmwareIntf::Activation::Activations::Active);
+    auto associationList =
+        std::vector<std::tuple<std::string, std::string, std::string>>{
+            {"running", "ran_on", config.inventoryPath}};
+    currentFirmware->associations(associationList);
+
+    info("Firmware version {VERSION} for {NAME} at {DEVICE_ADDRESS}", "VERSION",
+         strValue, "NAME", config.name, "DEVICE_ADDRESS", config.address);
+}
+
+} // namespace phosphor::modbus::rtu::device