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