blob: 2eb3a42b83d45db38a320dd694f0b5e2f2f35a8d [file] [log] [blame]
Shawn McCarney472101c2024-04-17 16:31:09 -05001/**
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"
Shawn McCarneyfc3f31f2024-04-23 17:02:44 -050019#include "services.hpp"
Shawn McCarney472101c2024-04-17 16:31:09 -050020#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
33using namespace phosphor::power::sequencer;
34
35using ::testing::Return;
36using ::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 */
51class 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
Shawn McCarneyfc3f31f2024-04-23 17:02:44 -050069 MOCK_METHOD(std::vector<int>, getGPIOValues, (Services & services),
70 (override));
Shawn McCarney472101c2024-04-17 16:31:09 -050071 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 */
86std::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 */
108std::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
122TEST(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
149TEST(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
157TEST(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
182TEST(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}