rtu: add serial port interface

Add the interface classes for serial port with Port Factory classes to
make the code extensible for future in case a new hardware port type is
introduced. This also makes the unit testing easy by creating a Mock
Port using socat.

Tested:
```
meson test -C builddir test_port
ninja: Entering directory `/host/repos/Modbus/phosphor-modbus/builddir'
ninja: no work to do.
1/1 test_port        OK              5.02s

Ok:                1
Fail:              0
```

Change-Id: Ic6bd982abf1ae993f76c39e3503d3a0402a692fe
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/rtu/port/base_port.hpp b/rtu/port/base_port.hpp
new file mode 100644
index 0000000..6a30dab
--- /dev/null
+++ b/rtu/port/base_port.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "modbus/modbus.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <xyz/openbmc_project/Configuration/USBPort/client.hpp>
+
+#include <concepts>
+
+namespace phosphor::modbus::rtu::port
+{
+
+using ModbusIntf = phosphor::modbus::rtu::Modbus;
+
+namespace config
+{
+
+enum class PortMode
+{
+    rs232,
+    rs485,
+    unknown
+};
+
+static constexpr std::array<std::pair<std::string_view, PortMode>, 2>
+    validPortModes = {{{"RS232", PortMode::rs232}, {"RS485", PortMode::rs485}}};
+
+struct Config
+{
+    std::string name = "unknown";
+    PortMode portMode = PortMode::unknown;
+    uint32_t baudRate = 0;
+    uint16_t rtsDelay = 0;
+};
+
+template <typename T>
+concept HasPropertiesMembers = requires(T properties) {
+                                   {
+                                       properties.name
+                                   } -> std::same_as<std::string&>;
+                                   {
+                                       properties.mode
+                                   } -> std::same_as<std::string&>;
+                                   {
+                                       properties.baud_rate
+                                   } -> std::same_as<uint64_t&>;
+                                   {
+                                       properties.rts_delay
+                                   } -> std::same_as<uint64_t&>;
+                               };
+
+template <typename T>
+concept HasConfigMembers = requires(T config) {
+                               { config.name } -> std::same_as<std::string&>;
+                               { config.portMode } -> std::same_as<PortMode&>;
+                               { config.baudRate } -> std::same_as<uint32_t&>;
+                               { config.rtsDelay } -> std::same_as<uint16_t&>;
+                           };
+
+template <HasConfigMembers BaseConfig, HasPropertiesMembers BaseProperties>
+auto updateBaseConfig(BaseConfig& config, const BaseProperties& properties)
+    -> bool
+{
+    PHOSPHOR_LOG2_USING;
+
+    config.name = properties.name;
+    config.baudRate = static_cast<uint32_t>(properties.baud_rate);
+    config.rtsDelay = static_cast<uint16_t>(properties.rts_delay);
+
+    for (const auto& [modeStr, portMode] : config::validPortModes)
+    {
+        if (modeStr == properties.mode)
+        {
+            config.portMode = portMode;
+            break;
+        }
+    }
+    if (config.portMode == PortMode::unknown)
+    {
+        error("Invalid port mode {PORT_MODE} for {NAME}", "PORT_MODE",
+              properties.mode, "NAME", properties.name);
+        return false;
+    }
+
+    debug("Base Port config: {NAME} {PORT_MODE} {BAUD_RATE} {RTS_DELAY}",
+          "NAME", config.name, "PORT_MODE", config.portMode, "BAUD_RATE",
+          config.baudRate, "RTS_DELAY", config.rtsDelay);
+
+    return true;
+}
+
+} // namespace config
+
+class BasePort
+{
+  public:
+    explicit BasePort(sdbusplus::async::context& ctx,
+                      const config::Config& config,
+                      const std::string& devicePath);
+
+    auto readHoldingRegisters(uint8_t deviceAddress, uint16_t registerOffset,
+                              uint32_t baudRate, Parity parity,
+                              std::vector<uint16_t>& registers)
+        -> sdbusplus::async::task<bool>;
+
+  private:
+    std::string name;
+    int fd = -1;
+    std::unique_ptr<ModbusIntf> modbus;
+    sdbusplus::async::mutex mutex;
+};
+
+} // namespace phosphor::modbus::rtu::port