i2c: Implement read function

Implement I2CDevice::read() by invoking i2c_smbus_read_xxx() APIs.
The code is referenced from i2c-tools' i2cget.c:

 https://github.com/ev3dev/i2c-tools/blob/ev3dev-stretch/tools/i2cget.c

Tested: Verify on Witherspoon that it reads the PSU ppgrade mode status
        register (1 byte) and CRC16 register (2 bytes) correctly.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I8759b6a35229f81120acf77f08429f7f79458b8b
diff --git a/tools/i2c/i2c.cpp b/tools/i2c/i2c.cpp
index aa565b4..da121d8 100644
--- a/tools/i2c/i2c.cpp
+++ b/tools/i2c/i2c.cpp
@@ -1,11 +1,19 @@
 #include "i2c.hpp"
 
 #include <fcntl.h>
+#include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include <cassert>
 #include <cerrno>
 
+extern "C" {
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+}
+
 namespace i2c
 {
 
@@ -16,6 +24,11 @@
     {
         throw I2CException("Failed to open", busStr, devAddr, errno);
     }
+
+    if (ioctl(fd, I2C_SLAVE, devAddr) < 0)
+    {
+        throw I2CException("Failed to set I2C_SLAVE", busStr, devAddr, errno);
+    }
 }
 
 void I2CDevice::close()
@@ -23,32 +36,98 @@
     ::close(fd);
 }
 
+void I2CDevice::checkReadFuncs(int type)
+{
+    unsigned long funcs;
+
+    /* Check adapter functionality */
+    if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
+    {
+        throw I2CException("Failed to get funcs", busStr, devAddr, errno);
+    }
+
+    switch (type)
+    {
+        case I2C_SMBUS_BYTE:
+            if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE))
+            {
+                throw I2CException("Missing SMBUS_READ_BYTE", busStr, devAddr);
+            }
+            break;
+        case I2C_SMBUS_BYTE_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA))
+            {
+                throw I2CException("Missing SMBUS_READ_BYTE_DATA", busStr,
+                                   devAddr);
+            }
+            break;
+
+        case I2C_SMBUS_WORD_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_READ_WORD_DATA))
+            {
+                throw I2CException("Missing SMBUS_READ_WORD_DATA", busStr,
+                                   devAddr);
+            }
+            break;
+        case I2C_SMBUS_BLOCK_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_READ_BLOCK_DATA))
+            {
+                throw I2CException("Missing SMBUS_READ_BLOCK_DATA", busStr,
+                                   devAddr);
+            }
+            break;
+        default:
+            fprintf(stderr, "Unexpected read size type: %d\n", type);
+            assert(false);
+            break;
+    }
+}
+
 void I2CDevice::read(uint8_t& data)
 {
-    // TODO
-    (void)data;
+    checkReadFuncs(I2C_SMBUS_BYTE);
+
+    int ret = i2c_smbus_read_byte(fd);
+    if (ret < 0)
+    {
+        throw I2CException("Failed to read byte", busStr, devAddr, errno);
+    }
+    data = static_cast<uint8_t>(ret);
 }
 
 void I2CDevice::read(uint8_t addr, uint8_t& data)
 {
-    // TODO
-    (void)addr;
-    (void)data;
+    checkReadFuncs(I2C_SMBUS_BYTE_DATA);
+
+    int ret = i2c_smbus_read_byte_data(fd, addr);
+    if (ret < 0)
+    {
+        throw I2CException("Failed to read byte data", busStr, devAddr, errno);
+    }
+    data = static_cast<uint8_t>(ret);
 }
 
 void I2CDevice::read(uint8_t addr, uint16_t& data)
 {
-    // TODO
-    (void)addr;
-    (void)data;
+    checkReadFuncs(I2C_SMBUS_WORD_DATA);
+    int ret = i2c_smbus_read_word_data(fd, addr);
+    if (ret < 0)
+    {
+        throw I2CException("Failed to read word data", busStr, devAddr, errno);
+    }
+    data = static_cast<uint16_t>(ret);
 }
 
 void I2CDevice::read(uint8_t addr, uint8_t& size, uint8_t* data)
 {
-    // TODO
-    (void)addr;
-    (void)size;
-    (void)data;
+    checkReadFuncs(I2C_SMBUS_BLOCK_DATA);
+
+    int ret = i2c_smbus_read_block_data(fd, addr, data);
+    if (ret < 0)
+    {
+        throw I2CException("Failed to read block data", busStr, devAddr, errno);
+    }
+    size = static_cast<uint8_t>(ret);
 }
 
 void I2CDevice::write(uint8_t data)
diff --git a/tools/i2c/i2c.hpp b/tools/i2c/i2c.hpp
index 45bb024..7fe064f 100644
--- a/tools/i2c/i2c.hpp
+++ b/tools/i2c/i2c.hpp
@@ -42,6 +42,17 @@
     /** @brief The i2c bus path in /dev */
     std::string busStr;
 
+    /** @brief Check i2c adapter read functionality
+     *
+     * Check if the i2c adapter has the functionality specified by the SMBus
+     * transaction type
+     *
+     * @param[in] type - The SMBus transaction type defined in linux/i2c.h
+     *
+     * @throw I2CException if the function is not supported
+     */
+    void checkReadFuncs(int type);
+
   public:
     ~I2CDevice()
     {
diff --git a/tools/i2c/meson.build b/tools/i2c/meson.build
index 258ac77..71a5aab 100644
--- a/tools/i2c/meson.build
+++ b/tools/i2c/meson.build
@@ -5,3 +5,7 @@
 )
 
 libi2c_inc = include_directories('.')
+libi2c_dep = declare_dependency(
+    link_with: libi2c_dev,
+    include_directories : libi2c_inc,
+    link_args : '-li2c')
diff --git a/tools/power-utils/meson.build b/tools/power-utils/meson.build
index 2e6adad..142683b 100644
--- a/tools/power-utils/meson.build
+++ b/tools/power-utils/meson.build
@@ -6,12 +6,12 @@
     dependencies: [
         phosphor_dbus_interfaces,
         phosphor_logging,
+        libi2c_dep,
     ],
     include_directories: [libpower_inc, libi2c_inc],
     install: true,
     link_with: [
         libpower,
-        libi2c_dev,
     ]
 )
 
diff --git a/tools/power-utils/test/test_updater.cpp b/tools/power-utils/test/test_updater.cpp
index 4d051f7..d2dfd11 100644
--- a/tools/power-utils/test/test_updater.cpp
+++ b/tools/power-utils/test/test_updater.cpp
@@ -95,7 +95,10 @@
     updater = std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
     updater->createI2CDevice();
     auto& i2c = getMockedI2c();
+    EXPECT_CALL(i2c, read(An<uint8_t&>()));
     EXPECT_CALL(i2c, read(_, An<uint8_t&>()));
+    EXPECT_CALL(i2c, read(_, An<uint16_t&>()));
+    EXPECT_CALL(i2c, read(_, An<uint8_t&>(), _));
     updater->doUpdate();
 }
 
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index 749b5dd..2668030 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -244,7 +244,21 @@
 {
     // TODO
     uint8_t data;
-    i2c->read(0x00, data);
+    uint8_t size;
+    uint16_t word;
+    std::vector<uint8_t> blockData(32);
+
+    i2c->read(data);
+    printf("Read byte 0x%02x\n", data);
+
+    i2c->read(0xf1, data);
+    printf("First read of 0x%02x, 0x%02x\n", 0xf1, data);
+
+    i2c->read(0xbd, word);
+    printf("Read word of 0x%02x, 0x%04x\n", 0xbd, word);
+
+    i2c->read(0x00, size, blockData.data()); // This throws on the device
+    printf("Read block data, size: %d\n", size);
     return 0;
 }