rtu: add mock modbus test device
Add the mocked modbus test device/server. The purpose of this mocked
device is to help facilitate testing on Qemu VM. More details are
captured in Readme.md file.
Change-Id: I3b09f4057ea5cc03db101b81f435ae772d51d601
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/mocked_test_device/mock_modbus_server.cpp b/mocked_test_device/mock_modbus_server.cpp
new file mode 100644
index 0000000..e89ef5a
--- /dev/null
+++ b/mocked_test_device/mock_modbus_server.cpp
@@ -0,0 +1,156 @@
+#include "modbus/modbus.hpp"
+#include "modbus/modbus_commands.hpp"
+#include "modbus/modbus_message.hpp"
+
+#include <fcntl.h>
+
+#include <sdbusplus/async.hpp>
+
+#include <iostream>
+#include <string>
+
+namespace phosphor::modbus::test
+{
+
+class MessageIntf : public phosphor::modbus::rtu::Message
+{
+ friend class TestServer;
+};
+
+class TestServer
+{
+ public:
+ explicit TestServer(sdbusplus::async::context& ctx, int fd);
+
+ private:
+ auto processRequests() -> sdbusplus::async::task<void>;
+ void processMessage(MessageIntf& request, size_t requestSize,
+ MessageIntf& response);
+
+ void processReadHoldingRegisters(MessageIntf& request, size_t requestSize,
+ MessageIntf& response);
+
+ sdbusplus::async::context& ctx;
+ int fd;
+ sdbusplus::async::fdio fdioInstance;
+};
+
+TestServer::TestServer(sdbusplus::async::context& ctx, int fd) :
+ ctx(ctx), fd(fd), fdioInstance(ctx, fd)
+{
+ ctx.spawn(processRequests());
+}
+
+auto TestServer::processRequests() -> sdbusplus::async::task<void>
+{
+ while (!ctx.stop_requested())
+ {
+ MessageIntf request;
+ co_await fdioInstance.next();
+ auto ret = read(fd, request.raw.data(), request.raw.size());
+ // Request message need to be at least 4 bytes long - address(1),
+ // function code(1), ..., CRC(2)
+ if (ret < 4)
+ {
+ std::cerr << "Invalid Server message size:" << ret << ", drop it"
+ << std::endl;
+ continue;
+ }
+
+ MessageIntf response;
+ processMessage(request, ret, response);
+
+ ret = write(fd, response.raw.data(), response.len);
+ if (ret < 0)
+ {
+ std::cerr << "Failed to send response" << std::endl;
+ }
+ }
+}
+
+void TestServer::processMessage(MessageIntf& request, size_t requestSize,
+ MessageIntf& response)
+{
+ constexpr uint8_t readHoldingRegistersFunctionCode = 0x3;
+
+ switch (request.functionCode)
+ {
+ case readHoldingRegistersFunctionCode:
+ processReadHoldingRegisters(request, requestSize, response);
+ break;
+ default:
+ std::cerr << "Server received unknown request" << std::endl;
+ break;
+ }
+}
+
+void TestServer::processReadHoldingRegisters(
+ MessageIntf& request, size_t requestSize, MessageIntf& response)
+{
+ uint16_t registerOffset = request.raw[2] << 8 | request.raw[3];
+ uint16_t registerCount = request.raw[4] << 8 | request.raw[5];
+
+ std::cout << "Received readHoldingRegisters request with size:"
+ << requestSize << ", registerOffset:" << registerOffset
+ << ", registerCount:" << registerCount << std::endl;
+
+ response << request.raw[0] << request.raw[1] << uint8_t(2 * registerCount);
+ for (int i = 0; i < registerCount; i++)
+ {
+ constexpr uint16_t readHoldingRegisterResponse = 0x4142;
+ response << uint16_t(readHoldingRegisterResponse);
+ }
+ response.appendCRC();
+}
+
+} // namespace phosphor::modbus::test
+
+int main(int argc, char* argv[])
+{
+ if (argc < 2)
+ {
+ std::cerr << "Usage: " << argv[0] << " <integer_value>" << std::endl;
+ return 1;
+ }
+
+ int interfaceValue = 1;
+ try
+ {
+ interfaceValue = std::stoi(argv[1]);
+ }
+ catch (const std::invalid_argument& e)
+ {
+ std::cerr << "Invalid argument: " << e.what() << std::endl;
+ return 1;
+ }
+ catch (const std::out_of_range& e)
+ {
+ std::cerr << "Argument out of range: " << e.what() << std::endl;
+ return 1;
+ }
+
+ using TestServerIntf = phosphor::modbus::test::TestServer;
+ std::string devicePathStr =
+ std::string("/dev/ttyV") + std::to_string(interfaceValue);
+ const char* serverDevicePath = devicePathStr.c_str();
+ std::cout << "Starting at device path" << serverDevicePath << std::endl;
+ constexpr auto path = "/xyz/openbmc_project";
+ constexpr auto serviceName = "xyz.openbmc_project.ModbusRTUTestServer";
+ sdbusplus::async::context ctx;
+
+ auto fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ if (fdServer == -1)
+ {
+ std::cerr << "Failed to open serial port " << serverDevicePath
+ << " with error: " << strerror(errno) << std::endl;
+ return 1;
+ }
+
+ std::cout << "Creating Modbus RTU test server at " << path << std::endl;
+ TestServerIntf server{ctx, fdServer};
+
+ ctx.request_name(serviceName);
+
+ ctx.run();
+ return 0;
+}