blob: 977ad1a19e1e0a3295d01800adf622b74837ffba [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"
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
32using namespace phosphor::power::sequencer;
33
34using ::testing::Return;
35using ::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 */
50class 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 */
84std::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 */
106std::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
120TEST(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
147TEST(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
155TEST(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
180TEST(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}