[autotest] Add ProgrammerV3 for updating bios and ec using servo V3

Update firmware_programmer with a new class of ProgrammerV3 for updating bios
and ec using servo V3.

BUG=chromium:279410
TEST=local flash firmware, test script:
import common
from autotest_lib.server import hosts

machine = '172.27.215.232'
host = hosts.create_host(machine, initialize=True)
host.firmware_install('veyron_jerry-firmware/R41-6588.10.0')

local change in servo_host needed:
1. ServoHost._initialize, force server proxy to use localhost:
remote = 'http://%s:%s' % ('localhost', servo_port)
2. servo_host.create_servo_host, use remote servo:
    return ServoHost(servo_host='172.27.215.231', is_in_lab=False,
                             required_by_test=True)
3. ServoHost._update_image force to skip.

Change-Id: I6ae7162f53c3f8c5e5314f1a8ac35fa89cca4dda
Reviewed-on: https://chromium-review.googlesource.com/271987
Tested-by: Dan Shi <dshi@chromium.org>
Reviewed-by: Tom Tam <waihong@google.com>
Commit-Queue: Dan Shi <dshi@chromium.org>
diff --git a/server/cros/servo/firmware_programmer.py b/server/cros/servo/firmware_programmer.py
index 85f6ea5..29d9a83 100644
--- a/server/cros/servo/firmware_programmer.py
+++ b/server/cros/servo/firmware_programmer.py
@@ -64,6 +64,7 @@
             logging.warn("Ignoring exception when verify required bins : %s",
                          ' '.join(req_list))
 
+
     def _set_servo_state(self):
         """Set servo for programming, while saving the current state."""
         logging.debug("Setting servo state for programming")
@@ -115,14 +116,22 @@
         try:
             vpd_sections = [('RW_VPD', self._rw_vpd), ('RO_VPD', self._ro_vpd)]
             gbb_section = [('GBB', self._gbb)]
-            ft2232_programmer = 'ft2232_spi:type=servo-v2'
-            if self._servo.servo_serial:
-                ft2232_programmer += ',serial=%s' % self._servo.servo_serial
-
+            servo_version = self._servo.get_servo_version()
+            servo_v2_programmer = 'ft2232_spi:type=servo-v2'
+            servo_v3_programmer = 'linux_spi'
+            if servo_version == 'servo_v2':
+                programmer = servo_v2_programmer
+                if self._servo.servo_serial:
+                    programmer += ',serial=%s' % self._servo.servo_serial
+            elif servo_version == 'servo_v3':
+                programmer = servo_v3_programmer
+            else:
+                raise Exception('Servo version %s is not supported.' %
+                                servo_version)
             # Save needed sections from current firmware
             for section in vpd_sections + gbb_section:
                 self._servo.system(' '.join([
-                    'flashrom', '-V', '-p', ft2232_programmer,
+                    'flashrom', '-V', '-p', programmer,
                     '-r', self._fw_main, '-i', '%s:%s' % section]))
 
             # Pack the saved VPD into new firmware
@@ -149,8 +158,8 @@
 
             # Flash the new firmware
             self._servo.system(' '.join([
-                'flashrom', '-V', '-p', ft2232_programmer,
-                '-w', self._fw_main]))
+                    'flashrom', '-V', '-p', programmer,
+                    '-w', self._fw_main]))
         finally:
             self._restore_servo_state()
 
@@ -181,18 +190,21 @@
         self._servo = servo
 
 
-    def prepare_programmer(self, image):
+    def prepare_programmer(self, image, board=None):
         """Prepare programmer for programming.
 
         @param image: string with the location of the image file
+        @param board: Name of the board used in EC image. Some board's name used
+                      by servod might be different from EC. Default to None.
         """
         # TODO: need to not have port be hardcoded
-        self._program_cmd = 'flash_ec --board=%s --image=%s --port=%s' % (
-            self._servo.get_board(), image, '9999')
+        self._program_cmd = ('flash_ec --board=%s --image=%s --port=%s' %
+                             (board or self._servo.get_board(), image, '9999'))
 
 
 class ProgrammerV2(object):
-    """Main programmer class which provides programmer for BIOS and EC."""
+    """Main programmer class which provides programmer for BIOS and EC with
+    servo V2."""
 
     def __init__(self, servo):
         self._servo = servo
@@ -310,6 +322,7 @@
         self._bios_programmer.prepare_programmer(image)
         self._bios_programmer.program()
 
+
     def program_ec(self, image):
         """Programs the DUT with provide ec image.
 
@@ -318,3 +331,42 @@
         """
         self._ec_programmer.prepare_programmer(image)
         self._ec_programmer.program()
+
+
+class ProgrammerV3(object):
+    """Main programmer class which provides programmer for BIOS and EC with
+    servo V3.
+
+    Different from programmer for servo v2, programmer for servo v3 does not
+    try to validate if the board can use servo V3 to update firmware. As long as
+    the servod process running in beagblebone with given board, the program will
+    attempt to flash bios and ec.
+
+    """
+
+    def __init__(self, servo):
+        self._servo = servo
+        self._bios_programmer = FlashromProgrammer(servo)
+        self._ec_programmer = FlashECProgrammer(servo)
+
+
+    def program_bios(self, image):
+        """Programs the DUT with provide bios image.
+
+        @param image: (required) location of bios image file.
+
+        """
+        self._bios_programmer.prepare_programmer(image)
+        self._bios_programmer.program()
+
+
+    def program_ec(self, image, board=None):
+        """Programs the DUT with provide ec image.
+
+        @param image: (required) location of ec image file.
+        @param board: Name of the board used in EC image. Some board's name used
+                      by servod might be different from EC. Default to None.
+
+        """
+        self._ec_programmer.prepare_programmer(image, board)
+        self._ec_programmer.program()
diff --git a/server/cros/servo/servo.py b/server/cros/servo/servo.py
index 5a71bbb..e0dd832 100644
--- a/server/cros/servo/servo.py
+++ b/server/cros/servo/servo.py
@@ -10,6 +10,7 @@
 import logging, re, time, xmlrpclib
 
 from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib import utils
 from autotest_lib.server.cros.servo import firmware_programmer
 
 
@@ -620,13 +621,24 @@
                                     args=args).stdout.strip()
 
 
+    def get_servo_version(self):
+        """Get the version of the servo, e.g., servo_v2 or servo_v3.
+
+        @return: The version of the servo.
+
+        """
+        return self._server.get_version()
+
+
     def _initialize_programmer(self):
         if self._programmer:
             return
         # Initialize firmware programmer
-        servo_version = self._server.get_version()
+        servo_version = self.get_servo_version()
         if servo_version.startswith('servo_v2'):
             self._programmer = firmware_programmer.ProgrammerV2(self)
+        elif servo_version.startswith('servo_v3'):
+            self._programmer = firmware_programmer.ProgrammerV3(self)
         else:
             raise error.TestError(
                     'No firmware programmer for servo version: %s' %
@@ -646,6 +658,19 @@
         self._programmer.program_bios(image)
 
 
+    def _get_board_from_ec(self, image):
+        """Get the board name from the EC image file.
+
+        @param image: string with the location of the image file
+
+        @return: Board name used in the EC image file.
+
+        """
+        cmd = ('strings "%s" | grep -oP "[a-z\-_0-9]*(?=_v[0-9]\.[0-9]\.)" | '
+               'head -n 1' % image)
+        return utils.run(cmd).stdout.strip()
+
+
     def program_ec(self, image):
         """Program ec on DUT with given image.
 
@@ -654,9 +679,11 @@
 
         """
         self._initialize_programmer()
+        board = self._get_board_from_ec(image)
+        logging.info('board name from ec image file is "%s".', board)
         if not self.is_localhost():
             image = self._scp_image(image)
-        self._programmer.program_ec(image)
+        self._programmer.program_ec(image, board=board)
 
 
     def _switch_usbkey_power(self, power_state, detection_delay=False):
@@ -701,9 +728,9 @@
             return
 
         if usb_state == 'off':
-           self._switch_usbkey_power('off')
-           self._usb_state = usb_state
-           return
+            self._switch_usbkey_power('off')
+            self._usb_state = usb_state
+            return
         elif usb_state == 'host':
             mux_direction = 'servo_sees_usbkey'
         elif usb_state == 'dut':