xref: /openbmc/phosphor-modbus/tests/test_port.cpp (revision b62e3dfe5505fdb8c2ab96156b62e3bfb878b7a6)
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