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