Autotest test case to utilized C shared library for TPM SmogCheck

Background
stevenh proposed a project called TPM SmogCheck to define and implement a suite
of Python APIs that would facilitate extensive testing of TPM module
functionalities /features, all from the existing Autotest framework.

What's in this CL?
[1] a new Python library that implements stevenh's API to exercise various
    capabilities of his TTCI board (specifically, PCA9555 and INA219B modules)
[2] a new Python Autotest test case that demonstrates the use of this new library
[3] a control file to launch the new Autotest (boiler plate)

Any dependency?
 - Yes. [1] above depends on a new C shared library called "libsmogcheck.so".
   I've filed a new issue (94960) against Build Infrastructure team to have a
   new repo set up to host the C code.
 - In order to run test cases built using [1], one needs to have a TTCI board
   (custom designed by stevenh)

Why is this CL over 1,000 lines?!
 - My apologies!
 - This CL was developed over a period of several weeks and started out as
   experimental code

BUG=chrome-os-partner:5172
TEST=manual (special hardware required to perform test)

Change-Id: Ida5ff18fe1806586d560910007fd3427a3466bec
Reviewed-on: http://gerrit.chromium.org/gerrit/6907
Reviewed-by: Tan Gao <tgao@chromium.org>
Tested-by: Tan Gao <tgao@chromium.org>
diff --git a/client/common_lib/smogcheck_pca9555.py b/client/common_lib/smogcheck_pca9555.py
new file mode 100644
index 0000000..9f82950
--- /dev/null
+++ b/client/common_lib/smogcheck_pca9555.py
@@ -0,0 +1,254 @@
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A Python library to interact with PCA9555 module for TPM testing.
+
+Background
+ - PCA9555 is one of two modules on TTCI board
+ - This library provides methods to interact with PCA9555 programmatically
+
+Dependency
+ - This library depends on a new C shared library called "libsmogcheck.so".
+ - In order to run test cases built using this API, one needs a TTCI board
+
+Notes:
+ - An exception is raised if it doesn't make logical sense to continue program
+   flow (e.g. I/O error prevents test case from executing)
+ - An exception is caught and then converted to an error code if the caller
+   expects to check for error code per API definition
+"""
+
+import logging
+from autotest_lib.client.common_lib import i2c_slave
+
+
+# I2C constants
+PCA9555_SLV = 0x27  # I2C slave address of PCA9555
+
+# PCA9555 registers
+PCA_REG = {
+    'IN0': 0,    # Input Port 0
+    'IN1': 1,    # Input Port 1
+    'OUT0': 2,   # Output Port 0
+    'OUT1': 3,   # Output Port 1
+    'PI0': 4,    # Polarity Inversion 0
+    'PI1': 5,    # Polarity Inversion 1
+    'CONF0': 6,  # Configuration 0
+    'CONF1': 7,  # Configuration 1
+    }
+
+# Each '1' represents turning on corresponding LED via writing to PCA9555
+# Output Port Registers
+PCA_BIT_ONE = {
+    'unalloc_0':    0x01,
+    'unalloc_1':    0x02,
+    'unalloc_2':    0x04,
+    'tpm_i2c':      0x08,
+    'yellow_led':   0x10,
+    'main_power':   0x10,
+    'red_led':      0x20,
+    'backup_power': 0x20,
+    'reset':        0x40,
+    'pp':           0x80,
+   }
+
+# Constants used to initialize PCA registers
+# TODO(tgao): document these bits after stevenh replies
+PCA_OUT0_INIT_VAL = 0xff7f
+PCA_CONF0_INIT_VAL = 0xc007
+
+
+class PcaError(Exception):
+    """Base class for all errors in this module."""
+
+
+class PcaController(i2c_slave.I2cSlave):
+    """Object to control PCA9555 module on TTCI board."""
+
+    def __init__(self):
+        """Initialize PCA9555 module on the TTCI board.
+
+        Raises:
+          PcaError: if error initializing PCA9555 module.
+        """
+        super(PcaController, self).__init__()
+        logging.info('Attempt to initialize PCA9555 module')
+        try:
+            self.setSlaveAddress(PCA9555_SLV)
+            self.writeWord(PCA_REG['OUT0'], PCA_OUT0_INIT_VAL)
+            self.writeWord(PCA_REG['PI0'], 0)
+            self.writeWord(PCA_REG['CONF0'], PCA_CONF0_INIT_VAL)
+        except PcaError, e:
+            raise PcaError('Error initializing PCA9555: %s' % e)
+
+    def setPCAcontrol(self, key, turn_on):
+        """Sets specific bit value in Output Port 0 of PCA9555.
+
+        Args:
+          key: a string, valid dict keys in PCA_BIT_ONE.
+          turn_on: a boolean, true = set bit value to 1.
+
+        Returns:
+          an integer, 0 for success and -1 for error.
+        """
+        logging.info('Attempt to set %r bit to %r', key, turn_on)
+        try:
+            byte_read = self.readByte(PCA_REG['OUT0'])
+            if turn_on:
+                write_byte = byte_read | PCA_BIT_ONE[key]
+            else:
+                write_byte = byte_read & ~PCA_BIT_ONE[key]
+            self.writeByte(PCA_REG['OUT0'], write_byte)
+            return 0
+        except PcaError, e:
+            logging.error('Error setting PCA9555 Output Port 0: %s', e)
+            return -1
+
+    def _computeLEDmask(self, bit_value, failure, warning):
+        """Computes proper bit mask to set LED values.
+
+        Args:
+          <see docstring for TTCI_Set_LEDs()>
+
+        Returns:
+          an integer, 8-bit mask.
+
+        Raises:
+          PcaError: if bit value is out of range.
+        """
+        bit_mask = 0
+        if bit_value < 0 or bit_value > 15:
+            raise PcaError('Error: bit_value out of range [0, 15]')
+
+        bit_mask = bit_value
+        if failure:
+            bit_mask |= 0x20
+        if warning:
+            bit_mask |= 0x10
+
+        return bit_mask
+
+    def getPCAbitStatus(self, key):
+        """Gets specific bit value from Output Port 0 of PCA9555.
+
+        Args:
+          key: a string, valid dict keys in PCA_BIT_ONE.
+
+        Returns:
+          an integer, 0 for success and -1 for error.
+          status: a boolean, True if bit value is '1' and False if bit value
+                  is '0'.
+        """
+        status = False
+        try:
+            if PCA_BIT_ONE[key] & self.readByte(PCA_REG['OUT0']):
+                status = True
+            return (0, status)
+        except PcaError, e:
+            logging.error('Error reading from PCA9555 Output Port 0: %s', e)
+            return (-1, status)
+
+    def setLEDs(self, bit_value, failure, warning):
+        """De/activate PCA9555 LEDs.
+
+        Mapping of LED to bit values in Output Port 1 (register 3)
+        (default bit value = 1 <--> LED OFF)
+          LED 0 (GREEN):  O1.0 (mask = 0x01)
+          LED 1 (GREEN):  O1.1 (mask = 0x02)
+          LED 2 (GREEN):  O1.2 (mask = 0x04)
+          LED 3 (GREEN):  O1.3 (mask = 0x08)
+          LED 4 (YELLOW): O1.4 (mask = 0x10)
+          LED 5 (RED):    O1.5 (mask = 0x20)
+
+        To change LED bit values:
+        1) read byte value from register 3
+        2) set all 6 lower bits to 1 (LED OFF) by logical OR with 0x3f
+        3) set appropriate bits to 0 (LED ON) by logical XOR with proper mask
+        4) write updated byte value to register 3
+
+        An example: bit_value=9, failure=False, warning=True
+        1) read back, say, 0x96, or 1001 0110 (LEDs 0, 3, 5 ON)
+        2) 0x96 | 0x3f = 0xbf (all LEDs OFF)
+        3) bit_value=9 -> turn on LEDs 1, 2
+           failure=False -> keep LED 5 off
+           warning=True -> turn on LED 4
+           proper mask = 0001 0110, or 0x16
+           0xbf ^ 0x16 = 0xa9, or 1010 1001 (LEDs 1, 2, 4 ON)
+        4) write 0xa9 to register 3
+
+        Args:
+          bit_value: an integer between 0 and 15, representing 4-bit binary
+                     value for green LEDs (i.e. 0~3).
+          failure: a boolean, true = set red LED value to 0.
+          warning: a boolean, true = set yellow LED value to 0.
+
+        Returns:
+          an integer, 0 for success and -1 for error.
+        """
+        logging.info('Attempt to set LED values: bit_value=%r, failure=%r, '
+                     'warning=%r', bit_value, failure, warning)
+        try:
+            byte_read = self.readByte(PCA_REG['OUT1'])
+            reset_low6 = byte_read | 0x3f
+            bit_mask = self._computeLEDmask(bit_value, failure, warning)
+            write_byte = reset_low6 ^ bit_mask
+            logging.debug('byte_read = 0x%x, reset_low6 = 0x%x, '
+                          'bit_mask = 0x%x, write_byte = 0x%x',
+                          byte_read, reset_low6, bit_mask, write_byte)
+            self.writeByte(PCA_REG['OUT1'], write_byte)
+            return 0
+        except PcaError, e:
+            logging.error('Error setting PCA9555 Output Port 0: %s', e)
+            return -1
+
+    def getSwitchStatus(self):
+        """Checks status of DIP Switches (2-bit).
+
+        Returns:
+          ret: an integer, error code. 0 = no error.
+          status: an integer, valid value in range [0, 3].
+        """
+        logging.info('Attempt to read DIP switch status')
+        ret = -1
+        status = -1
+        try:
+            byte_read = self.readByte(PCA_REG['IN1'])
+            # Right shift 6-bit to get 2 high-order bits
+            status = byte_read >> 6
+            logging.info('DIP switch status = 0x%x', status)
+            ret = 0
+        except PcaError, e:
+            logging.error('No byte read from PCA9555 Input Port 1: %s', e)
+
+        return (ret, status)
+
+    def getLEDstatus(self):
+        """Checks LED status.
+
+        Returns:
+          ret: an integer, 0 for success and -1 for error.
+          bit_value: an integer between 0 and 15, representing 4-bit binary
+                     value for green LEDs (i.e. 0~3). Default: -1.
+          failure: a boolean, true = red LED has value 0. Default: False.
+          warning: a boolean, true = yellow LED has value 0. Default: False.
+        """
+        ret = -1
+        bit_value = -1
+        failure = False
+        warning = False
+
+        try:
+            byte_read = self.readByte(PCA_REG['OUT1'])
+            if not (byte_read | PCA_BIT_ONE['red_led']):
+                failure = True
+            if not (byte_read | PCA_BIT_ONE['yellow_led']):
+                warning = True
+            bit_value = byte_read & 0xf  # Get lower 4-bit value
+            logging.info('LED bit_value = %r, failure = %r, warning = %r',
+                         bit_value, failure, warning)
+            ret = 0
+        except PcaError, e:
+            logging.error('No byte read from PCA9555 Output Port 1: %s', e)
+
+        return (ret, bit_value, failure, warning)