psu-ng: INPUT_HISTORY syncHistory

Add in the function that syncs the power supply input history data
between all the installed power supplies.

Use the GPIO line name instead of gpiochip and number. Use libgpiod via
helper utility. Create a toggleLowHigh() to use for synchronizing the
input history.

Add in indicator and helper functions to indicate a syncHistory is
needed if a power supply goes from missing to present.

Trace when syncHistory is called. This should be infrequent enough that
I do not think it would be a problem.

Initial testing on Rainier 2S2U did not need a lengthy delay between
lowering and raising the power-ffs-sync-history GPIO, but testing on
Rainier 2S4u required a longer delay.

Depends-On: Ib1ac2456f7f715360d089dfa4b6b379b516439ab

Change-Id: I022806155139d70fb4a42cc27eb9f279f6a3aedc
Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index 254c9c7..b364b36 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -204,6 +204,10 @@
         {
             onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
             clearFaults();
+            // Indicate that the input history data and timestamps between all
+            // the power supplies that are present in the system need to be
+            // synchronized.
+            syncHistoryRequired = true;
         }
     }
 }
@@ -1001,6 +1005,7 @@
                 log<level::DEBUG>(
                     fmt::format("{} avgPath: {}", shortName, avgPath).c_str());
             }
+
             if (!maximum)
             {
                 auto maxPath = historyObjectPath + '/' + history::Maximum::name;
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index b81599b..566deec 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -488,6 +488,26 @@
         }
     }
 
+    /**
+     * @brief Returns true when INPUT_HISTORY sync is required.
+     */
+    bool isSyncHistoryRequired() const
+    {
+        return syncHistoryRequired;
+    }
+
+    /**
+     * @brief Clears the indicator that sync required for INPUT_HISTORY.
+     *
+     * Sets variable to false to indicate that the sync is no longer required.
+     * This can be used after the PSUManager has reacted to the need for the
+     * INPUT_HISTORY data to be synchronized.
+     */
+    void clearSyncHistoryRequired()
+    {
+        syncHistoryRequired = false;
+    }
+
   private:
     /** @brief systemd bus member */
     sdbusplus::bus::bus& bus;
@@ -858,6 +878,15 @@
     bool inputHistorySupported{false};
 
     /**
+     * @brief Set to true when INPUT_HISTORY sync is required.
+     *
+     * A power supply will need to synchronize its INPUT_HISTORY data with the
+     * other power supplies installed in the system when it goes from missing to
+     * present.
+     */
+    bool syncHistoryRequired{false};
+
+    /**
      * @brief Class that manages the input power history records.
      **/
     std::unique_ptr<history::RecordManager> recordManager;
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index acc3c2b..d7cfdfc 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include "psu_manager.hpp"
 
 #include "utility.hpp"
@@ -28,6 +30,8 @@
 constexpr auto supportedConfIntf =
     "xyz.openbmc_project.Configuration.SupportedConfiguration";
 
+constexpr auto INPUT_HISTORY_SYNC_DELAY = 1100;
+
 PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
     bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
     objectManager(bus, objectManagerObjPath),
@@ -454,8 +458,38 @@
     }
 }
 
+void PSUManager::syncHistory()
+{
+    log<level::INFO>("Synchronize INPUT_HISTORY");
+
+    if (!syncHistoryGPIO)
+    {
+        syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
+    }
+    if (syncHistoryGPIO)
+    {
+        const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
+        syncHistoryGPIO->toggleLowHigh(delay);
+        for (auto& psu : psus)
+        {
+            psu->clearSyncHistoryRequired();
+        }
+    }
+
+    log<level::INFO>("Synchronize INPUT_HISTORY completed");
+}
+
 void PSUManager::analyze()
 {
+    auto syncHistoryRequired =
+        std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
+            return psu->isSyncHistoryRequired();
+        });
+    if (syncHistoryRequired)
+    {
+        syncHistory();
+    }
+
     for (auto& psu : psus)
     {
         psu->analyze();
diff --git a/phosphor-power-supply/psu_manager.hpp b/phosphor-power-supply/psu_manager.hpp
index 8725ce8..46560a7 100644
--- a/phosphor-power-supply/psu_manager.hpp
+++ b/phosphor-power-supply/psu_manager.hpp
@@ -377,6 +377,25 @@
      * the /org/open_power/sensors root D-Bus path.
      */
     sdbusplus::server::manager_t historyManager;
+
+    /**
+     * @brief GPIO to toggle to 'sync' power supply input history.
+     */
+    std::unique_ptr<GPIOInterfaceBase> syncHistoryGPIO = nullptr;
+
+    /**
+     * @brief Toggles the GPIO to sync power supply input history readings
+     *
+     * This GPIO is connected to all supplies.  This will clear the
+     * previous readings out of the supplies and restart them both at the
+     * same time zero and at record ID 0.  The supplies will return 0
+     * bytes of data for the input history command right after this until
+     * a new entry shows up.
+     *
+     * This will cause the code to delete all previous history data and
+     * start fresh.
+     */
+    void syncHistory();
 };
 
 } // namespace phosphor::power::manager
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
index 1235c01..13d22e6 100644
--- a/phosphor-power-supply/test/mock.hpp
+++ b/phosphor-power-supply/test/mock.hpp
@@ -69,6 +69,8 @@
   public:
     MOCK_METHOD(int, read, (), (override));
     MOCK_METHOD(void, write, (int value, std::bitset<32> flags), (override));
+    MOCK_METHOD(void, toggleLowHigh, (const std::chrono::milliseconds& delay),
+                (override));
     MOCK_METHOD(std::string, getName, (), (const, override));
 };
 
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 88caee8..fabe535 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -1968,3 +1968,31 @@
     EXPECT_EQ(psu.hasInputHistory(), true);
     EXPECT_EQ(psu.getNumInputHistoryRecords(), 0);
 }
+
+TEST_F(PowerSupplyTests, IsSyncHistoryRequired)
+{
+    auto bus = sdbusplus::bus::new_default();
+    PowerSupply psu{bus,  PSUInventoryPath, 8,
+                    0x6f, "ibm-cffps",      PSUGPIOLineName};
+    EXPECT_EQ(psu.hasInputHistory(), false);
+    EXPECT_EQ(psu.isSyncHistoryRequired(), false);
+    MockedGPIOInterface* mockPresenceGPIO =
+        static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    setMissingToPresentExpects(mockPMBus, mockedUtil);
+    PMBusExpectations expectations;
+    setPMBusExpectations(mockPMBus, expectations);
+    EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
+        .Times(1)
+        .WillRepeatedly(Return("205000"));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
+    psu.analyze();
+    // The ibm-cffps power supply should support input history
+    EXPECT_EQ(psu.hasInputHistory(), true);
+    // Missing -> Present requires history sync
+    EXPECT_EQ(psu.isSyncHistoryRequired(), true);
+    psu.clearSyncHistoryRequired();
+    EXPECT_EQ(psu.isSyncHistoryRequired(), false);
+}
diff --git a/phosphor-power-supply/util.cpp b/phosphor-power-supply/util.cpp
index 0a7e20d..503e1c3 100644
--- a/phosphor-power-supply/util.cpp
+++ b/phosphor-power-supply/util.cpp
@@ -106,6 +106,14 @@
     }
 }
 
+void GPIOInterface::toggleLowHigh(const std::chrono::milliseconds& delay)
+{
+    auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
+    write(0, flags);
+    std::this_thread::sleep_for(delay);
+    write(1, flags);
+}
+
 std::unique_ptr<GPIOInterfaceBase> createGPIO(const std::string& namedGpio)
 {
     return GPIOInterface::createGPIO(namedGpio);
diff --git a/phosphor-power-supply/util.hpp b/phosphor-power-supply/util.hpp
index 92e4eeb..caa5bac 100644
--- a/phosphor-power-supply/util.hpp
+++ b/phosphor-power-supply/util.hpp
@@ -11,6 +11,7 @@
 #include <phosphor-logging/log.hpp>
 
 #include <bitset>
+#include <chrono>
 
 namespace phosphor::power::psu
 {
@@ -260,6 +261,15 @@
     void write(int value, std::bitset<32> flags) override;
 
     /**
+     * @brief Attempts to toggle (write) a GPIO low then high.
+     *
+     * Relies on write, so throws exception if line not found, etc.
+     *
+     * @param[in] delay - Milliseconds to delay betwen low/high toggle.
+     */
+    void toggleLowHigh(const std::chrono::milliseconds& delay) override;
+
+    /**
      * @brief Returns the name of the GPIO, if not empty.
      */
     std::string getName() const override;
diff --git a/phosphor-power-supply/util_base.hpp b/phosphor-power-supply/util_base.hpp
index 576a965..684abdf 100644
--- a/phosphor-power-supply/util_base.hpp
+++ b/phosphor-power-supply/util_base.hpp
@@ -5,6 +5,7 @@
 #include <sdbusplus/bus/match.hpp>
 
 #include <bitset>
+#include <chrono>
 
 namespace phosphor::power::psu
 {
@@ -67,6 +68,7 @@
 
     virtual int read() = 0;
     virtual void write(int value, std::bitset<32> flags) = 0;
+    virtual void toggleLowHigh(const std::chrono::milliseconds& delay) = 0;
     virtual std::string getName() const = 0;
 };