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