regulators: Add close method to Device class

Add a close method to the Device class.  This will be used to close all
devices in the system when monitoring is disabled.  Monitoring is
disabled when the system is being powered off.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I0712ad698ebbcac5d7b17a822443b94dda230429
diff --git a/phosphor-regulators/src/device.cpp b/phosphor-regulators/src/device.cpp
index dd662c9..b1a136b 100644
--- a/phosphor-regulators/src/device.cpp
+++ b/phosphor-regulators/src/device.cpp
@@ -17,8 +17,12 @@
 #include "device.hpp"
 
 #include "chassis.hpp"
+#include "exception_utils.hpp"
+#include "journal.hpp"
 #include "system.hpp"
 
+#include <exception>
+
 namespace phosphor::power::regulators
 {
 
@@ -34,6 +38,26 @@
     }
 }
 
+void Device::close()
+{
+    try
+    {
+        // Close I2C interface if it is open
+        if (i2cInterface->isOpen())
+        {
+            i2cInterface->close();
+        }
+    }
+    catch (const std::exception& e)
+    {
+        // Log error messages in journal
+        exception_utils::log(e);
+        journal::logErr("Unable to close device " + id);
+
+        // TODO: Create error log entry
+    }
+}
+
 void Device::configure(System& system, Chassis& chassis)
 {
     // If configuration changes are defined for this device, apply them
diff --git a/phosphor-regulators/src/device.hpp b/phosphor-regulators/src/device.hpp
index e9369fc..ab24e4b 100644
--- a/phosphor-regulators/src/device.hpp
+++ b/phosphor-regulators/src/device.hpp
@@ -86,6 +86,14 @@
     void addToIDMap(IDMap& idMap);
 
     /**
+     * Closes this device.
+     *
+     * Closes any interfaces that are open to this device.  Releases any other
+     * operating system resources associated with this device.
+     */
+    void close();
+
+    /**
      * Configure this device.
      *
      * Applies the configuration changes that are defined for this device, if
diff --git a/phosphor-regulators/test/device_tests.cpp b/phosphor-regulators/test/device_tests.cpp
index 901f1cf..d9bc8d3 100644
--- a/phosphor-regulators/test/device_tests.cpp
+++ b/phosphor-regulators/test/device_tests.cpp
@@ -22,6 +22,7 @@
 #include "journal.hpp"
 #include "mock_action.hpp"
 #include "mock_journal.hpp"
+#include "mocked_i2c_interface.hpp"
 #include "presence_detection.hpp"
 #include "rail.hpp"
 #include "rule.hpp"
@@ -41,6 +42,7 @@
 using namespace phosphor::power::regulators::test_utils;
 
 using ::testing::Return;
+using ::testing::Throw;
 
 TEST(DeviceTests, Constructor)
 {
@@ -138,6 +140,69 @@
     EXPECT_THROW(idMap.getRail("vdd2"), std::invalid_argument);
 }
 
+TEST(DeviceTests, Close)
+{
+    // Test where works: I2C interface is not open
+    {
+        // Create mock I2CInterface
+        std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
+            std::make_unique<i2c::MockedI2CInterface>();
+        EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(false));
+        EXPECT_CALL(*i2cInterface, close).Times(0);
+
+        // Create Device
+        Device device{"vdd_reg", true, "/system/chassis/motherboard/reg2",
+                      std::move(i2cInterface)};
+
+        // Close Device
+        journal::clear();
+        device.close();
+        EXPECT_EQ(journal::getErrMessages().size(), 0);
+    }
+
+    // Test where works: I2C interface is open
+    {
+        // Create mock I2CInterface
+        std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
+            std::make_unique<i2c::MockedI2CInterface>();
+        EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
+        EXPECT_CALL(*i2cInterface, close).Times(1);
+
+        // Create Device
+        Device device{"vdd_reg", true, "/system/chassis/motherboard/reg2",
+                      std::move(i2cInterface)};
+
+        // Close Device
+        journal::clear();
+        device.close();
+        EXPECT_EQ(journal::getErrMessages().size(), 0);
+    }
+
+    // Test where fails: closing I2C interface fails
+    {
+        // Create mock I2CInterface
+        std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
+            std::make_unique<i2c::MockedI2CInterface>();
+        EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
+        EXPECT_CALL(*i2cInterface, close)
+            .Times(1)
+            .WillOnce(Throw(
+                i2c::I2CException{"Failed to close", "/dev/i2c-1", 0x70}));
+
+        // Create Device
+        Device device{"vdd_reg", true, "/system/chassis/motherboard/reg2",
+                      std::move(i2cInterface)};
+
+        // Close Device
+        journal::clear();
+        device.close();
+        std::vector<std::string> expectedErrMessages{
+            "I2CException: Failed to close: bus /dev/i2c-1, addr 0x70",
+            "Unable to close device vdd_reg"};
+        EXPECT_EQ(journal::getErrMessages(), expectedErrMessages);
+    }
+}
+
 TEST(DeviceTests, Configure)
 {
     // Test where Configuration and Rails were not specified in constructor