1 #include "common/entity_manager_interface.hpp"
2 #include "modbus_server_tester.hpp"
3 #include "port/port_factory.hpp"
4
5 #include <fcntl.h>
6
7 #include <xyz/openbmc_project/Configuration/USBPort/aserver.hpp>
8 #include <xyz/openbmc_project/Inventory/Item/client.hpp>
9
10 #include <gtest/gtest.h>
11
12 using namespace std::literals;
13
14 class PortTest;
15
16 namespace TestIntf = phosphor::modbus::test;
17 namespace PortIntf = phosphor::modbus::rtu::port;
18 namespace PortConfigIntf = PortIntf::config;
19 namespace RTUIntf = phosphor::modbus::rtu;
20 using PortFactoryIntf = PortIntf::PortFactory;
21 using USBPortConfigServerIntf =
22 sdbusplus::aserver::xyz::openbmc_project::configuration::USBPort<PortTest>;
23
24 struct properties_t
25 {
26 std::string name = {};
27 std::string mode = {};
28 uint64_t baud_rate = {};
29 uint64_t rts_delay = {};
30 };
31
32 class MockPort : public PortIntf::BasePort
33 {
34 public:
MockPort(sdbusplus::async::context & ctx,const PortConfigIntf::Config & config,const std::string & devicePath)35 MockPort(sdbusplus::async::context& ctx,
36 const PortConfigIntf::Config& config,
37 const std::string& devicePath) : BasePort(ctx, config, devicePath)
38 {}
39 };
40
41 class 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;
53 bool getPortConfigPassed = false;
54
PortTest()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
~PortTest()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
SetUp()99 void SetUp() override
100 {
101 getPortConfigPassed = false;
102 }
103
TestHoldingRegisters(PortConfigIntf::Config & config,MockPort & port,uint16_t registerOffset,bool res)104 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 }
130
131 template <typename Config, typename Properties>
VerifyConfig(const Config & config,const Properties & property)132 static inline void VerifyConfig(const Config& config,
133 const Properties& property)
134 {
135 EXPECT_EQ(config, property);
136 }
137
TestGetUSBPortConfig(const USBPortConfigServerIntf::properties_t properties,bool shouldPass)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 }
166 };
167
TEST_F(PortTest,TestUpdateConfig)168 TEST_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
TEST_F(PortTest,TestGetUSBPortConfigSucess)180 TEST_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
TEST_F(PortTest,TestGetUSBPortConfigFailureForInvalidPortMode)207 TEST_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
TEST_F(PortTest,TestReadHoldingRegisterSuccess)234 TEST_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
TEST_F(PortTest,TestReadHoldingRegisterFailure)253 TEST_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 }
271