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_services.hpp"
18 #include "rail.hpp"
19 #include "standard_device.hpp"
20 
21 #include <cstdint>
22 #include <map>
23 #include <memory>
24 #include <optional>
25 #include <string>
26 #include <utility>
27 #include <vector>
28 
29 #include <gmock/gmock.h>
30 #include <gtest/gtest.h>
31 
32 using namespace phosphor::power::sequencer;
33 
34 using ::testing::Return;
35 using ::testing::Throw;
36 
37 /**
38  * @class StandardDeviceImpl
39  *
40  * Concrete subclass of the StandardDevice abstract class.
41  *
42  * This subclass is required for two reasons:
43  * - StandardDevice has some pure virtual methods so it cannot be instantiated.
44  * - The pure virtual methods provide the PMBus and GPIO information.  Mocking
45  *   these makes it possible to test the pgood fault detection algorithm.
46  *
47  * This class is not intended to be used outside of this file.  It is
48  * implementation detail for testing the the StandardDevice class.
49  */
50 class StandardDeviceImpl : public StandardDevice
51 {
52   public:
53     // Specify which compiler-generated methods we want
54     StandardDeviceImpl() = delete;
55     StandardDeviceImpl(const StandardDeviceImpl&) = delete;
56     StandardDeviceImpl(StandardDeviceImpl&&) = delete;
57     StandardDeviceImpl& operator=(const StandardDeviceImpl&) = delete;
58     StandardDeviceImpl& operator=(StandardDeviceImpl&&) = delete;
59     virtual ~StandardDeviceImpl() = default;
60 
61     // Constructor just calls StandardDevice constructor
62     explicit StandardDeviceImpl(const std::string& name,
63                                 std::vector<std::unique_ptr<Rail>> rails) :
64         StandardDevice(name, std::move(rails))
65     {}
66 
67     // Mock pure virtual methods
68     MOCK_METHOD(std::vector<int>, getGPIOValues, (), (override));
69     MOCK_METHOD(uint16_t, getStatusWord, (uint8_t page), (override));
70     MOCK_METHOD(uint8_t, getStatusVout, (uint8_t page), (override));
71     MOCK_METHOD(double, getReadVout, (uint8_t page), (override));
72     MOCK_METHOD(double, getVoutUVFaultLimit, (uint8_t page), (override));
73 };
74 
75 /**
76  * Creates a Rail object that checks for a pgood fault using STATUS_VOUT.
77  *
78  * @param name Unique name for the rail
79  * @param isPowerSupplyRail Specifies whether the rail is produced by a
80                             power supply
81  * @param pageNum PMBus PAGE number of the rail
82  * @return Rail object
83  */
84 std::unique_ptr<Rail> createRailStatusVout(const std::string& name,
85                                            bool isPowerSupplyRail,
86                                            uint8_t pageNum)
87 {
88     std::optional<std::string> presence{};
89     std::optional<uint8_t> page{pageNum};
90     bool checkStatusVout{true};
91     bool compareVoltageToLimit{false};
92     std::optional<GPIO> gpio{};
93     return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
94                                   checkStatusVout, compareVoltageToLimit, gpio);
95 }
96 
97 /**
98  * Creates a Rail object that checks for a pgood fault using a GPIO.
99  *
100  * @param name Unique name for the rail
101  * @param isPowerSupplyRail Specifies whether the rail is produced by a
102                             power supply
103  * @param gpio GPIO line to read to determine the pgood status of the rail
104  * @return Rail object
105  */
106 std::unique_ptr<Rail> createRailGPIO(const std::string& name,
107                                      bool isPowerSupplyRail,
108                                      unsigned int gpioLine)
109 {
110     std::optional<std::string> presence{};
111     std::optional<uint8_t> page{};
112     bool checkStatusVout{false};
113     bool compareVoltageToLimit{false};
114     bool activeLow{false};
115     std::optional<GPIO> gpio{GPIO{gpioLine, activeLow}};
116     return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
117                                   checkStatusVout, compareVoltageToLimit, gpio);
118 }
119 
120 TEST(StandardDeviceTests, Constructor)
121 {
122     // Empty vector of rails
123     {
124         std::vector<std::unique_ptr<Rail>> rails{};
125         StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
126 
127         EXPECT_EQ(device.getName(), "xyz_pseq");
128         EXPECT_TRUE(device.getRails().empty());
129     }
130 
131     // Non-empty vector of rails
132     {
133         std::vector<std::unique_ptr<Rail>> rails{};
134         rails.emplace_back(createRailGPIO("PSU", true, 3));
135         rails.emplace_back(createRailStatusVout("VDD", false, 5));
136         rails.emplace_back(createRailStatusVout("VIO", false, 7));
137         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
138 
139         EXPECT_EQ(device.getName(), "abc_pseq");
140         EXPECT_EQ(device.getRails().size(), 3);
141         EXPECT_EQ(device.getRails()[0]->getName(), "PSU");
142         EXPECT_EQ(device.getRails()[1]->getName(), "VDD");
143         EXPECT_EQ(device.getRails()[2]->getName(), "VIO");
144     }
145 }
146 
147 TEST(StandardDeviceTests, GetName)
148 {
149     std::vector<std::unique_ptr<Rail>> rails{};
150     StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
151 
152     EXPECT_EQ(device.getName(), "xyz_pseq");
153 }
154 
155 TEST(StandardDeviceTests, GetRails)
156 {
157     // Empty vector of rails
158     {
159         std::vector<std::unique_ptr<Rail>> rails{};
160         StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
161 
162         EXPECT_TRUE(device.getRails().empty());
163     }
164 
165     // Non-empty vector of rails
166     {
167         std::vector<std::unique_ptr<Rail>> rails{};
168         rails.emplace_back(createRailGPIO("PSU", true, 3));
169         rails.emplace_back(createRailStatusVout("VDD", false, 5));
170         rails.emplace_back(createRailStatusVout("VIO", false, 7));
171         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
172 
173         EXPECT_EQ(device.getRails().size(), 3);
174         EXPECT_EQ(device.getRails()[0]->getName(), "PSU");
175         EXPECT_EQ(device.getRails()[1]->getName(), "VDD");
176         EXPECT_EQ(device.getRails()[2]->getName(), "VIO");
177     }
178 }
179 
180 TEST(StandardDeviceTests, FindPgoodFault)
181 {
182     // No rail has a pgood fault
183     {
184         std::vector<std::unique_ptr<Rail>> rails{};
185         rails.emplace_back(createRailGPIO("PSU", true, 2));
186         rails.emplace_back(createRailStatusVout("VDD", false, 5));
187         rails.emplace_back(createRailStatusVout("VIO", false, 7));
188         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
189 
190         std::vector<int> gpioValues{1, 1, 1};
191         EXPECT_CALL(device, getGPIOValues)
192             .Times(1)
193             .WillOnce(Return(gpioValues));
194         EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x00));
195         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x00));
196 
197         MockServices services{};
198 
199         std::string powerSupplyError{};
200         std::map<std::string, std::string> additionalData{};
201         std::string error = device.findPgoodFault(services, powerSupplyError,
202                                                   additionalData);
203         EXPECT_TRUE(error.empty());
204         EXPECT_EQ(additionalData.size(), 0);
205     }
206 
207     // First rail has a pgood fault
208     // Is a PSU rail: No PSU error specified
209     {
210         std::vector<std::unique_ptr<Rail>> rails{};
211         rails.emplace_back(createRailGPIO("PSU", true, 2));
212         rails.emplace_back(createRailStatusVout("VDD", false, 5));
213         rails.emplace_back(createRailStatusVout("VIO", false, 7));
214         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
215 
216         std::vector<int> gpioValues{1, 1, 0};
217         EXPECT_CALL(device, getGPIOValues)
218             .Times(1)
219             .WillOnce(Return(gpioValues));
220         EXPECT_CALL(device, getStatusVout).Times(0);
221 
222         MockServices services{};
223         EXPECT_CALL(services,
224                     logInfoMsg("Device abc_pseq GPIO values: [1, 1, 0]"))
225             .Times(1);
226         EXPECT_CALL(
227             services,
228             logErrorMsg(
229                 "Pgood fault found in rail monitored by device abc_pseq"))
230             .Times(1);
231         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail PSU"))
232             .Times(1);
233         EXPECT_CALL(
234             services,
235             logErrorMsg(
236                 "Rail PSU pgood GPIO line offset 2 has inactive value 0"))
237             .Times(1);
238 
239         std::string powerSupplyError{};
240         std::map<std::string, std::string> additionalData{};
241         std::string error = device.findPgoodFault(services, powerSupplyError,
242                                                   additionalData);
243         EXPECT_EQ(error,
244                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
245         EXPECT_EQ(additionalData.size(), 5);
246         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
247         EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 0]");
248         EXPECT_EQ(additionalData["RAIL_NAME"], "PSU");
249         EXPECT_EQ(additionalData["GPIO_LINE"], "2");
250         EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
251     }
252 
253     // First rail has a pgood fault
254     // Is a PSU rail: PSU error specified
255     {
256         std::vector<std::unique_ptr<Rail>> rails{};
257         rails.emplace_back(createRailGPIO("PSU", true, 2));
258         rails.emplace_back(createRailStatusVout("VDD", false, 5));
259         rails.emplace_back(createRailStatusVout("VIO", false, 7));
260         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
261 
262         std::vector<int> gpioValues{1, 1, 0};
263         EXPECT_CALL(device, getGPIOValues)
264             .Times(1)
265             .WillOnce(Return(gpioValues));
266         EXPECT_CALL(device, getStatusVout).Times(0);
267 
268         MockServices services{};
269         EXPECT_CALL(services,
270                     logInfoMsg("Device abc_pseq GPIO values: [1, 1, 0]"))
271             .Times(1);
272         EXPECT_CALL(
273             services,
274             logErrorMsg(
275                 "Pgood fault found in rail monitored by device abc_pseq"))
276             .Times(1);
277         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail PSU"))
278             .Times(1);
279         EXPECT_CALL(
280             services,
281             logErrorMsg(
282                 "Rail PSU pgood GPIO line offset 2 has inactive value 0"))
283             .Times(1);
284 
285         std::string powerSupplyError{"Undervoltage fault: PSU1"};
286         std::map<std::string, std::string> additionalData{};
287         std::string error = device.findPgoodFault(services, powerSupplyError,
288                                                   additionalData);
289         EXPECT_EQ(error, powerSupplyError);
290         EXPECT_EQ(additionalData.size(), 5);
291         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
292         EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 0]");
293         EXPECT_EQ(additionalData["RAIL_NAME"], "PSU");
294         EXPECT_EQ(additionalData["GPIO_LINE"], "2");
295         EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
296     }
297 
298     // Middle rail has a pgood fault
299     // Not a PSU rail: PSU error specified
300     {
301         std::vector<std::unique_ptr<Rail>> rails{};
302         rails.emplace_back(createRailGPIO("PSU", true, 2));
303         rails.emplace_back(createRailStatusVout("VDD", false, 5));
304         rails.emplace_back(createRailStatusVout("VIO", false, 7));
305         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
306 
307         std::vector<int> gpioValues{1, 1, 1};
308         EXPECT_CALL(device, getGPIOValues)
309             .Times(1)
310             .WillOnce(Return(gpioValues));
311         EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x10));
312         EXPECT_CALL(device, getStatusVout(7)).Times(0);
313         EXPECT_CALL(device, getStatusWord(5)).Times(1).WillOnce(Return(0xbeef));
314 
315         MockServices services{};
316         EXPECT_CALL(services,
317                     logInfoMsg("Device abc_pseq GPIO values: [1, 1, 1]"))
318             .Times(1);
319         EXPECT_CALL(
320             services,
321             logErrorMsg(
322                 "Pgood fault found in rail monitored by device abc_pseq"))
323             .Times(1);
324         EXPECT_CALL(services, logInfoMsg("Rail VDD STATUS_WORD: 0xbeef"))
325             .Times(1);
326         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD"))
327             .Times(1);
328         EXPECT_CALL(
329             services,
330             logErrorMsg("Rail VDD has fault bits set in STATUS_VOUT: 0x10"))
331             .Times(1);
332 
333         std::string powerSupplyError{"Undervoltage fault: PSU1"};
334         std::map<std::string, std::string> additionalData{};
335         std::string error = device.findPgoodFault(services, powerSupplyError,
336                                                   additionalData);
337         EXPECT_EQ(error,
338                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
339         EXPECT_EQ(additionalData.size(), 5);
340         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
341         EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 1]");
342         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD");
343         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x10");
344         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
345     }
346 
347     // Last rail has a pgood fault
348     // Device returns 0 GPIO values
349     // Does not halt pgood fault detection because GPIO values not used by rails
350     {
351         std::vector<std::unique_ptr<Rail>> rails{};
352         rails.emplace_back(createRailStatusVout("PSU", true, 3));
353         rails.emplace_back(createRailStatusVout("VDD", false, 5));
354         rails.emplace_back(createRailStatusVout("VIO", false, 7));
355         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
356 
357         std::vector<int> gpioValues{};
358         EXPECT_CALL(device, getGPIOValues)
359             .Times(1)
360             .WillOnce(Return(gpioValues));
361         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
362         EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x00));
363         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x11));
364         EXPECT_CALL(device, getStatusWord(7)).Times(1).WillOnce(Return(0xbeef));
365 
366         MockServices services{};
367         EXPECT_CALL(
368             services,
369             logErrorMsg(
370                 "Pgood fault found in rail monitored by device abc_pseq"))
371             .Times(1);
372         EXPECT_CALL(services, logInfoMsg("Rail VIO STATUS_WORD: 0xbeef"))
373             .Times(1);
374         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VIO"))
375             .Times(1);
376         EXPECT_CALL(
377             services,
378             logErrorMsg("Rail VIO has fault bits set in STATUS_VOUT: 0x11"))
379             .Times(1);
380 
381         std::string powerSupplyError{};
382         std::map<std::string, std::string> additionalData{};
383         std::string error = device.findPgoodFault(services, powerSupplyError,
384                                                   additionalData);
385         EXPECT_EQ(error,
386                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
387         EXPECT_EQ(additionalData.size(), 4);
388         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
389         EXPECT_EQ(additionalData["RAIL_NAME"], "VIO");
390         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x11");
391         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
392     }
393 
394     // Last rail has a pgood fault
395     // Exception occurs trying to obtain GPIO values from device
396     // Does not halt pgood fault detection because GPIO values not used by rails
397     {
398         std::vector<std::unique_ptr<Rail>> rails{};
399         rails.emplace_back(createRailStatusVout("PSU", true, 3));
400         rails.emplace_back(createRailStatusVout("VDD", false, 5));
401         rails.emplace_back(createRailStatusVout("VIO", false, 7));
402         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
403 
404         EXPECT_CALL(device, getGPIOValues)
405             .Times(1)
406             .WillOnce(Throw(std::runtime_error{"Unable to acquire GPIO line"}));
407         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
408         EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x00));
409         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x11));
410         EXPECT_CALL(device, getStatusWord(7)).Times(1).WillOnce(Return(0xbeef));
411 
412         MockServices services{};
413         EXPECT_CALL(
414             services,
415             logErrorMsg(
416                 "Pgood fault found in rail monitored by device abc_pseq"))
417             .Times(1);
418         EXPECT_CALL(services, logInfoMsg("Rail VIO STATUS_WORD: 0xbeef"))
419             .Times(1);
420         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VIO"))
421             .Times(1);
422         EXPECT_CALL(
423             services,
424             logErrorMsg("Rail VIO has fault bits set in STATUS_VOUT: 0x11"))
425             .Times(1);
426 
427         std::string powerSupplyError{};
428         std::map<std::string, std::string> additionalData{};
429         std::string error = device.findPgoodFault(services, powerSupplyError,
430                                                   additionalData);
431         EXPECT_EQ(error,
432                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
433         EXPECT_EQ(additionalData.size(), 4);
434         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
435         EXPECT_EQ(additionalData["RAIL_NAME"], "VIO");
436         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x11");
437         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
438     }
439 
440     // Exception is thrown during pgood fault detection
441     {
442         std::vector<std::unique_ptr<Rail>> rails{};
443         rails.emplace_back(createRailGPIO("PSU", true, 2));
444         rails.emplace_back(createRailStatusVout("VDD", false, 5));
445         rails.emplace_back(createRailStatusVout("VIO", false, 7));
446         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
447 
448         std::vector<int> gpioValues{1, 1, 1};
449         EXPECT_CALL(device, getGPIOValues)
450             .Times(1)
451             .WillOnce(Return(gpioValues));
452         EXPECT_CALL(device, getStatusVout(5))
453             .Times(1)
454             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
455         EXPECT_CALL(device, getStatusVout(7)).Times(0);
456 
457         MockServices services{};
458 
459         std::string powerSupplyError{};
460         std::map<std::string, std::string> additionalData{};
461         try
462         {
463             device.findPgoodFault(services, powerSupplyError, additionalData);
464             ADD_FAILURE() << "Should not have reached this line.";
465         }
466         catch (const std::exception& e)
467         {
468             EXPECT_STREQ(
469                 e.what(),
470                 "Unable to determine if a pgood fault occurred in device abc_pseq: "
471                 "Unable to read STATUS_VOUT value for rail VDD: "
472                 "File does not exist");
473         }
474     }
475 }
476