Home | History | Annotate | Download | only in servo
      1 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """A utility to program Chrome OS devices' firmware using servo.
      6 
      7 This utility expects the DUT to be connected to a servo device. This allows us
      8 to put the DUT into the required state and to actually program the DUT's
      9 firmware using FTDI, USB and/or serial interfaces provided by servo.
     10 
     11 Servo state is preserved across the programming process.
     12 """
     13 
     14 import glob
     15 import logging
     16 import os
     17 import re
     18 import site
     19 import time
     20 import xml.etree.ElementTree
     21 
     22 from autotest_lib.client.common_lib import error
     23 from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
     24 
     25 
     26 # Number of seconds for program EC/BIOS to time out.
     27 FIRMWARE_PROGRAM_TIMEOUT_SEC = 600
     28 
     29 class ProgrammerError(Exception):
     30     """Local exception class wrapper."""
     31     pass
     32 
     33 
     34 class _BaseProgrammer(object):
     35     """Class implementing base programmer services.
     36 
     37     Private attributes:
     38       _servo: a servo object controlling the servo device
     39       _servo_prog_state: a tuple of strings of "<control>:<value>" pairs,
     40                          listing servo controls and their required values for
     41                          programming
     42       _servo_prog_state_delay: time in second to wait after changing servo
     43                                controls for programming.
     44       _servo_saved_state: a list of the same elements as _servo_prog_state,
     45                           those which need to be restored after programming
     46       _program_cmd: a string, the shell command to run on the servo host
     47                     to actually program the firmware. Dependent on
     48                     firmware/hardware type, set by subclasses.
     49     """
     50 
     51     def __init__(self, servo, req_list):
     52         """Base constructor.
     53         @param servo: a servo object controlling the servo device
     54         @param req_list: a list of strings, names of the utilities required
     55                          to be in the path for the programmer to succeed
     56         """
     57         self._servo = servo
     58         self._servo_prog_state = ()
     59         self._servo_prog_state_delay = 0
     60         self._servo_saved_state = []
     61         self._program_cmd = ''
     62         try:
     63             self._servo.system('which %s' % ' '.join(req_list))
     64         except error.AutoservRunError:
     65             # TODO: We turn this exception into a warn since the fw programmer
     66             # is not working right now, and some systems do not package the
     67             # required utilities its checking for.
     68             # We should reinstate this exception once the programmer is working
     69             # to indicate the missing utilities earlier in the test cycle.
     70             # Bug chromium:371011 filed to track this.
     71             logging.warn("Ignoring exception when verify required bins : %s",
     72                          ' '.join(req_list))
     73 
     74 
     75     def _set_servo_state(self):
     76         """Set servo for programming, while saving the current state."""
     77         logging.debug("Setting servo state for programming")
     78         for item in self._servo_prog_state:
     79             key, value = item.split(':')
     80             try:
     81                 present = self._servo.get(key)
     82             except error.TestFail:
     83                 logging.warn('Missing servo control: %s', key)
     84                 continue
     85             if present != value:
     86                 self._servo_saved_state.append('%s:%s' % (key, present))
     87             self._servo.set(key, value)
     88         time.sleep(self._servo_prog_state_delay)
     89 
     90 
     91     def _restore_servo_state(self):
     92         """Restore previously saved servo state."""
     93         logging.debug("Restoring servo state after programming")
     94         self._servo_saved_state.reverse()  # Do it in the reverse order.
     95         for item in self._servo_saved_state:
     96             key, value = item.split(':')
     97             self._servo.set(key, value)
     98 
     99 
    100     def program(self):
    101         """Program the firmware as configured by a subclass."""
    102         self._set_servo_state()
    103         try:
    104             logging.debug("Programmer command: %s", self._program_cmd)
    105             self._servo.system(self._program_cmd,
    106                                timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
    107         finally:
    108             self._restore_servo_state()
    109 
    110 
    111 class FlashromProgrammer(_BaseProgrammer):
    112     """Class for programming AP flashrom."""
    113 
    114     def __init__(self, servo, keep_ro=False):
    115         """Configure required servo state.
    116 
    117         @param servo: a servo object controlling the servo device
    118         @param keep_ro: True to keep the RO portion unchanged
    119         """
    120         super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
    121         self._keep_ro = keep_ro
    122         self._fw_path = None
    123         self._tmp_path = '/tmp'
    124         self._fw_main = os.path.join(self._tmp_path, 'fw_main')
    125         self._wp_ro = os.path.join(self._tmp_path, 'wp_ro')
    126         self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
    127         self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
    128         self._gbb = os.path.join(self._tmp_path, 'gbb')
    129         self._servo_version = self._servo.get_servo_version()
    130         self._servo_serials = self._servo._server.get_servo_serials()
    131 
    132 
    133     def program(self):
    134         """Program the firmware but preserve VPD and HWID."""
    135         assert self._fw_path is not None
    136         self._set_servo_state()
    137         try:
    138             wp_ro_section = [('WP_RO', self._wp_ro)]
    139             rw_vpd_section = [('RW_VPD', self._rw_vpd)]
    140             ro_vpd_section = [('RO_VPD', self._ro_vpd)]
    141             gbb_section = [('GBB', self._gbb)]
    142             if self._keep_ro:
    143                 # Keep the whole RO portion
    144                 preserved_sections = wp_ro_section + rw_vpd_section
    145             else:
    146                 preserved_sections = ro_vpd_section + rw_vpd_section
    147 
    148             servo_v2_programmer = 'ft2232_spi:type=servo-v2'
    149             servo_v3_programmer = 'linux_spi'
    150             servo_v4_with_micro_programmer = 'raiden_spi'
    151             servo_v4_with_ccd_programmer = 'raiden_debug_spi:target=AP'
    152             if self._servo_version == 'servo_v2':
    153                 programmer = servo_v2_programmer
    154                 servo_serial = self._servo_serials.get('main')
    155                 if servo_serial:
    156                     programmer += ',serial=%s' % servo_serial
    157             elif self._servo_version == 'servo_v3':
    158                 programmer = servo_v3_programmer
    159             elif self._servo_version == 'servo_v4':
    160                 # Get the serial of the servo micro if it exists.
    161                 servo_micro_serial = self._servo_serials.get('servo_micro')
    162                 ccd_serial = self._servo_serials.get('ccd')
    163                 # When a uServo is connected to a DUT with CCD support, the
    164                 # firmware programmer will always use the uServo to program.
    165                 if servo_micro_serial:
    166                     programmer = servo_v4_with_micro_programmer
    167                     programmer += ',serial=%s' % servo_micro_serial
    168                 elif ccd_serial:
    169                     programmer = servo_v4_with_ccd_programmer
    170                     programmer += ',serial=%s' % ccd_serial
    171             else:
    172                 raise Exception('Servo version %s is not supported.' %
    173                                 self._servo_version)
    174             # Save needed sections from current firmware
    175             for section in preserved_sections + gbb_section:
    176                 self._servo.system(' '.join([
    177                     'flashrom', '-V', '-p', programmer,
    178                     '-r', self._fw_main, '-i', '%s:%s' % section]),
    179                     timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
    180 
    181             # Pack the saved VPD into new firmware
    182             self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
    183             img_size = self._servo.system_output(
    184                     "stat -c '%%s' %s" % self._fw_main)
    185             pack_cmd = ['flashrom',
    186                     '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
    187                         self._fw_main, img_size),
    188                     '-w', self._fw_main]
    189             for section in preserved_sections:
    190                 pack_cmd.extend(['-i', '%s:%s' % section])
    191             self._servo.system(' '.join(pack_cmd),
    192                                timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
    193 
    194             # HWID is inside the RO portion. Don't preserve HWID if we keep RO.
    195             if not self._keep_ro:
    196                 # Read original HWID. The output format is:
    197                 #    hardware_id: RAMBI TEST A_A 0128
    198                 gbb_hwid_output = self._servo.system_output(
    199                         'gbb_utility -g --hwid %s' % self._gbb)
    200                 original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
    201 
    202                 # Write HWID to new firmware
    203                 self._servo.system("gbb_utility -s --hwid='%s' %s" %
    204                         (original_hwid, self._fw_main))
    205 
    206             # Flash the new firmware
    207             self._servo.system(' '.join([
    208                     'flashrom', '-V', '-p', programmer,
    209                     '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
    210         finally:
    211             self._servo.get_power_state_controller().reset()
    212             self._restore_servo_state()
    213 
    214 
    215     def prepare_programmer(self, path):
    216         """Prepare programmer for programming.
    217 
    218         @param path: a string, name of the file containing the firmware image.
    219         """
    220         self._fw_path = path
    221         # CCD takes care holding AP/EC. Don't need the following steps.
    222         if not (self._servo_version == 'servo_v4' and
    223                 'ccd' in self._servo_serials):
    224             faft_config = FAFTConfig(self._servo.get_board())
    225             self._servo_prog_state_delay = faft_config.servo_prog_state_delay
    226             self._servo_prog_state = (
    227                 'spi2_vref:%s' % faft_config.spi_voltage,
    228                 'spi2_buf_en:on',
    229                 'spi2_buf_on_flex_en:on',
    230                 'spi_hold:off',
    231                 'cold_reset:on',
    232                 'usbpd_reset:on',
    233                 )
    234 
    235 
    236 class FlashECProgrammer(_BaseProgrammer):
    237     """Class for programming AP flashrom."""
    238 
    239     def __init__(self, servo):
    240         """Configure required servo state."""
    241         super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
    242         self._servo = servo
    243         self._servo_serials = self._servo._server.get_servo_serials()
    244 
    245     def prepare_programmer(self, image):
    246         """Prepare programmer for programming.
    247 
    248         @param image: string with the location of the image file
    249         """
    250         port = self._servo._servo_host.servo_port
    251         self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' %
    252                              (self._servo.get('ec_chip'), image, port))
    253         if 'ccd' in self._servo_serials:
    254             self._program_cmd += ' --raiden'
    255 
    256 
    257 class ProgrammerV2(object):
    258     """Main programmer class which provides programmer for BIOS and EC with
    259     servo V2."""
    260 
    261     def __init__(self, servo):
    262         self._servo = servo
    263         self._valid_boards = self._get_valid_v2_boards()
    264         self._bios_programmer = self._factory_bios(self._servo)
    265         self._ec_programmer = self._factory_ec(self._servo)
    266 
    267 
    268     @staticmethod
    269     def _get_valid_v2_boards():
    270         """Greps servod config files to look for valid v2 boards.
    271 
    272         @return A list of valid board names.
    273         """
    274         site_packages_paths = site.getsitepackages()
    275         SERVOD_CONFIG_DATA_DIR = None
    276         for p in site_packages_paths:
    277             servo_data_path = os.path.join(p, 'servo', 'data')
    278             if os.path.exists(servo_data_path):
    279                 SERVOD_CONFIG_DATA_DIR = servo_data_path
    280                 break
    281         if not SERVOD_CONFIG_DATA_DIR:
    282             raise ProgrammerError(
    283                     'Unable to locate data directory of Python servo module')
    284         SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
    285         SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
    286         SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
    287 
    288         def is_v2_compatible_board(board_config_path):
    289             """Check if the given board config file is v2-compatible.
    290 
    291             @param board_config_path: Path to a board config XML file.
    292 
    293             @return True if the board is v2-compatible; False otherwise.
    294             """
    295             configs = []
    296             def get_all_includes(config_path):
    297                 """Get all included XML config names in the given config file.
    298 
    299                 @param config_path: Path to a servo config file.
    300                 """
    301                 root = xml.etree.ElementTree.parse(config_path).getroot()
    302                 for element in root.findall('include'):
    303                     include_name = element.find('name').text
    304                     configs.append(include_name)
    305                     get_all_includes(os.path.join(
    306                             SERVOD_CONFIG_DATA_DIR, include_name))
    307 
    308             get_all_includes(board_config_path)
    309             return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
    310 
    311         result = []
    312         board_overlays = glob.glob(
    313                 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
    314         for overlay_path in board_overlays:
    315             if is_v2_compatible_board(overlay_path):
    316                 result.append(re.search(SERVO_CONFIG_REGEXP,
    317                                         overlay_path).group('board'))
    318         return result
    319 
    320 
    321     def _get_flashrom_programmer(self, servo):
    322         """Gets a proper flashrom programmer.
    323 
    324         @param servo: A servo object.
    325 
    326         @return A programmer for flashrom.
    327         """
    328         return FlashromProgrammer(servo)
    329 
    330 
    331     def _factory_bios(self, servo):
    332         """Instantiates and returns (bios, ec) programmers for the board.
    333 
    334         @param servo: A servo object.
    335 
    336         @return A programmer for ec. If the programmer is not supported
    337             for the board, None will be returned.
    338         """
    339         _bios_prog = None
    340         _board = servo.get_board()
    341 
    342         logging.debug('Setting up BIOS programmer for board: %s', _board)
    343         if _board in self._valid_boards:
    344             _bios_prog = self._get_flashrom_programmer(servo)
    345         else:
    346             logging.warning('No BIOS programmer found for board: %s', _board)
    347 
    348         return _bios_prog
    349 
    350 
    351     def _factory_ec(self, servo):
    352         """Instantiates and returns ec programmer for the board.
    353 
    354         @param servo: A servo object.
    355 
    356         @return A programmer for ec. If the programmer is not supported
    357             for the board, None will be returned.
    358         """
    359         _ec_prog = None
    360         _board = servo.get_board()
    361 
    362         logging.debug('Setting up EC programmer for board: %s', _board)
    363         if _board in self._valid_boards:
    364             _ec_prog = FlashECProgrammer(servo)
    365         else:
    366             logging.warning('No EC programmer found for board: %s', _board)
    367 
    368         return _ec_prog
    369 
    370 
    371     def program_bios(self, image):
    372         """Programs the DUT with provide bios image.
    373 
    374         @param image: (required) location of bios image file.
    375 
    376         """
    377         self._bios_programmer.prepare_programmer(image)
    378         self._bios_programmer.program()
    379 
    380 
    381     def program_ec(self, image):
    382         """Programs the DUT with provide ec image.
    383 
    384         @param image: (required) location of ec image file.
    385 
    386         """
    387         self._ec_programmer.prepare_programmer(image)
    388         self._ec_programmer.program()
    389 
    390 
    391 class ProgrammerV2RwOnly(ProgrammerV2):
    392     """Main programmer class which provides programmer for only updating the RW
    393     portion of BIOS with servo V2.
    394 
    395     It does nothing on EC, as EC software sync on the next boot will
    396     automatically overwrite the EC RW portion, using the EC RW image inside
    397     the BIOS RW image.
    398 
    399     """
    400 
    401     def _get_flashrom_programmer(self, servo):
    402         """Gets a proper flashrom programmer.
    403 
    404         @param servo: A servo object.
    405 
    406         @return A programmer for flashrom.
    407         """
    408         return FlashromProgrammer(servo, keep_ro=True)
    409 
    410 
    411     def program_ec(self, image):
    412         """Programs the DUT with provide ec image.
    413 
    414         @param image: (required) location of ec image file.
    415 
    416         """
    417         # Do nothing. EC software sync will update the EC RW.
    418         pass
    419 
    420 
    421 class ProgrammerV3(object):
    422     """Main programmer class which provides programmer for BIOS and EC with
    423     servo V3.
    424 
    425     Different from programmer for servo v2, programmer for servo v3 does not
    426     try to validate if the board can use servo V3 to update firmware. As long as
    427     the servod process running in beagblebone with given board, the program will
    428     attempt to flash bios and ec.
    429 
    430     """
    431 
    432     def __init__(self, servo):
    433         self._servo = servo
    434         self._bios_programmer = FlashromProgrammer(servo)
    435         self._ec_programmer = FlashECProgrammer(servo)
    436 
    437 
    438     def program_bios(self, image):
    439         """Programs the DUT with provide bios image.
    440 
    441         @param image: (required) location of bios image file.
    442 
    443         """
    444         self._bios_programmer.prepare_programmer(image)
    445         self._bios_programmer.program()
    446 
    447 
    448     def program_ec(self, image):
    449         """Programs the DUT with provide ec image.
    450 
    451         @param image: (required) location of ec image file.
    452 
    453         """
    454         self._ec_programmer.prepare_programmer(image)
    455         self._ec_programmer.program()
    456 
    457 
    458 class ProgrammerV3RwOnly(ProgrammerV3):
    459     """Main programmer class which provides programmer for only updating the RW
    460     portion of BIOS with servo V3.
    461 
    462     It does nothing on EC, as EC software sync on the next boot will
    463     automatically overwrite the EC RW portion, using the EC RW image inside
    464     the BIOS RW image.
    465 
    466     """
    467 
    468     def __init__(self, servo):
    469         self._servo = servo
    470         self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
    471 
    472 
    473     def program_ec(self, image):
    474         """Programs the DUT with provide ec image.
    475 
    476         @param image: (required) location of ec image file.
    477 
    478         """
    479         # Do nothing. EC software sync will update the EC RW.
    480         pass
    481