Update analyze function to check STATUS_WORD

The STATUS_WORD PMBus command response will be the start of the power
supply fault analysis. Update the analyze() function to read its value
and process (select) fault bits.

Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
Change-Id: If7274ad237c0604a56008676ae64804a5fd2854e
diff --git a/phosphor-power-supply/meson.build b/phosphor-power-supply/meson.build
index 9554dd9..922a67e 100644
--- a/phosphor-power-supply/meson.build
+++ b/phosphor-power-supply/meson.build
@@ -1,9 +1,10 @@
 
-executable(
+phosphor_psu_monitor = executable(
     'phosphor-psu-monitor',
     'main.cpp',
     'psu_manager.cpp',
     'power_supply.cpp',
+    'util.cpp',
     dependencies: [
         sdbusplus,
         sdeventplus,
@@ -14,3 +15,9 @@
         libpower,
     ]
 )
+
+power_supply = phosphor_psu_monitor.extract_objects('power_supply.cpp')
+
+if get_option('tests').enabled()
+  subdir('test')
+endif
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index a6be9f5..7e617a9 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -1,24 +1,21 @@
 #include "power_supply.hpp"
 
 #include "types.hpp"
-#include "utility.hpp"
+#include "util.hpp"
 
-namespace phosphor
-{
-namespace power
-{
-namespace psu
+#include <xyz/openbmc_project/Common/Device/error.hpp>
+
+namespace phosphor::power::psu
 {
 
 using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
 
 void PowerSupply::updatePresence()
 {
     try
     {
-        // Use getProperty utility function to get presence status.
-        util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath,
-                          INVENTORY_MGR_IFACE, bus, this->present);
+        present = getPresence(bus, inventoryPath);
     }
     catch (const sdbusplus::exception::SdBusError& e)
     {
@@ -28,6 +25,74 @@
     }
 }
 
+void PowerSupply::analyze()
+{
+    using namespace phosphor::pmbus;
+
+    if (present)
+    {
+        try
+        {
+            auto statusWord{pmbusIntf->read(STATUS_WORD, Type::Debug)};
+
+            if (statusWord)
+            {
+                if (statusWord & status_word::INPUT_FAULT_WARN)
+                {
+                    if (!inputFault)
+                    {
+                        log<level::INFO>(
+                            "INPUT fault",
+                            entry("STATUS_WORD=0x%04X",
+                                  static_cast<uint16_t>(statusWord)));
+                    }
+
+                    faultFound = true;
+                    inputFault = true;
+                }
+
+                if (statusWord & status_word::MFR_SPECIFIC_FAULT)
+                {
+                    if (!mfrFault)
+                    {
+                        log<level::INFO>(
+                            "MFRSPECIFIC fault",
+                            entry("STATUS_WORD=0x%04X",
+                                  static_cast<uint16_t>(statusWord)));
+                    }
+                    faultFound = true;
+                    mfrFault = true;
+                }
+
+                if (statusWord & status_word::VIN_UV_FAULT)
+                {
+                    if (!vinUVFault)
+                    {
+                        log<level::INFO>(
+                            "VIN_UV fault",
+                            entry("STATUS_WORD=0x%04X",
+                                  static_cast<uint16_t>(statusWord)));
+                    }
+
+                    faultFound = true;
+                    vinUVFault = true;
+                }
+            }
+            else
+            {
+                faultFound = false;
+                inputFault = false;
+                mfrFault = false;
+                vinUVFault = false;
+            }
+        }
+        catch (ReadFailure& e)
+        {
+            phosphor::logging::commit<ReadFailure>();
+        }
+    }
+}
+
 void PowerSupply::inventoryChanged(sdbusplus::message::message& msg)
 {
     std::string msgSensor;
@@ -53,6 +118,4 @@
     }
 }
 
-} // namespace psu
-} // namespace power
-} // namespace phosphor
+} // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index c3ff051..30fbf74 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -2,11 +2,13 @@
 
 #include "pmbus.hpp"
 #include "types.hpp"
+#include "utility.hpp"
 
 #include <sdbusplus/bus/match.hpp>
 
 namespace phosphor::power::psu
 {
+
 /**
  * @class PowerSupply
  * Represents a PMBus power supply device.
@@ -48,6 +50,11 @@
         updatePresence();
     }
 
+    phosphor::pmbus::PMBusBase& getPMBus()
+    {
+        return *pmbusIntf;
+    }
+
     /**
      * Power supply specific function to analyze for faults/errors.
      *
@@ -55,9 +62,7 @@
      * If a certain fault bits are on, the appropriate error will be
      * committed.
      */
-    void analyze()
-    {
-    }
+    void analyze();
 
     /**
      * Write PMBus CLEAR_FAULTS
@@ -69,6 +74,10 @@
      */
     void clearFaults()
     {
+        faultFound = false;
+        inputFault = false;
+        mfrFault = false;
+        vinUVFault = false;
     }
 
     /**
@@ -98,6 +107,38 @@
         return present;
     }
 
+    /**
+     * @brief Returns true if a fault was found.
+     */
+    bool isFaulted() const
+    {
+        return faultFound;
+    }
+
+    /**
+     * @brief Returns true if INPUT fault occurred.
+     */
+    bool hasInputFault() const
+    {
+        return inputFault;
+    }
+
+    /**
+     * @brief Returns true if MFRSPECIFIC occurred.
+     */
+    bool hasMFRFault() const
+    {
+        return mfrFault;
+    }
+
+    /**
+     * @brief Returns true if VIN_UV_FAULT occurred.
+     */
+    bool hasVINUVFault() const
+    {
+        return vinUVFault;
+    }
+
   private:
     /** @brief systemd bus member */
     sdbusplus::bus::bus& bus;
@@ -105,6 +146,15 @@
     /** @brief True if a fault has already been found and not cleared */
     bool faultFound = false;
 
+    /** @brief True if bit 5 of STATUS_WORD high byte is on. */
+    bool inputFault = false;
+
+    /** @brief True if bit 4 of STATUS_WORD high byte is on. */
+    bool mfrFault = false;
+
+    /** @brief True if bit 3 of STATUS_WORD low byte is on. */
+    bool vinUVFault = false;
+
     /**
      * @brief D-Bus path to use for this power supply's inventory status.
      **/
diff --git a/phosphor-power-supply/psu_manager.hpp b/phosphor-power-supply/psu_manager.hpp
index a4628c4..a3bcae0 100644
--- a/phosphor-power-supply/psu_manager.hpp
+++ b/phosphor-power-supply/psu_manager.hpp
@@ -111,6 +111,8 @@
         {
             psu->clearFaults();
         }
+
+        faultLogged = false;
     }
 
   private:
@@ -135,11 +137,36 @@
         {
             psu->analyze();
         }
+
+        for (auto& psu : psus)
+        {
+            // TODO: Fault priorities #918
+            if (!faultLogged && psu->isFaulted())
+            {
+                if (psu->hasInputFault())
+                {
+                    // TODO: Create error log
+                }
+
+                if (psu->hasMFRFault())
+                {
+                    // TODO: Create error log
+                }
+
+                if (psu->hasVINUVFault())
+                {
+                    // TODO: Create error log
+                }
+            }
+        }
     }
 
     /** @brief True if the power is on. */
     bool powerOn = false;
 
+    /** @brief True if fault logged. Clear in clearFaults(). */
+    bool faultLogged = false;
+
     /** @brief Used as part of subscribing to power on state changes*/
     std::string powerService;
 
diff --git a/phosphor-power-supply/test/meson.build b/phosphor-power-supply/test/meson.build
new file mode 100644
index 0000000..a71ac57
--- /dev/null
+++ b/phosphor-power-supply/test/meson.build
@@ -0,0 +1,22 @@
+test('phosphor-power-supply-tests',
+     executable('phosphor-power-supply-tests',
+                'power_supply_tests.cpp',
+                'mock.cpp',
+                dependencies: [
+                    gmock,
+                    gtest,
+                    sdbusplus,
+                    sdeventplus,
+                    phosphor_logging,
+                ],
+                implicit_include_directories: false,
+                include_directories: [
+                    '.',
+                    '..',
+                    '../..'
+                ],
+                link_args: dynamic_linker,
+                build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
+                objects: power_supply,
+     )
+)
diff --git a/phosphor-power-supply/test/mock.cpp b/phosphor-power-supply/test/mock.cpp
new file mode 100644
index 0000000..e13db2b
--- /dev/null
+++ b/phosphor-power-supply/test/mock.cpp
@@ -0,0 +1,15 @@
+#include <mock.hpp>
+
+namespace phosphor
+{
+namespace pmbus
+{
+
+std::unique_ptr<PMBusBase> createPMBus(std::uint8_t /*bus*/,
+                                       const std::string& /*address*/)
+{
+    return std::make_unique<MockedPMBus>();
+}
+
+} // namespace pmbus
+} // namespace phosphor
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
new file mode 100644
index 0000000..0c64e00
--- /dev/null
+++ b/phosphor-power-supply/test/mock.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "pmbus.hpp"
+#include "util_base.hpp"
+
+#include <gmock/gmock.h>
+
+namespace phosphor
+{
+namespace pmbus
+{
+class MockedPMBus : public PMBusBase
+{
+
+  public:
+    virtual ~MockedPMBus() = default;
+
+    MOCK_METHOD(uint64_t, read, (const std::string& name, Type type),
+                (override));
+};
+} // namespace pmbus
+
+namespace power
+{
+namespace psu
+{
+class MockedUtil : public UtilBase
+{
+  public:
+    virtual ~MockedUtil() = default;
+
+    MOCK_METHOD(bool, getPresence,
+                (sdbusplus::bus::bus & bus, const std::string& invpath),
+                (const, override));
+};
+
+static std::unique_ptr<MockedUtil> util;
+inline const UtilBase& getUtils()
+{
+    if (!util)
+    {
+        util = std::make_unique<MockedUtil>();
+    }
+    return *util;
+}
+
+inline void freeUtils()
+{
+    util.reset();
+}
+
+} // namespace psu
+} // namespace power
+
+} // namespace phosphor
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
new file mode 100644
index 0000000..9674153
--- /dev/null
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -0,0 +1,277 @@
+#include "../power_supply.hpp"
+#include "mock.hpp"
+
+#include <xyz/openbmc_project/Common/Device/error.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::psu;
+using namespace phosphor::pmbus;
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::StrEq;
+
+static auto PSUInventoryPath = "/xyz/bmc/inv/sys/chassis/board/powersupply0";
+
+class PowerSupplyTests : public ::testing::Test
+{
+  public:
+    PowerSupplyTests() :
+        mockedUtil(reinterpret_cast<const MockedUtil&>(getUtils()))
+    {
+        ON_CALL(mockedUtil, getPresence(_, _)).WillByDefault(Return(false));
+    }
+
+    ~PowerSupplyTests() override
+    {
+        freeUtils();
+    }
+
+    const MockedUtil& mockedUtil;
+};
+
+TEST_F(PowerSupplyTests, Constructor)
+{
+    /**
+     * @param[in] invpath - String for inventory path to use
+     * @param[in] i2cbus - The bus number this power supply is on
+     * @param[in] i2caddr - The 16-bit I2C address of the power supply
+     */
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
+    auto psu = std::make_unique<PowerSupply>(bus, PSUInventoryPath, 3, "0068");
+
+    EXPECT_EQ(psu->isPresent(), false);
+    EXPECT_EQ(psu->isFaulted(), false);
+    EXPECT_EQ(psu->hasInputFault(), false);
+    EXPECT_EQ(psu->hasMFRFault(), false);
+    EXPECT_EQ(psu->hasVINUVFault(), false);
+}
+
+TEST_F(PowerSupplyTests, Analyze)
+{
+    auto bus = sdbusplus::bus::new_default();
+
+    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
+    PowerSupply psu{bus, PSUInventoryPath, 4, "0069"};
+    psu.analyze();
+    // By default, nothing should change.
+    EXPECT_EQ(psu.isPresent(), false);
+    EXPECT_EQ(psu.isFaulted(), false);
+    EXPECT_EQ(psu.hasInputFault(), false);
+    EXPECT_EQ(psu.hasMFRFault(), false);
+    EXPECT_EQ(psu.hasVINUVFault(), false);
+
+    // In order to get the various faults tested, the power supply needs to be
+    // present in order to read from the PMBus device(s).
+    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+        .Times(1)
+        .WillOnce(Return(true)); // present
+    PowerSupply psu2{bus, PSUInventoryPath, 5, "006a"};
+    EXPECT_EQ(psu2.isPresent(), true);
+
+    // STATUS_WORD 0x0000 is powered on, no faults.
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu2.getPMBus());
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    psu2.analyze();
+    EXPECT_EQ(psu2.isPresent(), true);
+    EXPECT_EQ(psu2.isFaulted(), false);
+    EXPECT_EQ(psu2.hasInputFault(), false);
+    EXPECT_EQ(psu2.hasMFRFault(), false);
+    EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+    // STATUS_WORD input fault/warn
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .Times(1)
+        .WillOnce(Return(status_word::INPUT_FAULT_WARN));
+    psu2.analyze();
+    EXPECT_EQ(psu2.isPresent(), true);
+    EXPECT_EQ(psu2.isFaulted(), true);
+    EXPECT_EQ(psu2.hasInputFault(), true);
+    EXPECT_EQ(psu2.hasMFRFault(), false);
+    EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+    // STATUS_WORD INPUT/UV fault.
+    // First need it to return good status, then the fault
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .WillOnce(Return(0x0000))
+        .WillOnce(Return(status_word::VIN_UV_FAULT));
+    psu2.analyze();
+    psu2.analyze();
+    EXPECT_EQ(psu2.isPresent(), true);
+    EXPECT_EQ(psu2.isFaulted(), true);
+    EXPECT_EQ(psu2.hasInputFault(), false);
+    EXPECT_EQ(psu2.hasMFRFault(), false);
+    EXPECT_EQ(psu2.hasVINUVFault(), true);
+
+    // STATUS_WORD MFR fault.
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .WillOnce(Return(0x0000))
+        .WillOnce(Return(status_word::MFR_SPECIFIC_FAULT));
+    psu2.analyze();
+    psu2.analyze();
+    EXPECT_EQ(psu2.isPresent(), true);
+    EXPECT_EQ(psu2.isFaulted(), true);
+    EXPECT_EQ(psu2.hasInputFault(), false);
+    EXPECT_EQ(psu2.hasMFRFault(), true);
+    EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+    // Ignore Temperature fault.
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .WillOnce(Return(0x0000))
+        .WillOnce(Return(status_word::TEMPERATURE_FAULT_WARN));
+    psu2.analyze();
+    psu2.analyze();
+    EXPECT_EQ(psu2.isPresent(), true);
+    EXPECT_EQ(psu2.isFaulted(), false);
+    EXPECT_EQ(psu2.hasInputFault(), false);
+    EXPECT_EQ(psu2.hasMFRFault(), false);
+    EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+    // Ignore fan fault
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .WillOnce(Return(0x0000))
+        .WillOnce(Return(status_word::FAN_FAULT));
+    psu2.analyze();
+    psu2.analyze();
+    EXPECT_EQ(psu2.isPresent(), true);
+    EXPECT_EQ(psu2.isFaulted(), false);
+    EXPECT_EQ(psu2.hasInputFault(), false);
+    EXPECT_EQ(psu2.hasMFRFault(), false);
+    EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+    // TODO: ReadFailure
+}
+
+TEST_F(PowerSupplyTests, ClearFaults)
+{
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+        .Times(1)
+        .WillOnce(Return(true)); // present
+    PowerSupply psu{bus, PSUInventoryPath, 13, "0068"};
+    EXPECT_EQ(psu.isPresent(), true);
+    EXPECT_EQ(psu.isFaulted(), false);
+    EXPECT_EQ(psu.hasInputFault(), false);
+    EXPECT_EQ(psu.hasMFRFault(), false);
+    EXPECT_EQ(psu.hasVINUVFault(), false);
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0xFFFF));
+    psu.analyze();
+    EXPECT_EQ(psu.isPresent(), true);
+    EXPECT_EQ(psu.isFaulted(), true);
+    EXPECT_EQ(psu.hasInputFault(), true);
+    EXPECT_EQ(psu.hasMFRFault(), true);
+    EXPECT_EQ(psu.hasVINUVFault(), true);
+    psu.clearFaults();
+    EXPECT_EQ(psu.isPresent(), true);
+    EXPECT_EQ(psu.isFaulted(), false);
+    EXPECT_EQ(psu.hasInputFault(), false);
+    EXPECT_EQ(psu.hasMFRFault(), false);
+    EXPECT_EQ(psu.hasVINUVFault(), false);
+}
+
+TEST_F(PowerSupplyTests, UpdateInventory)
+{
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+        .Times(1)
+        .WillOnce(Return(true)); // present
+    PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+    psu.updateInventory();
+    // TODO: Checks / Story #921
+}
+
+TEST_F(PowerSupplyTests, IsPresent)
+{
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
+    PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+    EXPECT_EQ(psu.isPresent(), false);
+
+    EXPECT_CALL(mockedUtil, getPresence(_, _))
+        .WillOnce(Return(true)); // present
+    PowerSupply psu2{bus, PSUInventoryPath, 10, "006b"};
+    EXPECT_EQ(psu2.isPresent(), true);
+}
+
+TEST_F(PowerSupplyTests, IsFaulted)
+{
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, _))
+        .WillOnce(Return(true)); // present
+    PowerSupply psu{bus, PSUInventoryPath, 11, "006f"};
+    EXPECT_EQ(psu.isFaulted(), false);
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0xFFFF));
+    psu.analyze();
+    EXPECT_EQ(psu.isFaulted(), true);
+}
+
+TEST_F(PowerSupplyTests, HasInputFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, _))
+        .WillOnce(Return(true)); // present
+    PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    EXPECT_EQ(psu.hasInputFault(), false);
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    psu.analyze();
+    EXPECT_EQ(psu.hasInputFault(), false);
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .Times(1)
+        .WillOnce(Return(status_word::INPUT_FAULT_WARN));
+    psu.analyze();
+    EXPECT_EQ(psu.hasInputFault(), true);
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    psu.analyze();
+    EXPECT_EQ(psu.hasInputFault(), false);
+}
+
+TEST_F(PowerSupplyTests, HasMFRFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, _))
+        .WillOnce(Return(true)); // present
+    PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    EXPECT_EQ(psu.hasMFRFault(), false);
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    psu.analyze();
+    EXPECT_EQ(psu.hasMFRFault(), false);
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .Times(1)
+        .WillOnce(Return(status_word::MFR_SPECIFIC_FAULT));
+    psu.analyze();
+    EXPECT_EQ(psu.hasMFRFault(), true);
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    psu.analyze();
+    EXPECT_EQ(psu.hasMFRFault(), false);
+}
+
+TEST_F(PowerSupplyTests, HasVINUVFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+    EXPECT_CALL(mockedUtil, getPresence(_, _))
+        .WillOnce(Return(true)); // present
+    PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    EXPECT_EQ(psu.hasVINUVFault(), false);
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    psu.analyze();
+    EXPECT_EQ(psu.hasVINUVFault(), false);
+    EXPECT_CALL(mockPMBus, read(_, _))
+        .Times(1)
+        .WillOnce(Return(status_word::VIN_UV_FAULT));
+    psu.analyze();
+    EXPECT_EQ(psu.hasVINUVFault(), true);
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    psu.analyze();
+    EXPECT_EQ(psu.hasVINUVFault(), false);
+}
diff --git a/phosphor-power-supply/util.cpp b/phosphor-power-supply/util.cpp
new file mode 100644
index 0000000..c4555ae
--- /dev/null
+++ b/phosphor-power-supply/util.cpp
@@ -0,0 +1,12 @@
+#include "util.hpp"
+
+namespace phosphor::power::psu
+{
+
+const UtilBase& getUtils()
+{
+    static Util util;
+    return util;
+}
+
+} // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/util.hpp b/phosphor-power-supply/util.hpp
new file mode 100644
index 0000000..2e4afe2
--- /dev/null
+++ b/phosphor-power-supply/util.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "util_base.hpp"
+#include "utility.hpp"
+
+namespace phosphor::power::psu
+{
+
+class Util : public UtilBase
+{
+  public:
+    //~Util(){};
+    bool getPresence(sdbusplus::bus::bus& bus,
+                     const std::string& invpath) const override
+    {
+        bool present = false;
+
+        // Use getProperty utility function to get presence status.
+        util::getProperty(INVENTORY_IFACE, PRESENT_PROP, invpath,
+                          INVENTORY_MGR_IFACE, bus, present);
+
+        return present;
+    }
+};
+
+} // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/util_base.hpp b/phosphor-power-supply/util_base.hpp
new file mode 100644
index 0000000..ab1a962
--- /dev/null
+++ b/phosphor-power-supply/util_base.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "types.hpp"
+
+#include <sdbusplus/bus/match.hpp>
+
+namespace phosphor::power::psu
+{
+
+/**
+ * @class UtilBase
+ * A base class to allow for mocking certain utility functions.
+ */
+class UtilBase
+{
+  public:
+    virtual ~UtilBase() = default;
+
+    virtual bool getPresence(sdbusplus::bus::bus& bus,
+                             const std::string& invpath) const = 0;
+};
+
+const UtilBase& getUtils();
+
+inline bool getPresence(sdbusplus::bus::bus& bus, const std::string& invpath)
+{
+    return getUtils().getPresence(bus, invpath);
+}
+
+} // namespace phosphor::power::psu
diff --git a/pmbus.hpp b/pmbus.hpp
index 89c3118..5e252b2 100644
--- a/pmbus.hpp
+++ b/pmbus.hpp
@@ -52,6 +52,10 @@
 // to see if the INPUT FAULT OR WARNING bit is on.
 constexpr auto INPUT_FAULT_WARN = 0x2000;
 
+// The bit mask representing the MFRSPECIFIC fault, bit 4 of STATUS_WORD high
+// byte. A manufacturer specific fault or warning has occurred.
+constexpr auto MFR_SPECIFIC_FAULT = 0x1000;
+
 // The bit mask representing the POWER_GOOD Negated bit of the STATUS_WORD.
 constexpr auto POWER_GOOD_NEGATED = 0x0800;
 
@@ -118,6 +122,8 @@
 {
   public:
     virtual ~PMBusBase() = default;
+
+    virtual uint64_t read(const std::string& name, Type type) = 0;
 };
 
 /**
@@ -233,7 +239,7 @@
      *
      * @return uint64_t - Up to 8 bytes of data read from file.
      */
-    uint64_t read(const std::string& name, Type type);
+    uint64_t read(const std::string& name, Type type) override;
 
     /**
      * Read a string from file in sysfs.