blob: 2eb3a42b83d45db38a320dd694f0b5e2f2f35a8d [file] [log] [blame]
/**
* Copyright © 2024 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mock_services.hpp"
#include "rail.hpp"
#include "services.hpp"
#include "standard_device.hpp"
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace phosphor::power::sequencer;
using ::testing::Return;
using ::testing::Throw;
/**
* @class StandardDeviceImpl
*
* Concrete subclass of the StandardDevice abstract class.
*
* This subclass is required for two reasons:
* - StandardDevice has some pure virtual methods so it cannot be instantiated.
* - The pure virtual methods provide the PMBus and GPIO information. Mocking
* these makes it possible to test the pgood fault detection algorithm.
*
* This class is not intended to be used outside of this file. It is
* implementation detail for testing the the StandardDevice class.
*/
class StandardDeviceImpl : public StandardDevice
{
public:
// Specify which compiler-generated methods we want
StandardDeviceImpl() = delete;
StandardDeviceImpl(const StandardDeviceImpl&) = delete;
StandardDeviceImpl(StandardDeviceImpl&&) = delete;
StandardDeviceImpl& operator=(const StandardDeviceImpl&) = delete;
StandardDeviceImpl& operator=(StandardDeviceImpl&&) = delete;
virtual ~StandardDeviceImpl() = default;
// Constructor just calls StandardDevice constructor
explicit StandardDeviceImpl(const std::string& name,
std::vector<std::unique_ptr<Rail>> rails) :
StandardDevice(name, std::move(rails))
{}
// Mock pure virtual methods
MOCK_METHOD(std::vector<int>, getGPIOValues, (Services & services),
(override));
MOCK_METHOD(uint16_t, getStatusWord, (uint8_t page), (override));
MOCK_METHOD(uint8_t, getStatusVout, (uint8_t page), (override));
MOCK_METHOD(double, getReadVout, (uint8_t page), (override));
MOCK_METHOD(double, getVoutUVFaultLimit, (uint8_t page), (override));
};
/**
* Creates a Rail object that checks for a pgood fault using STATUS_VOUT.
*
* @param name Unique name for the rail
* @param isPowerSupplyRail Specifies whether the rail is produced by a
power supply
* @param pageNum PMBus PAGE number of the rail
* @return Rail object
*/
std::unique_ptr<Rail> createRailStatusVout(const std::string& name,
bool isPowerSupplyRail,
uint8_t pageNum)
{
std::optional<std::string> presence{};
std::optional<uint8_t> page{pageNum};
bool checkStatusVout{true};
bool compareVoltageToLimit{false};
std::optional<GPIO> gpio{};
return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
checkStatusVout, compareVoltageToLimit, gpio);
}
/**
* Creates a Rail object that checks for a pgood fault using a GPIO.
*
* @param name Unique name for the rail
* @param isPowerSupplyRail Specifies whether the rail is produced by a
power supply
* @param gpio GPIO line to read to determine the pgood status of the rail
* @return Rail object
*/
std::unique_ptr<Rail> createRailGPIO(const std::string& name,
bool isPowerSupplyRail,
unsigned int gpioLine)
{
std::optional<std::string> presence{};
std::optional<uint8_t> page{};
bool checkStatusVout{false};
bool compareVoltageToLimit{false};
bool activeLow{false};
std::optional<GPIO> gpio{GPIO{gpioLine, activeLow}};
return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
checkStatusVout, compareVoltageToLimit, gpio);
}
TEST(StandardDeviceTests, Constructor)
{
// Empty vector of rails
{
std::vector<std::unique_ptr<Rail>> rails{};
StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
EXPECT_EQ(device.getName(), "xyz_pseq");
EXPECT_TRUE(device.getRails().empty());
}
// Non-empty vector of rails
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailGPIO("PSU", true, 3));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
EXPECT_EQ(device.getName(), "abc_pseq");
EXPECT_EQ(device.getRails().size(), 3);
EXPECT_EQ(device.getRails()[0]->getName(), "PSU");
EXPECT_EQ(device.getRails()[1]->getName(), "VDD");
EXPECT_EQ(device.getRails()[2]->getName(), "VIO");
}
}
TEST(StandardDeviceTests, GetName)
{
std::vector<std::unique_ptr<Rail>> rails{};
StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
EXPECT_EQ(device.getName(), "xyz_pseq");
}
TEST(StandardDeviceTests, GetRails)
{
// Empty vector of rails
{
std::vector<std::unique_ptr<Rail>> rails{};
StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
EXPECT_TRUE(device.getRails().empty());
}
// Non-empty vector of rails
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailGPIO("PSU", true, 3));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
EXPECT_EQ(device.getRails().size(), 3);
EXPECT_EQ(device.getRails()[0]->getName(), "PSU");
EXPECT_EQ(device.getRails()[1]->getName(), "VDD");
EXPECT_EQ(device.getRails()[2]->getName(), "VIO");
}
}
TEST(StandardDeviceTests, FindPgoodFault)
{
// No rail has a pgood fault
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailGPIO("PSU", true, 2));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
std::vector<int> gpioValues{1, 1, 1};
EXPECT_CALL(device, getGPIOValues)
.Times(1)
.WillOnce(Return(gpioValues));
EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x00));
EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x00));
MockServices services{};
std::string powerSupplyError{};
std::map<std::string, std::string> additionalData{};
std::string error = device.findPgoodFault(services, powerSupplyError,
additionalData);
EXPECT_TRUE(error.empty());
EXPECT_EQ(additionalData.size(), 0);
}
// First rail has a pgood fault
// Is a PSU rail: No PSU error specified
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailGPIO("PSU", true, 2));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
std::vector<int> gpioValues{1, 1, 0};
EXPECT_CALL(device, getGPIOValues)
.Times(1)
.WillOnce(Return(gpioValues));
EXPECT_CALL(device, getStatusVout).Times(0);
MockServices services{};
EXPECT_CALL(services,
logInfoMsg("Device abc_pseq GPIO values: [1, 1, 0]"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg(
"Pgood fault found in rail monitored by device abc_pseq"))
.Times(1);
EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail PSU"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg(
"Rail PSU pgood GPIO line offset 2 has inactive value 0"))
.Times(1);
std::string powerSupplyError{};
std::map<std::string, std::string> additionalData{};
std::string error = device.findPgoodFault(services, powerSupplyError,
additionalData);
EXPECT_EQ(error,
"xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
EXPECT_EQ(additionalData.size(), 5);
EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 0]");
EXPECT_EQ(additionalData["RAIL_NAME"], "PSU");
EXPECT_EQ(additionalData["GPIO_LINE"], "2");
EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
}
// First rail has a pgood fault
// Is a PSU rail: PSU error specified
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailGPIO("PSU", true, 2));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
std::vector<int> gpioValues{1, 1, 0};
EXPECT_CALL(device, getGPIOValues)
.Times(1)
.WillOnce(Return(gpioValues));
EXPECT_CALL(device, getStatusVout).Times(0);
MockServices services{};
EXPECT_CALL(services,
logInfoMsg("Device abc_pseq GPIO values: [1, 1, 0]"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg(
"Pgood fault found in rail monitored by device abc_pseq"))
.Times(1);
EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail PSU"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg(
"Rail PSU pgood GPIO line offset 2 has inactive value 0"))
.Times(1);
std::string powerSupplyError{"Undervoltage fault: PSU1"};
std::map<std::string, std::string> additionalData{};
std::string error = device.findPgoodFault(services, powerSupplyError,
additionalData);
EXPECT_EQ(error, powerSupplyError);
EXPECT_EQ(additionalData.size(), 5);
EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 0]");
EXPECT_EQ(additionalData["RAIL_NAME"], "PSU");
EXPECT_EQ(additionalData["GPIO_LINE"], "2");
EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
}
// Middle rail has a pgood fault
// Not a PSU rail: PSU error specified
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailGPIO("PSU", true, 2));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
std::vector<int> gpioValues{1, 1, 1};
EXPECT_CALL(device, getGPIOValues)
.Times(1)
.WillOnce(Return(gpioValues));
EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x10));
EXPECT_CALL(device, getStatusVout(7)).Times(0);
EXPECT_CALL(device, getStatusWord(5)).Times(1).WillOnce(Return(0xbeef));
MockServices services{};
EXPECT_CALL(services,
logInfoMsg("Device abc_pseq GPIO values: [1, 1, 1]"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg(
"Pgood fault found in rail monitored by device abc_pseq"))
.Times(1);
EXPECT_CALL(services, logInfoMsg("Rail VDD STATUS_WORD: 0xbeef"))
.Times(1);
EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg("Rail VDD has fault bits set in STATUS_VOUT: 0x10"))
.Times(1);
std::string powerSupplyError{"Undervoltage fault: PSU1"};
std::map<std::string, std::string> additionalData{};
std::string error = device.findPgoodFault(services, powerSupplyError,
additionalData);
EXPECT_EQ(error,
"xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
EXPECT_EQ(additionalData.size(), 5);
EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 1]");
EXPECT_EQ(additionalData["RAIL_NAME"], "VDD");
EXPECT_EQ(additionalData["STATUS_VOUT"], "0x10");
EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
}
// Last rail has a pgood fault
// Device returns 0 GPIO values
// Does not halt pgood fault detection because GPIO values not used by rails
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailStatusVout("PSU", true, 3));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
std::vector<int> gpioValues{};
EXPECT_CALL(device, getGPIOValues)
.Times(1)
.WillOnce(Return(gpioValues));
EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x00));
EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x11));
EXPECT_CALL(device, getStatusWord(7)).Times(1).WillOnce(Return(0xbeef));
MockServices services{};
EXPECT_CALL(
services,
logErrorMsg(
"Pgood fault found in rail monitored by device abc_pseq"))
.Times(1);
EXPECT_CALL(services, logInfoMsg("Rail VIO STATUS_WORD: 0xbeef"))
.Times(1);
EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VIO"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg("Rail VIO has fault bits set in STATUS_VOUT: 0x11"))
.Times(1);
std::string powerSupplyError{};
std::map<std::string, std::string> additionalData{};
std::string error = device.findPgoodFault(services, powerSupplyError,
additionalData);
EXPECT_EQ(error,
"xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
EXPECT_EQ(additionalData.size(), 4);
EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
EXPECT_EQ(additionalData["RAIL_NAME"], "VIO");
EXPECT_EQ(additionalData["STATUS_VOUT"], "0x11");
EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
}
// Last rail has a pgood fault
// Exception occurs trying to obtain GPIO values from device
// Does not halt pgood fault detection because GPIO values not used by rails
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailStatusVout("PSU", true, 3));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
EXPECT_CALL(device, getGPIOValues)
.Times(1)
.WillOnce(Throw(std::runtime_error{"Unable to acquire GPIO line"}));
EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
EXPECT_CALL(device, getStatusVout(5)).Times(1).WillOnce(Return(0x00));
EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x11));
EXPECT_CALL(device, getStatusWord(7)).Times(1).WillOnce(Return(0xbeef));
MockServices services{};
EXPECT_CALL(
services,
logErrorMsg(
"Pgood fault found in rail monitored by device abc_pseq"))
.Times(1);
EXPECT_CALL(services, logInfoMsg("Rail VIO STATUS_WORD: 0xbeef"))
.Times(1);
EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VIO"))
.Times(1);
EXPECT_CALL(
services,
logErrorMsg("Rail VIO has fault bits set in STATUS_VOUT: 0x11"))
.Times(1);
std::string powerSupplyError{};
std::map<std::string, std::string> additionalData{};
std::string error = device.findPgoodFault(services, powerSupplyError,
additionalData);
EXPECT_EQ(error,
"xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
EXPECT_EQ(additionalData.size(), 4);
EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
EXPECT_EQ(additionalData["RAIL_NAME"], "VIO");
EXPECT_EQ(additionalData["STATUS_VOUT"], "0x11");
EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
}
// Exception is thrown during pgood fault detection
{
std::vector<std::unique_ptr<Rail>> rails{};
rails.emplace_back(createRailGPIO("PSU", true, 2));
rails.emplace_back(createRailStatusVout("VDD", false, 5));
rails.emplace_back(createRailStatusVout("VIO", false, 7));
StandardDeviceImpl device{"abc_pseq", std::move(rails)};
std::vector<int> gpioValues{1, 1, 1};
EXPECT_CALL(device, getGPIOValues)
.Times(1)
.WillOnce(Return(gpioValues));
EXPECT_CALL(device, getStatusVout(5))
.Times(1)
.WillOnce(Throw(std::runtime_error{"File does not exist"}));
EXPECT_CALL(device, getStatusVout(7)).Times(0);
MockServices services{};
std::string powerSupplyError{};
std::map<std::string, std::string> additionalData{};
try
{
device.findPgoodFault(services, powerSupplyError, additionalData);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::exception& e)
{
EXPECT_STREQ(
e.what(),
"Unable to determine if a pgood fault occurred in device abc_pseq: "
"Unable to read STATUS_VOUT value for rail VDD: "
"File does not exist");
}
}
}