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