xref: /openbmc/phosphor-power/phosphor-power-sequencer/test/pmbus_driver_device_tests.cpp (revision 77c1c26ff2a90bfbbd80eb6d35366f49ce70eb87)
1 /**
2  * Copyright © 2024 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "mock_pmbus.hpp"
18 #include "mock_services.hpp"
19 #include "pmbus.hpp"
20 #include "pmbus_driver_device.hpp"
21 #include "rail.hpp"
22 #include "temporary_subdirectory.hpp"
23 
24 #include <cstdint>
25 #include <exception>
26 #include <filesystem>
27 #include <format>
28 #include <fstream>
29 #include <map>
30 #include <memory>
31 #include <optional>
32 #include <stdexcept>
33 #include <string>
34 #include <utility>
35 #include <vector>
36 
37 #include <gmock/gmock.h>
38 #include <gtest/gtest.h>
39 
40 using namespace phosphor::power::sequencer;
41 using namespace phosphor::pmbus;
42 using namespace phosphor::power::util;
43 namespace fs = std::filesystem;
44 
45 using ::testing::Return;
46 using ::testing::Throw;
47 
48 class PMBusDriverDeviceTests : public ::testing::Test
49 {
50   protected:
51     /**
52      * Constructor.
53      *
54      * Creates a temporary directory to use in simulating sysfs files.
55      */
PMBusDriverDeviceTests()56     PMBusDriverDeviceTests() : ::testing::Test{}
57     {
58         tempDirPath = tempDir.getPath();
59     }
60 
61     /**
62      * Creates a Rail object that checks for a pgood fault using STATUS_VOUT.
63      *
64      * @param name Unique name for the rail
65      * @param pageNum PMBus PAGE number of the rail
66      * @return Rail object
67      */
createRail(const std::string & name,uint8_t pageNum)68     std::unique_ptr<Rail> createRail(const std::string& name, uint8_t pageNum)
69     {
70         std::optional<std::string> presence{};
71         std::optional<uint8_t> page{pageNum};
72         bool isPowerSupplyRail{false};
73         bool checkStatusVout{true};
74         bool compareVoltageToLimit{false};
75         std::optional<PgoodGPIO> gpio{};
76         return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
77                                       checkStatusVout, compareVoltageToLimit,
78                                       gpio);
79     }
80 
81     /**
82      * Creates a file with the specified contents within the temporary
83      * directory.
84      *
85      * @param name File name
86      * @param contents File contents
87      */
createFile(const std::string & name,const std::string & contents="")88     void createFile(const std::string& name, const std::string& contents = "")
89     {
90         fs::path path{tempDirPath / name};
91         std::ofstream out{path};
92         out << contents;
93         out.close();
94     }
95 
96     /**
97      * Temporary subdirectory used to create simulated sysfs / hmmon files.
98      */
99     TemporarySubDirectory tempDir;
100 
101     /**
102      * Path to temporary subdirectory.
103      */
104     fs::path tempDirPath;
105 };
106 
TEST_F(PMBusDriverDeviceTests,Constructor)107 TEST_F(PMBusDriverDeviceTests, Constructor)
108 {
109     // Test where works; optional parameters not specified
110     {
111         std::string name{"XYZ_PSEQ"};
112         uint8_t bus{3};
113         uint16_t address{0x72};
114         std::string powerControlGPIOName{"power-chassis-control"};
115         std::string powerGoodGPIOName{"power-chassis-good"};
116         std::vector<std::unique_ptr<Rail>> rails;
117         rails.emplace_back(createRail("VDD", 5));
118         rails.emplace_back(createRail("VIO", 7));
119         PMBusDriverDevice device{name,
120                                  bus,
121                                  address,
122                                  powerControlGPIOName,
123                                  powerGoodGPIOName,
124                                  std::move(rails)};
125 
126         EXPECT_EQ(device.getName(), name);
127         EXPECT_EQ(device.getBus(), bus);
128         EXPECT_EQ(device.getAddress(), address);
129         EXPECT_EQ(device.getPowerControlGPIOName(), powerControlGPIOName);
130         EXPECT_EQ(device.getPowerGoodGPIOName(), powerGoodGPIOName);
131         EXPECT_EQ(device.getRails().size(), 2);
132         EXPECT_EQ(device.getRails()[0]->getName(), "VDD");
133         EXPECT_EQ(device.getRails()[1]->getName(), "VIO");
134         EXPECT_EQ(device.getDriverName(), "");
135         EXPECT_EQ(device.getInstance(), 0);
136     }
137 
138     // Test where works; optional parameters specified
139     {
140         std::string name{"XYZ_PSEQ"};
141         uint8_t bus{3};
142         uint16_t address{0x72};
143         std::string powerControlGPIOName{"power-on"};
144         std::string powerGoodGPIOName{"pgood"};
145         std::vector<std::unique_ptr<Rail>> rails;
146         rails.emplace_back(createRail("VDD", 5));
147         rails.emplace_back(createRail("VIO", 7));
148         std::string driverName{"xyzdev"};
149         size_t instance{3};
150         PMBusDriverDevice device{
151             name,
152             bus,
153             address,
154             powerControlGPIOName,
155             powerGoodGPIOName,
156             std::move(rails),
157             driverName,
158             instance};
159 
160         EXPECT_EQ(device.getName(), name);
161         EXPECT_EQ(device.getBus(), bus);
162         EXPECT_EQ(device.getAddress(), address);
163         EXPECT_EQ(device.getPowerControlGPIOName(), powerControlGPIOName);
164         EXPECT_EQ(device.getPowerGoodGPIOName(), powerGoodGPIOName);
165         EXPECT_EQ(device.getRails().size(), 2);
166         EXPECT_EQ(device.getRails()[0]->getName(), "VDD");
167         EXPECT_EQ(device.getRails()[1]->getName(), "VIO");
168         EXPECT_EQ(device.getDriverName(), driverName);
169         EXPECT_EQ(device.getInstance(), instance);
170     }
171 }
172 
TEST_F(PMBusDriverDeviceTests,GetDriverName)173 TEST_F(PMBusDriverDeviceTests, GetDriverName)
174 {
175     std::string name{"XYZ_PSEQ"};
176     uint8_t bus{3};
177     uint16_t address{0x72};
178     std::string powerControlGPIOName{"power-chassis-control"};
179     std::string powerGoodGPIOName{"power-chassis-good"};
180     std::vector<std::unique_ptr<Rail>> rails;
181     std::string driverName{"xyzdev"};
182     PMBusDriverDevice device{
183         name,
184         bus,
185         address,
186         powerControlGPIOName,
187         powerGoodGPIOName,
188         std::move(rails),
189         driverName};
190 
191     EXPECT_EQ(device.getDriverName(), driverName);
192 }
193 
TEST_F(PMBusDriverDeviceTests,GetInstance)194 TEST_F(PMBusDriverDeviceTests, GetInstance)
195 {
196     std::string name{"XYZ_PSEQ"};
197     uint8_t bus{3};
198     uint16_t address{0x72};
199     std::string powerControlGPIOName{"power-chassis-control"};
200     std::string powerGoodGPIOName{"power-chassis-good"};
201     std::vector<std::unique_ptr<Rail>> rails;
202     std::string driverName{"xyzdev"};
203     size_t instance{3};
204     PMBusDriverDevice device{
205         name,
206         bus,
207         address,
208         powerControlGPIOName,
209         powerGoodGPIOName,
210         std::move(rails),
211         driverName,
212         instance};
213 
214     EXPECT_EQ(device.getInstance(), instance);
215 }
216 
TEST_F(PMBusDriverDeviceTests,Open)217 TEST_F(PMBusDriverDeviceTests, Open)
218 {
219     std::string name{"XYZ_PSEQ"};
220     uint8_t bus{3};
221     uint16_t address{0x72};
222     std::string powerControlGPIOName{"power-chassis-control"};
223     std::string powerGoodGPIOName{"power-chassis-good"};
224     std::vector<std::unique_ptr<Rail>> rails;
225     PMBusDriverDevice device{name,
226                              bus,
227                              address,
228                              powerControlGPIOName,
229                              powerGoodGPIOName,
230                              std::move(rails)};
231 
232     // Test where works
233     EXPECT_FALSE(device.isOpen());
234     MockServices services;
235     device.open(services);
236     EXPECT_TRUE(device.isOpen());
237 
238     MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
239     EXPECT_CALL(pmbus, read("status13_vout", Type::Debug, true))
240         .Times(1)
241         .WillOnce(Return(0xde));
242     uint8_t page{13};
243     EXPECT_EQ(device.getStatusVout(page), 0xde);
244 
245     MockGPIO& gpio = static_cast<MockGPIO&>(device.getPowerControlGPIO());
246     EXPECT_CALL(gpio, setValue(1)).Times(1);
247     device.powerOn();
248 
249     // Test where does nothing because device is already open
250     device.open(services);
251     EXPECT_TRUE(device.isOpen());
252 }
253 
TEST_F(PMBusDriverDeviceTests,Close)254 TEST_F(PMBusDriverDeviceTests, Close)
255 {
256     // Test where works
257     {
258         std::string name{"XYZ_PSEQ"};
259         uint8_t bus{3};
260         uint16_t address{0x72};
261         std::string powerControlGPIOName{"power-chassis-control"};
262         std::string powerGoodGPIOName{"power-chassis-good"};
263         std::vector<std::unique_ptr<Rail>> rails;
264         PMBusDriverDevice device{name,
265                                  bus,
266                                  address,
267                                  powerControlGPIOName,
268                                  powerGoodGPIOName,
269                                  std::move(rails)};
270 
271         MockServices services;
272         device.open(services);
273         EXPECT_TRUE(device.isOpen());
274 
275         MockGPIO& gpio = static_cast<MockGPIO&>(device.getPowerGoodGPIO());
276         EXPECT_CALL(gpio, release).Times(1);
277         device.close();
278         EXPECT_FALSE(device.isOpen());
279 
280         // Test where does nothing because device already closed
281         device.close();
282         EXPECT_FALSE(device.isOpen());
283     }
284 
285     // Test where fails: Exception thrown
286     try
287     {
288         std::string name{"XYZ_PSEQ"};
289         uint8_t bus{3};
290         uint16_t address{0x72};
291         std::string powerControlGPIOName{"power-chassis-control"};
292         std::string powerGoodGPIOName{"power-chassis-good"};
293         std::vector<std::unique_ptr<Rail>> rails;
294         PMBusDriverDevice device{name,
295                                  bus,
296                                  address,
297                                  powerControlGPIOName,
298                                  powerGoodGPIOName,
299                                  std::move(rails)};
300 
301         MockServices services;
302         device.open(services);
303         EXPECT_TRUE(device.isOpen());
304         MockGPIO& gpio = static_cast<MockGPIO&>(device.getPowerGoodGPIO());
305         // Note: release() called twice. Once directly and once by destructor.
306         EXPECT_CALL(gpio, release)
307             .Times(2)
308             .WillOnce(Throw(std::runtime_error{"Unable to release GPIO"}))
309             .WillOnce(Return());
310         device.close();
311         ADD_FAILURE() << "Should not have reached this line.";
312     }
313     catch (const std::exception& e)
314     {
315         EXPECT_STREQ(e.what(), "Unable to release GPIO");
316     }
317 }
318 
TEST_F(PMBusDriverDeviceTests,GetPMBusInterface)319 TEST_F(PMBusDriverDeviceTests, GetPMBusInterface)
320 {
321     std::string name{"XYZ_PSEQ"};
322     uint8_t bus{3};
323     uint16_t address{0x72};
324     std::string powerControlGPIOName{"power-chassis-control"};
325     std::string powerGoodGPIOName{"power-chassis-good"};
326     std::vector<std::unique_ptr<Rail>> rails;
327     PMBusDriverDevice device{name,
328                              bus,
329                              address,
330                              powerControlGPIOName,
331                              powerGoodGPIOName,
332                              std::move(rails)};
333 
334     // Test where works
335     MockServices services;
336     device.open(services);
337     MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
338     EXPECT_CALL(pmbus, read("status13_vout", Type::Debug, true))
339         .Times(1)
340         .WillOnce(Return(0xde));
341     uint8_t page{13};
342     EXPECT_EQ(device.getStatusVout(page), 0xde);
343 
344     // Test where fails: Device not open
345     try
346     {
347         device.close();
348         device.getPMBusInterface();
349         ADD_FAILURE() << "Should not have reached this line.";
350     }
351     catch (const std::exception& e)
352     {
353         EXPECT_STREQ(e.what(), "Device not open: XYZ_PSEQ");
354     }
355 }
356 
TEST_F(PMBusDriverDeviceTests,GetGPIOValues)357 TEST_F(PMBusDriverDeviceTests, GetGPIOValues)
358 {
359     // Test where works
360     {
361         std::string name{"ABC_382%#, ZY"};
362         uint8_t bus{3};
363         uint16_t address{0x72};
364         std::string powerControlGPIOName{"power-chassis-control"};
365         std::string powerGoodGPIOName{"power-chassis-good"};
366         std::vector<std::unique_ptr<Rail>> rails;
367         PMBusDriverDevice device{name,
368                                  bus,
369                                  address,
370                                  powerControlGPIOName,
371                                  powerGoodGPIOName,
372                                  std::move(rails)};
373 
374         MockServices services;
375         std::vector<int> gpioValues{1, 1, 1};
376         EXPECT_CALL(services, getGPIOValues("abc_382%#, zy"))
377             .Times(1)
378             .WillOnce(Return(gpioValues));
379 
380         device.open(services);
381         EXPECT_TRUE(device.getGPIOValues(services) == gpioValues);
382     }
383 
384     // Test where fails
385     {
386         std::string name{"XYZ_PSEQ"};
387         uint8_t bus{3};
388         uint16_t address{0x72};
389         std::string powerControlGPIOName{"power-chassis-control"};
390         std::string powerGoodGPIOName{"power-chassis-good"};
391         std::vector<std::unique_ptr<Rail>> rails;
392         PMBusDriverDevice device{name,
393                                  bus,
394                                  address,
395                                  powerControlGPIOName,
396                                  powerGoodGPIOName,
397                                  std::move(rails)};
398 
399         MockServices services;
400         EXPECT_CALL(services, getGPIOValues("xyz_pseq"))
401             .Times(1)
402             .WillOnce(
403                 Throw(std::runtime_error{"libgpiod: Unable to open chip"}));
404 
405         // Device not open
406         try
407         {
408             device.getGPIOValues(services);
409             ADD_FAILURE() << "Should not have reached this line.";
410         }
411         catch (const std::exception& e)
412         {
413             EXPECT_STREQ(e.what(), "Device not open: XYZ_PSEQ");
414         }
415 
416         // Exception thrown
417         try
418         {
419             device.open(services);
420             device.getGPIOValues(services);
421             ADD_FAILURE() << "Should not have reached this line.";
422         }
423         catch (const std::exception& e)
424         {
425             EXPECT_STREQ(e.what(),
426                          "Unable to read GPIO values from device XYZ_PSEQ "
427                          "using label xyz_pseq: "
428                          "libgpiod: Unable to open chip");
429         }
430     }
431 }
432 
TEST_F(PMBusDriverDeviceTests,GetStatusWord)433 TEST_F(PMBusDriverDeviceTests, GetStatusWord)
434 {
435     // Test where works
436     {
437         std::string name{"xyz_pseq"};
438         uint8_t bus{3};
439         uint16_t address{0x72};
440         std::string powerControlGPIOName{"power-chassis-control"};
441         std::string powerGoodGPIOName{"power-chassis-good"};
442         std::vector<std::unique_ptr<Rail>> rails;
443         PMBusDriverDevice device{name,
444                                  bus,
445                                  address,
446                                  powerControlGPIOName,
447                                  powerGoodGPIOName,
448                                  std::move(rails)};
449 
450         MockServices services;
451         device.open(services);
452         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
453         EXPECT_CALL(pmbus, read("status13", Type::Debug, true))
454             .Times(1)
455             .WillOnce(Return(0x1234));
456 
457         uint8_t page{13};
458         EXPECT_EQ(device.getStatusWord(page), 0x1234);
459     }
460 
461     // Test where fails
462     {
463         std::string name{"xyz_pseq"};
464         uint8_t bus{3};
465         uint16_t address{0x72};
466         std::string powerControlGPIOName{"power-chassis-control"};
467         std::string powerGoodGPIOName{"power-chassis-good"};
468         std::vector<std::unique_ptr<Rail>> rails;
469         PMBusDriverDevice device{name,
470                                  bus,
471                                  address,
472                                  powerControlGPIOName,
473                                  powerGoodGPIOName,
474                                  std::move(rails)};
475 
476         // Device not open
477         try
478         {
479             uint8_t page{0};
480             device.getStatusWord(page);
481             ADD_FAILURE() << "Should not have reached this line.";
482         }
483         catch (const std::exception& e)
484         {
485             EXPECT_STREQ(e.what(), "Device not open: xyz_pseq");
486         }
487 
488         // Exception thrown
489         MockServices services;
490         device.open(services);
491         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
492         EXPECT_CALL(pmbus, read("status0", Type::Debug, true))
493             .Times(1)
494             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
495         try
496         {
497             uint8_t page{0};
498             device.getStatusWord(page);
499             ADD_FAILURE() << "Should not have reached this line.";
500         }
501         catch (const std::exception& e)
502         {
503             EXPECT_STREQ(
504                 e.what(),
505                 "Unable to read STATUS_WORD for PAGE 0 of device xyz_pseq: "
506                 "File does not exist");
507         }
508     }
509 }
510 
TEST_F(PMBusDriverDeviceTests,GetStatusVout)511 TEST_F(PMBusDriverDeviceTests, GetStatusVout)
512 {
513     // Test where works
514     {
515         std::string name{"xyz_pseq"};
516         uint8_t bus{3};
517         uint16_t address{0x72};
518         std::string powerControlGPIOName{"power-chassis-control"};
519         std::string powerGoodGPIOName{"power-chassis-good"};
520         std::vector<std::unique_ptr<Rail>> rails;
521         PMBusDriverDevice device{name,
522                                  bus,
523                                  address,
524                                  powerControlGPIOName,
525                                  powerGoodGPIOName,
526                                  std::move(rails)};
527 
528         MockServices services;
529         device.open(services);
530         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
531         EXPECT_CALL(pmbus, read("status13_vout", Type::Debug, true))
532             .Times(1)
533             .WillOnce(Return(0xde));
534 
535         uint8_t page{13};
536         EXPECT_EQ(device.getStatusVout(page), 0xde);
537     }
538 
539     // Test where fails
540     {
541         std::string name{"xyz_pseq"};
542         uint8_t bus{3};
543         uint16_t address{0x72};
544         std::string powerControlGPIOName{"power-chassis-control"};
545         std::string powerGoodGPIOName{"power-chassis-good"};
546         std::vector<std::unique_ptr<Rail>> rails;
547         PMBusDriverDevice device{name,
548                                  bus,
549                                  address,
550                                  powerControlGPIOName,
551                                  powerGoodGPIOName,
552                                  std::move(rails)};
553 
554         // Device not open
555         try
556         {
557             uint8_t page{0};
558             device.getStatusVout(page);
559             ADD_FAILURE() << "Should not have reached this line.";
560         }
561         catch (const std::exception& e)
562         {
563             EXPECT_STREQ(e.what(), "Device not open: xyz_pseq");
564         }
565 
566         // Exception thrown
567         MockServices services;
568         device.open(services);
569         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
570         EXPECT_CALL(pmbus, read("status0_vout", Type::Debug, true))
571             .Times(1)
572             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
573         try
574         {
575             uint8_t page{0};
576             device.getStatusVout(page);
577             ADD_FAILURE() << "Should not have reached this line.";
578         }
579         catch (const std::exception& e)
580         {
581             EXPECT_STREQ(
582                 e.what(),
583                 "Unable to read STATUS_VOUT for PAGE 0 of device xyz_pseq: "
584                 "File does not exist");
585         }
586     }
587 }
588 
TEST_F(PMBusDriverDeviceTests,GetReadVout)589 TEST_F(PMBusDriverDeviceTests, GetReadVout)
590 {
591     // Test where works
592     {
593         // Create simulated hwmon voltage label file
594         createFile("in13_label"); // PAGE 9 -> file number 13
595 
596         std::string name{"xyz_pseq"};
597         uint8_t bus{3};
598         uint16_t address{0x72};
599         std::string powerControlGPIOName{"power-chassis-control"};
600         std::string powerGoodGPIOName{"power-chassis-good"};
601         std::vector<std::unique_ptr<Rail>> rails;
602         PMBusDriverDevice device{name,
603                                  bus,
604                                  address,
605                                  powerControlGPIOName,
606                                  powerGoodGPIOName,
607                                  std::move(rails)};
608 
609         MockServices services;
610         device.open(services);
611         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
612         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
613             .Times(1)
614             .WillOnce(Return(tempDirPath));
615         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
616             .Times(1)
617             .WillOnce(Return("vout10")); // PAGE number 9 + 1
618         EXPECT_CALL(pmbus, readString("in13_input", Type::Hwmon))
619             .Times(1)
620             .WillOnce(Return("851"));
621 
622         uint8_t page{9};
623         EXPECT_EQ(device.getReadVout(page), 0.851);
624     }
625 
626     // Test where fails
627     {
628         // Create simulated hwmon voltage label file
629         createFile("in13_label"); // PAGE 8 -> file number 13
630 
631         std::string name{"xyz_pseq"};
632         uint8_t bus{3};
633         uint16_t address{0x72};
634         std::string powerControlGPIOName{"power-chassis-control"};
635         std::string powerGoodGPIOName{"power-chassis-good"};
636         std::vector<std::unique_ptr<Rail>> rails;
637         PMBusDriverDevice device{name,
638                                  bus,
639                                  address,
640                                  powerControlGPIOName,
641                                  powerGoodGPIOName,
642                                  std::move(rails)};
643 
644         // Device not open
645         try
646         {
647             uint8_t page{9};
648             device.getReadVout(page);
649             ADD_FAILURE() << "Should not have reached this line.";
650         }
651         catch (const std::exception& e)
652         {
653             EXPECT_STREQ(e.what(), "Device not open: xyz_pseq");
654         }
655 
656         // Exception thrown
657         MockServices services;
658         device.open(services);
659         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
660         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
661             .Times(1)
662             .WillOnce(Return(tempDirPath));
663         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
664             .Times(1)
665             .WillOnce(Return("vout9")); // PAGE number 8 + 1
666         try
667         {
668             uint8_t page{9};
669             device.getReadVout(page);
670             ADD_FAILURE() << "Should not have reached this line.";
671         }
672         catch (const std::exception& e)
673         {
674             EXPECT_STREQ(
675                 e.what(),
676                 "Unable to read READ_VOUT for PAGE 9 of device xyz_pseq: "
677                 "Unable to find hwmon file number for PAGE 9 of device xyz_pseq");
678         }
679     }
680 }
681 
TEST_F(PMBusDriverDeviceTests,GetVoutUVFaultLimit)682 TEST_F(PMBusDriverDeviceTests, GetVoutUVFaultLimit)
683 {
684     // Test where works
685     {
686         // Create simulated hwmon voltage label file
687         createFile("in1_label"); // PAGE 6 -> file number 1
688 
689         std::string name{"xyz_pseq"};
690         uint8_t bus{3};
691         uint16_t address{0x72};
692         std::string powerControlGPIOName{"power-chassis-control"};
693         std::string powerGoodGPIOName{"power-chassis-good"};
694         std::vector<std::unique_ptr<Rail>> rails;
695         PMBusDriverDevice device{name,
696                                  bus,
697                                  address,
698                                  powerControlGPIOName,
699                                  powerGoodGPIOName,
700                                  std::move(rails)};
701 
702         MockServices services;
703         device.open(services);
704         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
705         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
706             .Times(1)
707             .WillOnce(Return(tempDirPath));
708         EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
709             .Times(1)
710             .WillOnce(Return("vout7")); // PAGE number 6 + 1
711         EXPECT_CALL(pmbus, readString("in1_lcrit", Type::Hwmon))
712             .Times(1)
713             .WillOnce(Return("1329"));
714 
715         uint8_t page{6};
716         EXPECT_EQ(device.getVoutUVFaultLimit(page), 1.329);
717     }
718 
719     // Test where fails
720     {
721         // Create simulated hwmon voltage label file
722         createFile("in1_label"); // PAGE 7 -> file number 1
723 
724         std::string name{"xyz_pseq"};
725         uint8_t bus{3};
726         uint16_t address{0x72};
727         std::string powerControlGPIOName{"power-chassis-control"};
728         std::string powerGoodGPIOName{"power-chassis-good"};
729         std::vector<std::unique_ptr<Rail>> rails;
730         PMBusDriverDevice device{name,
731                                  bus,
732                                  address,
733                                  powerControlGPIOName,
734                                  powerGoodGPIOName,
735                                  std::move(rails)};
736 
737         // Device not open
738         try
739         {
740             uint8_t page{6};
741             device.getVoutUVFaultLimit(page);
742             ADD_FAILURE() << "Should not have reached this line.";
743         }
744         catch (const std::exception& e)
745         {
746             EXPECT_STREQ(e.what(), "Device not open: xyz_pseq");
747         }
748 
749         // Exception thrown
750         MockServices services;
751         device.open(services);
752         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
753         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
754             .Times(1)
755             .WillOnce(Return(tempDirPath));
756         EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
757             .Times(1)
758             .WillOnce(Return("vout8")); // PAGE number 7 + 1
759         try
760         {
761             uint8_t page{6};
762             device.getVoutUVFaultLimit(page);
763             ADD_FAILURE() << "Should not have reached this line.";
764         }
765         catch (const std::exception& e)
766         {
767             EXPECT_STREQ(
768                 e.what(),
769                 "Unable to read VOUT_UV_FAULT_LIMIT for PAGE 6 of device xyz_pseq: "
770                 "Unable to find hwmon file number for PAGE 6 of device xyz_pseq");
771         }
772     }
773 }
774 
TEST_F(PMBusDriverDeviceTests,GetPageToFileNumberMap)775 TEST_F(PMBusDriverDeviceTests, GetPageToFileNumberMap)
776 {
777     // Test where works: No voltage label files/mappings found
778     {
779         // Create simulated hwmon files.  None are valid voltage label files.
780         createFile("in1_input");   // Not a label file
781         createFile("in9_lcrit");   // Not a label file
782         createFile("in_label");    // Invalid voltage label file name
783         createFile("in9a_label");  // Invalid voltage label file name
784         createFile("fan3_label");  // Not a voltage label file
785         createFile("temp8_label"); // Not a voltage label file
786 
787         std::string name{"xyz_pseq"};
788         uint8_t bus{3};
789         uint16_t address{0x72};
790         std::string powerControlGPIOName{"power-chassis-control"};
791         std::string powerGoodGPIOName{"power-chassis-good"};
792         std::vector<std::unique_ptr<Rail>> rails;
793         PMBusDriverDevice device{name,
794                                  bus,
795                                  address,
796                                  powerControlGPIOName,
797                                  powerGoodGPIOName,
798                                  std::move(rails)};
799 
800         MockServices services;
801         device.open(services);
802         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
803         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
804             .Times(1)
805             .WillOnce(Return(tempDirPath));
806         EXPECT_CALL(pmbus, readString).Times(0);
807 
808         const std::map<uint8_t, unsigned int>& map =
809             device.getPageToFileNumberMap();
810         EXPECT_TRUE(map.empty());
811     }
812 
813     // Test where works: Multiple voltage label files/mappings found
814     {
815         // Create simulated hwmon files
816         createFile("in9_label");   // PAGE 3 -> file number 9
817         createFile("in13_label");  // PAGE 7 -> file number 13
818         createFile("in0_label");   // PAGE 12 -> file number 0
819         createFile("in11_label");  // No mapping; invalid contents
820         createFile("in12_label");  // No mapping; invalid contents
821         createFile("in1_input");   // Not a label file
822         createFile("in7_lcrit");   // Not a label file
823         createFile("fan3_label");  // Not a voltage label file
824         createFile("temp8_label"); // Not a voltage label file
825 
826         std::string name{"xyz_pseq"};
827         uint8_t bus{3};
828         uint16_t address{0x72};
829         std::string powerControlGPIOName{"power-chassis-control"};
830         std::string powerGoodGPIOName{"power-chassis-good"};
831         std::vector<std::unique_ptr<Rail>> rails;
832         PMBusDriverDevice device{name,
833                                  bus,
834                                  address,
835                                  powerControlGPIOName,
836                                  powerGoodGPIOName,
837                                  std::move(rails)};
838 
839         MockServices services;
840         device.open(services);
841         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
842         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
843             .Times(1)
844             .WillOnce(Return(tempDirPath));
845         EXPECT_CALL(pmbus, readString("in9_label", Type::Hwmon))
846             .Times(1)
847             .WillOnce(Return("vout4")); // PAGE number 3 + 1
848         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
849             .Times(1)
850             .WillOnce(Return("vout8")); // PAGE number 7 + 1
851         EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
852             .Times(1)
853             .WillOnce(Return("vout13")); // PAGE number 12 + 1
854         EXPECT_CALL(pmbus, readString("in11_label", Type::Hwmon))
855             .Times(1)
856             .WillOnce(Return("vout")); // Invalid format
857         EXPECT_CALL(pmbus, readString("in12_label", Type::Hwmon))
858             .Times(1)
859             .WillOnce(Return("vout13a")); // Invalid format
860 
861         const std::map<uint8_t, unsigned int>& map =
862             device.getPageToFileNumberMap();
863         EXPECT_EQ(map.size(), 3);
864         EXPECT_EQ(map.at(uint8_t{3}), 9);
865         EXPECT_EQ(map.at(uint8_t{7}), 13);
866         EXPECT_EQ(map.at(uint8_t{12}), 0);
867     }
868 
869     // Test where fails: Device not open
870     {
871         std::string name{"xyz_pseq"};
872         uint8_t bus{3};
873         uint16_t address{0x72};
874         std::string powerControlGPIOName{"power-chassis-control"};
875         std::string powerGoodGPIOName{"power-chassis-good"};
876         std::vector<std::unique_ptr<Rail>> rails;
877         PMBusDriverDevice device{name,
878                                  bus,
879                                  address,
880                                  powerControlGPIOName,
881                                  powerGoodGPIOName,
882                                  std::move(rails)};
883 
884         try
885         {
886             device.getPageToFileNumberMap();
887             ADD_FAILURE() << "Should not have reached this line.";
888         }
889         catch (const std::exception& e)
890         {
891             EXPECT_STREQ(e.what(), "Device not open: xyz_pseq");
892         }
893     }
894 
895     // Test where fails: hwmon directory path is actually a file
896     {
897         // Create file that will be returned as the hwmon directory path
898         createFile("in9_label");
899 
900         std::string name{"xyz_pseq"};
901         uint8_t bus{3};
902         uint16_t address{0x72};
903         std::string powerControlGPIOName{"power-chassis-control"};
904         std::string powerGoodGPIOName{"power-chassis-good"};
905         std::vector<std::unique_ptr<Rail>> rails;
906         PMBusDriverDevice device{name,
907                                  bus,
908                                  address,
909                                  powerControlGPIOName,
910                                  powerGoodGPIOName,
911                                  std::move(rails)};
912 
913         MockServices services;
914         device.open(services);
915         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
916         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
917             .Times(1)
918             .WillOnce(Return(tempDirPath / "in9_label"));
919         EXPECT_CALL(pmbus, readString).Times(0);
920 
921         const std::map<uint8_t, unsigned int>& map =
922             device.getPageToFileNumberMap();
923         EXPECT_TRUE(map.empty());
924     }
925 
926     // Test where fails: hwmon directory path does not exist
927     {
928         std::string name{"xyz_pseq"};
929         uint8_t bus{3};
930         uint16_t address{0x72};
931         std::string powerControlGPIOName{"power-chassis-control"};
932         std::string powerGoodGPIOName{"power-chassis-good"};
933         std::vector<std::unique_ptr<Rail>> rails;
934         PMBusDriverDevice device{name,
935                                  bus,
936                                  address,
937                                  powerControlGPIOName,
938                                  powerGoodGPIOName,
939                                  std::move(rails)};
940 
941         MockServices services;
942         device.open(services);
943         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
944         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
945             .Times(1)
946             .WillOnce(Return(tempDirPath / "does_not_exist"));
947         EXPECT_CALL(pmbus, readString).Times(0);
948 
949         const std::map<uint8_t, unsigned int>& map =
950             device.getPageToFileNumberMap();
951         EXPECT_TRUE(map.empty());
952     }
953 
954     // Test where fails: hwmon directory path is not readable
955     {
956         // Create simulated hwmon files
957         createFile("in9_label");
958         createFile("in13_label");
959         createFile("in0_label");
960 
961         // Change temporary directory to be unreadable
962         fs::permissions(tempDirPath, fs::perms::none);
963 
964         std::string name{"xyz_pseq"};
965         uint8_t bus{3};
966         uint16_t address{0x72};
967         std::string powerControlGPIOName{"power-chassis-control"};
968         std::string powerGoodGPIOName{"power-chassis-good"};
969         std::vector<std::unique_ptr<Rail>> rails;
970         PMBusDriverDevice device{name,
971                                  bus,
972                                  address,
973                                  powerControlGPIOName,
974                                  powerGoodGPIOName,
975                                  std::move(rails)};
976 
977         MockServices services;
978         device.open(services);
979         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
980         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
981             .Times(1)
982             .WillOnce(Return(tempDirPath));
983         EXPECT_CALL(pmbus, readString).Times(0);
984 
985         try
986         {
987             device.getPageToFileNumberMap();
988             ADD_FAILURE() << "Should not have reached this line.";
989         }
990         catch (const std::exception& e)
991         {
992             // Error message varies
993         }
994 
995         // Change temporary directory to be readable/writable
996         fs::permissions(tempDirPath, fs::perms::owner_all);
997     }
998 }
999 
TEST_F(PMBusDriverDeviceTests,GetFileNumber)1000 TEST_F(PMBusDriverDeviceTests, GetFileNumber)
1001 {
1002     // Test where works
1003     {
1004         // Create simulated hwmon voltage label files
1005         createFile("in0_label");  // PAGE 6 -> file number 0
1006         createFile("in13_label"); // PAGE 9 -> file number 13
1007 
1008         std::string name{"xyz_pseq"};
1009         uint8_t bus{3};
1010         uint16_t address{0x72};
1011         std::string powerControlGPIOName{"power-chassis-control"};
1012         std::string powerGoodGPIOName{"power-chassis-good"};
1013         std::vector<std::unique_ptr<Rail>> rails;
1014         PMBusDriverDevice device{name,
1015                                  bus,
1016                                  address,
1017                                  powerControlGPIOName,
1018                                  powerGoodGPIOName,
1019                                  std::move(rails)};
1020 
1021         MockServices services;
1022         device.open(services);
1023         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
1024         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
1025             .Times(1)
1026             .WillOnce(Return(tempDirPath));
1027         EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
1028             .Times(1)
1029             .WillOnce(Return("vout7")); // PAGE number 6 + 1
1030         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
1031             .Times(1)
1032             .WillOnce(Return("vout10")); // PAGE number 9 + 1
1033 
1034         // Map was empty and needs to be built
1035         uint8_t page{6};
1036         EXPECT_EQ(device.getFileNumber(page), 0);
1037 
1038         // Map had already been built
1039         page = 9;
1040         EXPECT_EQ(device.getFileNumber(page), 13);
1041     }
1042 
1043     // Test where fails: Device not open
1044     {
1045         std::string name{"xyz_pseq"};
1046         uint8_t bus{3};
1047         uint16_t address{0x72};
1048         std::string powerControlGPIOName{"power-chassis-control"};
1049         std::string powerGoodGPIOName{"power-chassis-good"};
1050         std::vector<std::unique_ptr<Rail>> rails;
1051         PMBusDriverDevice device{name,
1052                                  bus,
1053                                  address,
1054                                  powerControlGPIOName,
1055                                  powerGoodGPIOName,
1056                                  std::move(rails)};
1057 
1058         try
1059         {
1060             uint8_t page{13};
1061             device.getFileNumber(page);
1062             ADD_FAILURE() << "Should not have reached this line.";
1063         }
1064         catch (const std::exception& e)
1065         {
1066             EXPECT_STREQ(e.what(), "Device not open: xyz_pseq");
1067         }
1068     }
1069 
1070     // Test where fails: No mapping for specified PMBus PAGE
1071     {
1072         // Create simulated hwmon voltage label files
1073         createFile("in0_label");  // PAGE 6 -> file number 0
1074         createFile("in13_label"); // PAGE 9 -> file number 13
1075 
1076         std::string name{"xyz_pseq"};
1077         uint8_t bus{3};
1078         uint16_t address{0x72};
1079         std::string powerControlGPIOName{"power-chassis-control"};
1080         std::string powerGoodGPIOName{"power-chassis-good"};
1081         std::vector<std::unique_ptr<Rail>> rails;
1082         PMBusDriverDevice device{name,
1083                                  bus,
1084                                  address,
1085                                  powerControlGPIOName,
1086                                  powerGoodGPIOName,
1087                                  std::move(rails)};
1088 
1089         MockServices services;
1090         device.open(services);
1091         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
1092         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
1093             .Times(1)
1094             .WillOnce(Return(tempDirPath));
1095         EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
1096             .Times(1)
1097             .WillOnce(Return("vout7")); // PAGE number 6 + 1
1098         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
1099             .Times(1)
1100             .WillOnce(Return("vout10")); // PAGE number 9 + 1
1101 
1102         try
1103         {
1104             uint8_t page{13};
1105             device.getFileNumber(page);
1106             ADD_FAILURE() << "Should not have reached this line.";
1107         }
1108         catch (const std::exception& e)
1109         {
1110             EXPECT_STREQ(
1111                 e.what(),
1112                 "Unable to find hwmon file number for PAGE 13 of device xyz_pseq");
1113         }
1114     }
1115 }
1116 
TEST_F(PMBusDriverDeviceTests,PrepareForPgoodFaultDetection)1117 TEST_F(PMBusDriverDeviceTests, PrepareForPgoodFaultDetection)
1118 {
1119     // This is a protected method and cannot be called directly from a gtest.
1120     // Call findPgoodFault() which calls prepareForPgoodFaultDetection().
1121 
1122     // Create simulated hwmon voltage label file
1123     createFile("in1_label"); // PAGE 6 -> file number 1
1124 
1125     std::string name{"xyz_pseq"};
1126     uint8_t bus{3};
1127     uint16_t address{0x72};
1128     std::string powerControlGPIOName{"power-chassis-control"};
1129     std::string powerGoodGPIOName{"power-chassis-good"};
1130     std::vector<std::unique_ptr<Rail>> rails;
1131     PMBusDriverDevice device{name,
1132                              bus,
1133                              address,
1134                              powerControlGPIOName,
1135                              powerGoodGPIOName,
1136                              std::move(rails)};
1137 
1138     MockServices services;
1139     std::vector<int> gpioValues{1, 1, 1};
1140     EXPECT_CALL(services, getGPIOValues("xyz_pseq"))
1141         .Times(1)
1142         .WillOnce(Return(gpioValues));
1143 
1144     device.open(services);
1145 
1146     // Methods that get hwmon file info should be called twice
1147     MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
1148     EXPECT_CALL(pmbus, getPath(Type::Hwmon))
1149         .Times(2)
1150         .WillRepeatedly(Return(tempDirPath));
1151     EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
1152         .Times(2)
1153         .WillRepeatedly(Return("vout7")); // PAGE number 6 + 1
1154 
1155     // Map was empty and needs to be built
1156     uint8_t page{6};
1157     EXPECT_EQ(device.getFileNumber(page), 1);
1158 
1159     // Call findPgoodFault() which calls prepareForPgoodFaultDetection() which
1160     // rebuilds the map.
1161     std::string powerSupplyError{};
1162     std::map<std::string, std::string> additionalData{};
1163     std::string error =
1164         device.findPgoodFault(services, powerSupplyError, additionalData);
1165     EXPECT_TRUE(error.empty());
1166     EXPECT_EQ(additionalData.size(), 0);
1167 
1168     EXPECT_EQ(device.getFileNumber(page), 1);
1169 }
1170