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_with_servo_micro':
    160                 # When a uServo is connected to a DUT with CCD support, the
    161                 # firmware programmer will always use the uServo to program.
    162                 servo_micro_serial = self._servo_serials.get('servo_micro')
    163                 programmer = servo_v4_with_micro_programmer
    164                 programmer += ',serial=%s' % servo_micro_serial
    165             elif self._servo_version == 'servo_v4_with_ccd_cr50':
    166                 ccd_serial = self._servo_serials.get('ccd')
    167                 programmer = servo_v4_with_ccd_programmer
    168                 programmer += ',serial=%s' % ccd_serial
    169             else:
    170                 raise Exception('Servo version %s is not supported.' %
    171                                 self._servo_version)
    172             # Save needed sections from current firmware
    173             for section in preserved_sections + gbb_section:
    174                 self._servo.system(' '.join([
    175                     'flashrom', '-V', '-p', programmer,
    176                     '-r', self._fw_main, '-i', '%s:%s' % section]),
    177                     timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
    178 
    179             # Pack the saved VPD into new firmware
    180             self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
    181             img_size = self._servo.system_output(
    182                     "stat -c '%%s' %s" % self._fw_main)
    183             pack_cmd = ['flashrom',
    184                     '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
    185                         self._fw_main, img_size),
    186                     '-w', self._fw_main]
    187             for section in preserved_sections:
    188                 pack_cmd.extend(['-i', '%s:%s' % section])
    189             self._servo.system(' '.join(pack_cmd),
    190                                timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
    191 
    192             # HWID is inside the RO portion. Don't preserve HWID if we keep RO.
    193             if not self._keep_ro:
    194                 # Read original HWID. The output format is:
    195                 #    hardware_id: RAMBI TEST A_A 0128
    196                 gbb_hwid_output = self._servo.system_output(
    197                         'gbb_utility -g --hwid %s' % self._gbb)
    198                 original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
    199 
    200                 # Write HWID to new firmware
    201                 self._servo.system("gbb_utility -s --hwid='%s' %s" %
    202                         (original_hwid, self._fw_main))
    203 
    204             # Flash the new firmware
    205             self._servo.system(' '.join([
    206                     'flashrom', '-V', '-p', programmer,
    207                     '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
    208         finally:
    209             self._servo.get_power_state_controller().reset()
    210             self._restore_servo_state()
    211 
    212 
    213     def prepare_programmer(self, path):
    214         """Prepare programmer for programming.
    215 
    216         @param path: a string, name of the file containing the firmware image.
    217         """
    218         self._fw_path = path
    219         # CCD takes care holding AP/EC. Don't need the following steps.
    220         if self._servo_version != 'servo_v4_with_ccd_cr50':
    221             faft_config = FAFTConfig(self._servo.get_board())
    222             self._servo_prog_state_delay = faft_config.servo_prog_state_delay
    223             self._servo_prog_state = (
    224                 'spi2_vref:%s' % faft_config.spi_voltage,
    225                 'spi2_buf_en:on',
    226                 'spi2_buf_on_flex_en:on',
    227                 'spi_hold:off',
    228                 'cold_reset:on',
    229                 'usbpd_reset:on',
    230                 )
    231 
    232 
    233 class FlashECProgrammer(_BaseProgrammer):
    234     """Class for programming AP flashrom."""
    235 
    236     def __init__(self, servo):
    237         """Configure required servo state."""
    238         super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
    239         self._servo = servo
    240         self._servo_version = self._servo.get_servo_version()
    241 
    242     def prepare_programmer(self, image):
    243         """Prepare programmer for programming.
    244 
    245         @param image: string with the location of the image file
    246         """
    247         port = self._servo._servo_host.servo_port
    248         self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' %
    249                              (self._servo.get('ec_chip'), image, port))
    250         if self._servo_version == 'servo_v4_with_ccd_cr50':
    251             self._program_cmd += ' --raiden'
    252 
    253 
    254 class ProgrammerV2(object):
    255     """Main programmer class which provides programmer for BIOS and EC with
    256     servo V2."""
    257 
    258     def __init__(self, servo):
    259         self._servo = servo
    260         self._valid_boards = self._get_valid_v2_boards()
    261         self._bios_programmer = self._factory_bios(self._servo)
    262         self._ec_programmer = self._factory_ec(self._servo)
    263 
    264 
    265     @staticmethod
    266     def _get_valid_v2_boards():
    267         """Greps servod config files to look for valid v2 boards.
    268 
    269         @return A list of valid board names.
    270         """
    271         site_packages_paths = site.getsitepackages()
    272         SERVOD_CONFIG_DATA_DIR = None
    273         for p in site_packages_paths:
    274             servo_data_path = os.path.join(p, 'servo', 'data')
    275             if os.path.exists(servo_data_path):
    276                 SERVOD_CONFIG_DATA_DIR = servo_data_path
    277                 break
    278         if not SERVOD_CONFIG_DATA_DIR:
    279             raise ProgrammerError(
    280                     'Unable to locate data directory of Python servo module')
    281         SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
    282         SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
    283         SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
    284 
    285         def is_v2_compatible_board(board_config_path):
    286             """Check if the given board config file is v2-compatible.
    287 
    288             @param board_config_path: Path to a board config XML file.
    289 
    290             @return True if the board is v2-compatible; False otherwise.
    291             """
    292             configs = []
    293             def get_all_includes(config_path):
    294                 """Get all included XML config names in the given config file.
    295 
    296                 @param config_path: Path to a servo config file.
    297                 """
    298                 root = xml.etree.ElementTree.parse(config_path).getroot()
    299                 for element in root.findall('include'):
    300                     include_name = element.find('name').text
    301                     configs.append(include_name)
    302                     get_all_includes(os.path.join(
    303                             SERVOD_CONFIG_DATA_DIR, include_name))
    304 
    305             get_all_includes(board_config_path)
    306             return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
    307 
    308         result = []
    309         board_overlays = glob.glob(
    310                 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
    311         for overlay_path in board_overlays:
    312             if is_v2_compatible_board(overlay_path):
    313                 result.append(re.search(SERVO_CONFIG_REGEXP,
    314                                         overlay_path).group('board'))
    315         return result
    316 
    317 
    318     def _get_flashrom_programmer(self, servo):
    319         """Gets a proper flashrom programmer.
    320 
    321         @param servo: A servo object.
    322 
    323         @return A programmer for flashrom.
    324         """
    325         return FlashromProgrammer(servo)
    326 
    327 
    328     def _factory_bios(self, servo):
    329         """Instantiates and returns (bios, ec) programmers for the board.
    330 
    331         @param servo: A servo object.
    332 
    333         @return A programmer for ec. If the programmer is not supported
    334             for the board, None will be returned.
    335         """
    336         _bios_prog = None
    337         _board = servo.get_board()
    338 
    339         logging.debug('Setting up BIOS programmer for board: %s', _board)
    340         if _board in self._valid_boards:
    341             _bios_prog = self._get_flashrom_programmer(servo)
    342         else:
    343             logging.warning('No BIOS programmer found for board: %s', _board)
    344 
    345         return _bios_prog
    346 
    347 
    348     def _factory_ec(self, servo):
    349         """Instantiates and returns ec programmer for the board.
    350 
    351         @param servo: A servo object.
    352 
    353         @return A programmer for ec. If the programmer is not supported
    354             for the board, None will be returned.
    355         """
    356         _ec_prog = None
    357         _board = servo.get_board()
    358 
    359         logging.debug('Setting up EC programmer for board: %s', _board)
    360         if _board in self._valid_boards:
    361             _ec_prog = FlashECProgrammer(servo)
    362         else:
    363             logging.warning('No EC programmer found for board: %s', _board)
    364 
    365         return _ec_prog
    366 
    367 
    368     def program_bios(self, image):
    369         """Programs the DUT with provide bios image.
    370 
    371         @param image: (required) location of bios image file.
    372 
    373         """
    374         self._bios_programmer.prepare_programmer(image)
    375         self._bios_programmer.program()
    376 
    377 
    378     def program_ec(self, image):
    379         """Programs the DUT with provide ec image.
    380 
    381         @param image: (required) location of ec image file.
    382 
    383         """
    384         self._ec_programmer.prepare_programmer(image)
    385         self._ec_programmer.program()
    386 
    387 
    388 class ProgrammerV2RwOnly(ProgrammerV2):
    389     """Main programmer class which provides programmer for only updating the RW
    390     portion of BIOS with servo V2.
    391 
    392     It does nothing on EC, as EC software sync on the next boot will
    393     automatically overwrite the EC RW portion, using the EC RW image inside
    394     the BIOS RW image.
    395 
    396     """
    397 
    398     def _get_flashrom_programmer(self, servo):
    399         """Gets a proper flashrom programmer.
    400 
    401         @param servo: A servo object.
    402 
    403         @return A programmer for flashrom.
    404         """
    405         return FlashromProgrammer(servo, keep_ro=True)
    406 
    407 
    408     def program_ec(self, image):
    409         """Programs the DUT with provide ec image.
    410 
    411         @param image: (required) location of ec image file.
    412 
    413         """
    414         # Do nothing. EC software sync will update the EC RW.
    415         pass
    416 
    417 
    418 class ProgrammerV3(object):
    419     """Main programmer class which provides programmer for BIOS and EC with
    420     servo V3.
    421 
    422     Different from programmer for servo v2, programmer for servo v3 does not
    423     try to validate if the board can use servo V3 to update firmware. As long as
    424     the servod process running in beagblebone with given board, the program will
    425     attempt to flash bios and ec.
    426 
    427     """
    428 
    429     def __init__(self, servo):
    430         self._servo = servo
    431         self._bios_programmer = FlashromProgrammer(servo)
    432         self._ec_programmer = FlashECProgrammer(servo)
    433 
    434 
    435     def program_bios(self, image):
    436         """Programs the DUT with provide bios image.
    437 
    438         @param image: (required) location of bios image file.
    439 
    440         """
    441         self._bios_programmer.prepare_programmer(image)
    442         self._bios_programmer.program()
    443 
    444 
    445     def program_ec(self, image):
    446         """Programs the DUT with provide ec image.
    447 
    448         @param image: (required) location of ec image file.
    449 
    450         """
    451         self._ec_programmer.prepare_programmer(image)
    452         self._ec_programmer.program()
    453 
    454 
    455 class ProgrammerV3RwOnly(ProgrammerV3):
    456     """Main programmer class which provides programmer for only updating the RW
    457     portion of BIOS with servo V3.
    458 
    459     It does nothing on EC, as EC software sync on the next boot will
    460     automatically overwrite the EC RW portion, using the EC RW image inside
    461     the BIOS RW image.
    462 
    463     """
    464 
    465     def __init__(self, servo):
    466         self._servo = servo
    467         self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
    468 
    469 
    470     def program_ec(self, image):
    471         """Programs the DUT with provide ec image.
    472 
    473         @param image: (required) location of ec image file.
    474 
    475         """
    476         # Do nothing. EC software sync will update the EC RW.
    477         pass
    478