blob: d5c25a05ec8f570fa3de9ee42d21b6f9a5c318aa [file] [log] [blame]
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -07001#include "common/entity_manager_interface.hpp"
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -07002#include "modbus_server_tester.hpp"
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -07003#include "port/port_factory.hpp"
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -07004
5#include <fcntl.h>
6
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -07007#include <xyz/openbmc_project/Configuration/USBPort/aserver.hpp>
8#include <xyz/openbmc_project/Inventory/Item/client.hpp>
9
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -070010#include <gtest/gtest.h>
11
12using namespace std::literals;
13
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -070014class PortTest;
15
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -070016namespace TestIntf = phosphor::modbus::test;
17namespace PortIntf = phosphor::modbus::rtu::port;
18namespace PortConfigIntf = PortIntf::config;
19namespace RTUIntf = phosphor::modbus::rtu;
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -070020using PortFactoryIntf = PortIntf::PortFactory;
21using USBPortConfigServerIntf =
22 sdbusplus::aserver::xyz::openbmc_project::configuration::USBPort<PortTest>;
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -070023
24struct properties_t
25{
26 std::string name = {};
27 std::string mode = {};
28 uint64_t baud_rate = {};
29 uint64_t rts_delay = {};
30};
31
32class MockPort : public PortIntf::BasePort
33{
34 public:
35 MockPort(sdbusplus::async::context& ctx,
36 const PortConfigIntf::Config& config,
37 const std::string& devicePath) : BasePort(ctx, config, devicePath)
38 {}
39};
40
41class PortTest : public ::testing::Test
42{
43 public:
44 static constexpr properties_t properties = {"TestPort", "RS485", 115200, 1};
45 static constexpr const char* clientDevicePath = "/tmp/ttyPortV0";
46 static constexpr const char* serverDevicePath = "/tmp/ttyPortV1";
47 static constexpr const auto defaultBaudeRate = "b115200";
48 int socat_pid = -1;
49 sdbusplus::async::context ctx;
50 int fdClient = -1;
51 std::unique_ptr<TestIntf::ServerTester> serverTester;
52 int fdServer = -1;
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -070053 bool getPortConfigPassed = false;
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -070054
55 PortTest()
56 {
57 std::string socatCmd = std::format(
58 "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
59 serverDevicePath, defaultBaudeRate, clientDevicePath,
60 defaultBaudeRate);
61
62 // Start socat in the background and capture its PID
63 FILE* fp = popen(socatCmd.c_str(), "r");
64 EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
65 EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
66 pclose(fp);
67
68 // Wait for socat to start up
69 sleep(1);
70
71 fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
72 EXPECT_NE(fdClient, -1)
73 << "Failed to open serial port " << clientDevicePath
74 << " with error: " << strerror(errno);
75
76 fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
77 EXPECT_NE(fdServer, -1)
78 << "Failed to open serial port " << serverDevicePath
79 << " with error: " << strerror(errno);
80
81 serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
82 }
83
84 ~PortTest() noexcept override
85 {
86 if (fdClient != -1)
87 {
88 close(fdClient);
89 fdClient = -1;
90 }
91 if (fdServer != -1)
92 {
93 close(fdServer);
94 fdServer = -1;
95 }
96 kill(socat_pid, SIGTERM);
97 }
98
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -070099 void SetUp() override
100 {
101 getPortConfigPassed = false;
102 }
103
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -0700104 auto TestHoldingRegisters(PortConfigIntf::Config& config, MockPort& port,
105 uint16_t registerOffset, bool res)
106 -> sdbusplus::async::task<void>
107 {
108 std::vector<uint16_t> registers(
109 TestIntf::testSuccessReadHoldingRegisterCount);
110
111 auto ret = co_await port.readHoldingRegisters(
112 TestIntf::testDeviceAddress, registerOffset, config.baudRate,
113 RTUIntf::Parity::none, registers);
114
115 EXPECT_EQ(ret, res) << "Failed to read holding registers";
116
117 if (!res)
118 {
119 co_return;
120 }
121
122 for (auto i = 0; i < TestIntf::testSuccessReadHoldingRegisterCount; i++)
123 {
124 EXPECT_EQ(registers[i],
125 TestIntf::testSuccessReadHoldingRegisterResponse[i]);
126 }
127
128 co_return;
129 }
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -0700130
131 template <typename Config, typename Properties>
132 static inline void VerifyConfig(const Config& config,
133 const Properties& property)
134 {
135 EXPECT_EQ(config, property);
136 }
137
138 auto TestGetUSBPortConfig(
139 const USBPortConfigServerIntf::properties_t properties, bool shouldPass)
140 -> sdbusplus::async::task<void>
141 {
142 static constexpr auto objectPath =
143 "/xyz/openbmc_project/inventory/system/board/Ventura_Modbus/DevTTYUSB0";
144
145 auto configServer = std::make_unique<USBPortConfigServerIntf>(
146 ctx, objectPath, properties);
147
148 auto config = co_await PortFactoryIntf::getConfig(
149 ctx, std::string(objectPath), USBPortConfigServerIntf::interface);
150
151 if (!shouldPass)
152 {
153 VerifyConfig(config, nullptr);
154 co_return;
155 }
156
157 VerifyConfig(config->name, properties.name);
158 VerifyConfig(config->portMode, PortConfigIntf::PortMode::rs485);
159 VerifyConfig(config->baudRate, properties.baud_rate);
160 VerifyConfig(config->rtsDelay, properties.rts_delay);
161
162 getPortConfigPassed = true;
163
164 co_return;
165 }
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -0700166};
167
168TEST_F(PortTest, TestUpdateConfig)
169{
170 PortConfigIntf::Config config = {};
171 auto res = PortConfigIntf::updateBaseConfig(config, properties);
172 EXPECT_TRUE(res) << "Failed to update config";
173
174 EXPECT_EQ(config.name, properties.name);
175 EXPECT_EQ(config.portMode, PortConfigIntf::PortMode::rs485);
176 EXPECT_EQ(config.baudRate, properties.baud_rate);
177 EXPECT_EQ(config.rtsDelay, properties.rts_delay);
178}
179
Jagpal Singh Gillb62e3df2025-10-22 18:10:40 -0700180TEST_F(PortTest, TestGetUSBPortConfigSucess)
181{
182 using InventoryIntf =
183 sdbusplus::client::xyz::openbmc_project::inventory::Item<>;
184 sdbusplus::server::manager_t manager{ctx, InventoryIntf::namespace_path};
185
186 const USBPortConfigServerIntf::properties_t properties = {
187 .type = "USBPort",
188 .name = "USBPort1",
189 .device_address = "0xa",
190 .device_interface = 1,
191 .port = 1,
192 .mode = "RS485",
193 .baud_rate = 115200,
194 .rts_delay = 100};
195
196 ctx.spawn(TestGetUSBPortConfig(properties, true));
197
198 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
199 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
200
201 ctx.request_name(entity_manager::EntityManagerInterface::serviceName);
202 ctx.run();
203
204 EXPECT_EQ(getPortConfigPassed, true);
205}
206
207TEST_F(PortTest, TestGetUSBPortConfigFailureForInvalidPortMode)
208{
209 using InventoryIntf =
210 sdbusplus::client::xyz::openbmc_project::inventory::Item<>;
211 sdbusplus::server::manager_t manager{ctx, InventoryIntf::namespace_path};
212
213 const USBPortConfigServerIntf::properties_t properties = {
214 .type = "USBPort",
215 .name = "USBPort1",
216 .device_address = "0xa",
217 .device_interface = 1,
218 .port = 1,
219 .mode = "RSXXX", // Invalid port mode
220 .baud_rate = 115200,
221 .rts_delay = 100};
222
223 ctx.spawn(TestGetUSBPortConfig(properties, false));
224
225 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
226 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
227
228 ctx.request_name(entity_manager::EntityManagerInterface::serviceName);
229 ctx.run();
230
231 EXPECT_EQ(getPortConfigPassed, false);
232}
233
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -0700234TEST_F(PortTest, TestReadHoldingRegisterSuccess)
235{
236 PortConfigIntf::Config config = {};
237 auto res = PortConfigIntf::updateBaseConfig(config, properties);
238 EXPECT_TRUE(res) << "Failed to update config";
239
240 MockPort port(ctx, config, clientDevicePath);
241
242 ctx.spawn(serverTester->processRequests());
243
244 ctx.spawn(TestHoldingRegisters(
245 config, port, TestIntf::testSuccessReadHoldingRegisterOffset, true));
246
247 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
248 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
249
250 ctx.run();
251}
252
253TEST_F(PortTest, TestReadHoldingRegisterFailure)
254{
255 PortConfigIntf::Config config = {};
256 auto res = PortConfigIntf::updateBaseConfig(config, properties);
257 EXPECT_TRUE(res) << "Failed to update config";
258
259 MockPort port(ctx, config, clientDevicePath);
260
261 ctx.spawn(serverTester->processRequests());
262
263 ctx.spawn(TestHoldingRegisters(
264 config, port, TestIntf::testFailureReadHoldingRegister, false));
265
266 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
267 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
268
269 ctx.run();
270}