psu-ng: Add code to set ON_OFF_CONFIG

Add code that sends the appropriate ON_OFF_CONFIG mask for the power
supply power on operation.

Tested:
    Ran gtest using x86sdk
    Used simulator to change ON_OFF_CONFIG value, restarted service,
    verified ON_OFF_CONFIG changed.
    Used i2cget on hardware to change ON_OFF_CONFIG, restarted service,
    verified ON_OFF_CONFIG changed.

Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
Change-Id: Ifd9d63fcd2e86a62b7358e08958b5e2dbd21db9f
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index 37e5666..8c80ef3 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -93,6 +93,30 @@
     }
 }
 
+void PowerSupply::onOffConfig(uint8_t data)
+{
+    using namespace phosphor::pmbus;
+
+    if (present)
+    {
+        log<level::INFO>("ON_OFF_CONFIG write", entry("DATA=0x%02X", data));
+        try
+        {
+            std::vector<uint8_t> configData{data};
+            pmbusIntf->writeBinary(ON_OFF_CONFIG, configData,
+                                   Type::HwmonDeviceDebug);
+        }
+        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.
+        }
+    }
+}
+
 void PowerSupply::clearFaults()
 {
     faultFound = false;
@@ -133,6 +157,7 @@
         if (std::get<bool>(valPropMap->second))
         {
             present = true;
+            onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
             clearFaults();
         }
         else
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index 707a745..487fa53 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -65,6 +65,16 @@
     void analyze();
 
     /**
+     * Write PMBus ON_OFF_CONFIG
+     *
+     * This function will be called to cause the PMBus device driver to send the
+     * ON_OFF_CONFIG command. Takes one byte of data.
+     *
+     * @param[in] data - The ON_OFF_CONFIG data byte mask.
+     */
+    void onOffConfig(uint8_t data);
+
+    /**
      * Write PMBus CLEAR_FAULTS
      *
      * This function will be called in various situations in order to clear
diff --git a/phosphor-power-supply/psu_manager.hpp b/phosphor-power-supply/psu_manager.hpp
index a3bcae0..a9a0bd5 100644
--- a/phosphor-power-supply/psu_manager.hpp
+++ b/phosphor-power-supply/psu_manager.hpp
@@ -87,6 +87,7 @@
             powerOn = false;
         }
 
+        onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
         clearFaults();
         updateInventory();
     }
@@ -100,6 +101,20 @@
     }
 
     /**
+     * Write PMBus ON_OFF_CONFIG
+     *
+     * This function will be called to cause the PMBus device driver to send the
+     * ON_OFF_CONFIG command. Takes one byte of data.
+     */
+    void onOffConfig(const uint8_t data)
+    {
+        for (auto& psu : psus)
+        {
+            psu->onOffConfig(data);
+        }
+    }
+
+    /**
      * This function will be called in various situations in order to clear
      * any fault status bits that may have been set, in order to start over
      * with a clean state. Presence changes and power state changes will want
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
index 0c64e00..c4c2c9d 100644
--- a/phosphor-power-supply/test/mock.hpp
+++ b/phosphor-power-supply/test/mock.hpp
@@ -17,6 +17,9 @@
 
     MOCK_METHOD(uint64_t, read, (const std::string& name, Type type),
                 (override));
+    MOCK_METHOD(void, writeBinary,
+                (const std::string& name, std::vector<uint8_t> data, Type type),
+                (override));
 };
 } // namespace pmbus
 
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index c6d4814..5eb4b3e 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -11,8 +11,11 @@
 using namespace phosphor::pmbus;
 
 using ::testing::_;
+using ::testing::Args;
 using ::testing::Assign;
 using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::NotNull;
 using ::testing::Return;
 using ::testing::StrEq;
 
@@ -148,6 +151,46 @@
     // TODO: ReadFailure
 }
 
+TEST_F(PowerSupplyTests, OnOffConfig)
+{
+    auto bus = sdbusplus::bus::new_default();
+    uint8_t data = 0x15;
+
+    // Test where PSU is NOT present
+    try
+    {
+        EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+            .Times(1)
+            .WillOnce(Return(false));
+        PowerSupply psu{bus, PSUInventoryPath, 4, "0069"};
+        MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+        // If it is not present, I should not be trying to write to it.
+        EXPECT_CALL(mockPMBus, writeBinary(_, _, _)).Times(0);
+        psu.onOffConfig(data);
+    }
+    catch (...)
+    {
+    }
+
+    // Test where PSU is present
+    try
+    {
+        EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+            .Times(1)
+            .WillOnce(Return(true)); // present
+        PowerSupply psu{bus, PSUInventoryPath, 5, "006a"};
+        MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+        // TODO: ???should I check the filename?
+        EXPECT_CALL(mockPMBus,
+                    writeBinary(_, ElementsAre(0x15), Type::HwmonDeviceDebug))
+            .Times(1);
+        psu.onOffConfig(data);
+    }
+    catch (...)
+    {
+    }
+}
+
 TEST_F(PowerSupplyTests, ClearFaults)
 {
     auto bus = sdbusplus::bus::new_default();
diff --git a/pmbus.cpp b/pmbus.cpp
index d8b1804..b2fe2fd 100644
--- a/pmbus.cpp
+++ b/pmbus.cpp
@@ -303,6 +303,40 @@
     }
 }
 
+void PMBus::writeBinary(const std::string& name, std::vector<uint8_t> data,
+                        Type type)
+{
+    std::ofstream file;
+    fs::path path = getPath(type);
+
+    path /= name;
+
+    file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
+                    std::ofstream::eofbit);
+
+    try
+    {
+        // I need to specify binary mode when I construct the ofstream
+        file.open(path, std::ios::out | std::ios_base::binary);
+        log<level::DEBUG>("Write data to sysfs file",
+                          entry("FILENAME=%s", path.c_str()));
+        file.write(reinterpret_cast<const char*>(&data[0]), data.size());
+    }
+    catch (const std::exception& e)
+    {
+        auto rc = errno;
+
+        log<level::ERR>("Failed to write binary data to sysfs file",
+                        entry("FILENAME=%s", path.c_str()));
+
+        using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
+
+        elog<WriteFailure>(
+            metadata::CALLOUT_ERRNO(rc),
+            metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str()));
+    }
+}
+
 void PMBus::findHwmonDir()
 {
     fs::path path{basePath};
diff --git a/pmbus.hpp b/pmbus.hpp
index 5e252b2..123e4fd 100644
--- a/pmbus.hpp
+++ b/pmbus.hpp
@@ -101,6 +101,24 @@
 constexpr auto OT_FAULT = 0x80;
 } // namespace status_temperature
 
+constexpr auto ON_OFF_CONFIG = "on_off_config";
+
+// From PMBus Specification Part II Revsion 1.2:
+// The ON_OFF_CONFIG command configures the combination of CONTROL pin input
+// and serial bus commands needed to turn the unit on and off. This includes how
+// the unit responds when power is applied.
+// Bits [7:5] - 000 - Reserved
+// Bit 4 - 1 - Unit does not power up until commanded by the CONTROL pin and
+// OPERATION command (as programmed in bits [3:0]).
+// Bit 3 - 0 - Unit ignores the on/off portion of the OPERATION command from
+// serial bus.
+// Bit 2 - 1 - Unit requires the CONTROL pin to be asserted to start the unit.
+// Bit 1 - 0 - Polarity of the CONTROL pin. Active low (Pull pin low to start
+// the unit).
+// Bit 0 - 1 - Turn off the output and stop transferring energy to the output as
+// fast as possible.
+constexpr auto ON_OFF_CONFIG_CONTROL_PIN_ONLY = 0x15;
+
 /**
  * Where the access should be done
  */
@@ -124,6 +142,8 @@
     virtual ~PMBusBase() = default;
 
     virtual uint64_t read(const std::string& name, Type type) = 0;
+    virtual void writeBinary(const std::string& name, std::vector<uint8_t> data,
+                             Type type) = 0;
 };
 
 /**
@@ -275,6 +295,16 @@
     void write(const std::string& name, int value, Type type);
 
     /**
+     * Writes binary data to a file in sysfs.
+     *
+     * @param[in] name - path concatenated to basePath to write
+     * @param[in] data - The data to write to the file
+     * @param[in] type - Path type
+     */
+    void writeBinary(const std::string& name, std::vector<uint8_t> data,
+                     Type type) override;
+
+    /**
      * Returns the sysfs base path of this device
      */
     inline const auto& path() const