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