i2c: Cache adapter functionality

Enhance the I2CDevice class to cache the I2C adapter functionality
value.  The adapter functionality value should not change while the
device interface is open.

Caching the functionality value will reduce the number of ioctl() calls
by 50% when doing long sequences of I2C operations.

Tested:
* Verified adapter functionality value is cached
* Verified read() method works on a Witherspoon
* Verified close() method discards the cached functionality value

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I2b17d91ae4e21aaf52cd4cec9eddb5aab7be37e5
diff --git a/tools/i2c/i2c.cpp b/tools/i2c/i2c.cpp
index 04b9c27..bad5b89 100644
--- a/tools/i2c/i2c.cpp
+++ b/tools/i2c/i2c.cpp
@@ -17,16 +17,24 @@
 namespace i2c
 {
 
-void I2CDevice::checkReadFuncs(int type)
+unsigned long I2CDevice::getFuncs()
 {
-    unsigned long funcs;
-
-    /* Check adapter functionality */
-    if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
+    // If functionality has not been cached
+    if (cachedFuncs == NO_FUNCS)
     {
-        throw I2CException("Failed to get funcs", busStr, devAddr, errno);
+        // Get functionality from adapter
+        if (ioctl(fd, I2C_FUNCS, &cachedFuncs) < 0)
+        {
+            throw I2CException("Failed to get funcs", busStr, devAddr, errno);
+        }
     }
 
+    return cachedFuncs;
+}
+
+void I2CDevice::checkReadFuncs(int type)
+{
+    unsigned long funcs = getFuncs();
     switch (type)
     {
         case I2C_SMBUS_BYTE:
@@ -73,14 +81,7 @@
 
 void I2CDevice::checkWriteFuncs(int type)
 {
-    unsigned long funcs;
-
-    /* Check adapter functionality */
-    if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
-    {
-        throw I2CException("Failed to get funcs", busStr, devAddr, errno);
-    }
-
+    unsigned long funcs = getFuncs();
     switch (type)
     {
         case I2C_SMBUS_BYTE:
@@ -154,6 +155,7 @@
         throw I2CException("Failed to close", busStr, devAddr, errno);
     }
     fd = INVALID_FD;
+    cachedFuncs = NO_FUNCS;
 }
 
 void I2CDevice::read(uint8_t& data)
diff --git a/tools/i2c/i2c.hpp b/tools/i2c/i2c.hpp
index b5ddcb8..84e8386 100644
--- a/tools/i2c/i2c.hpp
+++ b/tools/i2c/i2c.hpp
@@ -23,7 +23,7 @@
     explicit I2CDevice(uint8_t busId, uint8_t devAddr,
                        InitialState initialState = InitialState::OPEN) :
         busId(busId),
-        devAddr(devAddr), fd(INVALID_FD)
+        devAddr(devAddr)
     {
         busStr = "/dev/i2c-" + std::to_string(busId);
         if (initialState == InitialState::OPEN)
@@ -35,6 +35,9 @@
     /** @brief Invalid file descriptor */
     static constexpr int INVALID_FD = -1;
 
+    /** @brief Empty adapter functionality value with no bit flags set */
+    static constexpr unsigned long NO_FUNCS = 0;
+
     /** @brief The I2C bus ID */
     uint8_t busId;
 
@@ -42,11 +45,14 @@
     uint8_t devAddr;
 
     /** @brief The file descriptor of the opened i2c device */
-    int fd;
+    int fd = INVALID_FD;
 
     /** @brief The i2c bus path in /dev */
     std::string busStr;
 
+    /** @brief Cached I2C adapter functionality value */
+    unsigned long cachedFuncs = NO_FUNCS;
+
     /** @brief Check that device interface is open
      *
      * @throw I2CException if device is not open
@@ -71,6 +77,16 @@
         }
     }
 
+    /** @brief Get I2C adapter functionality
+     *
+     * Caches the adapter functionality value since it shouldn't change after
+     * opening the device.
+     *
+     * @throw I2CException on error
+     * @return Adapter functionality value
+     */
+    unsigned long getFuncs();
+
     /** @brief Check i2c adapter read functionality
      *
      * Check if the i2c adapter has the functionality specified by the SMBus