psu-ng: Add in ability to get presence via GPIO

The device tree and entity-manager (D-Bus) properties have the power
supply GPIO presence lines named, so we can use those GPIO line names to
find the GPIO device to read for determining power supply presence.

Some systems have the power supply presence lines in a gpio-keys section
(notably IBM Rainier). To facilitate continued function while running
with a device tree that has not removed those device tree entries, allow
for a fallback to the old inventory D-Bus propertiesChanged or
interfaceAdded matches for presence.

Change-Id: I5002aa62e5b460463cc26328c889a3786b467a3c
Signed-off-by: B. J. Wyman <bjwyman@gmail.com>
diff --git a/meson.build b/meson.build
index 4b356c7..fff20a7 100644
--- a/meson.build
+++ b/meson.build
@@ -42,6 +42,7 @@
 stdplus = dependency('stdplus')
 boost = dependency('boost')
 fmt = dependency('fmt')
+libgpiodcxx = dependency('libgpiodcxx')
 
 systemd = dependency('systemd')
 servicedir = systemd.get_pkgconfig_variable('systemdsystemunitdir')
diff --git a/phosphor-power-supply/meson.build b/phosphor-power-supply/meson.build
index fa83ae7..f806861 100644
--- a/phosphor-power-supply/meson.build
+++ b/phosphor-power-supply/meson.build
@@ -12,6 +12,7 @@
         sdbusplus,
         sdeventplus,
         fmt,
+        libgpiodcxx,
     ],
     include_directories: '..',
     install: true,
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index 847e686..226d03b 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -11,46 +11,117 @@
 
 #include <chrono>  // sleep_for()
 #include <cstdint> // uint8_t...
-#include <thread>  // sleep_for()
+#include <fstream>
+#include <thread> // sleep_for()
 
 namespace phosphor::power::psu
 {
+// Amount of time in milliseconds to delay between power supply going from
+// missing to present before running the bind command(s).
+constexpr auto bindDelay = 1000;
 
 using namespace phosphor::logging;
 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
 
 PowerSupply::PowerSupply(sdbusplus::bus::bus& bus, const std::string& invpath,
-                         std::uint8_t i2cbus, std::uint16_t i2caddr) :
+                         std::uint8_t i2cbus, std::uint16_t i2caddr,
+                         const std::string& gpioLineName) :
     bus(bus),
-    inventoryPath(invpath)
+    inventoryPath(invpath), bindPath("/sys/bus/i2c/drivers/ibm-cffps")
 {
     if (inventoryPath.empty())
     {
         throw std::invalid_argument{"Invalid empty inventoryPath"};
     }
 
-    // Setup the functions to call when the D-Bus inventory path for the
-    // Present property changes.
-    presentMatch = std::make_unique<sdbusplus::bus::match_t>(
-        bus,
-        sdbusplus::bus::match::rules::propertiesChanged(inventoryPath,
-                                                        INVENTORY_IFACE),
-        [this](auto& msg) { this->inventoryChanged(msg); });
+    if (gpioLineName.empty())
+    {
+        throw std::invalid_argument{"Invalid empty gpioLineName"};
+    }
 
-    presentAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
-        bus,
-        sdbusplus::bus::match::rules::interfacesAdded() +
-            sdbusplus::bus::match::rules::argNpath(0, inventoryPath),
-        [this](auto& msg) { this->inventoryAdded(msg); });
+    log<level::DEBUG>(fmt::format("gpioLineName: {}", gpioLineName).c_str());
+    presenceGPIO = createGPIO(gpioLineName);
 
     std::ostringstream ss;
     ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
     std::string addrStr = ss.str();
+    std::string busStr = std::to_string(i2cbus);
+    bindDevice = busStr;
+    bindDevice.append("-");
+    bindDevice.append(addrStr);
+
     pmbusIntf = phosphor::pmbus::createPMBus(i2cbus, addrStr);
 
     // Get the current state of the Present property.
-    updatePresence();
-    updateInventory();
+    try
+    {
+        updatePresenceGPIO();
+    }
+    catch (...)
+    {
+        // If the above attempt to use the GPIO failed, it likely means that the
+        // GPIOs are in use by the kernel, meaning it is using gpio-keys.
+        // So, I should rely on phosphor-gpio-presence to update D-Bus, and
+        // work that way for power supply presence.
+        presenceGPIO = nullptr;
+        // Setup the functions to call when the D-Bus inventory path for the
+        // Present property changes.
+        presentMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::propertiesChanged(inventoryPath,
+                                                            INVENTORY_IFACE),
+            [this](auto& msg) { this->inventoryChanged(msg); });
+
+        presentAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::interfacesAdded() +
+                sdbusplus::bus::match::rules::argNpath(0, inventoryPath),
+            [this](auto& msg) { this->inventoryAdded(msg); });
+
+        updatePresence();
+        updateInventory();
+    }
+}
+
+void PowerSupply::bindOrUnbindDriver(bool present)
+{
+    auto action = (present) ? "bind" : "unbind";
+    auto path = bindPath / action;
+
+    if (present)
+    {
+        log<level::INFO>(
+            fmt::format("Binding device driver. path: {} device: {}",
+                        path.string(), bindDevice)
+                .c_str());
+    }
+    else
+    {
+        log<level::INFO>(
+            fmt::format("Unbinding device driver. path: {} device: {}",
+                        path.string(), bindDevice)
+                .c_str());
+    }
+
+    std::ofstream file;
+
+    file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
+                    std::ofstream::eofbit);
+
+    try
+    {
+        file.open(path);
+        file << bindDevice;
+        file.close();
+    }
+    catch (std::exception& e)
+    {
+        auto err = errno;
+
+        log<level::ERR>(
+            fmt::format("Failed binding or unbinding device. errno={}", err)
+                .c_str());
+    }
 }
 
 void PowerSupply::updatePresence()
@@ -70,10 +141,63 @@
     }
 }
 
+void PowerSupply::updatePresenceGPIO()
+{
+    bool presentOld = present;
+
+    try
+    {
+        if (presenceGPIO->read() > 0)
+        {
+            present = true;
+        }
+        else
+        {
+            present = false;
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("presenceGPIO read fail: {}", e.what()).c_str());
+        throw;
+    }
+
+    if (presentOld != present)
+    {
+        log<level::DEBUG>(
+            fmt::format("presentOld: {} present: {}", presentOld, present)
+                .c_str());
+        if (present)
+        {
+            std::this_thread::sleep_for(std::chrono::milliseconds(bindDelay));
+            bindOrUnbindDriver(present);
+            pmbusIntf->findHwmonDir();
+            onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
+            clearFaults();
+        }
+        else
+        {
+            bindOrUnbindDriver(present);
+        }
+
+        auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+        auto const lastSlashPos = invpath.find_last_of('/');
+        std::string prettyName = invpath.substr(lastSlashPos + 1);
+        setPresence(bus, invpath, present, prettyName);
+        updateInventory();
+    }
+}
+
 void PowerSupply::analyze()
 {
     using namespace phosphor::pmbus;
 
+    if (presenceGPIO)
+    {
+        updatePresenceGPIO();
+    }
+
     if ((present) && (readFail < LOG_LIMIT))
     {
         try
@@ -161,10 +285,10 @@
         catch (...)
         {
             // The underlying code in writeBinary will log a message to the
-            // journal if the write fails. If the ON_OFF_CONFIG is not setup as
-            // desired, later fault detection and analysis code should catch any
-            // of the fall out. We should not need to terminate the application
-            // if this write fails.
+            // journal if the write fails. If the ON_OFF_CONFIG is not setup
+            // as desired, later fault detection and analysis code should
+            // catch any of the fall out. We should not need to terminate
+            // the application if this write fails.
         }
     }
 }
@@ -195,9 +319,9 @@
         catch (ReadFailure& e)
         {
             // Since I do not care what the return value is, I really do not
-            // care much if it gets a ReadFailure either. However, this should
-            // not prevent the application from continuing to run, so catching
-            // the read failure.
+            // care much if it gets a ReadFailure either. However, this
+            // should not prevent the application from continuing to run, so
+            // catching the read failure.
         }
     }
 }
@@ -215,8 +339,8 @@
         if (std::get<bool>(valPropMap->second))
         {
             present = true;
-            // TODO: Immediately trying to read or write the "files" causes read
-            // or write failures.
+            // TODO: Immediately trying to read or write the "files" causes
+            // read or write failures.
             using namespace std::chrono_literals;
             std::this_thread::sleep_for(20ms);
             pmbusIntf->findHwmonDir();
@@ -287,6 +411,9 @@
     using ObjectMap = std::map<sdbusplus::message::object_path, InterfaceMap>;
     ObjectMap object;
 #endif
+    log<level::DEBUG>(
+        fmt::format("updateInventory() inventoryPath: {}", inventoryPath)
+            .c_str());
 
     if (present)
     {
@@ -301,7 +428,8 @@
         }
         catch (ReadFailure& e)
         {
-            // Ignore the read failure, let pmbus code indicate failure, path...
+            // Ignore the read failure, let pmbus code indicate failure,
+            // path...
             // TODO - ibm918
             // https://github.com/openbmc/docs/blob/master/designs/vpd-collection.md
             // The BMC must log errors if any of the VPD cannot be properly
@@ -315,7 +443,8 @@
         }
         catch (ReadFailure& e)
         {
-            // Ignore the read failure, let pmbus code indicate failure, path...
+            // Ignore the read failure, let pmbus code indicate failure,
+            // path...
         }
 
         try
@@ -324,7 +453,8 @@
         }
         catch (ReadFailure& e)
         {
-            // Ignore the read failure, let pmbus code indicate failure, path...
+            // Ignore the read failure, let pmbus code indicate failure,
+            // path...
         }
 
         try
@@ -336,7 +466,8 @@
         }
         catch (ReadFailure& e)
         {
-            // Ignore the read failure, let pmbus code indicate failure, path...
+            // Ignore the read failure, let pmbus code indicate failure,
+            // path...
         }
 
         try
@@ -347,7 +478,8 @@
         }
         catch (ReadFailure& e)
         {
-            // Ignore the read failure, let pmbus code indicate failure, path...
+            // Ignore the read failure, let pmbus code indicate failure,
+            // path...
         }
 
         ipzvpdVINIProps.emplace("CC",
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index 03c6928..6291deb 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -2,10 +2,13 @@
 
 #include "pmbus.hpp"
 #include "types.hpp"
+#include "util.hpp"
 #include "utility.hpp"
 
+#include <gpiod.hpp>
 #include <sdbusplus/bus/match.hpp>
 
+#include <filesystem>
 #include <stdexcept>
 
 namespace phosphor::power::psu
@@ -50,15 +53,23 @@
      * @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
+     * @param[in] gpioLineName - The gpio-line-name to read for presence. See
+     * https://github.com/openbmc/docs/blob/master/designs/device-tree-gpio-naming.md
      */
     PowerSupply(sdbusplus::bus::bus& bus, const std::string& invpath,
-                std::uint8_t i2cbus, const std::uint16_t i2caddr);
+                std::uint8_t i2cbus, const std::uint16_t i2caddr,
+                const std::string& gpioLineName);
 
     phosphor::pmbus::PMBusBase& getPMBus()
     {
         return *pmbusIntf;
     }
 
+    GPIOInterface* getPresenceGPIO()
+    {
+        return presenceGPIO.get();
+    }
+
     /**
      * Power supply specific function to analyze for faults/errors.
      *
@@ -257,6 +268,11 @@
      **/
     std::string inventoryPath;
 
+    /**
+     * @brief The libgpiod object for monitoring PSU presence
+     */
+    std::unique_ptr<GPIOInterface> presenceGPIO = nullptr;
+
     /** @brief True if the power supply is present. */
     bool present = false;
 
@@ -284,6 +300,28 @@
     std::string fwVersion;
 
     /**
+     * @brief The file system path used for binding the device driver.
+     */
+    const std::filesystem::path bindPath;
+
+    /* @brief The string to pass in for binding the device driver. */
+    std::string bindDevice;
+
+    /**
+     * @brief Binds or unbinds the power supply device driver
+     *
+     * Called when a presence change is detected to either bind the device
+     * driver for the power supply when it is installed, or unbind the device
+     * driver when the power supply is removed.
+     *
+     * Writes <device> to <path>/bind (or unbind)
+     *
+     * @param present - when true, will bind the device driver
+     *                  when false, will unbind the device driver
+     */
+    void bindOrUnbindDriver(bool present);
+
+    /**
      *  @brief Updates the presence status by querying D-Bus
      *
      * The D-Bus inventory properties for this power supply will be read to
@@ -293,10 +331,18 @@
     void updatePresence();
 
     /**
+     * @brief Updates the power supply presence by reading the GPIO line.
+     */
+    void updatePresenceGPIO();
+
+    /**
      * @brief Callback for inventory property changes
      *
      * Process change of Present property for power supply.
      *
+     * This is used if we are watching the D-Bus properties instead of reading
+     * the GPIO presence line ourselves.
+     *
      * @param[in]  msg - Data associated with Present change signal
      **/
     void inventoryChanged(sdbusplus::message::message& msg);
@@ -306,6 +352,9 @@
      *
      * Process add of the interface with the Present property for power supply.
      *
+     * This is used if we are watching the D-Bus properties instead of reading
+     * the GPIO presence line ourselves.
+     *
      * @param[in]  msg - Data associated with Present add signal
      **/
     void inventoryAdded(sdbusplus::message::message& msg);
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index fdb5d52..63473f7 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -16,6 +16,7 @@
 constexpr auto i2cBusProp = "I2CBus";
 constexpr auto i2cAddressProp = "I2CAddress";
 constexpr auto psuNameProp = "Name";
+constexpr auto presLineName = "NamedPresenceGpio";
 
 constexpr auto supportedConfIntf =
     "xyz.openbmc_project.Configuration.SupportedConfiguration";
@@ -98,6 +99,7 @@
     uint64_t* i2cbus = nullptr;
     uint64_t* i2caddr = nullptr;
     std::string* psuname = nullptr;
+    std::string* preslineptr = nullptr;
 
     for (const auto& property : properties)
     {
@@ -115,6 +117,11 @@
             {
                 psuname = std::get_if<std::string>(&properties[psuNameProp]);
             }
+            else if (property.first == presLineName)
+            {
+                preslineptr =
+                    std::get_if<std::string>(&properties[presLineName]);
+            }
         }
         catch (std::exception& e)
         {
@@ -125,11 +132,21 @@
     {
         std::string invpath = basePSUInvPath;
         invpath.push_back(psuname->back());
+        std::string presline = "";
 
         log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
 
-        auto psu =
-            std::make_unique<PowerSupply>(bus, invpath, *i2cbus, *i2caddr);
+        if (nullptr != preslineptr)
+        {
+            presline = *preslineptr;
+        }
+
+        log<level::DEBUG>(
+            fmt::format("make PowerSupply bus: {} addr: {} presline: {}",
+                        *i2cbus, *i2caddr, presline)
+                .c_str());
+        auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
+                                                 *i2caddr, presline);
         psus.emplace_back(std::move(psu));
     }
 
diff --git a/phosphor-power-supply/test/mock.cpp b/phosphor-power-supply/test/mock.cpp
index a68628b..fc7d658 100644
--- a/phosphor-power-supply/test/mock.cpp
+++ b/phosphor-power-supply/test/mock.cpp
@@ -32,6 +32,11 @@
     util.reset();
 }
 
+std::unique_ptr<GPIOInterface> createGPIO(const std::string& /*namedGpio*/)
+{
+    return std::make_unique<MockedGPIOReader>();
+}
+
 } // namespace psu
 } // namespace power
 } // namespace phosphor
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
index c1f8986..2731692 100644
--- a/phosphor-power-supply/test/mock.hpp
+++ b/phosphor-power-supply/test/mock.hpp
@@ -3,6 +3,10 @@
 #include "pmbus.hpp"
 #include "util_base.hpp"
 
+#include <gpiod.hpp>
+
+#include <bitset>
+
 #include <gmock/gmock.h>
 
 namespace phosphor
@@ -40,6 +44,16 @@
     MOCK_METHOD(bool, getPresence,
                 (sdbusplus::bus::bus & bus, const std::string& invpath),
                 (const, override));
+    MOCK_METHOD(void, setPresence,
+                (sdbusplus::bus::bus & bus, const std::string& invpath,
+                 bool present, const std::string& name),
+                (const, override));
+};
+
+class MockedGPIOReader : public GPIOInterface
+{
+  public:
+    MOCK_METHOD(int, read, (), (override));
 };
 
 const UtilBase& getUtils();
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 74d98c1..b298d6c 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -20,6 +20,7 @@
 using ::testing::StrEq;
 
 static auto PSUInventoryPath = "/xyz/bmc/inv/sys/chassis/board/powersupply0";
+static auto PSUGPIOLineName = "presence-ps0";
 
 class PowerSupplyTests : public ::testing::Test
 {
@@ -44,13 +45,18 @@
      * @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
+     * @param[in] gpioLineName - The string for the gpio-line-name to read for
+     * presence.
+     * @param[in] bindDelay - Time in milliseconds to delay binding the device
+     * driver after seeing the presence line go active.
      */
     auto bus = sdbusplus::bus::new_default();
 
     // Try where inventory path is empty, constructor should fail.
     try
     {
-        auto psu = std::make_unique<PowerSupply>(bus, "", 3, 0x68);
+        auto psu =
+            std::make_unique<PowerSupply>(bus, "", 3, 0x68, PSUGPIOLineName);
         ADD_FAILURE() << "Should not have reached this line.";
     }
     catch (const std::invalid_argument& e)
@@ -62,13 +68,31 @@
         ADD_FAILURE() << "Should not have caught exception.";
     }
 
-    // Test with valid arguments
+    // TODO: Try invalid i2c address?
+
+    // Try where gpioLineName is empty.
     try
     {
-        EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
-            .Times(1);
         auto psu =
-            std::make_unique<PowerSupply>(bus, PSUInventoryPath, 3, 0x68);
+            std::make_unique<PowerSupply>(bus, PSUInventoryPath, 3, 0x68, "");
+        ADD_FAILURE()
+            << "Should not have reached this line. Invalid gpioLineName.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid empty gpioLineName");
+    }
+    catch (...)
+    {
+        ADD_FAILURE() << "Should not have caught exception.";
+    }
+
+    // Test with valid arguments
+    // NOT using D-Bus inventory path for presence.
+    try
+    {
+        auto psu = std::make_unique<PowerSupply>(bus, PSUInventoryPath, 3, 0x68,
+                                                 PSUGPIOLineName);
 
         EXPECT_EQ(psu->isPresent(), false);
         EXPECT_EQ(psu->isFaulted(), false);
@@ -80,14 +104,36 @@
     {
         ADD_FAILURE() << "Should not have caught exception.";
     }
+
+    // Test with valid arguments
+    // TODO: Using D-Bus inventory path for presence.
+    try
+    {
+        // FIXME: How do I get that presenceGPIO.read() in the startup to throw
+        // an exception?
+
+        // EXPECT_CALL(mockedUtil, getPresence(_,
+        // StrEq(PSUInventoryPath)))
+        //    .Times(1);
+    }
+    catch (...)
+    {
+        ADD_FAILURE() << "Should not have caught exception.";
+    }
 }
 
 TEST_F(PowerSupplyTests, Analyze)
 {
     auto bus = sdbusplus::bus::new_default();
 
-    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
-    PowerSupply psu{bus, PSUInventoryPath, 4, 0x69};
+    // If I default to reading the GPIO, I will NOT expect a call to
+    // getPresence().
+
+    PowerSupply psu{bus, PSUInventoryPath, 4, 0x69, PSUGPIOLineName};
+    MockedGPIOReader* mockPresenceGPIO =
+        static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+    EXPECT_CALL(*mockPresenceGPIO, read()).Times(1).WillOnce(Return(0));
+
     psu.analyze();
     // By default, nothing should change.
     EXPECT_EQ(psu.isPresent(), false);
@@ -96,17 +142,19 @@
     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, 0x6a};
-    EXPECT_EQ(psu2.isPresent(), true);
+    PowerSupply psu2{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
+    // In order to get the various faults tested, the power supply needs to
+    // be present in order to read from the PMBus device(s).
+    MockedGPIOReader* mockPresenceGPIO2 =
+        static_cast<MockedGPIOReader*>(psu2.getPresenceGPIO());
+    ON_CALL(*mockPresenceGPIO2, read()).WillByDefault(Return(1));
+
+    EXPECT_EQ(psu2.isPresent(), false);
 
     // STATUS_WORD 0x0000 is powered on, no faults.
+    // It will read STATUS_MFR at the same time, so there are 2 reads.
     MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu2.getPMBus());
-    EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+    EXPECT_CALL(mockPMBus, read(_, _)).Times(2).WillRepeatedly(Return(0x0000));
     psu2.analyze();
     EXPECT_EQ(psu2.isPresent(), true);
     EXPECT_EQ(psu2.isFaulted(), false);
@@ -190,11 +238,14 @@
     // Test where PSU is NOT present
     try
     {
-        EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
-            .Times(1)
-            .WillOnce(Return(false));
-        PowerSupply psu{bus, PSUInventoryPath, 4, 0x69};
+        // Assume GPIO presence, not inventory presence?
+        PowerSupply psu{bus, PSUInventoryPath, 4, 0x69, PSUGPIOLineName};
+
+        MockedGPIOReader* mockPresenceGPIO =
+            static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+        ON_CALL(*mockPresenceGPIO, read()).WillByDefault(Return(0));
         MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+        // Constructor should set initial presence, default read returns 0.
         // If it is not present, I should not be trying to write to it.
         EXPECT_CALL(mockPMBus, writeBinary(_, _, _)).Times(0);
         psu.onOffConfig(data);
@@ -206,11 +257,15 @@
     // Test where PSU is present
     try
     {
-        EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
-            .Times(1)
-            .WillOnce(Return(true)); // present
-        PowerSupply psu{bus, PSUInventoryPath, 5, 0x6a};
+        // Assume GPIO presence, not inventory presence?
+        PowerSupply psu{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
+        MockedGPIOReader* mockPresenceGPIO =
+            static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+        ON_CALL(*mockPresenceGPIO, read()).WillByDefault(Return(1));
         MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+        // TODO: expect setPresence call?
+        // updatePresence() private function reads gpio, called by analyze().
+        psu.analyze();
         // TODO: ???should I check the filename?
         EXPECT_CALL(mockPMBus,
                     writeBinary(_, ElementsAre(0x15), Type::HwmonDeviceDebug))
@@ -225,16 +280,19 @@
 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, 0x68};
+    PowerSupply psu{bus, PSUInventoryPath, 13, 0x68, PSUGPIOLineName};
+    MockedGPIOReader* mockPresenceGPIO =
+        static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+    // GPIO read return 1 to indicate present.
+    ON_CALL(*mockPresenceGPIO, read()).WillByDefault(Return(1));
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    ON_CALL(mockPMBus, read(_, _)).WillByDefault(Return(0));
+    psu.analyze();
     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(2)
         .WillOnce(Return(0xFFFF))
@@ -254,6 +312,8 @@
     EXPECT_EQ(psu.hasInputFault(), false);
     EXPECT_EQ(psu.hasMFRFault(), false);
     EXPECT_EQ(psu.hasVINUVFault(), false);
+
+    // TODO: Faults clear on missing/present?
 }
 
 TEST_F(PowerSupplyTests, UpdateInventory)
@@ -262,10 +322,7 @@
 
     try
     {
-        EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
-            .Times(1)
-            .WillOnce(Return(false)); // missing
-        PowerSupply psu{bus, PSUInventoryPath, 3, 0x68};
+        PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
         MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
         // If it is not present, I should not be trying to read a string
         EXPECT_CALL(mockPMBus, readString(_, _)).Times(0);
@@ -278,10 +335,12 @@
 
     try
     {
-        EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
-            .Times(1)
-            .WillOnce(Return(true)); // present
-        PowerSupply psu{bus, PSUInventoryPath, 13, 0x69};
+        PowerSupply psu{bus, PSUInventoryPath, 13, 0x69, PSUGPIOLineName};
+        MockedGPIOReader* mockPresenceGPIO =
+            static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+        // GPIO read return 1 to indicate present.
+        EXPECT_CALL(*mockPresenceGPIO, read()).Times(1).WillOnce(Return(1));
+        psu.analyze();
         MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
         EXPECT_CALL(mockPMBus, readString(_, _)).WillRepeatedly(Return(""));
         psu.updateInventory();
@@ -307,22 +366,28 @@
 TEST_F(PowerSupplyTests, IsPresent)
 {
     auto bus = sdbusplus::bus::new_default();
-    EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
-    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68};
+
+    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
+    MockedGPIOReader* mockPresenceGPIO =
+        static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
     EXPECT_EQ(psu.isPresent(), false);
 
-    EXPECT_CALL(mockedUtil, getPresence(_, _))
-        .WillOnce(Return(true)); // present
-    PowerSupply psu2{bus, PSUInventoryPath, 10, 0x6b};
-    EXPECT_EQ(psu2.isPresent(), true);
+    // Change GPIO read to return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).Times(1).WillOnce(Return(1));
+    psu.analyze();
+    EXPECT_EQ(psu.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, 0x6f};
+
+    PowerSupply psu{bus, PSUInventoryPath, 11, 0x6f, PSUGPIOLineName};
+    MockedGPIOReader* mockPresenceGPIO =
+        static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    psu.analyze();
     EXPECT_EQ(psu.isFaulted(), false);
     MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
     EXPECT_CALL(mockPMBus, read(_, _))
@@ -336,9 +401,13 @@
 TEST_F(PowerSupplyTests, HasInputFault)
 {
     auto bus = sdbusplus::bus::new_default();
-    EXPECT_CALL(mockedUtil, getPresence(_, _))
-        .WillOnce(Return(true)); // present
-    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68};
+
+    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
+    MockedGPIOReader* mockPresenceGPIO =
+        static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    psu.analyze();
     MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
     EXPECT_EQ(psu.hasInputFault(), false);
     EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
@@ -358,9 +427,13 @@
 TEST_F(PowerSupplyTests, HasMFRFault)
 {
     auto bus = sdbusplus::bus::new_default();
-    EXPECT_CALL(mockedUtil, getPresence(_, _))
-        .WillOnce(Return(true)); // present
-    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68};
+
+    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
+    MockedGPIOReader* mockPresenceGPIO =
+        static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    psu.analyze();
     MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
     EXPECT_EQ(psu.hasMFRFault(), false);
     EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
@@ -380,9 +453,13 @@
 TEST_F(PowerSupplyTests, HasVINUVFault)
 {
     auto bus = sdbusplus::bus::new_default();
-    EXPECT_CALL(mockedUtil, getPresence(_, _))
-        .WillOnce(Return(true)); // present
-    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68};
+
+    PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
+    MockedGPIOReader* mockPresenceGPIO =
+        static_cast<MockedGPIOReader*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    psu.analyze();
     MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
     EXPECT_EQ(psu.hasVINUVFault(), false);
     EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
diff --git a/phosphor-power-supply/util.cpp b/phosphor-power-supply/util.cpp
index c4555ae..c64de33 100644
--- a/phosphor-power-supply/util.cpp
+++ b/phosphor-power-supply/util.cpp
@@ -1,5 +1,7 @@
 #include "util.hpp"
 
+#include <gpiod.hpp>
+
 namespace phosphor::power::psu
 {
 
@@ -9,4 +11,71 @@
     return util;
 }
 
+GPIOReader::GPIOReader(const std::string& namedGpio)
+{
+    try
+    {
+        line = gpiod::find_line(namedGpio);
+    }
+    catch (std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            fmt::format("Failed to find line: {}", e.what()).c_str());
+        throw;
+    }
+}
+
+std::unique_ptr<GPIOInterface>
+    GPIOReader::createGPIO(const std::string& namedGpio)
+{
+    return std::make_unique<GPIOReader>(namedGpio);
+}
+
+int GPIOReader::read()
+{
+    using namespace phosphor::logging;
+
+    int value = -1;
+
+    if (!line)
+    {
+        log<level::ERR>("Failed line");
+        throw std::runtime_error{std::string{"Failed to find line"}};
+    }
+
+    try
+    {
+        line.request({__FUNCTION__, gpiod::line_request::DIRECTION_INPUT,
+                      gpiod::line_request::FLAG_ACTIVE_LOW});
+        try
+        {
+            value = line.get_value();
+        }
+        catch (std::exception& e)
+        {
+            log<level::ERR>(
+                fmt::format("Failed to get_value of GPIO line: {}", e.what())
+                    .c_str());
+            line.release();
+            throw;
+        }
+
+        log<level::DEBUG>("release() line");
+        line.release();
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("Failed to request GPIO line",
+                        entry("MSG=%s", e.what()));
+        throw;
+    }
+
+    return value;
+}
+
+std::unique_ptr<GPIOInterface> createGPIO(const std::string& namedGpio)
+{
+    return GPIOReader::createGPIO(namedGpio);
+}
+
 } // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/util.hpp b/phosphor-power-supply/util.hpp
index 2e4afe2..2203b92 100644
--- a/phosphor-power-supply/util.hpp
+++ b/phosphor-power-supply/util.hpp
@@ -1,7 +1,14 @@
 #pragma once
-
 #include "util_base.hpp"
 #include "utility.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+
+#include <fmt/format.h>
+
+#include <gpiod.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
 
 namespace phosphor::power::psu
 {
@@ -9,7 +16,6 @@
 class Util : public UtilBase
 {
   public:
-    //~Util(){};
     bool getPresence(sdbusplus::bus::bus& bus,
                      const std::string& invpath) const override
     {
@@ -21,6 +27,102 @@
 
         return present;
     }
+
+    void setPresence(sdbusplus::bus::bus& bus, const std::string& invpath,
+                     bool present, const std::string& name) const override
+    {
+        using InternalFailure =
+            sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+        using Property = std::string;
+        using Value = std::variant<bool, std::string>;
+        // Association between property and its value
+        using PropertyMap = std::map<Property, Value>;
+        PropertyMap invProp;
+
+        invProp.emplace("Present", present);
+        invProp.emplace("PrettyName", name);
+
+        using Interface = std::string;
+        // Association between interface and the D-Bus property map
+        using InterfaceMap = std::map<Interface, PropertyMap>;
+        InterfaceMap invIntf;
+        invIntf.emplace("xyz.openbmc_project.Inventory.Item",
+                        std::move(invProp));
+
+        Interface extraIface = "xyz.openbmc_project.Inventory.Item.PowerSupply";
+
+        invIntf.emplace(extraIface, PropertyMap());
+
+        using Object = sdbusplus::message::object_path;
+        // Association between object and the interface map
+        using ObjectMap = std::map<Object, InterfaceMap>;
+        ObjectMap invObj;
+        invObj.emplace(std::move(invpath), std::move(invIntf));
+
+        using namespace phosphor::logging;
+        log<level::INFO>(fmt::format("Updating inventory present property. "
+                                     "present:{} invpath:{} name:{}",
+                                     present, invpath, name)
+                             .c_str());
+
+        try
+        {
+            auto invService = phosphor::power::util::getService(
+                INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
+
+            // Update inventory
+            auto invMsg =
+                bus.new_method_call(invService.c_str(), INVENTORY_OBJ_PATH,
+                                    INVENTORY_MGR_IFACE, "Notify");
+            invMsg.append(std::move(invObj));
+            auto invMgrResponseMsg = bus.call(invMsg);
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>(
+                fmt::format(
+                    "Error in inventory manager call to update inventory: {}",
+                    e.what())
+                    .c_str());
+            elog<InternalFailure>();
+        }
+    }
+};
+
+std::unique_ptr<GPIOInterface> createGPIO(const std::string& namedGpio);
+
+class GPIOReader : public GPIOInterface
+{
+  public:
+    GPIOReader() = delete;
+    virtual ~GPIOReader() = default;
+    GPIOReader(const GPIOReader&) = default;
+    GPIOReader& operator=(const GPIOReader&) = default;
+    GPIOReader(GPIOReader&&) = default;
+    GPIOReader& operator=(GPIOReader&&) = default;
+
+    /**
+     * Constructor
+     *
+     * @param[in] namedGpio - The string for the gpio-line-name
+     */
+    GPIOReader(const std::string& namedGpio);
+
+    static std::unique_ptr<GPIOInterface>
+        createGPIO(const std::string& namedGpio);
+
+    /**
+     * @brief Attempts to read the state of the GPIO line.
+     *
+     * Throws an exception if line not found, request line fails, or get_value
+     * from line fails.
+     *
+     * @return 1 for active (low/present), 0 for not active (high/not present).
+     */
+    int read() override;
+
+  private:
+    gpiod::line line;
 };
 
 } // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/util_base.hpp b/phosphor-power-supply/util_base.hpp
index ab1a962..194ddff 100644
--- a/phosphor-power-supply/util_base.hpp
+++ b/phosphor-power-supply/util_base.hpp
@@ -18,6 +18,10 @@
 
     virtual bool getPresence(sdbusplus::bus::bus& bus,
                              const std::string& invpath) const = 0;
+
+    virtual void setPresence(sdbusplus::bus::bus& bus,
+                             const std::string& invpath, bool present,
+                             const std::string& name) const = 0;
 };
 
 const UtilBase& getUtils();
@@ -27,4 +31,18 @@
     return getUtils().getPresence(bus, invpath);
 }
 
+inline void setPresence(sdbusplus::bus::bus& bus, const std::string& invpath,
+                        bool present, const std::string& name)
+{
+    return getUtils().setPresence(bus, invpath, present, name);
+}
+
+class GPIOInterface
+{
+  public:
+    virtual ~GPIOInterface() = default;
+
+    virtual int read() = 0;
+};
+
 } // namespace phosphor::power::psu