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