1 #include "device/device_factory.hpp"
2 #include "modbus_server_tester.hpp"
3 #include "port/base_port.hpp"
4
5 #include <fcntl.h>
6
7 #include <xyz/openbmc_project/Software/Version/client.hpp>
8
9 #include <gtest/gtest.h>
10
11 using namespace std::literals;
12 using namespace testing;
13 using SoftwareIntf =
14 sdbusplus::client::xyz::openbmc_project::software::Version<>;
15
16 namespace TestIntf = phosphor::modbus::test;
17 namespace ModbusIntf = phosphor::modbus::rtu;
18 namespace PortIntf = phosphor::modbus::rtu::port;
19 namespace PortConfigIntf = PortIntf::config;
20 namespace DeviceIntf = phosphor::modbus::rtu::device;
21 namespace DeviceConfigIntf = DeviceIntf::config;
22
23 class MockPort : public PortIntf::BasePort
24 {
25 public:
MockPort(sdbusplus::async::context & ctx,const PortConfigIntf::Config & config,const std::string & devicePath)26 MockPort(sdbusplus::async::context& ctx,
27 const PortConfigIntf::Config& config,
28 const std::string& devicePath) : BasePort(ctx, config, devicePath)
29 {}
30 };
31
32 class TestFirmware : public DeviceIntf::DeviceFirmware
33 {
34 public:
TestFirmware(sdbusplus::async::context & ctx,const DeviceConfigIntf::Config & config,PortIntf::BasePort & serialPort)35 TestFirmware(sdbusplus::async::context& ctx,
36 const DeviceConfigIntf::Config& config,
37 PortIntf::BasePort& serialPort) :
38 DeviceIntf::DeviceFirmware(ctx, config, serialPort)
39 {}
40
getObjectPath()41 auto getObjectPath() -> sdbusplus::message::object_path
42 {
43 return objectPath;
44 }
45 };
46
47 class FirmwareTest : public ::testing::Test
48 {
49 public:
50 PortConfigIntf::Config portConfig;
51 static constexpr const char* clientDevicePath = "/tmp/ttyFirmwareTestPort0";
52 static constexpr const char* serverDevicePath = "/tmp/ttyFirmwareTestPort1";
53 static constexpr auto portName = "TestPort0";
54 static constexpr auto baudRate = 115200;
55 static constexpr const auto strBaudeRate = "b115200";
56 std::string deviceName;
57 std::string objectPath;
58 static constexpr auto serviceName =
59 "xyz.openbmc_project.TestModbusRTUFirmware";
60 static constexpr auto firmwareName = "TestVersion";
61 int socat_pid = -1;
62 sdbusplus::async::context ctx;
63 int fdClient = -1;
64 std::unique_ptr<TestIntf::ServerTester> serverTester;
65 int fdServer = -1;
66 std::unique_ptr<MockPort> mockPort;
67
FirmwareTest()68 FirmwareTest()
69 {
70 portConfig.name = portName;
71 portConfig.portMode = PortConfigIntf::PortMode::rs485;
72 portConfig.baudRate = baudRate;
73 portConfig.rtsDelay = 1;
74
75 deviceName = std::format("ResorviorPumpUnit_{}_{}",
76 TestIntf::testDeviceAddress, portName);
77 objectPath =
78 std::format("{}/{}", SoftwareIntf::namespace_path, deviceName);
79
80 std::string socatCmd = std::format(
81 "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
82 serverDevicePath, strBaudeRate, clientDevicePath, strBaudeRate);
83
84 // Start socat in the background and capture its PID
85 FILE* fp = popen(socatCmd.c_str(), "r");
86 EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
87 EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
88 pclose(fp);
89
90 // Wait for socat to start up
91 sleep(1);
92
93 fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
94 EXPECT_NE(fdClient, -1)
95 << "Failed to open serial port " << clientDevicePath
96 << " with error: " << strerror(errno);
97
98 fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
99 EXPECT_NE(fdServer, -1)
100 << "Failed to open serial port " << serverDevicePath
101 << " with error: " << strerror(errno);
102
103 ctx.request_name(serviceName);
104
105 mockPort =
106 std::make_unique<MockPort>(ctx, portConfig, clientDevicePath);
107
108 serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
109 }
110
~FirmwareTest()111 ~FirmwareTest() noexcept override
112 {
113 if (fdClient != -1)
114 {
115 close(fdClient);
116 fdClient = -1;
117 }
118 if (fdServer != -1)
119 {
120 close(fdServer);
121 fdServer = -1;
122 }
123 kill(socat_pid, SIGTERM);
124 }
125
testFirmwareVersion(std::string objectPath,DeviceConfigIntf::FirmwareRegister firmwareRegister,std::string expectedVersion)126 auto testFirmwareVersion(
127 std::string objectPath,
128 DeviceConfigIntf::FirmwareRegister firmwareRegister,
129 std::string expectedVersion) -> sdbusplus::async::task<void>
130 {
131 DeviceConfigIntf::DeviceFactoryConfig deviceFactoryConfig = {
132 {
133 .address = TestIntf::testDeviceAddress,
134 .parity = ModbusIntf::Parity::none,
135 .baudRate = baudRate,
136 .name = deviceName,
137 .portName = portConfig.name,
138 .inventoryPath = sdbusplus::message::object_path(
139 "xyz/openbmc_project/Inventory/ResorviorPumpUnit"),
140 .sensorRegisters = {},
141 .statusRegisters = {},
142 .firmwareRegisters = {firmwareRegister},
143 },
144 DeviceConfigIntf::DeviceType::reservoirPumpUnit,
145 DeviceConfigIntf::DeviceModel::RDF040DSS5193E0,
146 };
147
148 auto deviceFirmware =
149 std::make_unique<TestFirmware>(ctx, deviceFactoryConfig, *mockPort);
150
151 co_await deviceFirmware->readVersionRegister();
152
153 EXPECT_TRUE(deviceFirmware->getObjectPath().str.starts_with(objectPath))
154 << "Invalid ObjectPath";
155
156 auto softwarePath = deviceFirmware->getObjectPath().str;
157
158 auto properties = co_await SoftwareIntf(ctx)
159 .service(serviceName)
160 .path(softwarePath)
161 .properties();
162
163 EXPECT_EQ(properties.version, expectedVersion)
164 << "Firmware version mismatch";
165
166 co_return;
167 }
168
SetUp()169 void SetUp() override
170 {
171 // Process request to read firmware version
172 ctx.spawn(serverTester->processRequests());
173 }
174 };
175
TEST_F(FirmwareTest,TestFirmwareVersion)176 TEST_F(FirmwareTest, TestFirmwareVersion)
177 {
178 const DeviceConfigIntf::FirmwareRegister firmwareRegister = {
179 .name = "",
180 .type = DeviceConfigIntf::FirmwareRegisterType::version,
181 .offset = TestIntf::testReadHoldingRegisterFirmwareVersionOffset,
182 .size = TestIntf::testReadHoldingRegisterFirmwareVersionCount};
183
184 ctx.spawn(testFirmwareVersion(
185 objectPath, firmwareRegister,
186 TestIntf::testReadHoldingRegisterFirmwareVersionStr));
187
188 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
189 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
190
191 ctx.run();
192 }
193