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/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",